factory-factory 0.3.4 → 0.3.5
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/dist/client/assets/{_basePickBy-kS1IrKFQ.js → _basePickBy-DOa44aLB.js} +1 -1
- package/dist/client/assets/{_baseUniq-DisTPj-I.js → _baseUniq-DuVP6l01.js} +1 -1
- package/dist/client/assets/{arc-CWpVTRns.js → arc-BRTVE1a5.js} +1 -1
- package/dist/client/assets/{architectureDiagram-VXUJARFQ-Dp1AYPMt.js → architectureDiagram-VXUJARFQ-DphOng2K.js} +1 -1
- package/dist/client/assets/{blockDiagram-VD42YOAC-DXpmez0y.js → blockDiagram-VD42YOAC-DvVRHTty.js} +1 -1
- package/dist/client/assets/{c4Diagram-YG6GDRKO-C6sDxR49.js → c4Diagram-YG6GDRKO-BZaLUCEP.js} +1 -1
- package/dist/client/assets/channel-v55vPUhK.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-CQxlRbYu.js → chunk-4BX2VUAB-n69YHsoN.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-DxubFDep.js → chunk-55IACEB6-C67Ak1Qy.js} +1 -1
- package/dist/client/assets/{chunk-B4BG7PRW-BtTQ0im9.js → chunk-B4BG7PRW-_Ja9GXA4.js} +1 -1
- package/dist/client/assets/{chunk-DI55MBZ5-C2tNYDZj.js → chunk-DI55MBZ5-C1TAOocX.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-kRQRN6vP.js → chunk-FMBD7UC4-Co8OmD7c.js} +1 -1
- package/dist/client/assets/{chunk-QN33PNHL-DP72lCp6.js → chunk-QN33PNHL-ESdvapXF.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-BfxMFZDN.js → chunk-QZHKN3VN-DQTGFT4I.js} +1 -1
- package/dist/client/assets/{chunk-TZMSLE5B-G3wPktxK.js → chunk-TZMSLE5B-C7a0BTNr.js} +1 -1
- package/dist/client/assets/classDiagram-2ON5EDUG-DV-E9odE.js +1 -0
- package/dist/client/assets/classDiagram-v2-WZHVMYZB-DV-E9odE.js +1 -0
- package/dist/client/assets/clone-DQgzPMba.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-QPsUxyAe.js → cose-bilkent-S5V4N54A-Dg_Elpr2.js} +1 -1
- package/dist/client/assets/{dagre-6UL2VRFP-BAmsmcnI.js → dagre-6UL2VRFP-DC_BntL_.js} +1 -1
- package/dist/client/assets/{diagram-PSM6KHXK-QA-3O0wr.js → diagram-PSM6KHXK-D4EnEadG.js} +1 -1
- package/dist/client/assets/{diagram-QEK2KX5R-VZkRJLXU.js → diagram-QEK2KX5R-DeDgxkkd.js} +1 -1
- package/dist/client/assets/{diagram-S2PKOQOG-DwLmogjW.js → diagram-S2PKOQOG-DqCrW2Mz.js} +1 -1
- package/dist/client/assets/{erDiagram-Q2GNP2WA-DMKIVzp4.js → erDiagram-Q2GNP2WA-CoIzOJMx.js} +1 -1
- package/dist/client/assets/{flowDiagram-NV44I4VS-op3xA_nS.js → flowDiagram-NV44I4VS-H-hzyVxN.js} +1 -1
- package/dist/client/assets/{ganttDiagram-JELNMOA3-nrnzVCJK.js → ganttDiagram-JELNMOA3-BZbu1Bvf.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-NY62KEGX-CK2ytvyZ.js → gitGraphDiagram-NY62KEGX-yHNzOwte.js} +1 -1
- package/dist/client/assets/{graph-D_lEcPMk.js → graph-0fFVLhuM.js} +1 -1
- package/dist/client/assets/index-CzQJ6qnR.css +1 -0
- package/dist/client/assets/{index-C2qHRaOH.js → index-D2gG7VAV.js} +249 -251
- package/dist/client/assets/{infoDiagram-WHAUD3N6-DO5X8HkW.js → infoDiagram-WHAUD3N6-BdYcSIzA.js} +1 -1
- package/dist/client/assets/{journeyDiagram-XKPGCS4Q-Bnhe4U1x.js → journeyDiagram-XKPGCS4Q-BgD0iboh.js} +1 -1
- package/dist/client/assets/{kanban-definition-3W4ZIXB7-DpTaTXko.js → kanban-definition-3W4ZIXB7-2cCK2J0j.js} +1 -1
- package/dist/client/assets/{layout-HT5qr--Q.js → layout-UQByNmge.js} +1 -1
- package/dist/client/assets/{linear-CDmQ8HG8.js → linear-COsW-oY4.js} +1 -1
- package/dist/client/assets/{mindmap-definition-VGOIOE7T-zDr7s4o2.js → mindmap-definition-VGOIOE7T-CnhVGcBr.js} +1 -1
- package/dist/client/assets/{pieDiagram-ADFJNKIX-BVBJCvYL.js → pieDiagram-ADFJNKIX-DX0wt7fJ.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-AYHSOK5B-DzdJzkAJ.js → quadrantDiagram-AYHSOK5B-nfJepDyE.js} +1 -1
- package/dist/client/assets/{requirementDiagram-UZGBJVZJ-ChrLzx8S.js → requirementDiagram-UZGBJVZJ-I_tnp42L.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-TZEHDZUN-GIzxIQQB.js → sankeyDiagram-TZEHDZUN-4r_mPLW_.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-WL72ISMW-BHxEpSCU.js → sequenceDiagram-WL72ISMW-DWymCFZa.js} +1 -1
- package/dist/client/assets/{stateDiagram-FKZM4ZOC-ByvdYZKl.js → stateDiagram-FKZM4ZOC-CCHlcmzO.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-4FDKWEC3-CuSoER-j.js +1 -0
- package/dist/client/assets/{terminal-instance-xdECz0dx.js → terminal-instance-DKydBe-u.js} +1 -1
- package/dist/client/assets/{timeline-definition-IT6M3QCI-BcCmIxix.js → timeline-definition-IT6M3QCI-EkYjHzU_.js} +1 -1
- package/dist/client/assets/{treemap-KMMF4GRG-CVpLLzhu.js → treemap-KMMF4GRG-BH9wTzrS.js} +1 -1
- package/dist/client/assets/{xychartDiagram-PRI3JC2R-DrgKBPUM.js → xychartDiagram-PRI3JC2R-DwOnTNwE.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/prompts/quick-actions/screenshots.md +12 -6
- package/dist/prompts/ratchet/dispatch.md +4 -0
- package/dist/src/backend/domains/ratchet/bridges.d.ts +5 -1
- package/dist/src/backend/domains/ratchet/bridges.d.ts.map +1 -1
- package/dist/src/backend/domains/ratchet/ratchet.service.d.ts.map +1 -1
- package/dist/src/backend/domains/ratchet/ratchet.service.js +27 -51
- package/dist/src/backend/domains/ratchet/ratchet.service.js.map +1 -1
- package/dist/src/backend/domains/ratchet/ratchet.service.test.js +18 -18
- package/dist/src/backend/domains/ratchet/ratchet.service.test.js.map +1 -1
- package/dist/src/backend/domains/session/acp/acp-runtime-manager.d.ts.map +1 -1
- package/dist/src/backend/domains/session/acp/acp-runtime-manager.js +5 -1
- package/dist/src/backend/domains/session/acp/acp-runtime-manager.js.map +1 -1
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.d.ts +13 -2
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.d.ts.map +1 -1
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.js +808 -106
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.js.map +1 -1
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.test.js +627 -8
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-app-server-acp-adapter.test.js.map +1 -1
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.d.ts +20 -0
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.d.ts.map +1 -1
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.js +16 -0
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.js.map +1 -1
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.test.js +15 -0
- package/dist/src/backend/domains/session/acp/codex-app-server-adapter/codex-zod.test.js.map +1 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.d.ts +2 -2
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.d.ts.map +1 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.js +32 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.js.map +1 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.test.js +79 -9
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/load-session.handler.test.js.map +1 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.d.ts +2 -2
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.d.ts.map +1 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.js +2 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.js.map +1 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.test.js +7 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers/handlers/stop.handler.test.js.map +1 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers/registry.js +2 -2
- package/dist/src/backend/domains/session/chat/chat-message-handlers/registry.js.map +1 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers.service.d.ts +1 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers.service.d.ts.map +1 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers.service.js +19 -10
- package/dist/src/backend/domains/session/chat/chat-message-handlers.service.js.map +1 -1
- package/dist/src/backend/domains/session/chat/chat-message-handlers.service.test.js +43 -0
- package/dist/src/backend/domains/session/chat/chat-message-handlers.service.test.js.map +1 -1
- package/dist/src/backend/domains/session/data/codex-session-history-loader.service.d.ts.map +1 -1
- package/dist/src/backend/domains/session/data/codex-session-history-loader.service.js +23 -59
- package/dist/src/backend/domains/session/data/codex-session-history-loader.service.js.map +1 -1
- package/dist/src/backend/domains/session/data/codex-session-history-loader.service.test.js +8 -21
- package/dist/src/backend/domains/session/data/codex-session-history-loader.service.test.js.map +1 -1
- package/dist/src/backend/domains/session/lifecycle/session.service.d.ts +7 -0
- package/dist/src/backend/domains/session/lifecycle/session.service.d.ts.map +1 -1
- package/dist/src/backend/domains/session/lifecycle/session.service.js +105 -6
- package/dist/src/backend/domains/session/lifecycle/session.service.js.map +1 -1
- package/dist/src/backend/domains/session/lifecycle/session.service.test.js +84 -0
- package/dist/src/backend/domains/session/lifecycle/session.service.test.js.map +1 -1
- package/dist/src/backend/domains/session/session-domain.service.d.ts +6 -0
- package/dist/src/backend/domains/session/session-domain.service.d.ts.map +1 -1
- package/dist/src/backend/domains/session/session-domain.service.js +26 -1
- package/dist/src/backend/domains/session/session-domain.service.js.map +1 -1
- package/dist/src/backend/domains/session/session-domain.service.test.js +21 -0
- package/dist/src/backend/domains/session/session-domain.service.test.js.map +1 -1
- package/dist/src/backend/domains/session/store/session-store.types.d.ts +1 -1
- package/dist/src/backend/domains/session/store/session-store.types.d.ts.map +1 -1
- package/dist/src/backend/domains/session/store/session-transcript.d.ts +1 -0
- package/dist/src/backend/domains/session/store/session-transcript.d.ts.map +1 -1
- package/dist/src/backend/domains/session/store/session-transcript.js +8 -0
- package/dist/src/backend/domains/session/store/session-transcript.js.map +1 -1
- package/dist/src/backend/interceptors/pr-detection.interceptor.d.ts.map +1 -1
- package/dist/src/backend/interceptors/pr-detection.interceptor.js +5 -1
- package/dist/src/backend/interceptors/pr-detection.interceptor.js.map +1 -1
- package/dist/src/backend/interceptors/pr-detection.interceptor.test.js +14 -0
- package/dist/src/backend/interceptors/pr-detection.interceptor.test.js.map +1 -1
- package/dist/src/backend/orchestration/cli-health.service.d.ts +1 -0
- package/dist/src/backend/orchestration/cli-health.service.d.ts.map +1 -1
- package/dist/src/backend/orchestration/cli-health.service.js +16 -1
- package/dist/src/backend/orchestration/cli-health.service.js.map +1 -1
- package/dist/src/backend/orchestration/cli-health.service.test.js +23 -0
- package/dist/src/backend/orchestration/cli-health.service.test.js.map +1 -1
- package/dist/src/backend/prompts/ratchet-dispatch.d.ts +8 -1
- package/dist/src/backend/prompts/ratchet-dispatch.d.ts.map +1 -1
- package/dist/src/backend/prompts/ratchet-dispatch.js +20 -3
- package/dist/src/backend/prompts/ratchet-dispatch.js.map +1 -1
- package/dist/src/backend/services/constants.d.ts +1 -0
- package/dist/src/backend/services/constants.d.ts.map +1 -1
- package/dist/src/backend/services/constants.js +1 -0
- package/dist/src/backend/services/constants.js.map +1 -1
- package/dist/src/backend/trpc/index.d.ts +1 -0
- package/dist/src/backend/trpc/index.d.ts.map +1 -1
- package/dist/src/backend/trpc/session.trpc.d.ts +1 -0
- package/dist/src/backend/trpc/session.trpc.d.ts.map +1 -1
- package/dist/src/backend/trpc/session.trpc.js +5 -1
- package/dist/src/backend/trpc/session.trpc.js.map +1 -1
- package/dist/tsconfig.backend.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/prompts/quick-actions/screenshots.md +12 -6
- package/prompts/ratchet/dispatch.md +4 -0
- package/dist/client/assets/channel-D5BCeybW.js +0 -1
- package/dist/client/assets/classDiagram-2ON5EDUG-BUS1JDwr.js +0 -1
- package/dist/client/assets/classDiagram-v2-WZHVMYZB-BUS1JDwr.js +0 -1
- package/dist/client/assets/clone-C5xhPZw8.js +0 -1
- package/dist/client/assets/index-D5ywoSOH.css +0 -1
- package/dist/client/assets/stateDiagram-v2-4FDKWEC3-ynQtAOKH.js +0 -1
|
@@ -6,6 +6,24 @@ const PENDING_TURN_ID = '__pending_turn__';
|
|
|
6
6
|
const MAX_CLOSE_WATCHER_ATTACH_RETRIES = 50;
|
|
7
7
|
const DEFAULT_APPROVAL_POLICIES = ['on-failure', 'on-request', 'never'];
|
|
8
8
|
const DEFAULT_SANDBOX_MODES = ['read-only', 'workspace-write', 'danger-full-access'];
|
|
9
|
+
const SHAPE_DRIFT_DETAILS_LIMIT = 700;
|
|
10
|
+
function toTurnItemKey(turnId, itemId) {
|
|
11
|
+
return `${turnId}:${itemId}`;
|
|
12
|
+
}
|
|
13
|
+
function toShapeDriftDetails(value) {
|
|
14
|
+
try {
|
|
15
|
+
const text = JSON.stringify(value);
|
|
16
|
+
if (typeof text !== 'string') {
|
|
17
|
+
return '[unserializable]';
|
|
18
|
+
}
|
|
19
|
+
return text.length > SHAPE_DRIFT_DETAILS_LIMIT
|
|
20
|
+
? `${text.slice(0, SHAPE_DRIFT_DETAILS_LIMIT)}...`
|
|
21
|
+
: text;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return '[unserializable]';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
9
27
|
function isNonEmptyString(value) {
|
|
10
28
|
return typeof value === 'string' && value.trim().length > 0;
|
|
11
29
|
}
|
|
@@ -107,8 +125,14 @@ function toSessionId(threadId) {
|
|
|
107
125
|
function toThreadId(sessionId) {
|
|
108
126
|
return sessionId.startsWith('sess_') ? sessionId.slice('sess_'.length) : sessionId;
|
|
109
127
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
128
|
+
function extractToolCallIdFromUnknown(value) {
|
|
129
|
+
if (!isRecord(value)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return asString(value.callId) ?? asString(value.call_id) ?? null;
|
|
133
|
+
}
|
|
134
|
+
function resolveToolCallId(params) {
|
|
135
|
+
return extractToolCallIdFromUnknown(params.source) ?? params.itemId;
|
|
112
136
|
}
|
|
113
137
|
function isPlanLikeMode(mode) {
|
|
114
138
|
return /plan/i.test(mode);
|
|
@@ -123,6 +147,16 @@ const PLAN_TEXT_PREFERRED_KEYS = [
|
|
|
123
147
|
'value',
|
|
124
148
|
'message',
|
|
125
149
|
];
|
|
150
|
+
const REASONING_TEXT_MAX_DEPTH = 8;
|
|
151
|
+
const REASONING_TEXT_PREFERRED_KEYS = [
|
|
152
|
+
'summary',
|
|
153
|
+
'summaryText',
|
|
154
|
+
'text',
|
|
155
|
+
'delta',
|
|
156
|
+
'message',
|
|
157
|
+
'content',
|
|
158
|
+
'reasoning',
|
|
159
|
+
];
|
|
126
160
|
function extractFirstPlanText(values, depth) {
|
|
127
161
|
for (const entry of values) {
|
|
128
162
|
const extracted = extractPlanTextLocal(entry, depth + 1);
|
|
@@ -155,6 +189,37 @@ function extractPlanTextLocal(value, depth = 0) {
|
|
|
155
189
|
}
|
|
156
190
|
return extractPlanTextFromRecord(value, depth);
|
|
157
191
|
}
|
|
192
|
+
function collectReasoningText(values, value, depth = 0) {
|
|
193
|
+
if (depth > REASONING_TEXT_MAX_DEPTH) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (typeof value === 'string') {
|
|
197
|
+
if (value.trim().length > 0) {
|
|
198
|
+
values.push(value);
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (Array.isArray(value)) {
|
|
203
|
+
for (const entry of value) {
|
|
204
|
+
collectReasoningText(values, entry, depth + 1);
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (!isRecord(value)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
for (const key of REASONING_TEXT_PREFERRED_KEYS) {
|
|
212
|
+
collectReasoningText(values, value[key], depth + 1);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function extractReasoningTextLocal(value) {
|
|
216
|
+
const collected = [];
|
|
217
|
+
collectReasoningText(collected, value);
|
|
218
|
+
if (collected.length === 0) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
return dedupeStrings(collected).join('\n\n');
|
|
222
|
+
}
|
|
158
223
|
function toMcpEnvRecord(envVars) {
|
|
159
224
|
const env = {};
|
|
160
225
|
for (const entry of envVars) {
|
|
@@ -293,6 +358,307 @@ function parseToolUserInputAnswersFromPermissionMeta(params) {
|
|
|
293
358
|
function asString(value) {
|
|
294
359
|
return typeof value === 'string' ? value : null;
|
|
295
360
|
}
|
|
361
|
+
function trimOptionalQuotes(token) {
|
|
362
|
+
if (token.length >= 2 && token.startsWith('"') && token.endsWith('"')) {
|
|
363
|
+
return { text: token.slice(1, -1), quote: '"' };
|
|
364
|
+
}
|
|
365
|
+
if (token.length >= 2 && token.startsWith("'") && token.endsWith("'")) {
|
|
366
|
+
return { text: token.slice(1, -1), quote: "'" };
|
|
367
|
+
}
|
|
368
|
+
return { text: token, quote: null };
|
|
369
|
+
}
|
|
370
|
+
function unescapeCommandToken(token, quote) {
|
|
371
|
+
if (quote === "'") {
|
|
372
|
+
return token.replace(/\\'/g, "'").replace(/\\\\/g, '\\');
|
|
373
|
+
}
|
|
374
|
+
return token.replace(/\\(["'\\\s|&;])/g, '$1');
|
|
375
|
+
}
|
|
376
|
+
function sanitizeCommandToken(token) {
|
|
377
|
+
const trimmed = token.trim();
|
|
378
|
+
const { text, quote } = trimOptionalQuotes(trimmed);
|
|
379
|
+
return unescapeCommandToken(text, quote).trim();
|
|
380
|
+
}
|
|
381
|
+
function tokenizeCommand(command) {
|
|
382
|
+
return (command.match(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\S+/g) ?? [])
|
|
383
|
+
.map((token) => sanitizeCommandToken(token))
|
|
384
|
+
.filter((token) => token.length > 0);
|
|
385
|
+
}
|
|
386
|
+
function commandName(token) {
|
|
387
|
+
const unix = token.split('/').at(-1) ?? token;
|
|
388
|
+
return (unix.split('\\').at(-1) ?? unix).toLowerCase();
|
|
389
|
+
}
|
|
390
|
+
function isChainSeparatorChar(char) {
|
|
391
|
+
return char === ';' || char === '\n' || char === '|' || char === '&';
|
|
392
|
+
}
|
|
393
|
+
function readChainSeparatorLength(command, index) {
|
|
394
|
+
const char = command[index];
|
|
395
|
+
if (!(char && isChainSeparatorChar(char))) {
|
|
396
|
+
return 0;
|
|
397
|
+
}
|
|
398
|
+
const next = command[index + 1];
|
|
399
|
+
if ((char === '|' || char === '&') && next === char) {
|
|
400
|
+
return 2;
|
|
401
|
+
}
|
|
402
|
+
return 1;
|
|
403
|
+
}
|
|
404
|
+
function isLikelyPathToken(token) {
|
|
405
|
+
if (!token || token.startsWith('-')) {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
if (token.startsWith('./') ||
|
|
409
|
+
token.startsWith('../') ||
|
|
410
|
+
token.startsWith('/') ||
|
|
411
|
+
token.startsWith('~')) {
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
return (token.includes('/') ||
|
|
415
|
+
token.includes('\\') ||
|
|
416
|
+
token.endsWith('.') ||
|
|
417
|
+
/\.[a-z0-9]+$/i.test(token));
|
|
418
|
+
}
|
|
419
|
+
function normalizeLocationPath(pathToken, cwd) {
|
|
420
|
+
if (pathToken.startsWith('/')) {
|
|
421
|
+
return pathToken;
|
|
422
|
+
}
|
|
423
|
+
if (pathToken.startsWith('~')) {
|
|
424
|
+
return pathToken;
|
|
425
|
+
}
|
|
426
|
+
const base = cwd.endsWith('/') ? cwd.slice(0, -1) : cwd;
|
|
427
|
+
return `${base}/${pathToken}`;
|
|
428
|
+
}
|
|
429
|
+
const READ_COMMANDS = new Set(['cat', 'head', 'tail', 'less', 'more']);
|
|
430
|
+
const LIST_OR_FIND_COMMANDS = new Set(['ls', 'tree', 'find', 'fd']);
|
|
431
|
+
const GREP_LIKE_COMMANDS = new Set(['rg', 'ripgrep', 'grep', 'ag']);
|
|
432
|
+
const SHELL_META_COMMANDS = new Set(['cd', 'export', 'set', 'unset', 'alias', 'unalias', 'source']);
|
|
433
|
+
function pushCommandChainPart(state) {
|
|
434
|
+
const trimmed = state.current.trim();
|
|
435
|
+
if (trimmed.length > 0) {
|
|
436
|
+
state.parts.push(trimmed);
|
|
437
|
+
}
|
|
438
|
+
state.current = '';
|
|
439
|
+
}
|
|
440
|
+
function consumeEscapedCommandChar(state, char) {
|
|
441
|
+
if (!state.escaped) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
state.current += char;
|
|
445
|
+
state.escaped = false;
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
function consumeEscapeInitiator(state, char) {
|
|
449
|
+
if (!(char === '\\' && state.quote !== "'")) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
state.current += char;
|
|
453
|
+
state.escaped = true;
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
function hasOddTrailingBackslashes(value) {
|
|
457
|
+
let count = 0;
|
|
458
|
+
for (let index = value.length - 1; index >= 0; index -= 1) {
|
|
459
|
+
if (value[index] !== '\\') {
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
count += 1;
|
|
463
|
+
}
|
|
464
|
+
return count % 2 === 1;
|
|
465
|
+
}
|
|
466
|
+
function consumeQuotedCommandChar(state, char) {
|
|
467
|
+
if (!state.quote) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
const escapedSingleQuote = state.quote === "'" && char === "'" && hasOddTrailingBackslashes(state.current);
|
|
471
|
+
state.current += char;
|
|
472
|
+
if (char === state.quote && !escapedSingleQuote) {
|
|
473
|
+
state.quote = null;
|
|
474
|
+
}
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
function consumeQuoteStart(state, char) {
|
|
478
|
+
if (!(char === '"' || char === "'")) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
state.quote = char;
|
|
482
|
+
state.current += char;
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
function consumeCommandChainSeparator(state, command, index) {
|
|
486
|
+
const separatorLength = readChainSeparatorLength(command, index);
|
|
487
|
+
if (separatorLength <= 0) {
|
|
488
|
+
return 0;
|
|
489
|
+
}
|
|
490
|
+
pushCommandChainPart(state);
|
|
491
|
+
return separatorLength;
|
|
492
|
+
}
|
|
493
|
+
function splitCommandChain(command) {
|
|
494
|
+
const state = {
|
|
495
|
+
parts: [],
|
|
496
|
+
current: '',
|
|
497
|
+
quote: null,
|
|
498
|
+
escaped: false,
|
|
499
|
+
};
|
|
500
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
501
|
+
const char = command[index];
|
|
502
|
+
if (!char) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (consumeEscapedCommandChar(state, char)) {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
if (consumeEscapeInitiator(state, char)) {
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (consumeQuotedCommandChar(state, char)) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
if (consumeQuoteStart(state, char)) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const consumedSeparatorLength = consumeCommandChainSeparator(state, command, index);
|
|
518
|
+
if (consumedSeparatorLength > 0) {
|
|
519
|
+
index += consumedSeparatorLength - 1;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
state.current += char;
|
|
523
|
+
}
|
|
524
|
+
pushCommandChainPart(state);
|
|
525
|
+
return state.parts;
|
|
526
|
+
}
|
|
527
|
+
function buildCommandDisplayContexts(rawCommand, cwd) {
|
|
528
|
+
const command = rawCommand?.trim();
|
|
529
|
+
if (!command) {
|
|
530
|
+
return [];
|
|
531
|
+
}
|
|
532
|
+
const contexts = [];
|
|
533
|
+
const segments = splitCommandChain(command);
|
|
534
|
+
for (const segment of segments) {
|
|
535
|
+
const tokens = tokenizeCommand(segment);
|
|
536
|
+
if (tokens.length === 0) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
const [firstToken] = tokens;
|
|
540
|
+
if (!firstToken) {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
const firstCommand = commandName(firstToken);
|
|
544
|
+
const nonFlagArgs = tokens.slice(1).filter((token) => !token.startsWith('-'));
|
|
545
|
+
const pathArg = nonFlagArgs.find(isLikelyPathToken) ?? null;
|
|
546
|
+
const commandPath = pathArg ? normalizeLocationPath(pathArg, cwd) : null;
|
|
547
|
+
const locations = commandPath ? [{ path: commandPath }] : [];
|
|
548
|
+
contexts.push({
|
|
549
|
+
command: segment,
|
|
550
|
+
firstCommand,
|
|
551
|
+
nonFlagArgs,
|
|
552
|
+
pathArg,
|
|
553
|
+
cwd,
|
|
554
|
+
locations,
|
|
555
|
+
isShellMeta: SHELL_META_COMMANDS.has(firstCommand),
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
return contexts;
|
|
559
|
+
}
|
|
560
|
+
function chooseActionableCommandContexts(contexts) {
|
|
561
|
+
const actionable = contexts.filter((context) => !context.isShellMeta);
|
|
562
|
+
return actionable.length > 0 ? actionable : contexts;
|
|
563
|
+
}
|
|
564
|
+
function selectCombinedCommandKind(displays) {
|
|
565
|
+
const firstNonExecute = displays.find((display) => display.kind !== 'execute');
|
|
566
|
+
return firstNonExecute?.kind ?? 'execute';
|
|
567
|
+
}
|
|
568
|
+
function resolveCommandDisplay(params) {
|
|
569
|
+
const contexts = chooseActionableCommandContexts(buildCommandDisplayContexts(params.command, params.cwd));
|
|
570
|
+
if (contexts.length === 0) {
|
|
571
|
+
return { title: 'commandExecution', kind: 'execute', locations: [] };
|
|
572
|
+
}
|
|
573
|
+
const [singleContext] = contexts;
|
|
574
|
+
if (singleContext && contexts.length === 1) {
|
|
575
|
+
return resolveCommandDisplayFromContext(singleContext);
|
|
576
|
+
}
|
|
577
|
+
const displays = contexts.map((context) => resolveCommandDisplayFromContext(context));
|
|
578
|
+
const title = dedupeStrings(displays.map((display) => display.title)).join(', ');
|
|
579
|
+
const kind = selectCombinedCommandKind(displays);
|
|
580
|
+
const locations = dedupeLocations(displays.flatMap((display) => display.locations)).map((location) => ({ path: location.path }));
|
|
581
|
+
return { title, kind, locations };
|
|
582
|
+
}
|
|
583
|
+
function resolveCommandDisplayFromContext(context) {
|
|
584
|
+
if (READ_COMMANDS.has(context.firstCommand)) {
|
|
585
|
+
const label = context.pathArg ?? context.command;
|
|
586
|
+
return { title: `Read ${label}`, kind: 'read', locations: context.locations };
|
|
587
|
+
}
|
|
588
|
+
if (LIST_OR_FIND_COMMANDS.has(context.firstCommand)) {
|
|
589
|
+
const target = context.pathArg
|
|
590
|
+
? normalizeLocationPath(context.pathArg, context.cwd)
|
|
591
|
+
: context.cwd;
|
|
592
|
+
const title = context.firstCommand === 'find' || context.firstCommand === 'fd'
|
|
593
|
+
? `Search ${target}`
|
|
594
|
+
: `List ${target}`;
|
|
595
|
+
return { title, kind: 'search', locations: context.locations };
|
|
596
|
+
}
|
|
597
|
+
if (GREP_LIKE_COMMANDS.has(context.firstCommand)) {
|
|
598
|
+
const query = context.nonFlagArgs.find((token) => !isLikelyPathToken(token));
|
|
599
|
+
const target = context.pathArg ? ` in ${context.pathArg}` : '';
|
|
600
|
+
const title = query ? `Search ${query}${target}` : `Search ${context.command}`;
|
|
601
|
+
return { title, kind: 'search', locations: context.locations };
|
|
602
|
+
}
|
|
603
|
+
return { title: context.command, kind: 'execute', locations: context.locations };
|
|
604
|
+
}
|
|
605
|
+
function normalizeCwdForScope(cwd) {
|
|
606
|
+
if (cwd.length > 1 && cwd.endsWith('/')) {
|
|
607
|
+
return cwd.slice(0, -1);
|
|
608
|
+
}
|
|
609
|
+
return cwd;
|
|
610
|
+
}
|
|
611
|
+
function normalizeScopeToken(token) {
|
|
612
|
+
return token.replace(/\s+/g, ' ').trim();
|
|
613
|
+
}
|
|
614
|
+
function buildCommandApprovalScopeKey(params) {
|
|
615
|
+
const contexts = buildCommandDisplayContexts(params.command, params.cwd);
|
|
616
|
+
if (contexts.length === 0) {
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
let scopeCwd = normalizeCwdForScope(params.cwd);
|
|
620
|
+
const segments = [];
|
|
621
|
+
for (const context of contexts) {
|
|
622
|
+
if (context.firstCommand === 'cd') {
|
|
623
|
+
const rawTarget = context.nonFlagArgs[0];
|
|
624
|
+
const target = rawTarget ? normalizeScopeToken(rawTarget) : '';
|
|
625
|
+
if (target.length > 0) {
|
|
626
|
+
scopeCwd = normalizeCwdForScope(normalizeLocationPath(target, scopeCwd));
|
|
627
|
+
segments.push(`cd ${target} -> ${scopeCwd}`);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
segments.push('cd');
|
|
631
|
+
}
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const args = context.nonFlagArgs.map(normalizeScopeToken).filter((arg) => arg.length > 0);
|
|
635
|
+
const normalized = normalizeScopeToken([context.firstCommand, ...args].join(' '));
|
|
636
|
+
if (normalized.length === 0) {
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
segments.push(`[cwd=${scopeCwd}] ${normalized}`);
|
|
640
|
+
}
|
|
641
|
+
const normalizedSegments = segments
|
|
642
|
+
.map(normalizeScopeToken)
|
|
643
|
+
.filter((segment) => segment.length > 0);
|
|
644
|
+
if (normalizedSegments.length === 0) {
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
return `cwd=${normalizeCwdForScope(params.cwd)}|${normalizedSegments.join(' && ')}`;
|
|
648
|
+
}
|
|
649
|
+
function dedupeLocations(locations) {
|
|
650
|
+
const seen = new Set();
|
|
651
|
+
const result = [];
|
|
652
|
+
for (const location of locations) {
|
|
653
|
+
const key = `${location.path}:${location.line ?? ''}`;
|
|
654
|
+
if (seen.has(key)) {
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
seen.add(key);
|
|
658
|
+
result.push(location);
|
|
659
|
+
}
|
|
660
|
+
return result;
|
|
661
|
+
}
|
|
296
662
|
function parseTextFromPromptBlock(block) {
|
|
297
663
|
if (block.type === 'text') {
|
|
298
664
|
return block.text;
|
|
@@ -430,6 +796,7 @@ export class CodexAppServerAcpAdapter {
|
|
|
430
796
|
collaborationModes = [];
|
|
431
797
|
mcpServersByThreadId = new Map();
|
|
432
798
|
appliedMcpServerConfigJson = '{}';
|
|
799
|
+
shapeDriftCounts = new Map();
|
|
433
800
|
constructor(connection, codexClient) {
|
|
434
801
|
this.connection = connection;
|
|
435
802
|
this.codex =
|
|
@@ -542,10 +909,13 @@ export class CodexAppServerAcpAdapter {
|
|
|
542
909
|
},
|
|
543
910
|
activeTurn: null,
|
|
544
911
|
toolCallsByItemId: new Map(),
|
|
912
|
+
reasoningDeltaItemIds: new Set(),
|
|
545
913
|
planTextByItemId: new Map(),
|
|
546
914
|
planApprovalRequestedByTurnId: new Set(),
|
|
547
915
|
pendingPlanApprovalsByTurnId: new Map(),
|
|
548
916
|
pendingTurnCompletionsByTurnId: new Map(),
|
|
917
|
+
commandApprovalScopes: new Set(),
|
|
918
|
+
replayedTurnItemKeys: new Set(),
|
|
549
919
|
};
|
|
550
920
|
this.sessions.set(sessionId, session);
|
|
551
921
|
this.sessionIdByThreadId.set(session.threadId, sessionId);
|
|
@@ -587,10 +957,13 @@ export class CodexAppServerAcpAdapter {
|
|
|
587
957
|
},
|
|
588
958
|
activeTurn: null,
|
|
589
959
|
toolCallsByItemId: new Map(),
|
|
960
|
+
reasoningDeltaItemIds: new Set(),
|
|
590
961
|
planTextByItemId: new Map(),
|
|
591
962
|
planApprovalRequestedByTurnId: new Set(),
|
|
592
963
|
pendingPlanApprovalsByTurnId: new Map(),
|
|
593
964
|
pendingTurnCompletionsByTurnId: new Map(),
|
|
965
|
+
commandApprovalScopes: new Set(),
|
|
966
|
+
replayedTurnItemKeys: new Set(),
|
|
594
967
|
};
|
|
595
968
|
this.sessions.set(session.sessionId, session);
|
|
596
969
|
this.sessionIdByThreadId.set(session.threadId, session.sessionId);
|
|
@@ -1202,8 +1575,27 @@ export class CodexAppServerAcpAdapter {
|
|
|
1202
1575
|
});
|
|
1203
1576
|
}
|
|
1204
1577
|
async replayToolLikeHistoryItem(session, sessionId, turnId, item) {
|
|
1578
|
+
if (item.type === 'reasoning') {
|
|
1579
|
+
await this.emitSessionUpdate(sessionId, {
|
|
1580
|
+
sessionUpdate: 'tool_call',
|
|
1581
|
+
toolCallId: resolveToolCallId({ itemId: item.id, source: item }),
|
|
1582
|
+
title: 'reasoning',
|
|
1583
|
+
kind: 'think',
|
|
1584
|
+
status: 'completed',
|
|
1585
|
+
rawInput: item,
|
|
1586
|
+
rawOutput: item,
|
|
1587
|
+
});
|
|
1588
|
+
await this.emitReasoningThoughtChunkFromItem(sessionId, item);
|
|
1589
|
+
session.replayedTurnItemKeys.add(toTurnItemKey(turnId, item.id));
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1205
1592
|
const toolInfo = this.buildToolCallState(session, item, turnId);
|
|
1206
1593
|
if (!toolInfo) {
|
|
1594
|
+
this.reportShapeDrift('unhandled_replay_item', {
|
|
1595
|
+
turnId,
|
|
1596
|
+
itemType: item.type,
|
|
1597
|
+
itemId: item.id,
|
|
1598
|
+
});
|
|
1207
1599
|
return;
|
|
1208
1600
|
}
|
|
1209
1601
|
await this.emitSessionUpdate(sessionId, {
|
|
@@ -1215,11 +1607,15 @@ export class CodexAppServerAcpAdapter {
|
|
|
1215
1607
|
rawInput: item,
|
|
1216
1608
|
rawOutput: item,
|
|
1217
1609
|
});
|
|
1610
|
+
session.replayedTurnItemKeys.add(toTurnItemKey(turnId, item.id));
|
|
1218
1611
|
}
|
|
1219
1612
|
async handleCodexNotification(method, params) {
|
|
1220
1613
|
const parsed = knownCodexNotificationSchema.safeParse({ method, params });
|
|
1221
1614
|
if (!parsed.success) {
|
|
1222
|
-
|
|
1615
|
+
this.reportShapeDrift('malformed_notification', {
|
|
1616
|
+
method,
|
|
1617
|
+
issues: parsed.error.issues.slice(0, 3).map((issue) => issue.message),
|
|
1618
|
+
});
|
|
1223
1619
|
return;
|
|
1224
1620
|
}
|
|
1225
1621
|
const notification = parsed.data;
|
|
@@ -1248,16 +1644,20 @@ export class CodexAppServerAcpAdapter {
|
|
|
1248
1644
|
await this.handlePlanDelta(sessionId, session, notification.params.itemId, notification.params.delta);
|
|
1249
1645
|
return;
|
|
1250
1646
|
}
|
|
1647
|
+
if (notification.method === 'item/reasoning/summaryTextDelta') {
|
|
1648
|
+
await this.emitReasoningThoughtDelta(sessionId, session, notification.params.turnId, notification.params.itemId, notification.params.delta);
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1251
1651
|
if (notification.method === 'item/commandExecution/outputDelta') {
|
|
1252
|
-
await this.emitToolCallProgress(sessionId, session, notification.params.itemId, notification.params.delta);
|
|
1652
|
+
await this.emitToolCallProgress(sessionId, session, notification.params.turnId, notification.params.itemId, notification.params.delta);
|
|
1253
1653
|
return;
|
|
1254
1654
|
}
|
|
1255
1655
|
if (notification.method === 'item/fileChange/outputDelta') {
|
|
1256
|
-
await this.emitToolCallProgress(sessionId, session, notification.params.itemId, notification.params.delta);
|
|
1656
|
+
await this.emitToolCallProgress(sessionId, session, notification.params.turnId, notification.params.itemId, notification.params.delta);
|
|
1257
1657
|
return;
|
|
1258
1658
|
}
|
|
1259
1659
|
if (notification.method === 'item/mcpToolCall/progress') {
|
|
1260
|
-
await this.emitToolCallProgress(sessionId, session, notification.params.itemId, notification.params.message);
|
|
1660
|
+
await this.emitToolCallProgress(sessionId, session, notification.params.turnId, notification.params.itemId, notification.params.message);
|
|
1261
1661
|
return;
|
|
1262
1662
|
}
|
|
1263
1663
|
if (notification.method === 'item/started') {
|
|
@@ -1287,15 +1687,76 @@ export class CodexAppServerAcpAdapter {
|
|
|
1287
1687
|
],
|
|
1288
1688
|
});
|
|
1289
1689
|
}
|
|
1290
|
-
async
|
|
1690
|
+
async emitReasoningThoughtChunkFromItem(sessionId, item) {
|
|
1691
|
+
const text = extractReasoningTextLocal(item);
|
|
1692
|
+
if (!text) {
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
await this.emitSessionUpdate(sessionId, {
|
|
1696
|
+
sessionUpdate: 'agent_thought_chunk',
|
|
1697
|
+
content: { type: 'text', text },
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
isReplayedTurnItem(session, turnId, itemId) {
|
|
1701
|
+
return session.replayedTurnItemKeys.has(toTurnItemKey(turnId, itemId));
|
|
1702
|
+
}
|
|
1703
|
+
async emitReasoningThoughtDelta(sessionId, session, turnId, itemId, delta) {
|
|
1704
|
+
if (this.isReplayedTurnItem(session, turnId, itemId)) {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
if (delta.length === 0) {
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
const existing = session.toolCallsByItemId.get(itemId);
|
|
1711
|
+
if (!existing) {
|
|
1712
|
+
const toolCallId = resolveToolCallId({
|
|
1713
|
+
itemId,
|
|
1714
|
+
});
|
|
1715
|
+
session.toolCallsByItemId.set(itemId, {
|
|
1716
|
+
toolCallId,
|
|
1717
|
+
title: 'reasoning',
|
|
1718
|
+
kind: 'think',
|
|
1719
|
+
locations: [],
|
|
1720
|
+
});
|
|
1721
|
+
await this.emitSessionUpdate(sessionId, {
|
|
1722
|
+
sessionUpdate: 'tool_call',
|
|
1723
|
+
toolCallId,
|
|
1724
|
+
title: 'reasoning',
|
|
1725
|
+
kind: 'think',
|
|
1726
|
+
status: 'pending',
|
|
1727
|
+
rawInput: {
|
|
1728
|
+
type: 'reasoning',
|
|
1729
|
+
id: itemId,
|
|
1730
|
+
},
|
|
1731
|
+
});
|
|
1732
|
+
await this.emitSessionUpdate(sessionId, {
|
|
1733
|
+
sessionUpdate: 'tool_call_update',
|
|
1734
|
+
toolCallId,
|
|
1735
|
+
title: 'reasoning',
|
|
1736
|
+
kind: 'think',
|
|
1737
|
+
status: 'in_progress',
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
session.reasoningDeltaItemIds.add(itemId);
|
|
1741
|
+
await this.emitSessionUpdate(sessionId, {
|
|
1742
|
+
sessionUpdate: 'agent_thought_chunk',
|
|
1743
|
+
content: { type: 'text', text: delta },
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
async emitToolCallProgress(sessionId, session, turnId, itemId, output) {
|
|
1747
|
+
if (this.isReplayedTurnItem(session, turnId, itemId)) {
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1291
1750
|
const toolCall = session.toolCallsByItemId.get(itemId);
|
|
1292
1751
|
if (!toolCall) {
|
|
1752
|
+
this.reportShapeDrift('tool_progress_without_tool_call', { turnId, itemId });
|
|
1293
1753
|
return;
|
|
1294
1754
|
}
|
|
1295
1755
|
await this.emitSessionUpdate(sessionId, {
|
|
1296
1756
|
sessionUpdate: 'tool_call_update',
|
|
1297
1757
|
toolCallId: toolCall.toolCallId,
|
|
1298
1758
|
status: 'in_progress',
|
|
1759
|
+
...(toolCall.locations.length > 0 ? { locations: toolCall.locations } : {}),
|
|
1299
1760
|
rawOutput: output,
|
|
1300
1761
|
});
|
|
1301
1762
|
}
|
|
@@ -1338,8 +1799,48 @@ export class CodexAppServerAcpAdapter {
|
|
|
1338
1799
|
this.settleTurn(session, session.activeTurn?.cancelRequested ? 'cancelled' : 'end_turn');
|
|
1339
1800
|
}
|
|
1340
1801
|
async handleItemStarted(session, item, turnId) {
|
|
1802
|
+
if (this.isReplayedTurnItem(session, turnId, item.id)) {
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
if (item.type === 'reasoning') {
|
|
1806
|
+
const existing = session.toolCallsByItemId.get(item.id);
|
|
1807
|
+
const toolInfo = existing ??
|
|
1808
|
+
{
|
|
1809
|
+
toolCallId: resolveToolCallId({
|
|
1810
|
+
itemId: item.id,
|
|
1811
|
+
source: item,
|
|
1812
|
+
}),
|
|
1813
|
+
title: 'reasoning',
|
|
1814
|
+
kind: 'think',
|
|
1815
|
+
locations: [],
|
|
1816
|
+
};
|
|
1817
|
+
if (!existing) {
|
|
1818
|
+
session.toolCallsByItemId.set(item.id, toolInfo);
|
|
1819
|
+
await this.emitSessionUpdate(session.sessionId, {
|
|
1820
|
+
sessionUpdate: 'tool_call',
|
|
1821
|
+
toolCallId: toolInfo.toolCallId,
|
|
1822
|
+
title: toolInfo.title,
|
|
1823
|
+
kind: toolInfo.kind,
|
|
1824
|
+
status: 'pending',
|
|
1825
|
+
rawInput: item,
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
await this.emitSessionUpdate(session.sessionId, {
|
|
1829
|
+
sessionUpdate: 'tool_call_update',
|
|
1830
|
+
toolCallId: toolInfo.toolCallId,
|
|
1831
|
+
title: toolInfo.title,
|
|
1832
|
+
kind: toolInfo.kind,
|
|
1833
|
+
status: 'in_progress',
|
|
1834
|
+
});
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1341
1837
|
const toolInfo = this.buildToolCallState(session, item, turnId);
|
|
1342
1838
|
if (!toolInfo) {
|
|
1839
|
+
this.reportShapeDrift('unhandled_item_started_type', {
|
|
1840
|
+
turnId,
|
|
1841
|
+
itemType: item.type,
|
|
1842
|
+
itemId: item.id,
|
|
1843
|
+
});
|
|
1343
1844
|
return;
|
|
1344
1845
|
}
|
|
1345
1846
|
session.toolCallsByItemId.set(item.id, toolInfo);
|
|
@@ -1349,6 +1850,7 @@ export class CodexAppServerAcpAdapter {
|
|
|
1349
1850
|
title: toolInfo.title,
|
|
1350
1851
|
kind: toolInfo.kind,
|
|
1351
1852
|
status: 'pending',
|
|
1853
|
+
...(toolInfo.locations.length > 0 ? { locations: toolInfo.locations } : {}),
|
|
1352
1854
|
rawInput: item,
|
|
1353
1855
|
});
|
|
1354
1856
|
const itemStatus = toToolStatus(item.status);
|
|
@@ -1361,8 +1863,52 @@ export class CodexAppServerAcpAdapter {
|
|
|
1361
1863
|
}
|
|
1362
1864
|
}
|
|
1363
1865
|
async handleItemCompleted(session, item, turnId) {
|
|
1866
|
+
if (this.isReplayedTurnItem(session, turnId, item.id)) {
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
if (item.type === 'reasoning') {
|
|
1870
|
+
const existing = session.toolCallsByItemId.get(item.id);
|
|
1871
|
+
const reasoningToolCallId = existing?.toolCallId ??
|
|
1872
|
+
resolveToolCallId({
|
|
1873
|
+
itemId: item.id,
|
|
1874
|
+
source: item,
|
|
1875
|
+
});
|
|
1876
|
+
if (existing) {
|
|
1877
|
+
await this.emitSessionUpdate(session.sessionId, {
|
|
1878
|
+
sessionUpdate: 'tool_call_update',
|
|
1879
|
+
toolCallId: reasoningToolCallId,
|
|
1880
|
+
title: existing.title,
|
|
1881
|
+
kind: existing.kind,
|
|
1882
|
+
status: toToolStatus(item.status) ?? 'completed',
|
|
1883
|
+
rawOutput: item,
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
else {
|
|
1887
|
+
await this.emitSessionUpdate(session.sessionId, {
|
|
1888
|
+
sessionUpdate: 'tool_call',
|
|
1889
|
+
toolCallId: reasoningToolCallId,
|
|
1890
|
+
title: 'reasoning',
|
|
1891
|
+
kind: 'think',
|
|
1892
|
+
status: toToolStatus(item.status) ?? 'completed',
|
|
1893
|
+
rawInput: item,
|
|
1894
|
+
rawOutput: item,
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
session.toolCallsByItemId.delete(item.id);
|
|
1898
|
+
const sawDelta = session.reasoningDeltaItemIds.has(item.id);
|
|
1899
|
+
session.reasoningDeltaItemIds.delete(item.id);
|
|
1900
|
+
if (!sawDelta) {
|
|
1901
|
+
await this.emitReasoningThoughtChunkFromItem(session.sessionId, item);
|
|
1902
|
+
}
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1364
1905
|
const existing = session.toolCallsByItemId.get(item.id);
|
|
1365
1906
|
if (!existing) {
|
|
1907
|
+
this.reportShapeDrift('item_completed_without_started_state', {
|
|
1908
|
+
turnId,
|
|
1909
|
+
itemType: item.type,
|
|
1910
|
+
itemId: item.id,
|
|
1911
|
+
});
|
|
1366
1912
|
return;
|
|
1367
1913
|
}
|
|
1368
1914
|
const shouldHoldTurnForPlanApproval = item.type === 'plan' &&
|
|
@@ -1374,7 +1920,8 @@ export class CodexAppServerAcpAdapter {
|
|
|
1374
1920
|
}
|
|
1375
1921
|
const statusFromItem = toToolStatus(item.status);
|
|
1376
1922
|
const status = statusFromItem ?? 'completed';
|
|
1377
|
-
const
|
|
1923
|
+
const completionLocations = this.extractLocations(item);
|
|
1924
|
+
const locations = dedupeLocations([...existing.locations, ...completionLocations]);
|
|
1378
1925
|
await this.emitSessionUpdate(session.sessionId, {
|
|
1379
1926
|
sessionUpdate: 'tool_call_update',
|
|
1380
1927
|
toolCallId: existing.toolCallId,
|
|
@@ -1411,28 +1958,43 @@ export class CodexAppServerAcpAdapter {
|
|
|
1411
1958
|
}
|
|
1412
1959
|
return null;
|
|
1413
1960
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1961
|
+
getPlanExitModePriority(modeId) {
|
|
1962
|
+
const normalized = modeId.toLowerCase();
|
|
1963
|
+
const index = PLAN_EXIT_MODE_PREFERENCE.findIndex((preferred) => preferred.toLowerCase() === normalized);
|
|
1964
|
+
return index >= 0 ? index : PLAN_EXIT_MODE_PREFERENCE.length;
|
|
1965
|
+
}
|
|
1966
|
+
comparePlanExitModePreference(left, right) {
|
|
1967
|
+
const priorityDiff = this.getPlanExitModePriority(left) - this.getPlanExitModePriority(right);
|
|
1968
|
+
if (priorityDiff !== 0) {
|
|
1969
|
+
return priorityDiff;
|
|
1417
1970
|
}
|
|
1418
|
-
return
|
|
1971
|
+
return left.localeCompare(right);
|
|
1419
1972
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1973
|
+
resolveCollaborationModeLabel(modeId) {
|
|
1974
|
+
const entry = this.collaborationModes.find((candidate) => candidate.mode === modeId);
|
|
1975
|
+
if (entry?.name) {
|
|
1976
|
+
return entry.name;
|
|
1423
1977
|
}
|
|
1978
|
+
return sanitizeModeName(modeId);
|
|
1979
|
+
}
|
|
1980
|
+
buildPlanApprovalOptions(session) {
|
|
1981
|
+
const currentMode = session.defaults.collaborationMode;
|
|
1424
1982
|
const availableModes = this.getCollaborationModeValues(currentMode);
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1983
|
+
const nonPlanModes = availableModes
|
|
1984
|
+
.filter((modeId) => !isPlanLikeMode(modeId))
|
|
1985
|
+
.sort((left, right) => this.comparePlanExitModePreference(left, right));
|
|
1986
|
+
const approvableModeIds = new Set(nonPlanModes);
|
|
1987
|
+
const options = nonPlanModes.map((modeId) => ({
|
|
1988
|
+
optionId: modeId,
|
|
1989
|
+
name: `Approve and switch to ${this.resolveCollaborationModeLabel(modeId)}`,
|
|
1990
|
+
kind: 'allow_once',
|
|
1991
|
+
}));
|
|
1992
|
+
options.push({
|
|
1993
|
+
optionId: currentMode,
|
|
1994
|
+
name: 'Keep planning',
|
|
1995
|
+
kind: 'reject_once',
|
|
1996
|
+
});
|
|
1997
|
+
return { options, approvableModeIds };
|
|
1436
1998
|
}
|
|
1437
1999
|
holdTurnUntilPlanApprovalResolves(session, turnId) {
|
|
1438
2000
|
if (this.hasPendingPlanApprovals(session, turnId)) {
|
|
@@ -1494,6 +2056,18 @@ export class CodexAppServerAcpAdapter {
|
|
|
1494
2056
|
rawInput: approvalInput,
|
|
1495
2057
|
});
|
|
1496
2058
|
try {
|
|
2059
|
+
const { options: planApprovalOptions, approvableModeIds } = this.buildPlanApprovalOptions(session);
|
|
2060
|
+
if (approvableModeIds.size === 0) {
|
|
2061
|
+
await this.emitSessionUpdate(session.sessionId, {
|
|
2062
|
+
sessionUpdate: 'tool_call_update',
|
|
2063
|
+
toolCallId: approvalToolCallId,
|
|
2064
|
+
kind: 'switch_mode',
|
|
2065
|
+
title: 'ExitPlanMode',
|
|
2066
|
+
status: 'failed',
|
|
2067
|
+
rawOutput: 'Plan proposed, but no non-plan collaboration mode is available.',
|
|
2068
|
+
});
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
1497
2071
|
const permissionResult = await this.connection.requestPermission({
|
|
1498
2072
|
sessionId: session.sessionId,
|
|
1499
2073
|
toolCall: {
|
|
@@ -1503,30 +2077,19 @@ export class CodexAppServerAcpAdapter {
|
|
|
1503
2077
|
status: 'pending',
|
|
1504
2078
|
rawInput: approvalInput,
|
|
1505
2079
|
},
|
|
1506
|
-
options:
|
|
1507
|
-
{
|
|
1508
|
-
optionId: 'allow_once',
|
|
1509
|
-
name: 'Approve plan',
|
|
1510
|
-
kind: 'allow_once',
|
|
1511
|
-
},
|
|
1512
|
-
{
|
|
1513
|
-
optionId: 'reject_once',
|
|
1514
|
-
name: 'Request changes',
|
|
1515
|
-
kind: 'reject_once',
|
|
1516
|
-
},
|
|
1517
|
-
],
|
|
2080
|
+
options: planApprovalOptions,
|
|
1518
2081
|
});
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
}
|
|
2082
|
+
const selectedMode = permissionResult.outcome.outcome === 'selected' &&
|
|
2083
|
+
approvableModeIds.has(permissionResult.outcome.optionId)
|
|
2084
|
+
? permissionResult.outcome.optionId
|
|
2085
|
+
: null;
|
|
2086
|
+
const approved = selectedMode !== null;
|
|
2087
|
+
if (selectedMode && session.defaults.collaborationMode !== selectedMode) {
|
|
2088
|
+
session.defaults.collaborationMode = selectedMode;
|
|
2089
|
+
await this.emitSessionUpdate(session.sessionId, {
|
|
2090
|
+
sessionUpdate: 'config_option_update',
|
|
2091
|
+
configOptions: this.buildConfigOptions(session),
|
|
2092
|
+
});
|
|
1530
2093
|
}
|
|
1531
2094
|
await this.emitSessionUpdate(session.sessionId, {
|
|
1532
2095
|
sessionUpdate: 'tool_call_update',
|
|
@@ -1573,26 +2136,32 @@ export class CodexAppServerAcpAdapter {
|
|
|
1573
2136
|
}
|
|
1574
2137
|
return locations;
|
|
1575
2138
|
}
|
|
1576
|
-
buildToolCallState(session, item,
|
|
2139
|
+
buildToolCallState(session, item, _turnId) {
|
|
1577
2140
|
const kindByType = {
|
|
1578
2141
|
commandExecution: 'execute',
|
|
1579
2142
|
fileChange: 'edit',
|
|
1580
2143
|
mcpToolCall: 'fetch',
|
|
1581
2144
|
webSearch: 'search',
|
|
1582
2145
|
plan: 'think',
|
|
1583
|
-
reasoning: 'think',
|
|
1584
2146
|
};
|
|
1585
2147
|
const kind = kindByType[item.type];
|
|
1586
2148
|
if (!kind) {
|
|
1587
2149
|
return null;
|
|
1588
2150
|
}
|
|
1589
2151
|
let title = item.type;
|
|
2152
|
+
let kindToEmit = kind;
|
|
2153
|
+
let locations = [];
|
|
1590
2154
|
if (item.type === 'commandExecution') {
|
|
1591
2155
|
const command = asString(item.command);
|
|
1592
|
-
|
|
2156
|
+
const cwd = asString(item.cwd) ?? session.cwd;
|
|
2157
|
+
const parsed = resolveCommandDisplay({ command, cwd });
|
|
2158
|
+
title = parsed.title;
|
|
2159
|
+
kindToEmit = parsed.kind;
|
|
2160
|
+
locations = parsed.locations;
|
|
1593
2161
|
}
|
|
1594
2162
|
else if (item.type === 'fileChange') {
|
|
1595
2163
|
title = 'fileChange';
|
|
2164
|
+
locations = this.extractLocations(item);
|
|
1596
2165
|
}
|
|
1597
2166
|
else if (item.type === 'mcpToolCall') {
|
|
1598
2167
|
const server = asString(item.server) ?? 'mcp';
|
|
@@ -1604,15 +2173,81 @@ export class CodexAppServerAcpAdapter {
|
|
|
1604
2173
|
title = query ? `webSearch:${query}` : 'webSearch';
|
|
1605
2174
|
}
|
|
1606
2175
|
return {
|
|
1607
|
-
toolCallId:
|
|
1608
|
-
|
|
2176
|
+
toolCallId: resolveToolCallId({
|
|
2177
|
+
itemId: item.id,
|
|
2178
|
+
source: item,
|
|
2179
|
+
}),
|
|
2180
|
+
kind: kindToEmit,
|
|
1609
2181
|
title,
|
|
2182
|
+
locations,
|
|
1610
2183
|
};
|
|
1611
2184
|
}
|
|
2185
|
+
buildPermissionOptions(params) {
|
|
2186
|
+
if (params.method === 'item/tool/requestUserInput') {
|
|
2187
|
+
return buildToolUserInputPermissionOptions(params.questions);
|
|
2188
|
+
}
|
|
2189
|
+
if (params.method === 'item/commandExecution/requestApproval') {
|
|
2190
|
+
return [
|
|
2191
|
+
{
|
|
2192
|
+
optionId: 'allow_always',
|
|
2193
|
+
name: 'Allow for session',
|
|
2194
|
+
kind: 'allow_always',
|
|
2195
|
+
},
|
|
2196
|
+
{
|
|
2197
|
+
optionId: 'allow_once',
|
|
2198
|
+
name: 'Allow once',
|
|
2199
|
+
kind: 'allow_once',
|
|
2200
|
+
},
|
|
2201
|
+
{
|
|
2202
|
+
optionId: 'reject_once',
|
|
2203
|
+
name: 'Reject',
|
|
2204
|
+
kind: 'reject_once',
|
|
2205
|
+
},
|
|
2206
|
+
];
|
|
2207
|
+
}
|
|
2208
|
+
return [
|
|
2209
|
+
{
|
|
2210
|
+
optionId: 'allow_once',
|
|
2211
|
+
name: 'Allow once',
|
|
2212
|
+
kind: 'allow_once',
|
|
2213
|
+
},
|
|
2214
|
+
{
|
|
2215
|
+
optionId: 'reject_once',
|
|
2216
|
+
name: 'Reject',
|
|
2217
|
+
kind: 'reject_once',
|
|
2218
|
+
},
|
|
2219
|
+
];
|
|
2220
|
+
}
|
|
2221
|
+
async maybeAutoApproveCommandRequest(params) {
|
|
2222
|
+
if (params.request.method !== 'item/commandExecution/requestApproval') {
|
|
2223
|
+
return false;
|
|
2224
|
+
}
|
|
2225
|
+
const scopeKey = buildCommandApprovalScopeKey({
|
|
2226
|
+
command: asString(params.request.params.command),
|
|
2227
|
+
cwd: asString(params.request.params.cwd) ?? params.session.cwd,
|
|
2228
|
+
});
|
|
2229
|
+
if (!(scopeKey && params.session.commandApprovalScopes.has(scopeKey))) {
|
|
2230
|
+
return false;
|
|
2231
|
+
}
|
|
2232
|
+
await this.respondToCodexPermissionRequest({
|
|
2233
|
+
request: params.request,
|
|
2234
|
+
permission: {
|
|
2235
|
+
outcome: { outcome: 'selected', optionId: 'allow_always' },
|
|
2236
|
+
},
|
|
2237
|
+
session: params.session,
|
|
2238
|
+
toolCallId: params.toolCallId,
|
|
2239
|
+
questions: [],
|
|
2240
|
+
});
|
|
2241
|
+
return true;
|
|
2242
|
+
}
|
|
1612
2243
|
async handleCodexServerRequest(request) {
|
|
1613
2244
|
try {
|
|
1614
2245
|
const parsed = knownCodexServerRequestSchema.safeParse(request);
|
|
1615
2246
|
if (!parsed.success) {
|
|
2247
|
+
this.reportShapeDrift('malformed_server_request', {
|
|
2248
|
+
method: request.method,
|
|
2249
|
+
issues: parsed.error.issues.slice(0, 3).map((issue) => issue.message),
|
|
2250
|
+
});
|
|
1616
2251
|
this.codex.respondError(request.id, {
|
|
1617
2252
|
code: -32_602,
|
|
1618
2253
|
message: 'Unsupported codex server request payload',
|
|
@@ -1622,6 +2257,11 @@ export class CodexAppServerAcpAdapter {
|
|
|
1622
2257
|
const typed = parsed.data;
|
|
1623
2258
|
const sessionId = this.sessionIdByThreadId.get(typed.params.threadId);
|
|
1624
2259
|
if (!sessionId) {
|
|
2260
|
+
this.reportShapeDrift('request_for_unknown_thread', {
|
|
2261
|
+
method: typed.method,
|
|
2262
|
+
threadId: typed.params.threadId,
|
|
2263
|
+
itemId: typed.params.itemId,
|
|
2264
|
+
});
|
|
1625
2265
|
this.codex.respondError(typed.id, {
|
|
1626
2266
|
code: -32_603,
|
|
1627
2267
|
message: 'No ACP session mapped for this thread',
|
|
@@ -1630,6 +2270,11 @@ export class CodexAppServerAcpAdapter {
|
|
|
1630
2270
|
}
|
|
1631
2271
|
const session = this.sessions.get(sessionId);
|
|
1632
2272
|
if (!session) {
|
|
2273
|
+
this.reportShapeDrift('request_for_missing_session', {
|
|
2274
|
+
method: typed.method,
|
|
2275
|
+
sessionId,
|
|
2276
|
+
itemId: typed.params.itemId,
|
|
2277
|
+
});
|
|
1633
2278
|
this.codex.respondError(typed.id, {
|
|
1634
2279
|
code: -32_603,
|
|
1635
2280
|
message: 'No ACP session mapped for this thread',
|
|
@@ -1647,24 +2292,30 @@ export class CodexAppServerAcpAdapter {
|
|
|
1647
2292
|
title: toolCall.title,
|
|
1648
2293
|
kind: toolCall.kind,
|
|
1649
2294
|
status: 'pending',
|
|
2295
|
+
...(toolCall.locations.length > 0 ? { locations: toolCall.locations } : {}),
|
|
1650
2296
|
rawInput: typed.params,
|
|
1651
2297
|
});
|
|
1652
2298
|
}
|
|
2299
|
+
if (await this.maybeAutoApproveCommandRequest({
|
|
2300
|
+
request: typed,
|
|
2301
|
+
session,
|
|
2302
|
+
toolCallId: toolCall.toolCallId,
|
|
2303
|
+
})) {
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
1653
2306
|
const questions = typed.method === 'item/tool/requestUserInput' ? typed.params.questions : [];
|
|
1654
|
-
const permissionOptions =
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
},
|
|
1667
|
-
];
|
|
2307
|
+
const permissionOptions = this.buildPermissionOptions({
|
|
2308
|
+
method: typed.method,
|
|
2309
|
+
questions,
|
|
2310
|
+
});
|
|
2311
|
+
await this.emitSessionUpdate(sessionId, {
|
|
2312
|
+
sessionUpdate: 'tool_call_update',
|
|
2313
|
+
toolCallId: toolCall.toolCallId,
|
|
2314
|
+
title: toolCall.title,
|
|
2315
|
+
kind: toolCall.kind,
|
|
2316
|
+
status: 'pending',
|
|
2317
|
+
...(toolCall.locations.length > 0 ? { locations: toolCall.locations } : {}),
|
|
2318
|
+
});
|
|
1668
2319
|
const permissionResult = await this.connection.requestPermission({
|
|
1669
2320
|
sessionId,
|
|
1670
2321
|
toolCall: {
|
|
@@ -1693,67 +2344,110 @@ export class CodexAppServerAcpAdapter {
|
|
|
1693
2344
|
});
|
|
1694
2345
|
}
|
|
1695
2346
|
}
|
|
1696
|
-
buildApprovalToolCallState(session, method, itemId,
|
|
2347
|
+
buildApprovalToolCallState(session, method, itemId, _turnId, params) {
|
|
1697
2348
|
if (method === 'item/commandExecution/requestApproval') {
|
|
1698
2349
|
const command = asString(params.command);
|
|
2350
|
+
const cwd = asString(params.cwd) ?? session.cwd;
|
|
2351
|
+
const parsed = resolveCommandDisplay({ command, cwd });
|
|
1699
2352
|
return {
|
|
1700
|
-
toolCallId:
|
|
1701
|
-
|
|
1702
|
-
|
|
2353
|
+
toolCallId: resolveToolCallId({
|
|
2354
|
+
itemId,
|
|
2355
|
+
source: params,
|
|
2356
|
+
}),
|
|
2357
|
+
title: parsed.title,
|
|
2358
|
+
kind: parsed.kind,
|
|
2359
|
+
locations: parsed.locations,
|
|
1703
2360
|
};
|
|
1704
2361
|
}
|
|
1705
2362
|
if (method === 'item/fileChange/requestApproval') {
|
|
2363
|
+
const grantRoot = asString(params.grantRoot);
|
|
1706
2364
|
return {
|
|
1707
|
-
toolCallId:
|
|
2365
|
+
toolCallId: resolveToolCallId({
|
|
2366
|
+
itemId,
|
|
2367
|
+
source: params,
|
|
2368
|
+
}),
|
|
1708
2369
|
title: 'fileChange',
|
|
1709
2370
|
kind: 'edit',
|
|
2371
|
+
locations: grantRoot ? [{ path: grantRoot }] : [],
|
|
1710
2372
|
};
|
|
1711
2373
|
}
|
|
1712
2374
|
return {
|
|
1713
|
-
toolCallId:
|
|
2375
|
+
toolCallId: resolveToolCallId({
|
|
2376
|
+
itemId,
|
|
2377
|
+
source: params,
|
|
2378
|
+
}),
|
|
1714
2379
|
title: 'item/tool/requestUserInput',
|
|
1715
2380
|
kind: 'other',
|
|
2381
|
+
locations: [],
|
|
1716
2382
|
};
|
|
1717
2383
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
toolCallId: params.toolCallId,
|
|
1741
|
-
status: 'failed',
|
|
1742
|
-
rawOutput: { error: message },
|
|
1743
|
-
});
|
|
1744
|
-
return;
|
|
1745
|
-
}
|
|
1746
|
-
this.codex.respondSuccess(params.request.id, {
|
|
1747
|
-
answers,
|
|
2384
|
+
isAllowedPermissionSelection(selectedOptionId) {
|
|
2385
|
+
return selectedOptionId === 'allow_once' || selectedOptionId === 'allow_always';
|
|
2386
|
+
}
|
|
2387
|
+
async handleToolUserInputPermissionResponse(params) {
|
|
2388
|
+
if (params.request.method !== 'item/tool/requestUserInput') {
|
|
2389
|
+
return false;
|
|
2390
|
+
}
|
|
2391
|
+
const rejected = params.selectedOptionId === null || params.selectedOptionId === 'reject_once';
|
|
2392
|
+
let answers;
|
|
2393
|
+
try {
|
|
2394
|
+
answers = buildToolUserInputAnswers({
|
|
2395
|
+
questions: params.questions,
|
|
2396
|
+
permission: params.permission,
|
|
2397
|
+
selectedOptionId: params.selectedOptionId,
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
catch (error) {
|
|
2401
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2402
|
+
this.codex.respondError(params.request.id, {
|
|
2403
|
+
code: -32_602,
|
|
2404
|
+
message: 'Failed to map requestUserInput answers',
|
|
2405
|
+
data: { error: message },
|
|
1748
2406
|
});
|
|
1749
2407
|
await this.emitSessionUpdate(params.session.sessionId, {
|
|
1750
2408
|
sessionUpdate: 'tool_call_update',
|
|
1751
2409
|
toolCallId: params.toolCallId,
|
|
1752
|
-
status:
|
|
1753
|
-
rawOutput:
|
|
2410
|
+
status: 'failed',
|
|
2411
|
+
rawOutput: { error: message },
|
|
1754
2412
|
});
|
|
2413
|
+
return true;
|
|
2414
|
+
}
|
|
2415
|
+
this.codex.respondSuccess(params.request.id, {
|
|
2416
|
+
answers,
|
|
2417
|
+
});
|
|
2418
|
+
await this.emitSessionUpdate(params.session.sessionId, {
|
|
2419
|
+
sessionUpdate: 'tool_call_update',
|
|
2420
|
+
toolCallId: params.toolCallId,
|
|
2421
|
+
status: rejected ? 'failed' : 'completed',
|
|
2422
|
+
rawOutput: rejected ? { outcome: 'rejected' } : { answers },
|
|
2423
|
+
});
|
|
2424
|
+
return true;
|
|
2425
|
+
}
|
|
2426
|
+
async respondToCodexPermissionRequest(params) {
|
|
2427
|
+
const selected = params.permission.outcome.outcome === 'selected' ? params.permission.outcome.optionId : null;
|
|
2428
|
+
if (await this.handleToolUserInputPermissionResponse({
|
|
2429
|
+
...params,
|
|
2430
|
+
selectedOptionId: selected,
|
|
2431
|
+
})) {
|
|
1755
2432
|
return;
|
|
1756
2433
|
}
|
|
2434
|
+
if (params.request.method === 'item/commandExecution/requestApproval' &&
|
|
2435
|
+
selected === 'allow_always') {
|
|
2436
|
+
const scopeKey = buildCommandApprovalScopeKey({
|
|
2437
|
+
command: asString(params.request.params.command),
|
|
2438
|
+
cwd: asString(params.request.params.cwd) ?? params.session.cwd,
|
|
2439
|
+
});
|
|
2440
|
+
if (scopeKey) {
|
|
2441
|
+
params.session.commandApprovalScopes.add(scopeKey);
|
|
2442
|
+
}
|
|
2443
|
+
else {
|
|
2444
|
+
this.reportShapeDrift('allow_always_without_scope_key', {
|
|
2445
|
+
itemId: params.request.params.itemId,
|
|
2446
|
+
command: params.request.params.command,
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
const allow = this.isAllowedPermissionSelection(selected);
|
|
1757
2451
|
this.codex.respondSuccess(params.request.id, {
|
|
1758
2452
|
decision: allow ? 'accept' : 'decline',
|
|
1759
2453
|
});
|
|
@@ -1775,8 +2469,16 @@ export class CodexAppServerAcpAdapter {
|
|
|
1775
2469
|
session.planApprovalRequestedByTurnId.clear();
|
|
1776
2470
|
session.pendingPlanApprovalsByTurnId.clear();
|
|
1777
2471
|
session.toolCallsByItemId.clear();
|
|
2472
|
+
session.reasoningDeltaItemIds.clear();
|
|
1778
2473
|
session.pendingTurnCompletionsByTurnId.clear();
|
|
1779
2474
|
}
|
|
2475
|
+
reportShapeDrift(event, details) {
|
|
2476
|
+
const count = (this.shapeDriftCounts.get(event) ?? 0) + 1;
|
|
2477
|
+
this.shapeDriftCounts.set(event, count);
|
|
2478
|
+
const includeDetails = details !== undefined && (count <= 5 || count % 50 === 0);
|
|
2479
|
+
const detailsSuffix = includeDetails ? ` details=${toShapeDriftDetails(details)}` : '';
|
|
2480
|
+
process.stderr.write(`[codex-app-server-acp] shape-drift event=${event} count=${count}${detailsSuffix}\n`);
|
|
2481
|
+
}
|
|
1780
2482
|
async emitSessionUpdate(sessionId, update) {
|
|
1781
2483
|
try {
|
|
1782
2484
|
await this.connection.sessionUpdate({ sessionId, update });
|