@loicngr/kobo 1.11.39 → 1.11.40
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 +12 -0
- package/CHANGELOG.md +4 -0
- package/dist/mcp-server/kobo-tasks-handlers.js +16 -9
- package/dist/mcp-server/kobo-tasks-server.js +13 -2
- package/dist/mcp-server/workspace-dialogue-client.js +28 -0
- package/dist/server/db/mcp-message-schema.js +16 -0
- package/dist/server/db/migrations.js +4 -0
- package/dist/server/db/schema.js +4 -0
- package/dist/server/db/search-schema.js +53 -0
- package/dist/server/index.js +17 -86
- package/dist/server/middleware/network-auth-middleware.js +5 -1
- package/dist/server/routes/changelog.js +4 -49
- package/dist/server/routes/git.js +2 -2
- package/dist/server/routes/mcp.js +47 -0
- package/dist/server/routes/pull-requests.js +1 -1
- package/dist/server/routes/search.js +13 -4
- package/dist/server/routes/settings.js +5 -0
- package/dist/server/routes/workspaces.js +225 -241
- package/dist/server/services/agent/engines/claude-code/engine.js +31 -33
- package/dist/server/services/agent/engines/codex/engine.js +37 -8
- package/dist/server/services/agent/engines/codex/event-mapper.js +3 -1
- package/dist/server/services/agent/engines/codex/options-builder.js +4 -3
- package/dist/server/services/agent/engines/codex/server-requests.js +8 -1
- package/dist/server/services/agent/orchestrator.js +20 -3
- package/dist/server/services/change-source-branch-service.js +10 -42
- package/dist/server/services/chat-receipt-service.js +33 -0
- package/dist/server/services/cron-service.js +27 -1
- package/dist/server/services/mcp-client-context.js +12 -0
- package/dist/server/services/mcp-connection-info-service.js +63 -0
- package/dist/server/services/mcp-message-delivery-service.js +59 -0
- package/dist/server/services/mcp-message-request-service.js +64 -0
- package/dist/server/services/pr-checkout-service.js +10 -10
- package/dist/server/services/pr-watcher-service.js +14 -9
- package/dist/server/services/search/indexer.js +232 -0
- package/dist/server/services/search/worker.js +76 -0
- package/dist/server/services/search-service.js +104 -110
- package/dist/server/services/update-check-service.js +119 -0
- package/dist/server/services/websocket-service.js +121 -35
- package/dist/server/services/workspace-dialogue-service.js +128 -0
- package/dist/server/services/workspace-message-service.js +58 -0
- package/dist/server/services/workspace-permission-policy-service.js +33 -3
- package/dist/server/services/worktree-restore-service.js +2 -2
- package/dist/server/services/worktree-service.js +197 -124
- package/dist/server/utils/bounded-process.js +137 -0
- package/dist/server/utils/git-ops.js +76 -128
- package/dist/server/utils/git-repo-lock.js +27 -13
- package/dist/shared/workspace-dialogue-tools.js +89 -0
- package/dist/shared/workspace-message-types.js +19 -0
- package/package.json +2 -1
- package/src/client/dist/pwa/assets/{ActivityFeed-Chsw1Mh7.css → ActivityFeed-BYR5x7aa.css} +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-MqsC-95i.js +8 -0
- package/src/client/dist/pwa/assets/{ChangelogPage-D8563008.js → ChangelogPage-BxURWYxa.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-Bd0X08sg.js → ComparisonPanel-DjiMDAUi.js} +2 -2
- package/src/client/dist/pwa/assets/CreatePage-B-rhBmqv.js +2 -0
- package/src/client/dist/pwa/assets/DashboardPage-mA5RZf1t.js +1 -0
- package/src/client/dist/pwa/assets/DiffViewer-Ci7ucmy3.js +8 -0
- package/src/client/dist/pwa/assets/{DiffViewer-DnR79ycb.css → DiffViewer-DEPOa3D7.css} +1 -1
- package/src/client/dist/pwa/assets/{DocumentsPanel-BJsJ0fNs.js → DocumentsPanel-RZTvcJkT.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-BVLO5K5d.js → DrawerToggleButton-CTOc3yA0.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-_kL2gMoy.js +3 -0
- package/src/client/dist/pwa/assets/{HealthPage-RFZS7Lao.js → HealthPage-D3__hKJ5.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-B3JKUm3n.js +2 -0
- package/src/client/dist/pwa/assets/{QBtnDropdown-X8oNU5Mb.js → QBtnDropdown-td0RAVPx.js} +1 -1
- package/src/client/dist/pwa/assets/{QDrawer-Cij17skX.js → QDrawer-BkyFPV7a.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-WspK3KC7.js +1 -0
- package/src/client/dist/pwa/assets/{QTooltip-CwcCGSWq.js → QTooltip-7pw7iuV0.js} +1 -1
- package/src/client/dist/pwa/assets/SchedulePanel-BAA4Aajn.css +1 -0
- package/src/client/dist/pwa/assets/SchedulePanel-CLNM13oo.js +1 -0
- package/src/client/dist/pwa/assets/SearchPage-BGc1oQSM.css +1 -0
- package/src/client/dist/pwa/assets/SearchPage-CmRgIvu4.js +1 -0
- package/src/client/dist/pwa/assets/SettingsPage-Cj6Y_TlN.js +9 -0
- package/src/client/dist/pwa/assets/SettingsPage-sVlsJQ0b.css +1 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-D7lGFRKs.js → SplitWorkspacePage-CCHGEsKf.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-DI8DHb9e.js → TerminalPanel-ijmsYk-2.js} +9 -9
- package/src/client/dist/pwa/assets/{TouchPan-B3nrkCY8.js → TouchPan-DuQFjlQF.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-DU1UShMY.js → TourReplayButton-C5RF0lOK.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-C-yN5IX8.js → WorkspaceAttentionLabels-BR_XElog.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-BGCildyP.js +4 -0
- package/src/client/dist/pwa/assets/{WorkspacePage-BAC6u9OP.css → WorkspacePage-CqcMweNL.css} +1 -1
- package/src/client/dist/pwa/assets/{browser-f9MHQjfi.js → browser-DUqzdkbg.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-BWBzP8os.js → build-path-tree-ImiSpzfI.js} +1 -1
- package/src/client/dist/pwa/assets/de-CaHID9mW.js +17 -0
- package/src/client/dist/pwa/assets/{documents-C2cLnEBQ.js → documents-2C1hn1pw.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.api-sw146MWN.js → editor.api-Bm1vVaBr.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-C5ZZgCH4.js → efforts-_D8DFYXo.js} +1 -1
- package/src/client/dist/pwa/assets/engineFeatures-CZ9qfBYq.js +1 -0
- package/src/client/dist/pwa/assets/es-ZhzlCmer.js +17 -0
- package/src/client/dist/pwa/assets/{expand-template-B6xAlAaM.js → expand-template-CbDNnTPP.js} +1 -1
- package/src/client/dist/pwa/assets/{format-BwWP6Aay.js → format-pPImdFUy.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-DyWtO-nR.js → formatters-dTX38ZsV.js} +1 -1
- package/src/client/dist/pwa/assets/fr-BfXw7L7J.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-DZ5Hm8UH.js → freemarker2-DGluDnAm.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-BRUGpTA7.js → handlebars-BgviTnpD.js} +1 -1
- package/src/client/dist/pwa/assets/{html-DNOayNNs.js → html-CV-MrunL.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-CmXBenUp.js +1 -0
- package/src/client/dist/pwa/assets/index-9tiw3NjK.js +20 -0
- package/src/client/dist/pwa/assets/it-CHAurcTF.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-C0tDQU3z.js → javascript-C2VFcHf2.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-CN46O6OF.js → liquid-Dg9ZhbP5.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-DM-lrr8Q.js → mdx-ImgrPYNl.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-CkN7y-8c.js → monaco-contributions-waWZXS1T.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-2BcAOLjc.js → monaco.contribution-BB2rtCHM.js} +2 -2
- package/src/client/dist/pwa/assets/network-auth-h6O0qCMA.js +1 -0
- package/src/client/dist/pwa/assets/{notify-theme-a91Tcr6o.js → notify-theme-Dyg-FGI6.js} +1 -1
- package/src/client/dist/pwa/assets/{pending-diff-open-DOtELZfB.js → pending-diff-open-Dfpg92li.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-C-9Crj-c.js → permissionModes-DDWmNBW1.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-BRe4oyNx.js → project-color-XUfY_zbw.js} +1 -1
- package/src/client/dist/pwa/assets/{python-BRpJG6sZ.js → python-DVLs-DBA.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-CehCmvHl.js → razor-D34BqMwB.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-DlbQLZ02.js → render-chat-markdown-DwUkPy-O.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-Dt_8mVuj.js → typescript-C7-UpNXx.js} +1 -1
- package/src/client/dist/pwa/assets/use-search-index-status-DtLzygcy.js +1 -0
- package/src/client/dist/pwa/assets/use-tours-DfE-kwrX.js +2 -0
- package/src/client/dist/pwa/assets/{workspace-templates-GCObcjO6.js → workspace-templates-DwwdOEAq.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-_gOwdzB4.js → xml-Cx1oQhEr.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-BxeBqpGf.js → yaml-BQOiJsdx.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/mcp-server/README.md +118 -2
- package/src/mcp-server/kobo-tasks-handlers.ts +14 -11
- package/src/mcp-server/kobo-tasks-server.ts +19 -2
- package/src/mcp-server/workspace-dialogue-client.ts +34 -0
- package/src/client/dist/pwa/assets/ActivityFeed-BzzvcOmq.js +0 -8
- package/src/client/dist/pwa/assets/CreatePage-Bon8qIka.js +0 -2
- package/src/client/dist/pwa/assets/DashboardPage-DYxdxEul.js +0 -1
- package/src/client/dist/pwa/assets/DiffViewer-BpsLNxnj.js +0 -8
- package/src/client/dist/pwa/assets/GitPanel-Cal_wzC0.js +0 -3
- package/src/client/dist/pwa/assets/MainLayout-vvyiRcql.js +0 -2
- package/src/client/dist/pwa/assets/QSelect-xd-AlGo_.js +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-BunlzsOY.css +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-CXRUsz6G.js +0 -1
- package/src/client/dist/pwa/assets/SearchPage-CVgRa025.css +0 -1
- package/src/client/dist/pwa/assets/SearchPage-DopbgLxh.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-Do9d0Pov.css +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-Jx6TnOzR.js +0 -9
- package/src/client/dist/pwa/assets/WorkspacePage-DKRpdpPg.js +0 -4
- package/src/client/dist/pwa/assets/de-QkDLrcGq.js +0 -17
- package/src/client/dist/pwa/assets/engineFeatures-CAVMDawq.js +0 -1
- package/src/client/dist/pwa/assets/es-BgQFKlo8.js +0 -17
- package/src/client/dist/pwa/assets/fr-DAdLWjNM.js +0 -17
- package/src/client/dist/pwa/assets/i18n-CBez_vip.js +0 -1
- package/src/client/dist/pwa/assets/index-VL3fI9a3.js +0 -20
- package/src/client/dist/pwa/assets/it-Bumaewp1.js +0 -17
- package/src/client/dist/pwa/assets/network-auth-KYS4snQH.js +0 -1
- package/src/client/dist/pwa/assets/use-tours-CSVM_54K.js +0 -2
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
import { validateDialogueArguments } from '../../shared/workspace-dialogue-tools.js';
|
|
3
|
+
import { parseMessageSource } from '../../shared/workspace-message-types.js';
|
|
4
|
+
import { getDb } from '../db/index.js';
|
|
5
|
+
import { assertWorkspaceLifecycleAvailable } from '../utils/workspace-lifecycle-guard.js';
|
|
6
|
+
import { answerPendingQuestion, getPendingInputs } from './agent/orchestrator.js';
|
|
7
|
+
import { createMcpClientContext } from './mcp-client-context.js';
|
|
8
|
+
import { deliverKeyedMcpMessage } from './mcp-message-delivery-service.js';
|
|
9
|
+
import { deliverWorkspaceMessage } from './workspace-message-service.js';
|
|
10
|
+
import { getActiveSession, getWorkspace, listSessions, listTasks, listWorkspaces } from './workspace-service.js';
|
|
11
|
+
function readableMessage(row) {
|
|
12
|
+
let payload;
|
|
13
|
+
try {
|
|
14
|
+
payload = JSON.parse(row.payload);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
if (!payload || typeof payload !== 'object')
|
|
20
|
+
return [];
|
|
21
|
+
let text;
|
|
22
|
+
if (row.type === 'user:message' && typeof payload.content === 'string')
|
|
23
|
+
text = payload.content;
|
|
24
|
+
if (row.type === 'agent:event' && payload.kind === 'message:text' && typeof payload.text === 'string')
|
|
25
|
+
text = payload.text;
|
|
26
|
+
if (row.type === 'agent:output') {
|
|
27
|
+
const content = payload.message?.content;
|
|
28
|
+
if (Array.isArray(content))
|
|
29
|
+
text = content
|
|
30
|
+
.flatMap((block) => (block?.type === 'text' && typeof block.text === 'string' ? [block.text] : []))
|
|
31
|
+
.join('\n');
|
|
32
|
+
}
|
|
33
|
+
return text === undefined
|
|
34
|
+
? []
|
|
35
|
+
: [
|
|
36
|
+
{
|
|
37
|
+
id: row.id,
|
|
38
|
+
sessionId: row.session_id,
|
|
39
|
+
role: row.type === 'user:message' ? 'user' : 'assistant',
|
|
40
|
+
text,
|
|
41
|
+
messageId: typeof payload.messageId === 'string' ? payload.messageId : row.id,
|
|
42
|
+
createdAt: row.created_at,
|
|
43
|
+
...(parseMessageSource(payload.source) ? { source: parseMessageSource(payload.source) } : {}),
|
|
44
|
+
...(typeof payload.clientMessageId === 'string' ? { clientMessageId: payload.clientMessageId } : {}),
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
export async function executeWorkspaceDialogueTool(name, input, source = createMcpClientContext()) {
|
|
49
|
+
const args = validateDialogueArguments(name, input);
|
|
50
|
+
if (name === 'list_workspaces')
|
|
51
|
+
return listWorkspaces(args.include_archived === true).map((ws) => ({
|
|
52
|
+
id: ws.id,
|
|
53
|
+
name: ws.name,
|
|
54
|
+
status: ws.status,
|
|
55
|
+
projectPath: ws.projectPath,
|
|
56
|
+
archivedAt: ws.archivedAt,
|
|
57
|
+
}));
|
|
58
|
+
const workspaceId = args.workspace_id;
|
|
59
|
+
const workspace = getWorkspace(workspaceId);
|
|
60
|
+
if (!workspace)
|
|
61
|
+
throw new Error(`Workspace '${workspaceId}' not found`);
|
|
62
|
+
if (name === 'get_workspace')
|
|
63
|
+
return { workspace, tasks: listTasks(workspaceId), activeSession: getActiveSession(workspaceId) };
|
|
64
|
+
if (name === 'list_workspace_sessions')
|
|
65
|
+
return listSessions(workspaceId);
|
|
66
|
+
if (name === 'get_workspace_questions') {
|
|
67
|
+
const pending = getPendingInputs(workspaceId);
|
|
68
|
+
return {
|
|
69
|
+
questions: pending.filter((item) => item.kind === 'question'),
|
|
70
|
+
requiresHumanApproval: pending[0]?.kind === 'permission',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (name === 'answer_workspace_question') {
|
|
74
|
+
assertWorkspaceLifecycleAvailable(workspaceId);
|
|
75
|
+
if (workspace.archivedAt || workspace.worktreePurgedAt)
|
|
76
|
+
throw new Error('Restore the workspace before answering a question');
|
|
77
|
+
const head = getPendingInputs(workspaceId)[0];
|
|
78
|
+
if (head?.kind !== 'question' || head.toolCallId !== args.tool_call_id)
|
|
79
|
+
throw new Error('The requested question is not the current pending question');
|
|
80
|
+
await answerPendingQuestion(workspaceId, args.answers, args.tool_call_id, {
|
|
81
|
+
source,
|
|
82
|
+
});
|
|
83
|
+
return { accepted: true };
|
|
84
|
+
}
|
|
85
|
+
if (name === 'send_workspace_message' && args.idempotency_key)
|
|
86
|
+
return deliverKeyedMcpMessage(workspaceId, args.idempotency_key, {
|
|
87
|
+
content: args.content,
|
|
88
|
+
sessionId: args.session_id,
|
|
89
|
+
source,
|
|
90
|
+
});
|
|
91
|
+
if (name === 'send_workspace_message')
|
|
92
|
+
return {
|
|
93
|
+
accepted: true,
|
|
94
|
+
...(await deliverWorkspaceMessage(workspaceId, {
|
|
95
|
+
content: args.content,
|
|
96
|
+
sessionId: args.session_id,
|
|
97
|
+
clientMessageId: `mcp-${nanoid()}`,
|
|
98
|
+
source,
|
|
99
|
+
})),
|
|
100
|
+
};
|
|
101
|
+
const db = getDb();
|
|
102
|
+
let after = 0;
|
|
103
|
+
if (args.after_cursor) {
|
|
104
|
+
const cursor = db
|
|
105
|
+
.prepare('SELECT rowid FROM ws_events WHERE id = ? AND workspace_id = ?')
|
|
106
|
+
.get(args.after_cursor, workspaceId);
|
|
107
|
+
if (!cursor)
|
|
108
|
+
throw new Error('Unknown or expired history cursor for this workspace; restart reading without after_cursor');
|
|
109
|
+
after = cursor.rowid;
|
|
110
|
+
}
|
|
111
|
+
const limit = args.limit ?? 100;
|
|
112
|
+
const params = [workspaceId, after];
|
|
113
|
+
if (args.session_id)
|
|
114
|
+
params.push(args.session_id);
|
|
115
|
+
params.push(limit + 1);
|
|
116
|
+
const rows = db
|
|
117
|
+
.prepare(`SELECT id,session_id,type,payload,created_at FROM ws_events WHERE workspace_id = ? AND rowid > ?
|
|
118
|
+
${args.session_id ? 'AND (session_id = ? OR session_id IS NULL)' : ''} ORDER BY rowid LIMIT ?`)
|
|
119
|
+
.all(...params);
|
|
120
|
+
const page = rows.slice(0, limit);
|
|
121
|
+
return {
|
|
122
|
+
messages: page.flatMap(readableMessage),
|
|
123
|
+
nextCursor: page.at(-1)?.id ?? args.after_cursor ?? null,
|
|
124
|
+
hasMore: rows.length > limit,
|
|
125
|
+
workspaceStatus: workspace.status,
|
|
126
|
+
activeSessionId: getActiveSession(workspaceId)?.id ?? null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { assertWorkspaceLifecycleAvailable } from '../utils/workspace-lifecycle-guard.js';
|
|
2
|
+
import { isAgentUnavailableError, isShuttingDown, sendMessage, startAgent } from './agent/orchestrator.js';
|
|
3
|
+
import * as autoLoop from './auto-loop-service.js';
|
|
4
|
+
import { createChatReceipt } from './chat-receipt-service.js';
|
|
5
|
+
import { getActiveSession, getWorkspace, updateWorkspaceStatus } from './workspace-service.js';
|
|
6
|
+
/** Shared delivery for WebSocket chat and external MCP clients. */
|
|
7
|
+
export async function deliverWorkspaceMessage(workspaceId, message, hooks) {
|
|
8
|
+
const receipt = createChatReceipt(workspaceId, message, hooks?.persist);
|
|
9
|
+
let reason;
|
|
10
|
+
try {
|
|
11
|
+
if (!message.content.trim())
|
|
12
|
+
throw new Error('Message content must not be empty');
|
|
13
|
+
if (isShuttingDown())
|
|
14
|
+
throw new Error('Kōbō is shutting down');
|
|
15
|
+
assertWorkspaceLifecycleAvailable(workspaceId);
|
|
16
|
+
const workspace = getWorkspace(workspaceId);
|
|
17
|
+
if (!workspace)
|
|
18
|
+
throw new Error(`Workspace '${workspaceId}' not found`);
|
|
19
|
+
if (workspace.archivedAt || workspace.worktreePurgedAt)
|
|
20
|
+
throw new Error('Restore the workspace before sending a message');
|
|
21
|
+
if (workspace.status === 'compacting') {
|
|
22
|
+
reason = 'compacting';
|
|
23
|
+
throw new Error('Workspace is compacting its context; wait until compaction finishes before sending a message');
|
|
24
|
+
}
|
|
25
|
+
if (workspace.status === 'awaiting-user') {
|
|
26
|
+
reason = 'awaiting-user';
|
|
27
|
+
throw new Error('Answer the pending question or permission request before sending a chat message');
|
|
28
|
+
}
|
|
29
|
+
const loop = autoLoop.getStatus(workspaceId);
|
|
30
|
+
if (loop.auto_loop && loop.auto_loop_ready)
|
|
31
|
+
autoLoop.disable(workspaceId, 'user-action');
|
|
32
|
+
let sessionId = message.sessionId ?? getActiveSession(workspaceId)?.id;
|
|
33
|
+
try {
|
|
34
|
+
if (hooks?.beforeDispatch)
|
|
35
|
+
await sendMessage(workspaceId, message.content, message.sessionId, hooks.beforeDispatch);
|
|
36
|
+
else
|
|
37
|
+
await sendMessage(workspaceId, message.content, message.sessionId);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (!isAgentUnavailableError(error instanceof Error ? error.message : String(error)))
|
|
41
|
+
throw error;
|
|
42
|
+
// Re-read after asynchronous delivery: metadata may have changed meanwhile.
|
|
43
|
+
assertWorkspaceLifecycleAvailable(workspaceId);
|
|
44
|
+
const current = getWorkspace(workspaceId);
|
|
45
|
+
if (!current || current.archivedAt || current.worktreePurgedAt)
|
|
46
|
+
throw new Error('Workspace is unavailable');
|
|
47
|
+
const started = startAgent(workspaceId, current.worktreePath, message.content, current.model, true, message.agentPermissionModeOverride ?? current.agentPermissionMode, message.sessionId, current.reasoningEffort, ...(hooks?.beforeDispatch ? [hooks.beforeDispatch] : []));
|
|
48
|
+
updateWorkspaceStatus(workspaceId, 'executing');
|
|
49
|
+
sessionId = message.sessionId ?? started.agentSessionId;
|
|
50
|
+
}
|
|
51
|
+
receipt.accept(sessionId);
|
|
52
|
+
return { sessionId };
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
receipt.reject(error instanceof Error ? error.message : String(error), reason);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -27,8 +27,33 @@ function stable(value) {
|
|
|
27
27
|
export function permissionFingerprint(payload) {
|
|
28
28
|
return createHash('sha256').update(stable(payload)).digest('hex');
|
|
29
29
|
}
|
|
30
|
+
const CODEX_EDIT_FINGERPRINT_PREFIX = 'codex-edit-v2:';
|
|
31
|
+
function operationFingerprint(request) {
|
|
32
|
+
if (request.engine !== 'codex' || request.toolName !== 'Edit')
|
|
33
|
+
return permissionFingerprint(request.payload);
|
|
34
|
+
const payload = request.payload;
|
|
35
|
+
if (payload?.operationApprovalAvailable !== true || !Array.isArray(payload.changes) || payload.changes.length === 0)
|
|
36
|
+
return null;
|
|
37
|
+
if (typeof payload.cwd !== 'string' ||
|
|
38
|
+
payload.changes.some((change) => !change ||
|
|
39
|
+
typeof change.path !== 'string' ||
|
|
40
|
+
typeof change.diff !== 'string' ||
|
|
41
|
+
!change.kind ||
|
|
42
|
+
!['add', 'delete', 'update'].includes(change.kind.type)))
|
|
43
|
+
return null;
|
|
44
|
+
return (CODEX_EDIT_FINGERPRINT_PREFIX +
|
|
45
|
+
permissionFingerprint({
|
|
46
|
+
changes: payload.changes,
|
|
47
|
+
cwd: payload.cwd,
|
|
48
|
+
grantRoot: payload.grantRoot ?? null,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
30
51
|
function toRule(row) {
|
|
31
52
|
return {
|
|
53
|
+
active: !(row.scope === 'operation' &&
|
|
54
|
+
row.tool_name === 'Edit' &&
|
|
55
|
+
(row.engine ?? row.workspace_engine) === 'codex' &&
|
|
56
|
+
!String(row.fingerprint ?? '').startsWith(CODEX_EDIT_FINGERPRINT_PREFIX)),
|
|
32
57
|
id: String(row.id),
|
|
33
58
|
workspaceId: String(row.workspace_id),
|
|
34
59
|
engine: row.engine,
|
|
@@ -42,13 +67,17 @@ function toRule(row) {
|
|
|
42
67
|
}
|
|
43
68
|
export function listWorkspacePermissionRules(workspaceId) {
|
|
44
69
|
return getDb()
|
|
45
|
-
.prepare(
|
|
70
|
+
.prepare(`SELECT rules.*, workspaces.engine AS workspace_engine
|
|
71
|
+
FROM workspace_permission_rules AS rules JOIN workspaces ON workspaces.id=rules.workspace_id
|
|
72
|
+
WHERE workspace_id=? ORDER BY rules.created_at DESC`)
|
|
46
73
|
.all(workspaceId).map(toRule);
|
|
47
74
|
}
|
|
48
75
|
export function createWorkspacePermissionRule(workspaceId, request, scope) {
|
|
49
76
|
const now = new Date().toISOString();
|
|
50
77
|
const id = nanoid();
|
|
51
|
-
const fingerprint = scope === 'operation' ?
|
|
78
|
+
const fingerprint = scope === 'operation' ? operationFingerprint(request) : null;
|
|
79
|
+
if (scope === 'operation' && fingerprint === null)
|
|
80
|
+
throw new Error('Exact operation details are unavailable');
|
|
52
81
|
const displayLabel = scope === 'tool' ? request.toolName : `${request.toolName} — opération précise`;
|
|
53
82
|
getDb()
|
|
54
83
|
.prepare('INSERT INTO workspace_permission_rules (id,workspace_id,engine,tool_name,scope,fingerprint,display_label,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?)')
|
|
@@ -61,6 +90,7 @@ export function createWorkspacePermissionRule(workspaceId, request, scope) {
|
|
|
61
90
|
scope,
|
|
62
91
|
fingerprint,
|
|
63
92
|
displayLabel,
|
|
93
|
+
active: true,
|
|
64
94
|
createdAt: now,
|
|
65
95
|
updatedAt: now,
|
|
66
96
|
};
|
|
@@ -72,7 +102,7 @@ export function removeWorkspacePermissionRule(workspaceId, ruleId) {
|
|
|
72
102
|
export function isWorkspacePermissionAllowed(workspaceId, request) {
|
|
73
103
|
if (turnRules.get(workspaceId)?.has(turnKey(request)))
|
|
74
104
|
return true;
|
|
75
|
-
const fingerprint =
|
|
105
|
+
const fingerprint = operationFingerprint(request);
|
|
76
106
|
try {
|
|
77
107
|
return Boolean(getDb()
|
|
78
108
|
.prepare(`SELECT 1 FROM workspace_permission_rules WHERE workspace_id=? AND tool_name=? AND (engine IS NULL OR engine=?) AND (scope='tool' OR (scope='operation' AND fingerprint=?)) LIMIT 1`)
|
|
@@ -43,13 +43,13 @@ export async function restorePurgedWorktree(id) {
|
|
|
43
43
|
headCommitSha: savedHead(workspace.worktreePurgeRestoreData),
|
|
44
44
|
};
|
|
45
45
|
if (!workspace.worktreePurgedAt) {
|
|
46
|
-
if (!isMatchingWorkspaceWorktree(input)) {
|
|
46
|
+
if (!(await isMatchingWorkspaceWorktree(input))) {
|
|
47
47
|
throw new WorktreeRestoreError('not-purged', 'Workspace is not marked as purged and its checkout is missing or different.');
|
|
48
48
|
}
|
|
49
49
|
return { workspace, outcome: 'already-restored', source: 'existing-worktree' };
|
|
50
50
|
}
|
|
51
51
|
const checkout = await restoreWorktreeCheckoutUnlocked(input);
|
|
52
|
-
if (!isMatchingWorkspaceWorktree(input)) {
|
|
52
|
+
if (!(await isMatchingWorkspaceWorktree(input))) {
|
|
53
53
|
throw new WorktreeRestoreError('path-conflict', 'The recreated checkout does not match this workspace.');
|
|
54
54
|
}
|
|
55
55
|
// Preserve a valid checkout on DB failure. A retry or manual detection can
|