@loicngr/kobo 1.11.44 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +57 -4
- package/CHANGELOG.md +13 -0
- package/CODE_OF_CONDUCT.md +16 -0
- package/CONFIGURATION.md +1968 -0
- package/CONTRIBUTING.md +83 -0
- package/DESIGN.md +296 -0
- package/README.md +48 -8
- package/SECURITY.md +54 -0
- package/THIRD_PARTY_NOTICES.md +20 -0
- package/dist/mcp-server/kobo-tasks-handlers.js +2 -1
- package/dist/mcp-server/kobo-tasks-server.js +31 -5
- package/dist/server/db/migrations.js +37 -0
- package/dist/server/db/schema.js +6 -1
- package/dist/server/db/session-handoff-schema.js +24 -0
- package/dist/server/index.js +8 -0
- package/dist/server/routes/documents.js +1 -1
- package/dist/server/routes/environment.js +18 -0
- package/dist/server/routes/health.js +17 -2
- package/dist/server/routes/integrations.js +29 -0
- package/dist/server/routes/settings.js +5 -0
- package/dist/server/routes/sounds.js +60 -0
- package/dist/server/routes/workspaces.js +118 -20
- package/dist/server/services/agent/engines/claude-code/engine.js +15 -0
- package/dist/server/services/agent/engines/codex/engine.js +1 -0
- package/dist/server/services/agent/engines/codex/options-builder.js +1 -0
- package/dist/server/services/agent/engines/codex/spawn.js +2 -2
- package/dist/server/services/agent/integration-mcp.js +49 -0
- package/dist/server/services/agent/orchestrator.js +68 -11
- package/dist/server/services/auto-loop-service.js +45 -25
- package/dist/server/services/custom-sound-service.js +0 -0
- package/dist/server/services/engine-handoff-service.js +5 -5
- package/dist/server/services/environment-check-service.js +125 -0
- package/dist/server/services/initial-prompt-template-service.js +8 -21
- package/dist/server/services/integration-config-service.js +65 -0
- package/dist/server/services/legacy-public-prompts.js +112 -0
- package/dist/server/services/notion-service.js +25 -61
- package/dist/server/services/sentry-service.js +17 -12
- package/dist/server/services/session-activity-service.js +17 -0
- package/dist/server/services/session-handoff-runtime.js +26 -0
- package/dist/server/services/session-handoff-service.js +479 -0
- package/dist/server/services/settings-defaults.js +5 -0
- package/dist/server/services/settings-service.js +112 -23
- package/dist/server/services/skill-suite-prompts.js +29 -22
- package/dist/server/services/templates-service.js +31 -8
- package/dist/server/services/terminal-service.js +16 -0
- package/dist/server/services/wakeup-service.js +15 -5
- package/dist/server/services/workspace-service.js +30 -6
- package/dist/server/services/worktree-service.js +3 -1
- package/dist/server/utils/git-repo-lock.js +7 -3
- package/dist/server/utils/mcp-client.js +26 -12
- package/dist/server/utils/mcp-process.js +25 -0
- package/dist/server/utils/node-pty-spawn-helper.js +31 -0
- package/dist/server/utils/paths.js +7 -0
- package/dist/server/utils/workspace-lifecycle-guard.js +35 -20
- package/dist/shared/auto-loop-prompts.js +6 -2
- package/dist/shared/automatic-admission.js +1 -0
- package/dist/shared/document-roots.js +2 -0
- package/dist/shared/environment.js +1 -0
- package/dist/shared/notification-assets.js +73 -0
- package/dist/shared/session-handoff.js +4 -0
- package/dist/shared/skill-suite-prompts.js +6 -3
- package/dist/shared/workflow-policy.js +25 -0
- package/docs/getting-started.md +47 -0
- package/docs/troubleshooting.md +49 -0
- package/package.json +19 -7
- package/src/client/dist/pwa/assets/ActivityFeed-DRgetl1M.js +8 -0
- package/src/client/dist/pwa/assets/{ChangelogPage-DSKCvpr2.js → ChangelogPage-BJdICFvz.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-DDxcsxyC.js → ComparisonPanel-2GLd8KIy.js} +1 -1
- package/src/client/dist/pwa/assets/CreatePage-B9AdNJZj.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-B9C9J9Pw.css +1 -0
- package/src/client/dist/pwa/assets/DashboardPage-CzVOvjSP.js +1 -0
- package/src/client/dist/pwa/assets/DiffViewer-X25vuf83.js +8 -0
- package/src/client/dist/pwa/assets/{DocumentsPanel-DfEoI_f7.js → DocumentsPanel-BastjrEm.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-D85PwhKb.js → DrawerToggleButton-DbipGmBL.js} +1 -1
- package/src/client/dist/pwa/assets/{GitPanel-BFCnyhE2.css → GitPanel-C4wOcZV2.css} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-CnW4iVao.js +3 -0
- package/src/client/dist/pwa/assets/{HealthPage-CaLCpejR.js → HealthPage-aieNQly-.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-Cc4XzCAy.css +1 -0
- package/src/client/dist/pwa/assets/MainLayout-DqBbE8o4.js +2 -0
- package/src/client/dist/pwa/assets/{QBtnDropdown-qJWGcZpC.js → QBtnDropdown-BWy-rh45.js} +1 -1
- package/src/client/dist/pwa/assets/QExpansionItem-DfmdBGuW.js +1 -0
- package/src/client/dist/pwa/assets/QSelect-BfqaWakg.js +1 -0
- package/src/client/dist/pwa/assets/{QTooltip-cDK5AOQ5.js → QTooltip-Cy0T0vk2.js} +1 -1
- package/src/client/dist/pwa/assets/SchedulePanel-BYKSFoFj.css +1 -0
- package/src/client/dist/pwa/assets/SchedulePanel-YQPObRR3.js +1 -0
- package/src/client/dist/pwa/assets/{SearchPage-BO-J4aNc.js → SearchPage-BoBjmItc.js} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-0GtDcXRC.js +9 -0
- package/src/client/dist/pwa/assets/{SettingsPage-W6N01tGz.css → SettingsPage-BYdLXJEJ.css} +1 -1
- package/src/client/dist/pwa/assets/SplitWorkspacePage-4wFh65Lh.js +1 -0
- package/src/client/dist/pwa/assets/{TerminalPanel-DitOCad1.js → TerminalPanel-b70x_jqH.js} +9 -9
- package/src/client/dist/pwa/assets/{TouchPan-DvPAhZsa.js → TouchPan-CcTVcxwd.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-CJYdH3Jf.js → TourReplayButton-CpEMnQS8.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-C5lTHTp2.js +1 -0
- package/src/client/dist/pwa/assets/{WorkspacePage-DAVo-E4p.css → WorkspacePage--5HsnMQ1.css} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-D_V9gNON.js +4 -0
- package/src/client/dist/pwa/assets/{browser-BsB6cFv4.js → browser-LBbxVwWf.js} +1 -1
- package/src/client/dist/pwa/assets/build-path-tree-DyvtWxb0.js +1 -0
- package/src/client/dist/pwa/assets/custom-sounds-BpeLZUDW.js +1 -0
- package/src/client/dist/pwa/assets/de-v6i2HlE2.js +17 -0
- package/src/client/dist/pwa/assets/{documents-SCd3w8xV.js → documents-Bjx8lz-h.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.api-BUdM7I6y.js → editor.api-BC3PbJkx.js} +1 -1
- package/src/client/dist/pwa/assets/efforts-BDHAnNUs.js +9 -0
- package/src/client/dist/pwa/assets/engineFeatures-hlEiwqt-.js +1 -0
- package/src/client/dist/pwa/assets/es-Bc1KgkAT.js +17 -0
- package/src/client/dist/pwa/assets/expand-template-DLDl1qPx.js +1 -0
- package/src/client/dist/pwa/assets/{format-BjQxf4pe.js → format-Q94deO3I.js} +7 -7
- package/src/client/dist/pwa/assets/{formatters-BejhmNTV.js → formatters-CWxRHuUu.js} +1 -1
- package/src/client/dist/pwa/assets/fr-ByYPK5vr.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-5d5GhyRp.js → freemarker2-MPhk0eTh.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-DaafEsDK.js → handlebars-C60ru5gR.js} +1 -1
- package/src/client/dist/pwa/assets/{html-CwHBDd2A.js → html-ClzchR9j.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-Sy42tm-V.js +1 -0
- package/src/client/dist/pwa/assets/index-CqZ7sDcM.js +20 -0
- package/src/client/dist/pwa/assets/it-BYF_88z6.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-DSNIEEH5.js → javascript-C43UJmMx.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-Dma1UpA1.js → liquid-CYTrvTKF.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-D8rqEAws.js → mdx-CxsXOvkQ.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-2E7O2zoi.js → monaco-contributions-Cl4-iVpK.js} +1 -1
- package/src/client/dist/pwa/assets/monaco.contribution-DkjLUmyA.js +2 -0
- package/src/client/dist/pwa/assets/network-auth-CXQn2wO7.js +1 -0
- package/src/client/dist/pwa/assets/notify-theme-BrueBr2j.js +1 -0
- package/src/client/dist/pwa/assets/{pending-diff-open-CeiVIFZn.js → pending-diff-open-B4POItbR.js} +1 -1
- package/src/client/dist/pwa/assets/permissionModes-B-ciuUbz.js +36 -0
- package/src/client/dist/pwa/assets/{project-color-BCp9SWoI.js → project-color-CM3hflLH.js} +1 -1
- package/src/client/dist/pwa/assets/{python-f-9NqRlI.js → python-s2IMpsoV.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-DsCmoX0h.js → razor-DVNXiVBh.js} +1 -1
- package/src/client/dist/pwa/assets/render-chat-markdown-TU6CDfD8.js +69 -0
- package/src/client/dist/pwa/assets/{typescript-cEHg4L10.js → typescript-BYT_8ABE.js} +1 -1
- package/src/client/dist/pwa/assets/use-slide-transition-CiaDm-tR.js +1 -0
- package/src/client/dist/pwa/assets/use-tours-CNe8qb1q.js +2 -0
- package/src/client/dist/pwa/assets/workspace-templates-tBxXE52A.js +1 -0
- package/src/client/dist/pwa/assets/{xml-Plt-Y7we.js → xml-BIG-fmxk.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-ScEmaAeN.js → yaml-D7gu2CQh.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sounds/neutral.wav +0 -0
- package/src/client/dist/pwa/sounds/ready.wav +0 -0
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/mcp-server/README.md +8 -1
- package/src/mcp-server/kobo-tasks-handlers.ts +4 -1
- package/src/mcp-server/kobo-tasks-server.ts +38 -5
- package/src/client/dist/pwa/assets/ActivityFeed-D_y4aikp.js +0 -8
- package/src/client/dist/pwa/assets/CreatePage-DKVEknbL.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-TY9g3hZb.css +0 -1
- package/src/client/dist/pwa/assets/DashboardPage-Dg2s9nnv.js +0 -1
- package/src/client/dist/pwa/assets/DiffViewer-BSzNScIr.js +0 -8
- package/src/client/dist/pwa/assets/GitPanel-CuG8MlgF.js +0 -3
- package/src/client/dist/pwa/assets/MainLayout-CDxYIJ_s.js +0 -2
- package/src/client/dist/pwa/assets/MainLayout-DTLNunOi.css +0 -1
- package/src/client/dist/pwa/assets/QDrawer-DX3PhMqC.js +0 -1
- package/src/client/dist/pwa/assets/QSelect-CY4TWW_j.js +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-BAA4Aajn.css +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-LvHiZ6ZH.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-Aw89fRSq.js +0 -9
- package/src/client/dist/pwa/assets/SplitWorkspacePage-BIEceNwH.js +0 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-CY-9z9nw.js +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-xH_OyA3r.js +0 -4
- package/src/client/dist/pwa/assets/build-path-tree-CW-ER1Mt.js +0 -1
- package/src/client/dist/pwa/assets/de-Mu7biceO.js +0 -17
- package/src/client/dist/pwa/assets/efforts-DTJQP9or.js +0 -9
- package/src/client/dist/pwa/assets/engineFeatures-Bj6Ob41d.js +0 -1
- package/src/client/dist/pwa/assets/es-BY5IfNI2.js +0 -17
- package/src/client/dist/pwa/assets/expand-template-SM8vvYyO.js +0 -1
- package/src/client/dist/pwa/assets/fouet-ahh-DdxrUXRj.mp3 +0 -0
- package/src/client/dist/pwa/assets/fr-BMXCzFCM.js +0 -17
- package/src/client/dist/pwa/assets/i18n-CgWW5vfb.js +0 -1
- package/src/client/dist/pwa/assets/index-Di9Sz5E8.js +0 -20
- package/src/client/dist/pwa/assets/it-ByPZQK_K.js +0 -17
- package/src/client/dist/pwa/assets/monaco.contribution-DqOgo72G.js +0 -2
- package/src/client/dist/pwa/assets/network-auth-DNmhJTZn.js +0 -1
- package/src/client/dist/pwa/assets/notify-theme-B6Hiwvwb.js +0 -1
- package/src/client/dist/pwa/assets/permissionModes-DWdtf81n.js +0 -36
- package/src/client/dist/pwa/assets/render-chat-markdown-DK5BD9mU.js +0 -67
- package/src/client/dist/pwa/assets/use-tours-BmTbosmB.js +0 -2
- package/src/client/dist/pwa/assets/workspace-templates-DUWDLMIX.js +0 -1
- package/src/client/dist/pwa/sounds/7eme-compagnie-03.mp3 +0 -0
- package/src/client/dist/pwa/sounds/aller-ftg.mp3 +0 -0
- package/src/client/dist/pwa/sounds/arrete-de-mentir.mp3 +0 -0
- package/src/client/dist/pwa/sounds/arretez-les-messages.mp3 +0 -0
- package/src/client/dist/pwa/sounds/bah-alors-on-est-nul.mp3 +0 -0
- package/src/client/dist/pwa/sounds/basic-notification.mp3 +0 -0
- package/src/client/dist/pwa/sounds/ca_va_peter.mp3 +0 -0
- package/src/client/dist/pwa/sounds/dry-fart.mp3 +0 -0
- package/src/client/dist/pwa/sounds/faaah.mp3 +0 -0
- package/src/client/dist/pwa/sounds/for-shure.mp3 +0 -0
- package/src/client/dist/pwa/sounds/gta-v-death.mp3 +0 -0
- package/src/client/dist/pwa/sounds/hey.mp3 +0 -0
- package/src/client/dist/pwa/sounds/mais-laisse-moi-dormir-zebi.mp3 +0 -0
- package/src/client/dist/pwa/sounds/nan-tu-degages.mp3 +0 -0
- package/src/client/dist/pwa/sounds/nan-wallah-pardon.mp3 +0 -0
- package/src/client/dist/pwa/sounds/ouais-cest-greg.mp3 +0 -0
- package/src/client/dist/pwa/sounds/pas-ca-zinedine.mp3 +0 -0
- package/src/client/dist/pwa/sounds/ta-gueule.mp3 +0 -0
- package/src/client/dist/pwa/sounds/travail_termine.mp3 +0 -0
- package/src/client/dist/pwa/sounds/tu-vas-la-fermer.mp3 +0 -0
- package/src/client/dist/pwa/sounds/warcraft-3-humain-travail.mp3 +0 -0
- package/src/client/dist/pwa/sounds/yaaa.mp3 +0 -0
|
@@ -6,6 +6,8 @@ import { getDb } from '../server/db/index.js';
|
|
|
6
6
|
import { WORKSPACE_DIALOGUE_TOOLS } from '../shared/workspace-dialogue-tools.js';
|
|
7
7
|
import { createTaskHandler, cronListHandler, deleteTaskHandler, getDevServerStatusHandler, getSessionUsageHandler, getSettingsHandler, getTicketSourcesHandler, getWorkspaceInfoHandler, listDocumentsHandler, listTasksHandler, listWorkspaceImagesHandler, listWorkspacesHandler, logThoughtHandler, markAutoLoopReadyHandler, markTaskDoneHandler, readDocumentHandler, readWorkspaceEventsCsvHandler, setWorkspaceAgentDescriptionHandler, updateTaskHandler, } from './kobo-tasks-handlers.js';
|
|
8
8
|
import { callWorkspaceDialogueTool } from './workspace-dialogue-client.js';
|
|
9
|
+
const handoffId = process.env.KOBO_HANDOFF_ID;
|
|
10
|
+
const handoffToken = process.env.KOBO_HANDOFF_TOKEN;
|
|
9
11
|
const workspaceId = process.env.KOBO_WORKSPACE_ID;
|
|
10
12
|
const dbPath = process.env.KOBO_DB_PATH;
|
|
11
13
|
const settingsPath = process.env.KOBO_SETTINGS_PATH;
|
|
@@ -146,6 +148,16 @@ const TASK_ORDER_PROPERTIES = {
|
|
|
146
148
|
},
|
|
147
149
|
};
|
|
148
150
|
const WORKSPACE_SCOPED_TOOLS = [
|
|
151
|
+
{
|
|
152
|
+
name: 'submit_session_handoff',
|
|
153
|
+
description: 'Submit the final Markdown handoff requested by Kōbō for this generation turn, then end your turn. Only available during a backend-owned session transfer; does not launch or stop sessions.',
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: { report: { type: 'string', minLength: 1, maxLength: 24000 } },
|
|
157
|
+
required: ['report'],
|
|
158
|
+
},
|
|
159
|
+
annotations: { destructiveHint: false, openWorldHint: false },
|
|
160
|
+
},
|
|
149
161
|
{
|
|
150
162
|
name: 'list_tasks',
|
|
151
163
|
description: 'CALL FIRST on any non-trivial turn to know what the user wants done and what is already completed. Returns every task and acceptance criterion for the current workspace with its id, status, sort_order, role, and verification evidence. Re-call periodically (before marking something done, or after the user asks for a status) to stay in sync with user-added or external updates.',
|
|
@@ -412,13 +424,13 @@ const WORKSPACE_SCOPED_TOOLS = [
|
|
|
412
424
|
// ── Knowledge / context tools ─────────────────────────────────────────────
|
|
413
425
|
{
|
|
414
426
|
name: 'list_documents',
|
|
415
|
-
description: 'CALL EARLY on a new session to discover plans, specs, and thoughts previously written for this workspace. Recursively lists every .md under docs/plans/, docs/superpowers/, and .ai/
|
|
427
|
+
description: 'CALL EARLY on a new session to discover plans, specs, and thoughts previously written for this workspace. Recursively lists every .md under docs/plans/, docs/superpowers/, .ai/thoughts/, and .ai/handoffs/. Before writing a new plan, check if one already exists.',
|
|
416
428
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
417
429
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
418
430
|
},
|
|
419
431
|
{
|
|
420
432
|
name: 'read_document',
|
|
421
|
-
description: 'CALL AFTER list_documents when a file title looks relevant to the current task. Returns the full markdown content. Scoped to docs/plans/, docs/superpowers/, .ai/thoughts/ — reject anything else.',
|
|
433
|
+
description: 'CALL AFTER list_documents when a file title looks relevant to the current task. Returns the full markdown content. Scoped to docs/plans/, docs/superpowers/, .ai/thoughts/, .ai/handoffs/ — reject anything else.',
|
|
422
434
|
inputSchema: {
|
|
423
435
|
type: 'object',
|
|
424
436
|
properties: {
|
|
@@ -609,9 +621,13 @@ const GLOBAL_TOOLS = [
|
|
|
609
621
|
* one of the ~35 workspace-scoped branches.
|
|
610
622
|
*/
|
|
611
623
|
const GLOBAL_TOOL_NAMES = new Set(GLOBAL_TOOLS.map((t) => t.name));
|
|
612
|
-
|
|
613
|
-
tools
|
|
614
|
-
|
|
624
|
+
function availableTools() {
|
|
625
|
+
const tools = workspaceId ? [...WORKSPACE_SCOPED_TOOLS, ...GLOBAL_TOOLS] : GLOBAL_TOOLS;
|
|
626
|
+
return tools.filter((tool) => handoffId && handoffToken
|
|
627
|
+
? tool.name === 'submit_session_handoff' || tool.annotations?.readOnlyHint === true
|
|
628
|
+
: tool.name !== 'submit_session_handoff');
|
|
629
|
+
}
|
|
630
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: availableTools() }));
|
|
615
631
|
/** Wrap a successful result as an MCP tool response with JSON text content. */
|
|
616
632
|
function ok(data) {
|
|
617
633
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
@@ -641,6 +657,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
641
657
|
return fail(`Tool '${name}' requires a workspace-bound MCP session (KOBO_WORKSPACE_ID not set).`);
|
|
642
658
|
}
|
|
643
659
|
try {
|
|
660
|
+
if (handoffId && handoffToken && !availableTools().some((tool) => tool.name === name))
|
|
661
|
+
return fail('This turn is reserved for a handoff report. Only read tools and submit_session_handoff are available.');
|
|
662
|
+
if (name === 'submit_session_handoff') {
|
|
663
|
+
if (!workspaceId || !handoffId || !handoffToken)
|
|
664
|
+
return fail('No handoff generation is active for this MCP invocation.');
|
|
665
|
+
return ok(await backendRequest('POST', `/api/workspaces/${workspaceId}/session-handoffs/${handoffId}/report`, {
|
|
666
|
+
token: handoffToken,
|
|
667
|
+
report: a.report,
|
|
668
|
+
}));
|
|
669
|
+
}
|
|
644
670
|
if (WORKSPACE_DIALOGUE_TOOLS.some((tool) => tool.name === name)) {
|
|
645
671
|
if (workspaceId && a.workspace_id === workspaceId && name === 'send_workspace_message')
|
|
646
672
|
return fail('Use the normal conversation to communicate in your own workspace.');
|
|
@@ -3,6 +3,7 @@ import { initMcpMessageSchema } from './mcp-message-schema.js';
|
|
|
3
3
|
import { initReviewReturnSchema } from './review-return-schema.js';
|
|
4
4
|
import { initSchema } from './schema.js';
|
|
5
5
|
import { initSearchSchema } from './search-schema.js';
|
|
6
|
+
import { initSessionHandoffSchema } from './session-handoff-schema.js';
|
|
6
7
|
/** Ordered registry of all schema migrations. Append new entries at the end. */
|
|
7
8
|
export const migrations = [
|
|
8
9
|
{
|
|
@@ -748,6 +749,42 @@ export const migrations = [
|
|
|
748
749
|
{ version: 43, name: 'durable-mcp-message-requests', migrate: initMcpMessageSchema },
|
|
749
750
|
{ version: 44, name: 'review-session-returns', migrate: initReviewReturnSchema },
|
|
750
751
|
{ version: 45, name: 'durable-auto-loop', migrate: initAutoLoopSchema },
|
|
752
|
+
{ version: 46, name: 'session-handoffs', migrate: initSessionHandoffSchema },
|
|
753
|
+
{
|
|
754
|
+
version: 47,
|
|
755
|
+
name: 'session-activation-order',
|
|
756
|
+
migrate(db) {
|
|
757
|
+
const table = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'agent_sessions'").get();
|
|
758
|
+
if (!table)
|
|
759
|
+
return;
|
|
760
|
+
const columns = db.prepare('PRAGMA table_info(agent_sessions)').all();
|
|
761
|
+
if (!columns.some((column) => column.name === 'activation_order'))
|
|
762
|
+
db.exec('ALTER TABLE agent_sessions ADD COLUMN activation_order INTEGER NOT NULL DEFAULT 0');
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
version: 48,
|
|
767
|
+
name: 'workspace-workflow-preferences',
|
|
768
|
+
migrate(db) {
|
|
769
|
+
if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='workspaces'").get())
|
|
770
|
+
return;
|
|
771
|
+
const columns = db.prepare('PRAGMA table_info(workspaces)').all();
|
|
772
|
+
if (!columns.some((column) => column.name === 'workflow_policy')) {
|
|
773
|
+
db.exec('ALTER TABLE workspaces ADD COLUMN workflow_policy TEXT');
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
version: 49,
|
|
779
|
+
name: 'preserve-wakeup-deadline-on-retry',
|
|
780
|
+
migrate(db) {
|
|
781
|
+
if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='pending_wakeups'").get())
|
|
782
|
+
return;
|
|
783
|
+
const columns = db.prepare('PRAGMA table_info(pending_wakeups)').all();
|
|
784
|
+
if (!columns.some((column) => column.name === 'retry_at'))
|
|
785
|
+
db.exec('ALTER TABLE pending_wakeups ADD COLUMN retry_at TEXT');
|
|
786
|
+
},
|
|
787
|
+
},
|
|
751
788
|
];
|
|
752
789
|
/** Current schema version — always equals the highest migration version. */
|
|
753
790
|
export const SCHEMA_VERSION = migrations.length > 0 ? migrations[migrations.length - 1].version : 1;
|
package/dist/server/db/schema.js
CHANGED
|
@@ -2,6 +2,7 @@ import { initAutoLoopSchema } from './auto-loop-schema.js';
|
|
|
2
2
|
import { initMcpMessageSchema } from './mcp-message-schema.js';
|
|
3
3
|
import { initReviewReturnSchema } from './review-return-schema.js';
|
|
4
4
|
import { initSearchSchema } from './search-schema.js';
|
|
5
|
+
import { initSessionHandoffSchema } from './session-handoff-schema.js';
|
|
5
6
|
/** Create all tables and indexes for a fresh install. Not used for upgrades -- see migrations.ts. */
|
|
6
7
|
export function initSchema(db) {
|
|
7
8
|
db.exec(`
|
|
@@ -34,6 +35,7 @@ export function initSchema(db) {
|
|
|
34
35
|
auto_loop_session_mode TEXT NOT NULL DEFAULT 'per_task',
|
|
35
36
|
no_progress_streak INTEGER NOT NULL DEFAULT 0,
|
|
36
37
|
permission_profile TEXT NOT NULL DEFAULT 'bypass',
|
|
38
|
+
workflow_policy TEXT,
|
|
37
39
|
agent_permission_mode TEXT NOT NULL DEFAULT 'bypass',
|
|
38
40
|
description TEXT,
|
|
39
41
|
agent_description TEXT,
|
|
@@ -70,7 +72,8 @@ export function initSchema(db) {
|
|
|
70
72
|
ended_at TEXT,
|
|
71
73
|
name TEXT,
|
|
72
74
|
task_progress_baseline TEXT,
|
|
73
|
-
end_reason TEXT
|
|
75
|
+
end_reason TEXT,
|
|
76
|
+
activation_order INTEGER NOT NULL DEFAULT 0
|
|
74
77
|
);
|
|
75
78
|
|
|
76
79
|
CREATE TABLE IF NOT EXISTS ws_events (
|
|
@@ -149,6 +152,7 @@ export function initSchema(db) {
|
|
|
149
152
|
CREATE TABLE IF NOT EXISTS pending_wakeups (
|
|
150
153
|
workspace_id TEXT PRIMARY KEY REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
151
154
|
target_at TEXT NOT NULL,
|
|
155
|
+
retry_at TEXT,
|
|
152
156
|
prompt TEXT NOT NULL,
|
|
153
157
|
reason TEXT,
|
|
154
158
|
created_at TEXT NOT NULL,
|
|
@@ -229,4 +233,5 @@ export function initSchema(db) {
|
|
|
229
233
|
initMcpMessageSchema(db);
|
|
230
234
|
initReviewReturnSchema(db);
|
|
231
235
|
initAutoLoopSchema(db);
|
|
236
|
+
initSessionHandoffSchema(db);
|
|
232
237
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function initSessionHandoffSchema(db) {
|
|
2
|
+
db.exec(`CREATE TABLE IF NOT EXISTS session_handoffs (
|
|
3
|
+
id TEXT PRIMARY KEY,
|
|
4
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
5
|
+
request_id TEXT NOT NULL,
|
|
6
|
+
request_fingerprint TEXT NOT NULL,
|
|
7
|
+
source_session_id TEXT,
|
|
8
|
+
target_session_id TEXT,
|
|
9
|
+
source_configuration TEXT NOT NULL,
|
|
10
|
+
target_configuration TEXT NOT NULL,
|
|
11
|
+
source_model TEXT,
|
|
12
|
+
generate_summary INTEGER NOT NULL,
|
|
13
|
+
state TEXT NOT NULL,
|
|
14
|
+
report TEXT,
|
|
15
|
+
report_path TEXT,
|
|
16
|
+
generation_token TEXT,
|
|
17
|
+
error TEXT,
|
|
18
|
+
created_at TEXT NOT NULL,
|
|
19
|
+
updated_at TEXT NOT NULL,
|
|
20
|
+
UNIQUE(workspace_id, request_id)
|
|
21
|
+
);
|
|
22
|
+
CREATE UNIQUE INDEX IF NOT EXISTS session_handoffs_pending
|
|
23
|
+
ON session_handoffs(workspace_id) WHERE state NOT IN ('completed', 'cancelled');`);
|
|
24
|
+
}
|
package/dist/server/index.js
CHANGED
|
@@ -13,11 +13,13 @@ import changelogRouter from './routes/changelog.js';
|
|
|
13
13
|
import devServerRouter from './routes/dev-server.js';
|
|
14
14
|
import documentsRouter from './routes/documents.js';
|
|
15
15
|
import { enginesRouter } from './routes/engines.js';
|
|
16
|
+
import environmentRouter from './routes/environment.js';
|
|
16
17
|
import exportRouter from './routes/export.js';
|
|
17
18
|
import fsRouter from './routes/fs.js';
|
|
18
19
|
import gitRouter from './routes/git.js';
|
|
19
20
|
import healthRouter from './routes/health.js';
|
|
20
21
|
import imagesRouter from './routes/images.js';
|
|
22
|
+
import integrationsRouter from './routes/integrations.js';
|
|
21
23
|
import mcpRouter from './routes/mcp.js';
|
|
22
24
|
import { migrationRouter } from './routes/migration.js';
|
|
23
25
|
import notionRouter from './routes/notion.js';
|
|
@@ -25,6 +27,7 @@ import pullRequestsRouter from './routes/pull-requests.js';
|
|
|
25
27
|
import searchRouter from './routes/search.js';
|
|
26
28
|
import sentryRouter from './routes/sentry.js';
|
|
27
29
|
import settingsRouter from './routes/settings.js';
|
|
30
|
+
import soundsRouter from './routes/sounds.js';
|
|
28
31
|
import templatesRouter from './routes/templates.js';
|
|
29
32
|
import usageRoutes from './routes/usage.js';
|
|
30
33
|
import voiceRouter from './routes/voice.js';
|
|
@@ -43,6 +46,7 @@ import { startPrWatcher, stopPrWatcher } from './services/pr-watcher-service.js'
|
|
|
43
46
|
import * as quotaBackoffService from './services/quota-backoff-service.js';
|
|
44
47
|
import { reconcileReviewReturns } from './services/review-return-service.js';
|
|
45
48
|
import { startSearchIndex, stopSearchIndex } from './services/search-service.js';
|
|
49
|
+
import { reconcileSessionHandoffs } from './services/session-handoff-service.js';
|
|
46
50
|
import { getGlobalSettings, updateNetworkAccessSettings } from './services/settings-service.js';
|
|
47
51
|
import { reloadDefaultTemplates } from './services/templates-service.js';
|
|
48
52
|
import { createTerminal, destroyAllTerminals, getTerminal } from './services/terminal-service.js';
|
|
@@ -118,6 +122,7 @@ const dailyBackupScheduler = startDailyDbBackupScheduler(async () => {
|
|
|
118
122
|
// Initialize process cleanup, agent watchdog, PR watcher, and wakeup rehydration
|
|
119
123
|
reconcileOrphanSessions();
|
|
120
124
|
reconcileReviewReturns();
|
|
125
|
+
reconcileSessionHandoffs();
|
|
121
126
|
// Deliver any new default prompt templates to existing installs (seed-once via the
|
|
122
127
|
// seededDefaultSlugs watermark; never overwrites or re-adds deleted defaults).
|
|
123
128
|
try {
|
|
@@ -164,6 +169,9 @@ app.route('/api/workspaces', exportRouter);
|
|
|
164
169
|
app.route('/api/search', searchRouter);
|
|
165
170
|
app.route('/api/health', healthRouter);
|
|
166
171
|
app.route('/api/engines', enginesRouter);
|
|
172
|
+
app.route('/api/environment', environmentRouter);
|
|
173
|
+
app.route('/api/integrations', integrationsRouter);
|
|
174
|
+
app.route('/api/sounds', soundsRouter);
|
|
167
175
|
app.route('/api/migration', migrationRouter);
|
|
168
176
|
app.route('/api/voice', voiceRouter);
|
|
169
177
|
// Skills endpoint
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Hono } from 'hono';
|
|
4
|
+
import { DOCUMENT_DIRS } from '../../shared/document-roots.js';
|
|
4
5
|
import * as workspaceService from '../services/workspace-service.js';
|
|
5
6
|
import { isPathInside, resolveExistingPathInside } from '../utils/safe-path.js';
|
|
6
7
|
/** Hono sub-router for workspace document browsing (read-only). */
|
|
@@ -13,7 +14,6 @@ const app = new Hono();
|
|
|
13
14
|
* Kept intentionally narrow to avoid leaking unrelated project docs
|
|
14
15
|
* (README, product specs, …) into the panel.
|
|
15
16
|
*/
|
|
16
|
-
const DOCUMENT_DIRS = ['docs/plans', 'docs/superpowers', '.ai/thoughts'];
|
|
17
17
|
/** Only .md files are listed. */
|
|
18
18
|
const MD_EXT = '.md';
|
|
19
19
|
/** Depth cap to keep recursion bounded even on pathological symlink loops. */
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { getEnvironmentReport } from '../services/environment-check-service.js';
|
|
3
|
+
const app = new Hono();
|
|
4
|
+
app.get('/', async (c) => {
|
|
5
|
+
const engine = c.req.query('engine') ?? 'claude-code';
|
|
6
|
+
const projectPath = c.req.query('projectPath')?.trim();
|
|
7
|
+
if (!['claude-code', 'codex'].includes(engine) ||
|
|
8
|
+
(projectPath && (projectPath.length > 4096 || projectPath.includes('\0')))) {
|
|
9
|
+
return c.json({ error: 'Invalid environment check request' }, 400);
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
return c.json(await getEnvironmentReport({ engine: engine, projectPath }));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return c.json({ error: 'Environment check unavailable; retry shortly' }, 503);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
export default app;
|
|
@@ -5,6 +5,7 @@ import { Hono } from 'hono';
|
|
|
5
5
|
import { getDb } from '../db/index.js';
|
|
6
6
|
import { SCHEMA_VERSION } from '../db/migrations.js';
|
|
7
7
|
import { resolveCodexBinary } from '../services/agent/engines/codex/spawn.js';
|
|
8
|
+
import { hasIntegrationMcpConfig } from '../services/agent/integration-mcp.js';
|
|
8
9
|
import { getGlobalSettings, getProjectSettings, SETTINGS_SCHEMA_VERSION } from '../services/settings-service.js';
|
|
9
10
|
import { readRecentLogs } from '../utils/logger.js';
|
|
10
11
|
import { getDbPath, getKoboHome, getPackageVersion } from '../utils/paths.js';
|
|
@@ -12,6 +13,14 @@ import { slugifyProjectName } from '../utils/project-slug.js';
|
|
|
12
13
|
import { resolveWorkspaceWorktreePath } from '../utils/worktree-paths.js';
|
|
13
14
|
const app = new Hono();
|
|
14
15
|
const execFileAsync = promisify(execFileCb);
|
|
16
|
+
function integrationConfigured(integration, key) {
|
|
17
|
+
try {
|
|
18
|
+
return hasIntegrationMcpConfig(integration, key);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
15
24
|
async function checkCli(binary) {
|
|
16
25
|
try {
|
|
17
26
|
const { stdout } = await execFileAsync(binary, ['--version'], { encoding: 'utf-8', timeout: 2_000 });
|
|
@@ -182,8 +191,14 @@ app.get('/report', async (c) => {
|
|
|
182
191
|
},
|
|
183
192
|
agentSessions: { orphaned },
|
|
184
193
|
integrations: {
|
|
185
|
-
notion: {
|
|
186
|
-
|
|
194
|
+
notion: {
|
|
195
|
+
configured: integrationConfigured('notion', healthGlobalSettings.notionMcpKey),
|
|
196
|
+
enabled: healthGlobalSettings.notionEnabled,
|
|
197
|
+
},
|
|
198
|
+
sentry: {
|
|
199
|
+
configured: integrationConfigured('sentry', healthGlobalSettings.sentryMcpKey),
|
|
200
|
+
enabled: healthGlobalSettings.sentryEnabled,
|
|
201
|
+
},
|
|
187
202
|
editor: { configured: Boolean(healthGlobalSettings.editorCommand) },
|
|
188
203
|
},
|
|
189
204
|
active: {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { bodyLimit } from 'hono/body-limit';
|
|
3
|
+
import { getIntegrationStatus, saveIntegrationConfig, } from '../services/integration-config-service.js';
|
|
4
|
+
const app = new Hono();
|
|
5
|
+
app.use('*', bodyLimit({ maxSize: 64 * 1024 }));
|
|
6
|
+
app.use('/:integration', async (c, next) => {
|
|
7
|
+
if (!['notion', 'sentry'].includes(c.req.param('integration') ?? ''))
|
|
8
|
+
return c.json({ error: 'Unknown integration' }, 404);
|
|
9
|
+
await next();
|
|
10
|
+
});
|
|
11
|
+
app.get('/:integration', (c) => {
|
|
12
|
+
try {
|
|
13
|
+
return c.json(getIntegrationStatus(c.req.param('integration')));
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return c.json({ error: 'Integration configuration is unavailable' }, 503);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
app.put('/:integration', async (c) => {
|
|
20
|
+
try {
|
|
21
|
+
const value = await c.req.json();
|
|
22
|
+
saveIntegrationConfig(c.req.param('integration'), value);
|
|
23
|
+
return c.json(getIntegrationStatus(c.req.param('integration')));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return c.json({ error: 'Cannot save integration configuration; check the connection fields and local storage' }, 400);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
export default app;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { isValidSkillSuite } from '../../shared/skill-suite-prompts.js';
|
|
3
|
+
import { isWorkflowPolicy } from '../../shared/workflow-policy.js';
|
|
3
4
|
import { getDb } from '../db/index.js';
|
|
4
5
|
import { getBackendPort } from '../services/agent/orchestrator.js';
|
|
5
6
|
import { DEFAULT_NOTION_INITIAL_PROMPT, DEFAULT_SENTRY_INITIAL_PROMPT, } from '../services/initial-prompt-template-service.js';
|
|
@@ -163,6 +164,8 @@ app.put('/global', async (c) => {
|
|
|
163
164
|
// must answer 400 rather than do that.
|
|
164
165
|
if (!body)
|
|
165
166
|
return c.json({ error: 'Invalid JSON body' }, 400);
|
|
167
|
+
if (body.workflowPolicy !== undefined && !isWorkflowPolicy(body.workflowPolicy))
|
|
168
|
+
return c.json({ error: 'Invalid workflowPolicy' }, 400);
|
|
166
169
|
const updated = settingsService.updateGlobalSettings(body);
|
|
167
170
|
// The client assigns this response straight into its store, so echoing the
|
|
168
171
|
// real credentials back would undo the masking on GET the first time
|
|
@@ -211,6 +214,8 @@ app.put('/projects/:encodedPath', async (c) => {
|
|
|
211
214
|
// `{}` CREATES a project entry with defaults; a malformed body must not.
|
|
212
215
|
if (!body)
|
|
213
216
|
return c.json({ error: 'Invalid JSON body' }, 400);
|
|
217
|
+
if (body.workflowPolicy !== undefined && !isWorkflowPolicy(body.workflowPolicy))
|
|
218
|
+
return c.json({ error: 'Invalid workflowPolicy' }, 400);
|
|
214
219
|
const project = settingsService.upsertProject(projectPath, body);
|
|
215
220
|
return c.json(project);
|
|
216
221
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { bodyLimit } from 'hono/body-limit';
|
|
4
|
+
import { MAX_CUSTOM_SOUND_BYTES } from '../../shared/notification-assets.js';
|
|
5
|
+
import { CustomSoundError, deleteCustomSound, listCustomSounds, readCustomSound, saveCustomSound, } from '../services/custom-sound-service.js';
|
|
6
|
+
/** Multipart framing and the field name add a small, bounded overhead. */
|
|
7
|
+
const MAX_REQUEST_BYTES = MAX_CUSTOM_SOUND_BYTES + 64 * 1024;
|
|
8
|
+
const app = new Hono();
|
|
9
|
+
// `listCustomSounds` degrades a damaged manifest to an empty list; it never throws.
|
|
10
|
+
app.get('/', (c) => c.json({ sounds: listCustomSounds() }));
|
|
11
|
+
app.post('/', bodyLimit({ maxSize: MAX_REQUEST_BYTES }), async (c) => {
|
|
12
|
+
let form;
|
|
13
|
+
try {
|
|
14
|
+
form = await c.req.raw.formData();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return c.json({ error: 'Invalid multipart body' }, 400);
|
|
18
|
+
}
|
|
19
|
+
const files = form.getAll('sound');
|
|
20
|
+
if (files.length !== 1 || !(files[0] instanceof File))
|
|
21
|
+
return c.json({ error: 'Expected one sound file' }, 400);
|
|
22
|
+
try {
|
|
23
|
+
return c.json(await saveCustomSound(files[0]), 201);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
// Only the stable code crosses the boundary: a filesystem error may carry
|
|
27
|
+
// the layout of the user's home directory.
|
|
28
|
+
if (error instanceof CustomSoundError)
|
|
29
|
+
return c.json({ error: 'Invalid sound file', code: error.code }, 400);
|
|
30
|
+
return c.json({ error: 'Cannot store the sound file' }, 500);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
app.delete('/:id', (c) => {
|
|
34
|
+
try {
|
|
35
|
+
return deleteCustomSound(c.req.param('id')) ? c.body(null, 204) : c.json({ error: 'Sound not found' }, 404);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return c.json({ error: 'Cannot remove the sound file' }, 500);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// An unknown or deleted sound answers 404; the client then falls back to a bundled tone.
|
|
42
|
+
app.get('/:id/file', async (c) => {
|
|
43
|
+
const sound = readCustomSound(c.req.param('id'));
|
|
44
|
+
if (!sound)
|
|
45
|
+
return c.json({ error: 'Sound not found' }, 404);
|
|
46
|
+
let content;
|
|
47
|
+
try {
|
|
48
|
+
content = await fs.promises.readFile(sound.filePath);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// The file vanished between the manifest lookup and the read.
|
|
52
|
+
return c.json({ error: 'Sound not found' }, 404);
|
|
53
|
+
}
|
|
54
|
+
c.header('Content-Type', sound.contentType);
|
|
55
|
+
// Content-addressed by a generated id: the bytes behind an id never change.
|
|
56
|
+
c.header('Cache-Control', 'private, max-age=3600, immutable');
|
|
57
|
+
c.header('X-Content-Type-Options', 'nosniff');
|
|
58
|
+
return c.body(new Uint8Array(content));
|
|
59
|
+
});
|
|
60
|
+
export default app;
|