Files
openchamber/packages/web/server/lib/opencode/feature-routes-runtime.js
T
Bohdan Triapitsyn 07fa83cc72 feat(queue): deliver queued messages from the server
Messages queued while a session is busy used to live in the browser tab and
were sent by that tab once the session went idle, so closing the tab (or
losing the connection) stranded them. The web server now owns the queue:
it persists to <data-dir>/message-queue.json, watches session.status on the
global event hub, re-verifies idleness against OpenCode before sending, and
delivers the head of the queue via prompt_async (or /command for slash
commands) with the model, agent, variant, attachments, and agent mention
captured at queue time. Failed sends stay queued and retry with backoff; a
user abort holds delivery briefly; every change is broadcast so all clients
see one queue.

The shared UI store becomes a projection of the server queue outside VS
Code (hydrate on connect, apply broadcasts, optimistic mutations settled on
the server's copy, one-time upload of locally queued messages from older
builds). Edit / send-now take the full message back from the server. A
UI-driven auto-review run asks the server to hold that session's queue.
VS Code keeps its local queue and foreground auto-send.

Claude-Session: https://claude.ai/code/session_01HB9wdLQoZX2vfyDjwv6Rso
2026-09-04 14:08:08 +03:00

342 lines
11 KiB
JavaScript

import { registerFsRoutes } from '../fs/routes.js';
import { registerQuotaRoutes } from '../quota/routes.js';
import { registerSmallModelRoutes } from '../small-model/routes.js';
import { registerWalkthroughRoutes } from '../walkthrough/routes.js';
import { registerSessionGoalRoutes } from '../session-goal/routes.js';
import { registerGitHubRoutes } from '../github/routes.js';
import { registerLinearRoutes } from '../linear/routes.js';
import { registerGitRoutes } from '../git/routes.js';
import { registerDevServerRoutes } from '../dev-servers/routes.js';
import { registerMagicPromptRoutes } from '../magic-prompts/routes.js';
import { registerSessionFoldersRoutes } from '../session-folders/routes.js';
import { registerProjectContextRoutes } from '../project-context/routes.js';
import { registerAgentMemoryRoutes } from '../agent-memory/routes.js';
import { registerSessionKnowledgeRoutes } from '../session-knowledge/routes.js';
import { registerPermissionAutoAcceptRoutes } from '../permission-auto-accept/runtime.js';
import { registerMessageQueueRoutes } from '../message-queue/runtime.js';
import { registerConfigEntityRoutes } from './config-entity-routes.js';
import { registerSettingsUtilityRoutes } from './core-routes.js';
import { registerProjectIconRoutes } from './project-icon-routes.js';
import { registerScheduledTaskRoutes } from '../scheduled-tasks/routes.js';
import { registerOpenChamberSessionRoutes } from '../openchamber-sessions/routes.js';
import { registerOpenChamberControlRoutes } from '../openchamber-control/routes.js';
import { registerMarkdownImageGrantRoutes } from '../markdown-image-grants/routes.js';
import { registerSkillRoutes } from './skill-routes.js';
import { registerPluginRoutes } from './plugin-routes.js';
import { getNpmInfo, clearCache as clearNpmCache } from './npm-registry.js';
import { parseNpmSpec, parsePathSpec, isExactSemver } from './plugin-spec.js';
import { registerOpenCodeRoutes } from './routes.js';
import { getProviderSources, removeProviderConfig, upsertProviderConfig } from './providers.js';
import { getAgentSources, getAgentConfig, createAgent, updateAgent, deleteAgent } from './agents.js';
import { getCommandSources, createCommand, updateCommand, deleteCommand } from './commands.js';
import { listMcpConfigs, getMcpConfig, createMcpConfig, updateMcpConfig, deleteMcpConfig } from './mcp.js';
import { listSnippets, getSnippet, createSnippet, updateSnippet, deleteSnippet, expandSnippets } from './snippets.js';
import {
listPluginEntries,
getPluginEntry,
createPluginEntry,
updatePluginEntry,
deletePluginEntry,
listPluginDirFiles,
readPluginDirFile,
writePluginDirFile,
deletePluginDirFile,
encodePluginId,
decodePluginId,
} from './plugins.js';
import { SKILL_DIR, SKILL_SCOPE, readSkillSupportingFile, writeSkillSupportingFile, deleteSkillSupportingFile } from './shared.js';
import { getSkillSources, discoverSkills, mergeDiscoveredSkills, createSkill, updateSkill, deleteSkill, renameSkill, isManagedSkillPath } from './skills.js';
import { getCuratedSkillsSources } from '../skills-catalog/curated-sources.js';
import { getCacheKey, scanWithCache } from '../skills-catalog/cache.js';
import { parseSkillRepoSource } from '../skills-catalog/source.js';
import { scanSkillsRepository } from '../skills-catalog/scan.js';
import { installSkillsFromRepository } from '../skills-catalog/install.js';
import { fetchGitHubRepoMetas } from '../skills-catalog/github-meta.js';
export const createFeatureRoutesRuntime = (dependencies) => {
const {
clientReloadDelayMs,
} = dependencies;
let quotaProviders = null;
const getQuotaProviders = async () => {
if (!quotaProviders) {
quotaProviders = await import('../quota/index.js');
}
return quotaProviders;
};
let smallModelService = null;
const getSmallModelService = async () => {
if (!smallModelService) {
smallModelService = await import('../small-model/index.js');
}
return smallModelService;
};
let walkthroughService = null;
const getWalkthroughService = async () => {
if (!walkthroughService) {
const [service, pullRequest] = await Promise.all([
import('../walkthrough/index.js'),
import('../walkthrough/pull-request.js'),
]);
walkthroughService = { ...service, getPullRequestDiff: pullRequest.getPullRequestDiff };
}
return walkthroughService;
};
const registerRoutes = async (app, routeDependencies) => {
const {
crypto,
fs,
os,
path,
fsPromises,
spawn,
resolveGitBinaryForSpawn,
createFsSearchRuntime,
openchamberDataDir,
openchamberUserConfigRoot,
normalizeDirectoryPath,
resolveProjectDirectory,
resolveOptionalProjectDirectory,
validateDirectoryPath,
readCustomThemesFromDisk,
refreshOpenCodeAfterConfigChange,
getOpenCodeResolutionSnapshot,
getOpenCodeUpgradeCapability,
formatSettingsResponse,
readSettingsFromDisk,
readSettingsFromDiskMigrated,
persistSettings,
sanitizeProjects,
sanitizeSkillCatalogs,
isUnsafeSkillRelativePath,
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
getOpenCodePort,
getOwnPorts,
devServerScanner,
buildAugmentedPath,
projectConfigRuntime,
projectContextRuntime,
agentMemoryRuntime,
isAgentMemoryEnabled,
sessionKnowledgeRuntime,
scheduledTasksRuntime,
scheduledTaskService,
openChamberSessionService,
openChamberControlService,
waitForOpenCodeReady,
getOpenChamberEventClients,
writeSseEvent,
emitSessionCreatedEvent,
permissionAutoAcceptRuntime,
messageQueueRuntime,
} = routeDependencies;
registerSettingsUtilityRoutes(app, {
readCustomThemesFromDisk,
refreshOpenCodeAfterConfigChange,
clientReloadDelayMs,
});
registerPermissionAutoAcceptRoutes(app, permissionAutoAcceptRuntime);
registerMessageQueueRoutes(app, messageQueueRuntime);
registerOpenCodeRoutes(app, {
crypto,
clientReloadDelayMs,
getOpenCodeResolutionSnapshot,
getOpenCodeUpgradeCapability,
formatSettingsResponse,
readSettingsFromDisk,
readSettingsFromDiskMigrated,
persistSettings,
sanitizeProjects,
validateDirectoryPath,
resolveProjectDirectory,
getProviderSources,
removeProviderConfig,
upsertProviderConfig,
refreshOpenCodeAfterConfigChange,
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
});
registerProjectIconRoutes(app, {
fsPromises,
path,
crypto,
openchamberDataDir,
sanitizeProjects,
readSettingsFromDiskMigrated,
persistSettings,
createFsSearchRuntime,
spawn,
resolveGitBinaryForSpawn,
});
registerScheduledTaskRoutes(app, {
readSettingsFromDiskMigrated,
sanitizeProjects,
projectConfigRuntime,
scheduledTasksRuntime,
scheduledTaskService,
getOpenChamberEventClients,
writeSseEvent,
});
registerOpenChamberSessionRoutes(app, {
readSettingsFromDiskMigrated,
sanitizeProjects,
validateDirectoryPath,
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
waitForOpenCodeReady,
emitSessionCreatedEvent,
sessionService: openChamberSessionService,
});
registerOpenChamberControlRoutes(app, { controlService: openChamberControlService });
registerMarkdownImageGrantRoutes(app, {
fsPromises,
path,
os,
crypto,
validateDirectoryPath,
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
});
registerConfigEntityRoutes(app, {
resolveProjectDirectory,
resolveOptionalProjectDirectory,
refreshOpenCodeAfterConfigChange,
clientReloadDelayMs,
getAgentSources,
getAgentConfig,
createAgent,
updateAgent,
deleteAgent,
getCommandSources,
createCommand,
updateCommand,
deleteCommand,
listMcpConfigs,
getMcpConfig,
createMcpConfig,
updateMcpConfig,
deleteMcpConfig,
listSnippets,
getSnippet,
createSnippet,
updateSnippet,
deleteSnippet,
expandSnippets,
});
registerPluginRoutes(app, {
resolveOptionalProjectDirectory,
refreshOpenCodeAfterConfigChange,
clientReloadDelayMs,
listPluginEntries,
getPluginEntry,
createPluginEntry,
updatePluginEntry,
deletePluginEntry,
listPluginDirFiles,
readPluginDirFile,
writePluginDirFile,
deletePluginDirFile,
encodePluginId,
decodePluginId,
getNpmInfo,
parseNpmSpec,
parsePathSpec,
isExactSemver,
});
const { getProfiles, getProfile } = await import('../git/index.js');
registerSkillRoutes(app, {
fs,
path,
os,
resolveProjectDirectory,
resolveOptionalProjectDirectory,
readSettingsFromDisk,
sanitizeSkillCatalogs,
isUnsafeSkillRelativePath,
refreshOpenCodeAfterConfigChange,
clientReloadDelayMs,
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
getOpenCodePort,
getSkillSources,
discoverSkills,
mergeDiscoveredSkills,
createSkill,
updateSkill,
deleteSkill,
renameSkill,
isManagedSkillPath,
readSkillSupportingFile,
writeSkillSupportingFile,
deleteSkillSupportingFile,
SKILL_SCOPE,
SKILL_DIR,
getCuratedSkillsSources,
getCacheKey,
scanWithCache,
parseSkillRepoSource,
scanSkillsRepository,
installSkillsFromRepository,
fetchGitHubRepoMetas,
getProfiles,
getProfile,
});
registerQuotaRoutes(app, { getQuotaProviders });
registerSmallModelRoutes(app, { getSmallModelService });
registerWalkthroughRoutes(app, { getWalkthroughService });
registerSessionGoalRoutes(app);
registerGitHubRoutes(app);
registerLinearRoutes(app);
registerGitRoutes(app);
registerDevServerRoutes(app, { scanner: devServerScanner, getOwnPorts });
registerMagicPromptRoutes(app, {
fsPromises,
path,
openchamberDataDir,
});
registerProjectContextRoutes(app, { projectContextRuntime });
registerAgentMemoryRoutes(app, { agentMemoryRuntime, isAgentMemoryEnabled });
registerSessionKnowledgeRoutes(app, { sessionKnowledgeRuntime });
registerSessionFoldersRoutes(app, {
fsPromises,
path,
openchamberDataDir,
});
registerFsRoutes(app, {
os,
path,
fsPromises,
spawn,
crypto,
normalizeDirectoryPath,
resolveProjectDirectory,
buildAugmentedPath,
resolveGitBinaryForSpawn,
openchamberUserConfigRoot,
});
};
return {
registerRoutes,
};
};