@buildautomaton/cli 0.1.57 → 0.1.58

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/index.js CHANGED
@@ -23584,6 +23584,81 @@ function isUserEndedSessionTurnErrorText(errorText) {
23584
23584
  // ../types/src/session-kind.ts
23585
23585
  var SESSION_KINDS = ["regular", "planning", "planned"];
23586
23586
  var SESSION_KIND_SET = new Set(SESSION_KINDS);
23587
+ function parseSessionKind(raw) {
23588
+ if (raw === "todo") return "planned";
23589
+ if (typeof raw === "string" && SESSION_KIND_SET.has(raw)) return raw;
23590
+ return "regular";
23591
+ }
23592
+
23593
+ // ../types/src/planning-todo.ts
23594
+ function planningTodoTitle(item) {
23595
+ return item.content.title;
23596
+ }
23597
+
23598
+ // ../types/src/parse-planning-todo-agent-json.ts
23599
+ function parsePlanningTodoAgentJson(raw) {
23600
+ if (raw == null || raw.trim() === "") return [];
23601
+ let text = raw.trim();
23602
+ const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
23603
+ if (fence?.[1]) text = fence[1].trim();
23604
+ let parsed;
23605
+ try {
23606
+ parsed = JSON.parse(text);
23607
+ } catch {
23608
+ const start = text.indexOf("[");
23609
+ const end = text.lastIndexOf("]");
23610
+ if (start < 0 || end <= start) return [];
23611
+ try {
23612
+ parsed = JSON.parse(text.slice(start, end + 1));
23613
+ } catch {
23614
+ return [];
23615
+ }
23616
+ }
23617
+ if (!Array.isArray(parsed)) return [];
23618
+ const rows = [];
23619
+ for (const item of parsed) {
23620
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
23621
+ const o = item;
23622
+ const title = typeof o.title === "string" ? o.title.trim() : "";
23623
+ const prompt = typeof o.prompt === "string" ? o.prompt.trim() : typeof o.promptText === "string" ? o.promptText.trim() : "";
23624
+ if (!title || !prompt) continue;
23625
+ rows.push({ title, prompt });
23626
+ }
23627
+ return rows;
23628
+ }
23629
+
23630
+ // ../types/src/build-planning-session-agent-prompt.ts
23631
+ var PLANNING_INSTRUCTIONS = [
23632
+ "PLANNING MODE: You must NOT make any changes to the codebase, filesystem, or environment.",
23633
+ "Do not run tools that modify files or execute commands that change state.",
23634
+ "Your only task is to analyze and produce a plan.",
23635
+ "",
23636
+ "Respond with ONLY a single JSON array \u2014 no markdown fences, no other text before or after.",
23637
+ "Each array element must be an object with exactly:",
23638
+ '- "title": a one-line summary of the task',
23639
+ '- "prompt": the full prompt to use when creating a new draft session from this todo',
23640
+ "",
23641
+ "Example:",
23642
+ '[{"title":"Add user login","prompt":"Implement a login form with email and password..."}]'
23643
+ ].join("\n");
23644
+ function buildPlanningSessionAgentPrompt(userPrompt) {
23645
+ return [PLANNING_INSTRUCTIONS, "", "User request:", userPrompt.trim()].join("\n");
23646
+ }
23647
+ function buildPlanningSessionFollowUpAgentPrompt(userPrompt, existingTodos) {
23648
+ const todoLines = existingTodos.length > 0 ? existingTodos.map((t) => {
23649
+ const status = t.plannedSessionId ? "DONE (draft session already created)" : "OPEN";
23650
+ return `- [${status}] ${planningTodoTitle(t)}`;
23651
+ }).join("\n") : "(none yet)";
23652
+ return [
23653
+ PLANNING_INSTRUCTIONS,
23654
+ "",
23655
+ "Existing todo items from this planning session (consider these when responding; do not duplicate completed items):",
23656
+ todoLines,
23657
+ "",
23658
+ "User follow-up:",
23659
+ userPrompt.trim()
23660
+ ].join("\n");
23661
+ }
23587
23662
 
23588
23663
  // ../types/src/change-summary-path.ts
23589
23664
  function normalizeRepoRelativePath(p) {
@@ -23967,6 +24042,19 @@ function buildCliAutoApprovedPermissionRpcResult(requestParams) {
23967
24042
  };
23968
24043
  }
23969
24044
 
24045
+ // ../types/src/acp-agent-text-stream-bucket.ts
24046
+ function resolveAgentTextStreamBucket(params) {
24047
+ const k = params.transcriptRowKind;
24048
+ const su = params.sessionUpdate;
24049
+ if (k === "agent_thought_chunk" || su === "agent_thought_chunk" || k === "thinking" || su === "thinking") {
24050
+ return "thought";
24051
+ }
24052
+ if (k === "agent_message_chunk" || su === "agent_message_chunk" || k === "update" || su === "update") {
24053
+ return "message";
24054
+ }
24055
+ return null;
24056
+ }
24057
+
23970
24058
  // ../types/src/agent-config.ts
23971
24059
  var AGENT_CONFIG_CLAUDE_PERMISSION_MODE_KEY = "claude_permission_mode";
23972
24060
  var AGENT_CONFIG_CLI_PERMISSION_MODE_KEY = "cli_permission_mode";
@@ -25184,7 +25272,7 @@ function installBridgeProcessResilience() {
25184
25272
  }
25185
25273
 
25186
25274
  // src/cli-version.ts
25187
- var CLI_VERSION = "0.1.57".length > 0 ? "0.1.57" : "0.0.0-dev";
25275
+ var CLI_VERSION = "0.1.58".length > 0 ? "0.1.58" : "0.0.0-dev";
25188
25276
 
25189
25277
  // src/connection/heartbeat/constants.ts
25190
25278
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -31057,6 +31145,185 @@ function createBridgeOnRequest(opts) {
31057
31145
  };
31058
31146
  }
31059
31147
 
31148
+ // src/agents/acp/hooks/bridge-on-session-update/constants.ts
31149
+ var PATH_SNAPSHOT_DEBOUNCE_MS = 500;
31150
+
31151
+ // src/agents/acp/hooks/bridge-on-session-update/tool-key.ts
31152
+ function getToolCallId(params) {
31153
+ if (!params || typeof params !== "object") return "";
31154
+ const u = params;
31155
+ const id = u.toolCallId ?? u.tool_call_id;
31156
+ return typeof id === "string" ? id : "";
31157
+ }
31158
+ function isCompletedToolStatus(status) {
31159
+ if (typeof status !== "string") return false;
31160
+ const s = status.toLowerCase();
31161
+ return s === "completed" || s === "complete" || s === "succeeded" || s === "success";
31162
+ }
31163
+ function accumulateToolPaths(toolKey, paths, acc) {
31164
+ if (!toolKey || paths.length === 0) return;
31165
+ let s = acc.get(toolKey);
31166
+ if (!s) {
31167
+ s = /* @__PURE__ */ new Set();
31168
+ acc.set(toolKey, s);
31169
+ }
31170
+ for (const p of paths) s.add(p);
31171
+ }
31172
+
31173
+ // src/agents/acp/hooks/bridge-on-session-update/path-snapshot-tracker.ts
31174
+ var PathSnapshotTracker = class {
31175
+ lastSeenRunId;
31176
+ anonToolSeq = 0;
31177
+ lastAnonymousToolKey = null;
31178
+ beforeByToolKey = /* @__PURE__ */ new Map();
31179
+ debouncers = /* @__PURE__ */ new Map();
31180
+ accumulatedPathsByToolKey = /* @__PURE__ */ new Map();
31181
+ clearRunState() {
31182
+ for (const t of this.debouncers.values()) clearTimeout(t);
31183
+ this.debouncers.clear();
31184
+ this.beforeByToolKey.clear();
31185
+ this.accumulatedPathsByToolKey.clear();
31186
+ this.lastAnonymousToolKey = null;
31187
+ }
31188
+ onRunIdChanged(runId) {
31189
+ if (runId && runId !== this.lastSeenRunId) {
31190
+ this.lastSeenRunId = runId;
31191
+ this.clearRunState();
31192
+ }
31193
+ }
31194
+ resolveToolKey(params, updateKind) {
31195
+ const id = getToolCallId(params);
31196
+ if (id) return id;
31197
+ if (updateKind === "tool_call") {
31198
+ this.lastAnonymousToolKey = `anon:${++this.anonToolSeq}`;
31199
+ return this.lastAnonymousToolKey;
31200
+ }
31201
+ return this.lastAnonymousToolKey ?? "_unknown";
31202
+ }
31203
+ resetToolSnapshots(toolKey) {
31204
+ const t = this.debouncers.get(toolKey);
31205
+ if (t) clearTimeout(t);
31206
+ this.debouncers.delete(toolKey);
31207
+ this.beforeByToolKey.delete(toolKey);
31208
+ this.accumulatedPathsByToolKey.delete(toolKey);
31209
+ }
31210
+ captureBeforeFromDisk(toolKey, paths, sessionParentPath) {
31211
+ if (paths.length === 0) return;
31212
+ let m = this.beforeByToolKey.get(toolKey);
31213
+ if (!m) {
31214
+ m = /* @__PURE__ */ new Map();
31215
+ this.beforeByToolKey.set(toolKey, m);
31216
+ }
31217
+ for (const p of paths) {
31218
+ if (m.has(p)) continue;
31219
+ m.set(p, readUtf8WorkspaceFile(sessionParentPath, p));
31220
+ }
31221
+ }
31222
+ ensureBeforeFromHeadForMissing(toolKey, paths, sessionParentPath) {
31223
+ let m = this.beforeByToolKey.get(toolKey);
31224
+ if (!m) {
31225
+ m = /* @__PURE__ */ new Map();
31226
+ this.beforeByToolKey.set(toolKey, m);
31227
+ }
31228
+ for (const p of paths) {
31229
+ if (m.has(p)) continue;
31230
+ m.set(p, readGitHeadBlob(sessionParentPath, p));
31231
+ }
31232
+ }
31233
+ flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
31234
+ const t = this.debouncers.get(toolKey);
31235
+ if (t) clearTimeout(t);
31236
+ this.debouncers.delete(toolKey);
31237
+ const beforeMap = this.beforeByToolKey.get(toolKey);
31238
+ if (!beforeMap || beforeMap.size === 0) return;
31239
+ this.beforeByToolKey.delete(toolKey);
31240
+ if (!send || !runId || !sessionId) return;
31241
+ for (const [displayPath, oldText] of beforeMap) {
31242
+ const newText = readUtf8WorkspaceFile(sessionParentPath, displayPath);
31243
+ if (oldText === newText) continue;
31244
+ const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
31245
+ const dirFlags = getSessionFileChangeDirectoryFlags(sessionParentPath, displayPath);
31246
+ try {
31247
+ send({
31248
+ type: "session_file_change",
31249
+ sessionId,
31250
+ runId,
31251
+ path: displayPath,
31252
+ oldText,
31253
+ newText,
31254
+ patchContent,
31255
+ isDirectory: dirFlags.isDirectory,
31256
+ directoryRemoved: dirFlags.directoryRemoved
31257
+ });
31258
+ sentPaths.add(displayPath);
31259
+ } catch (err) {
31260
+ log2(`[Bridge service] Session file change failed for ${displayPath}: ${errorMessage(err)}`);
31261
+ }
31262
+ }
31263
+ }
31264
+ scheduleDebouncedFlush(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
31265
+ const prev = this.debouncers.get(toolKey);
31266
+ if (prev) clearTimeout(prev);
31267
+ this.debouncers.set(
31268
+ toolKey,
31269
+ setTimeout(() => {
31270
+ this.debouncers.delete(toolKey);
31271
+ this.flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
31272
+ }, PATH_SNAPSHOT_DEBOUNCE_MS)
31273
+ );
31274
+ }
31275
+ handleToolCallLifecycle(updateKind, toolKey, toolPaths, status, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
31276
+ if (updateKind === "tool_call") {
31277
+ this.resetToolSnapshots(toolKey);
31278
+ }
31279
+ if (updateKind === "tool_call") {
31280
+ this.captureBeforeFromDisk(toolKey, toolPaths, sessionParentPath);
31281
+ } else if (updateKind === "tool_call_update") {
31282
+ if (isCompletedToolStatus(status)) {
31283
+ this.ensureBeforeFromHeadForMissing(toolKey, toolPaths, sessionParentPath);
31284
+ this.flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
31285
+ } else {
31286
+ this.captureBeforeFromDisk(toolKey, toolPaths, sessionParentPath);
31287
+ if (this.beforeByToolKey.has(toolKey)) {
31288
+ this.scheduleDebouncedFlush(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
31289
+ }
31290
+ }
31291
+ }
31292
+ }
31293
+ };
31294
+
31295
+ // src/agents/acp/hooks/bridge-on-session-update/send-session-info-title-update.ts
31296
+ function extractSessionInfoTitle(params) {
31297
+ if (!params || typeof params !== "object") return null;
31298
+ const p = params;
31299
+ const title = typeof p.title === "string" ? p.title.trim() : "";
31300
+ return title ? title : null;
31301
+ }
31302
+ function sendSessionInfoTitleUpdate(params) {
31303
+ const title = extractSessionInfoTitle(params.payload);
31304
+ if (!title || !params.runId || !params.send) return;
31305
+ try {
31306
+ params.send({
31307
+ type: "session_title_update",
31308
+ ...params.sessionId ? { sessionId: params.sessionId } : {},
31309
+ runId: params.runId,
31310
+ title
31311
+ });
31312
+ } catch (err) {
31313
+ params.log(`[Bridge service] Session title update send failed: ${errorMessage(err)}`);
31314
+ }
31315
+ }
31316
+
31317
+ // src/agents/acp/hooks/bridge-on-session-update/parse-bridge-session-update.ts
31318
+ function parseBridgeSessionUpdate(params) {
31319
+ const p = params;
31320
+ const updateKind = p.sessionUpdate ?? p.session_update ?? p.type ?? "update";
31321
+ const isCompletedToolCallUpdate = updateKind === "tool_call_update" && isCompletedToolStatus(p.status);
31322
+ const toolName = p.toolCall?.name ?? p.tool_call?.name ?? "";
31323
+ const isToolUpdate = updateKind === "tool_call" || updateKind === "tool_call_update" || typeof toolName === "string" && toolName.length > 0;
31324
+ return { updateKind, toolName, isToolUpdate, isCompletedToolCallUpdate };
31325
+ }
31326
+
31060
31327
  // src/agents/acp/hooks/extract-acp-file-diffs-from-update/paths-and-text.ts
31061
31328
  import { fileURLToPath as fileURLToPath5 } from "node:url";
31062
31329
  function readOptionalTextField(v) {
@@ -31227,174 +31494,27 @@ function extractToolTargetDisplayPaths(update, sessionParentPath) {
31227
31494
  return [...out];
31228
31495
  }
31229
31496
 
31230
- // src/agents/acp/hooks/bridge-on-session-update/constants.ts
31231
- var PATH_SNAPSHOT_DEBOUNCE_MS = 500;
31232
-
31233
- // src/agents/acp/hooks/bridge-on-session-update/tool-key.ts
31234
- function getToolCallId(params) {
31235
- if (!params || typeof params !== "object") return "";
31236
- const u = params;
31237
- const id = u.toolCallId ?? u.tool_call_id;
31238
- return typeof id === "string" ? id : "";
31239
- }
31240
- function isCompletedToolStatus(status) {
31241
- if (typeof status !== "string") return false;
31242
- const s = status.toLowerCase();
31243
- return s === "completed" || s === "complete" || s === "succeeded" || s === "success";
31244
- }
31245
- function accumulateToolPaths(toolKey, paths, acc) {
31246
- if (!toolKey || paths.length === 0) return;
31247
- let s = acc.get(toolKey);
31248
- if (!s) {
31249
- s = /* @__PURE__ */ new Set();
31250
- acc.set(toolKey, s);
31251
- }
31252
- for (const p of paths) s.add(p);
31253
- }
31254
-
31255
- // src/agents/acp/hooks/bridge-on-session-update/path-snapshot-tracker.ts
31256
- var PathSnapshotTracker = class {
31257
- lastSeenRunId;
31258
- anonToolSeq = 0;
31259
- lastAnonymousToolKey = null;
31260
- beforeByToolKey = /* @__PURE__ */ new Map();
31261
- debouncers = /* @__PURE__ */ new Map();
31262
- accumulatedPathsByToolKey = /* @__PURE__ */ new Map();
31263
- clearRunState() {
31264
- for (const t of this.debouncers.values()) clearTimeout(t);
31265
- this.debouncers.clear();
31266
- this.beforeByToolKey.clear();
31267
- this.accumulatedPathsByToolKey.clear();
31268
- this.lastAnonymousToolKey = null;
31269
- }
31270
- onRunIdChanged(runId) {
31271
- if (runId && runId !== this.lastSeenRunId) {
31272
- this.lastSeenRunId = runId;
31273
- this.clearRunState();
31274
- }
31275
- }
31276
- resolveToolKey(params, updateKind) {
31277
- const id = getToolCallId(params);
31278
- if (id) return id;
31279
- if (updateKind === "tool_call") {
31280
- this.lastAnonymousToolKey = `anon:${++this.anonToolSeq}`;
31281
- return this.lastAnonymousToolKey;
31282
- }
31283
- return this.lastAnonymousToolKey ?? "_unknown";
31284
- }
31285
- resetToolSnapshots(toolKey) {
31286
- const t = this.debouncers.get(toolKey);
31287
- if (t) clearTimeout(t);
31288
- this.debouncers.delete(toolKey);
31289
- this.beforeByToolKey.delete(toolKey);
31290
- this.accumulatedPathsByToolKey.delete(toolKey);
31291
- }
31292
- captureBeforeFromDisk(toolKey, paths, sessionParentPath) {
31293
- if (paths.length === 0) return;
31294
- let m = this.beforeByToolKey.get(toolKey);
31295
- if (!m) {
31296
- m = /* @__PURE__ */ new Map();
31297
- this.beforeByToolKey.set(toolKey, m);
31298
- }
31299
- for (const p of paths) {
31300
- if (m.has(p)) continue;
31301
- m.set(p, readUtf8WorkspaceFile(sessionParentPath, p));
31302
- }
31303
- }
31304
- ensureBeforeFromHeadForMissing(toolKey, paths, sessionParentPath) {
31305
- let m = this.beforeByToolKey.get(toolKey);
31306
- if (!m) {
31307
- m = /* @__PURE__ */ new Map();
31308
- this.beforeByToolKey.set(toolKey, m);
31309
- }
31310
- for (const p of paths) {
31311
- if (m.has(p)) continue;
31312
- m.set(p, readGitHeadBlob(sessionParentPath, p));
31313
- }
31314
- }
31315
- flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
31316
- const t = this.debouncers.get(toolKey);
31317
- if (t) clearTimeout(t);
31318
- this.debouncers.delete(toolKey);
31319
- const beforeMap = this.beforeByToolKey.get(toolKey);
31320
- if (!beforeMap || beforeMap.size === 0) return;
31321
- this.beforeByToolKey.delete(toolKey);
31322
- if (!send || !runId || !sessionId) return;
31323
- for (const [displayPath, oldText] of beforeMap) {
31324
- const newText = readUtf8WorkspaceFile(sessionParentPath, displayPath);
31325
- if (oldText === newText) continue;
31326
- const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
31327
- const dirFlags = getSessionFileChangeDirectoryFlags(sessionParentPath, displayPath);
31328
- try {
31329
- send({
31330
- type: "session_file_change",
31331
- sessionId,
31332
- runId,
31333
- path: displayPath,
31334
- oldText,
31335
- newText,
31336
- patchContent,
31337
- isDirectory: dirFlags.isDirectory,
31338
- directoryRemoved: dirFlags.directoryRemoved
31339
- });
31340
- sentPaths.add(displayPath);
31341
- } catch (err) {
31342
- log2(`[Bridge service] Session file change failed for ${displayPath}: ${errorMessage(err)}`);
31343
- }
31344
- }
31345
- }
31346
- scheduleDebouncedFlush(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
31347
- const prev = this.debouncers.get(toolKey);
31348
- if (prev) clearTimeout(prev);
31349
- this.debouncers.set(
31350
- toolKey,
31351
- setTimeout(() => {
31352
- this.debouncers.delete(toolKey);
31353
- this.flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
31354
- }, PATH_SNAPSHOT_DEBOUNCE_MS)
31355
- );
31356
- }
31357
- handleToolCallLifecycle(updateKind, toolKey, toolPaths, status, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
31358
- if (updateKind === "tool_call") {
31359
- this.resetToolSnapshots(toolKey);
31360
- }
31361
- if (updateKind === "tool_call") {
31362
- this.captureBeforeFromDisk(toolKey, toolPaths, sessionParentPath);
31363
- } else if (updateKind === "tool_call_update") {
31364
- if (isCompletedToolStatus(status)) {
31365
- this.ensureBeforeFromHeadForMissing(toolKey, toolPaths, sessionParentPath);
31366
- this.flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
31367
- } else {
31368
- this.captureBeforeFromDisk(toolKey, toolPaths, sessionParentPath);
31369
- if (this.beforeByToolKey.has(toolKey)) {
31370
- this.scheduleDebouncedFlush(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
31371
- }
31372
- }
31373
- }
31374
- }
31375
- };
31376
-
31377
- // src/agents/acp/hooks/bridge-on-session-update/send-structured-file-changes.ts
31378
- function sendExtractedDiffsAsSessionFileChanges(diffs, send, sessionParentPath, sessionId, runId, sentPaths, log2) {
31379
- for (const d of diffs) {
31380
- try {
31381
- const patchContent = editSnippetToUnifiedDiff(d.path, d.oldText, d.newText);
31382
- const dirFlags = getSessionFileChangeDirectoryFlags(sessionParentPath, d.path);
31383
- send({
31384
- type: "session_file_change",
31385
- sessionId,
31386
- runId,
31387
- path: d.path,
31388
- oldText: d.oldText,
31389
- newText: d.newText,
31390
- patchContent,
31391
- isDirectory: dirFlags.isDirectory,
31392
- directoryRemoved: dirFlags.directoryRemoved
31393
- });
31394
- sentPaths.add(d.path);
31395
- } catch (err) {
31396
- log2(`[Bridge service] Session file change failed for ${d.path}: ${errorMessage(err)}`);
31397
- }
31497
+ // src/agents/acp/hooks/bridge-on-session-update/send-structured-file-changes.ts
31498
+ function sendExtractedDiffsAsSessionFileChanges(diffs, send, sessionParentPath, sessionId, runId, sentPaths, log2) {
31499
+ for (const d of diffs) {
31500
+ try {
31501
+ const patchContent = editSnippetToUnifiedDiff(d.path, d.oldText, d.newText);
31502
+ const dirFlags = getSessionFileChangeDirectoryFlags(sessionParentPath, d.path);
31503
+ send({
31504
+ type: "session_file_change",
31505
+ sessionId,
31506
+ runId,
31507
+ path: d.path,
31508
+ oldText: d.oldText,
31509
+ newText: d.newText,
31510
+ patchContent,
31511
+ isDirectory: dirFlags.isDirectory,
31512
+ directoryRemoved: dirFlags.directoryRemoved
31513
+ });
31514
+ sentPaths.add(d.path);
31515
+ } catch (err) {
31516
+ log2(`[Bridge service] Session file change failed for ${d.path}: ${errorMessage(err)}`);
31517
+ }
31398
31518
  }
31399
31519
  }
31400
31520
  function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send, sessionParentPath, sessionId, runId, log2) {
@@ -31424,25 +31544,158 @@ function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send, sessio
31424
31544
  }
31425
31545
  }
31426
31546
 
31427
- // src/agents/acp/hooks/bridge-on-session-update/send-session-info-title-update.ts
31428
- function extractSessionInfoTitle(params) {
31429
- if (!params || typeof params !== "object") return null;
31430
- const p = params;
31431
- const title = typeof p.title === "string" ? p.title.trim() : "";
31432
- return title ? title : null;
31547
+ // src/agents/acp/hooks/bridge-on-session-update/handle-bridge-tool-session-update.ts
31548
+ function handleBridgeToolSessionUpdate(params) {
31549
+ const {
31550
+ params: updateParams,
31551
+ updateKind,
31552
+ isToolUpdate,
31553
+ isCompletedToolCallUpdate,
31554
+ toolKey,
31555
+ toolPaths,
31556
+ status,
31557
+ sessionParentPath,
31558
+ pathTracker,
31559
+ sentFileChangePaths,
31560
+ send,
31561
+ runId,
31562
+ sessionId,
31563
+ log: log2
31564
+ } = params;
31565
+ if (updateKind === "tool_call") {
31566
+ pathTracker.resetToolSnapshots(toolKey);
31567
+ }
31568
+ if (isToolUpdate) {
31569
+ accumulateToolPaths(toolKey, toolPaths, pathTracker.accumulatedPathsByToolKey);
31570
+ }
31571
+ if (isToolUpdate) {
31572
+ const deliver = send && runId && sessionId ? (msg) => send(msg) : null;
31573
+ pathTracker.handleToolCallLifecycle(
31574
+ updateKind,
31575
+ toolKey,
31576
+ toolPaths,
31577
+ status,
31578
+ sessionParentPath,
31579
+ sentFileChangePaths,
31580
+ deliver,
31581
+ runId,
31582
+ sessionId,
31583
+ log2
31584
+ );
31585
+ }
31586
+ const diffs = extractAcpFileDiffsFromUpdate(updateParams, sessionParentPath);
31587
+ if (diffs.length > 0 && send && runId && sessionId) {
31588
+ sendExtractedDiffsAsSessionFileChanges(
31589
+ diffs,
31590
+ send,
31591
+ sessionParentPath,
31592
+ sessionId,
31593
+ runId,
31594
+ sentFileChangePaths,
31595
+ log2
31596
+ );
31597
+ } else if (diffs.length > 0) {
31598
+ log2(
31599
+ `[Bridge service] Agent file diff(s) not forwarded (${diffs.length}): session or run not wired to the bridge.`
31600
+ );
31601
+ }
31602
+ if (isCompletedToolCallUpdate && send && runId && sessionId) {
31603
+ const acc = pathTracker.accumulatedPathsByToolKey.get(toolKey);
31604
+ const merged = [.../* @__PURE__ */ new Set([...acc ? [...acc] : [], ...toolPaths])];
31605
+ sendGitHeadVsWorkspaceForToolPaths(
31606
+ merged,
31607
+ sentFileChangePaths,
31608
+ send,
31609
+ sessionParentPath,
31610
+ sessionId,
31611
+ runId,
31612
+ log2
31613
+ );
31614
+ pathTracker.accumulatedPathsByToolKey.delete(toolKey);
31615
+ }
31433
31616
  }
31434
- function sendSessionInfoTitleUpdate(params) {
31435
- const title = extractSessionInfoTitle(params.payload);
31436
- if (!title || !params.runId || !params.send) return;
31617
+ function resolveBridgeToolPaths(params, isToolUpdate, sessionParentPath) {
31618
+ return isToolUpdate ? extractToolTargetDisplayPaths(params, sessionParentPath) : [];
31619
+ }
31620
+
31621
+ // src/agents/planning/extract-session-update-message-text.ts
31622
+ function extractSessionUpdateMessageText(params) {
31623
+ if (params == null || typeof params !== "object" || Array.isArray(params)) return null;
31624
+ const o = params;
31625
+ if (typeof o.text === "string" && o.text.length > 0) return o.text;
31626
+ const content = o.content;
31627
+ if (typeof content === "string" && content.length > 0) return content;
31628
+ if (content != null && typeof content === "object" && !Array.isArray(content)) {
31629
+ const c = content;
31630
+ if (typeof c.text === "string" && c.text.length > 0) return c.text;
31631
+ }
31632
+ const update = o.update;
31633
+ if (typeof update === "string" && update.length > 0) return update;
31634
+ if (update != null && typeof update === "object" && !Array.isArray(update)) {
31635
+ const u = update;
31636
+ const innerContent = u.content;
31637
+ if (typeof innerContent === "string" && innerContent.length > 0) return innerContent;
31638
+ if (innerContent != null && typeof innerContent === "object" && !Array.isArray(innerContent)) {
31639
+ const ic = innerContent;
31640
+ if (typeof ic.text === "string" && ic.text.length > 0) return ic.text;
31641
+ }
31642
+ }
31643
+ return null;
31644
+ }
31645
+
31646
+ // src/agents/planning/planning-session-turn-buffer.ts
31647
+ var buffersByRunId = /* @__PURE__ */ new Map();
31648
+ function registerPlanningSessionTurn(runId) {
31649
+ buffersByRunId.set(runId, { messageBuffer: "" });
31650
+ }
31651
+ function clearPlanningSessionTurn(runId) {
31652
+ buffersByRunId.delete(runId);
31653
+ }
31654
+ function capturePlanningSessionUpdate(params) {
31655
+ const state = buffersByRunId.get(params.runId);
31656
+ if (!state) return false;
31657
+ const sessionUpdate = params.payload != null && typeof params.payload === "object" && !Array.isArray(params.payload) ? params.payload.sessionUpdate ?? params.payload.session_update : void 0;
31658
+ const bucket = resolveAgentTextStreamBucket({
31659
+ transcriptRowKind: params.updateKind,
31660
+ sessionUpdate: typeof sessionUpdate === "string" ? sessionUpdate : void 0
31661
+ });
31662
+ if (bucket !== "message") return false;
31663
+ const text = extractSessionUpdateMessageText(params.payload);
31664
+ if (text) state.messageBuffer += text;
31665
+ return true;
31666
+ }
31667
+ function getPlanningSessionTurnOutput(runId) {
31668
+ return buffersByRunId.get(runId)?.messageBuffer ?? "";
31669
+ }
31670
+ function consumePlanningSessionTurnOutput(runId) {
31671
+ const state = buffersByRunId.get(runId);
31672
+ buffersByRunId.delete(runId);
31673
+ return state?.messageBuffer ?? "";
31674
+ }
31675
+
31676
+ // src/agents/acp/hooks/bridge-on-session-update/forward-bridge-session-update.ts
31677
+ function forwardBridgeSessionUpdate(params) {
31678
+ const { runId, sessionId, updateKind, payload, send, nextTranscriptStreamSeq, log: log2 } = params;
31679
+ if (updateKind === "permission") {
31680
+ return;
31681
+ }
31682
+ if (capturePlanningSessionUpdate({ runId, updateKind, payload })) {
31683
+ return;
31684
+ }
31437
31685
  try {
31438
- params.send({
31439
- type: "session_title_update",
31440
- ...params.sessionId ? { sessionId: params.sessionId } : {},
31441
- runId: params.runId,
31442
- title
31686
+ const transcriptStreamSeq = nextTranscriptStreamSeq?.(runId);
31687
+ send({
31688
+ type: "session_update",
31689
+ ...sessionId ? { sessionId } : {},
31690
+ runId,
31691
+ kind: updateKind,
31692
+ payload,
31693
+ ...transcriptStreamSeq != null ? { transcriptStreamSeq } : {}
31443
31694
  });
31444
31695
  } catch (err) {
31445
- params.log(`[Bridge service] Session title update send failed: ${errorMessage(err)}`);
31696
+ log2(
31697
+ `[Bridge service] Session update send failed (${formatSessionUpdateKindForLog(updateKind)}): ${errorMessage(err)}`
31698
+ );
31446
31699
  }
31447
31700
  }
31448
31701
 
@@ -31457,8 +31710,7 @@ function createBridgeOnSessionUpdate(opts) {
31457
31710
  pathTracker.onRunIdChanged(runId);
31458
31711
  const send = getSendSessionUpdate();
31459
31712
  const sentFileChangePaths = /* @__PURE__ */ new Set();
31460
- const p = params;
31461
- const updateKind = p.sessionUpdate ?? p.session_update ?? p.type ?? "update";
31713
+ const { updateKind, isToolUpdate, isCompletedToolCallUpdate } = parseBridgeSessionUpdate(params);
31462
31714
  if (updateKind === "config_option_update") {
31463
31715
  return;
31464
31716
  }
@@ -31466,81 +31718,35 @@ function createBridgeOnSessionUpdate(opts) {
31466
31718
  sendSessionInfoTitleUpdate({ payload: params, runId, sessionId, send, log: log2 });
31467
31719
  return;
31468
31720
  }
31469
- const isCompletedToolCallUpdate = updateKind === "tool_call_update" && isCompletedToolStatus(p.status);
31470
- const toolName = p.toolCall?.name ?? p.tool_call?.name ?? "";
31471
- const isToolUpdate = updateKind === "tool_call" || updateKind === "tool_call_update" || typeof toolName === "string" && toolName.length > 0;
31472
- const toolPaths = isToolUpdate ? extractToolTargetDisplayPaths(params, sessionParentPath) : [];
31721
+ const toolPaths = resolveBridgeToolPaths(params, isToolUpdate, sessionParentPath);
31473
31722
  const toolKey = isToolUpdate ? pathTracker.resolveToolKey(params, updateKind) : "";
31474
- if (updateKind === "tool_call") {
31475
- pathTracker.resetToolSnapshots(toolKey);
31476
- }
31477
- if (isToolUpdate) {
31478
- accumulateToolPaths(toolKey, toolPaths, pathTracker.accumulatedPathsByToolKey);
31479
- }
31480
- if (isToolUpdate) {
31481
- const deliver = send && runId && sessionId ? (msg) => send(msg) : null;
31482
- pathTracker.handleToolCallLifecycle(
31483
- updateKind,
31484
- toolKey,
31485
- toolPaths,
31486
- p.status,
31487
- sessionParentPath,
31488
- sentFileChangePaths,
31489
- deliver,
31490
- runId ?? "",
31491
- sessionId ?? "",
31492
- log2
31493
- );
31494
- }
31495
- const diffs = extractAcpFileDiffsFromUpdate(params, sessionParentPath);
31496
- if (diffs.length > 0 && send && runId && sessionId) {
31497
- sendExtractedDiffsAsSessionFileChanges(
31498
- diffs,
31499
- send,
31500
- sessionParentPath,
31501
- sessionId,
31723
+ const status = params.status;
31724
+ handleBridgeToolSessionUpdate({
31725
+ params,
31726
+ updateKind,
31727
+ isToolUpdate,
31728
+ isCompletedToolCallUpdate,
31729
+ toolKey,
31730
+ toolPaths,
31731
+ status,
31732
+ sessionParentPath,
31733
+ pathTracker,
31734
+ sentFileChangePaths,
31735
+ send,
31736
+ runId: runId ?? "",
31737
+ sessionId: sessionId ?? "",
31738
+ log: log2
31739
+ });
31740
+ if (runId && send) {
31741
+ forwardBridgeSessionUpdate({
31502
31742
  runId,
31503
- sentFileChangePaths,
31504
- log2
31505
- );
31506
- } else if (diffs.length > 0) {
31507
- log2(
31508
- `[Bridge service] Agent file diff(s) not forwarded (${diffs.length}): session or run not wired to the bridge.`
31509
- );
31510
- }
31511
- if (isCompletedToolCallUpdate && send && runId && sessionId) {
31512
- const acc = pathTracker.accumulatedPathsByToolKey.get(toolKey);
31513
- const merged = [.../* @__PURE__ */ new Set([...acc ? [...acc] : [], ...toolPaths])];
31514
- sendGitHeadVsWorkspaceForToolPaths(
31515
- merged,
31516
- sentFileChangePaths,
31517
- send,
31518
- sessionParentPath,
31519
31743
  sessionId,
31520
- runId,
31521
- log2
31522
- );
31523
- pathTracker.accumulatedPathsByToolKey.delete(toolKey);
31524
- }
31525
- if (runId && send) {
31526
- if (updateKind === "permission") {
31527
- return;
31528
- }
31529
- try {
31530
- const transcriptStreamSeq = nextTranscriptStreamSeq?.(runId);
31531
- send({
31532
- type: "session_update",
31533
- ...sessionId ? { sessionId } : {},
31534
- runId,
31535
- kind: updateKind,
31536
- payload: params,
31537
- ...transcriptStreamSeq != null ? { transcriptStreamSeq } : {}
31538
- });
31539
- } catch (err) {
31540
- log2(
31541
- `[Bridge service] Session update send failed (${formatSessionUpdateKindForLog(updateKind)}): ${errorMessage(err)}`
31542
- );
31543
- }
31744
+ updateKind,
31745
+ payload: params,
31746
+ send,
31747
+ nextTranscriptStreamSeq,
31748
+ log: log2
31749
+ });
31544
31750
  }
31545
31751
  };
31546
31752
  }
@@ -31880,14 +32086,48 @@ async function disconnectAll(ctx) {
31880
32086
  ctx.pendingCancelRunIds.clear();
31881
32087
  }
31882
32088
 
31883
- // src/git/git-runtime.ts
31884
- var activeGitChildProcesses = /* @__PURE__ */ new Set();
31885
- function abortActiveGitChildProcesses() {
31886
- for (const child of activeGitChildProcesses) {
31887
- try {
31888
- child.kill("SIGTERM");
31889
- } catch {
31890
- }
32089
+ // src/agents/acp/manager/resolve-prompt-run-context.ts
32090
+ function resolvePromptRunContext(ctx, opts) {
32091
+ const { runId, mode, agentType, agentConfig, sessionId } = opts;
32092
+ const preferredForPrompt = agentType ?? ctx.backendFallbackAgentType ?? null;
32093
+ if (!runId) {
32094
+ ctx.log("[Agent] Prompt ignored: missing runId (cannot route session updates).");
32095
+ return null;
32096
+ }
32097
+ const acpAgentKey = computeAcpAgentKey(preferredForPrompt, mode, agentConfig ?? null);
32098
+ if (!acpAgentKey) {
32099
+ return null;
32100
+ }
32101
+ const activeAcpSessionAgentKey = computeAcpSessionAgentKey(sessionId, acpAgentKey);
32102
+ ctx.pendingCancelRunIds.delete(runId);
32103
+ ctx.promptRouting.registerRun({ sessionId, runId });
32104
+ ctx.runDispatch.set(runId, { acpSessionAgentKey: activeAcpSessionAgentKey });
32105
+ return {
32106
+ activeRunId: runId,
32107
+ activeAcpAgentKey: acpAgentKey,
32108
+ activeAcpSessionAgentKey,
32109
+ preferredForPrompt
32110
+ };
32111
+ }
32112
+
32113
+ // src/agents/acp/send-prompt-result-augment.ts
32114
+ function augmentPromptResultAuthFields(agentType, errorText) {
32115
+ const err = errorText ?? "";
32116
+ const at = agentType ?? null;
32117
+ const evaluated = Boolean(at && err.trim());
32118
+ const suggestsAuth = evaluated && at ? localAgentErrorSuggestsAuth(at, err) : false;
32119
+ if (!suggestsAuth || !agentType) return {};
32120
+ return { agentAuthRequired: true, agentType };
32121
+ }
32122
+
32123
+ // src/git/git-runtime.ts
32124
+ var activeGitChildProcesses = /* @__PURE__ */ new Set();
32125
+ function abortActiveGitChildProcesses() {
32126
+ for (const child of activeGitChildProcesses) {
32127
+ try {
32128
+ child.kill("SIGTERM");
32129
+ } catch {
32130
+ }
31891
32131
  }
31892
32132
  }
31893
32133
  var GitOperationAbortedError = class extends Error {
@@ -32335,130 +32575,136 @@ async function maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess(params) {
32335
32575
  }
32336
32576
  }
32337
32577
 
32338
- // src/agents/acp/send-prompt-result-augment.ts
32339
- function augmentPromptResultAuthFields(agentType, errorText) {
32340
- const err = errorText ?? "";
32341
- const at = agentType ?? null;
32342
- const evaluated = Boolean(at && err.trim());
32343
- const suggestsAuth = evaluated && at ? localAgentErrorSuggestsAuth(at, err) : false;
32344
- if (!suggestsAuth || !agentType) return {};
32345
- return { agentAuthRequired: true, agentType };
32346
- }
32347
-
32348
- // ../e2ee/src/constants.ts
32349
- var E2EE_NONCE_BYTES = 12;
32350
-
32351
- // ../e2ee/src/types.ts
32352
- function isE2eeEnvelope(value) {
32353
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
32354
- const o = value;
32355
- return typeof o.k === "string" && typeof o.n === "string" && typeof o.c === "string";
32356
- }
32357
-
32358
- // ../e2ee/src/encoding.ts
32359
- function base64UrlEncode(bytes) {
32360
- let binary = "";
32361
- for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
32362
- const b64 = btoa(binary);
32363
- return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
32364
- }
32365
- function base64UrlDecode(value) {
32366
- const b64 = value.replace(/-/g, "+").replace(/_/g, "/");
32367
- const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
32368
- const binary = atob(padded);
32369
- const out = new Uint8Array(binary.length);
32370
- for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
32371
- return out;
32578
+ // src/agents/planning/submit-planning-todos-for-turn.ts
32579
+ async function submitPlanningTodosForTurn(params) {
32580
+ const token = params.getCloudAccessToken();
32581
+ if (!token.trim()) return { ok: false, error: "Missing cloud access token" };
32582
+ const base = params.cloudApiBaseUrl.replace(/\/$/, "");
32583
+ const url2 = `${base}/internal/sessions/todos/submit`;
32584
+ const res = await fetch(url2, {
32585
+ method: "POST",
32586
+ headers: {
32587
+ Authorization: `Bearer ${token}`,
32588
+ "Content-Type": "application/json"
32589
+ },
32590
+ body: JSON.stringify({
32591
+ sessionId: params.sessionId,
32592
+ turnId: params.turnId,
32593
+ items: params.items
32594
+ })
32595
+ });
32596
+ if (!res.ok) {
32597
+ const body = await res.json().catch(() => null);
32598
+ return { ok: false, error: body?.error ?? `Planning todo submit failed (${res.status})` };
32599
+ }
32600
+ return { ok: true };
32372
32601
  }
32373
32602
 
32374
- // src/agents/acp/fetch-session-attachments.ts
32375
- function metaSaysEncrypted(meta) {
32376
- if (!meta) return false;
32377
- const e = meta.encrypted;
32378
- return e === true || e === "true" || e === 1;
32379
- }
32380
- function warnIfDecodedImageMagicUnexpected(buf, mimeType, idShort) {
32381
- const m = mimeType.toLowerCase();
32382
- if (!m.startsWith("image/") || buf.length < 4) return;
32383
- const b0 = buf[0];
32384
- const b1 = buf[1];
32385
- const b2 = buf[2];
32386
- const b3 = buf[3];
32387
- let looksOk = false;
32388
- if (m.includes("png") && b0 === 137 && b1 === 80 && b2 === 78 && b3 === 71) looksOk = true;
32389
- else if ((m.includes("jpeg") || m.includes("jpg")) && b0 === 255 && b1 === 216 && b2 === 255) looksOk = true;
32390
- else if (m.includes("gif") && b0 === 71 && b1 === 73 && b2 === 70) looksOk = true;
32391
- else if (m.includes("webp") && buf.length >= 12 && buf.subarray(0, 4).toString("ascii") === "RIFF" && buf.subarray(8, 12).toString("ascii") === "WEBP")
32392
- looksOk = true;
32393
- else if (!/(png|jpe?g|gif|webp)/i.test(m)) looksOk = true;
32394
- if (!looksOk) {
32395
- logDebug(
32396
- `[Agent] Attachment ${idShort} (${mimeType}): decoded bytes do not match common image signatures \u2014 wrong E2EE key, corrupt blob, or metadata mismatch. First 12 bytes (hex): ${buf.subarray(0, Math.min(12, buf.length)).toString("hex")}`
32397
- );
32603
+ // src/agents/planning/maybe-submit-planning-todos-after-agent-success.ts
32604
+ async function maybeSubmitPlanningTodosAfterAgentSuccess(params) {
32605
+ const { sessionId, runId, resultSuccess, output, cloudApiBaseUrl, getCloudAccessToken, log: log2 } = params;
32606
+ const buffered = runId ? getPlanningSessionTurnOutput(runId) : "";
32607
+ const outputFromResult = typeof output === "string" ? output : "";
32608
+ const outputStr = buffered.trim() !== "" ? buffered : outputFromResult;
32609
+ if (!sessionId || !runId || !resultSuccess || outputStr.trim() === "") {
32610
+ return { submitted: false, suppressOutput: false };
32398
32611
  }
32399
- }
32400
- async function fetchSessionAttachmentPayloadsForAgent(params) {
32401
- const { attachments, sessionId, cloudApiBaseUrl, getCloudAccessToken, e2ee, log: log2 } = params;
32402
- const token = getCloudAccessToken();
32403
- if (!token) {
32404
- return { ok: false, error: "Missing cloud access token; cannot download attachments." };
32612
+ if (!cloudApiBaseUrl || !getCloudAccessToken) {
32613
+ return { submitted: false, suppressOutput: false };
32405
32614
  }
32406
- const wantedCount = attachments.filter((a) => typeof a.attachmentId === "string" && a.attachmentId.trim() !== "").length;
32407
- if (wantedCount === 0) {
32408
- return { ok: false, error: "No valid attachment ids in prompt." };
32615
+ const items = parsePlanningTodoAgentJson(outputStr);
32616
+ if (items.length === 0) {
32617
+ return { submitted: false, suppressOutput: false };
32409
32618
  }
32410
- const base = cloudApiBaseUrl.replace(/\/+$/, "");
32411
- const out = [];
32412
- for (const a of attachments) {
32413
- const id = typeof a.attachmentId === "string" ? a.attachmentId.trim() : "";
32414
- if (!id) continue;
32415
- const metaUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/meta`;
32416
- const blobUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/blob`;
32417
- const metaRes = await fetch(metaUrl, { headers: { Authorization: `Bearer ${token}` } });
32418
- if (!metaRes.ok) {
32419
- const t = await metaRes.text().catch(() => "");
32420
- log2(`[Agent] Attachment meta fetch failed ${metaRes.status}: ${t.slice(0, 200)}`);
32421
- return { ok: false, error: `Could not load attachment (${id.slice(0, 8)}\u2026): ${metaRes.status}` };
32422
- }
32423
- const meta = await metaRes.json().catch(() => null);
32424
- const mimeType = typeof meta?.mimeType === "string" && meta.mimeType.trim() ? meta.mimeType.trim() : a.mimeType;
32425
- const blobRes = await fetch(blobUrl, { headers: { Authorization: `Bearer ${token}` } });
32426
- if (!blobRes.ok) {
32427
- const t = await blobRes.text().catch(() => "");
32428
- log2(`[Agent] Attachment blob fetch failed ${blobRes.status}: ${t.slice(0, 200)}`);
32429
- return { ok: false, error: `Could not load attachment data (${id.slice(0, 8)}\u2026): ${blobRes.status}` };
32430
- }
32431
- const buf = Buffer.from(await blobRes.arrayBuffer());
32432
- let imageBytes;
32433
- const encrypted = metaSaysEncrypted(meta);
32434
- if (encrypted) {
32435
- if (!e2ee) {
32436
- return { ok: false, error: "Encrypted attachments require E2EE keys on this bridge." };
32437
- }
32438
- const k = typeof meta?.k === "string" ? meta.k : "";
32439
- const n = typeof meta?.n === "string" ? meta.n : "";
32440
- if (!k || !n) {
32441
- return { ok: false, error: "Invalid encrypted attachment metadata (missing key id or nonce)." };
32442
- }
32443
- const c = base64UrlEncode(buf);
32444
- imageBytes = e2ee.decryptEnvelopeToBuffer({ k, n, c });
32619
+ const result = await submitPlanningTodosForTurn({
32620
+ sessionId,
32621
+ turnId: runId,
32622
+ items,
32623
+ cloudApiBaseUrl,
32624
+ getCloudAccessToken
32625
+ });
32626
+ if (!result.ok) {
32627
+ log2(`[Agent] Planning todo submit failed: ${result.error}`);
32628
+ return { submitted: false, suppressOutput: false };
32629
+ }
32630
+ if (runId) consumePlanningSessionTurnOutput(runId);
32631
+ log2(`[Agent] Planning session: stored ${items.length} todo(s) via internal API`);
32632
+ return { submitted: true, suppressOutput: true };
32633
+ }
32634
+
32635
+ // src/agents/acp/prompts/finalize-and-send-prompt-result.ts
32636
+ async function finalizeAndSendPromptResult(params) {
32637
+ const {
32638
+ result,
32639
+ sessionId,
32640
+ runId,
32641
+ promptId,
32642
+ agentType,
32643
+ agentCwd,
32644
+ isPlanningSession,
32645
+ followUpCatalogPromptId,
32646
+ sessionChangeSummaryFilePaths,
32647
+ cloudApiBaseUrl,
32648
+ getCloudAccessToken,
32649
+ e2ee,
32650
+ sendResult,
32651
+ sendSessionUpdate,
32652
+ log: log2
32653
+ } = params;
32654
+ if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
32655
+ await collectTurnGitDiffFromPreTurnSnapshot({
32656
+ sessionId,
32657
+ runId,
32658
+ agentCwd,
32659
+ sendSessionUpdate,
32660
+ log: log2
32661
+ });
32662
+ }
32663
+ await maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess({
32664
+ sessionId,
32665
+ runId,
32666
+ resultSuccess: result.success === true,
32667
+ output: result.output,
32668
+ e2ee,
32669
+ cloudApiBaseUrl,
32670
+ getCloudAccessToken,
32671
+ followUpCatalogPromptId,
32672
+ sessionChangeSummaryFilePaths,
32673
+ log: log2
32674
+ });
32675
+ const planningTodosSubmit = await maybeSubmitPlanningTodosAfterAgentSuccess({
32676
+ sessionId,
32677
+ runId,
32678
+ resultSuccess: result.success === true,
32679
+ output: result.output,
32680
+ cloudApiBaseUrl,
32681
+ getCloudAccessToken,
32682
+ log: log2
32683
+ });
32684
+ const errStr = typeof result.error === "string" ? result.error : void 0;
32685
+ const resultStop = typeof result.stopReason === "string" ? result.stopReason.trim() : "";
32686
+ const cancelledByAgent = resultStop.toLowerCase() === "cancelled" || errStr != null && isUserEndedSessionTurnErrorText(errStr);
32687
+ const planningFallbackOutput = !planningTodosSubmit.suppressOutput && isPlanningSession && runId ? consumePlanningSessionTurnOutput(runId) : "";
32688
+ sendResult({
32689
+ type: "prompt_result",
32690
+ id: promptId,
32691
+ ...sessionId ? { sessionId } : {},
32692
+ ...runId ? { runId } : {},
32693
+ ...result,
32694
+ ...planningTodosSubmit.suppressOutput ? { output: void 0 } : planningFallbackOutput.trim() !== "" ? { output: planningFallbackOutput } : {},
32695
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
32696
+ ...augmentPromptResultAuthFields(agentType, errStr),
32697
+ ...!result.success && cancelledByAgent ? { stopReason: "cancelled" } : {}
32698
+ });
32699
+ if (!result.success) {
32700
+ if (cancelledByAgent) {
32701
+ log2("[Agent] Run ended after stop request (stopped by user).");
32445
32702
  } else {
32446
- imageBytes = buf;
32703
+ log2(
32704
+ `[Agent] Prompt did not run successfully on the agent (no successful start/completion): ${result.error ?? "Unknown error"}`
32705
+ );
32447
32706
  }
32448
- warnIfDecodedImageMagicUnexpected(imageBytes, mimeType, id.slice(0, 8));
32449
- logDebug(
32450
- `[Agent] Loaded prompt image ${id.slice(0, 8)}\u2026: ${imageBytes.length} bytes, mime=${mimeType}, ${encrypted ? "E2EE decrypted" : "plaintext from storage"}`
32451
- );
32452
- const dataBase64 = imageBytes.toString("base64");
32453
- out.push({ mimeType, dataBase64 });
32454
- }
32455
- if (out.length !== wantedCount) {
32456
- return {
32457
- ok: false,
32458
- error: `Expected ${wantedCount} image attachment(s) but only loaded ${out.length} (check attachment ids and empty rows).`
32459
- };
32460
32707
  }
32461
- return { ok: true, images: out };
32462
32708
  }
32463
32709
 
32464
32710
  // src/agents/acp/build-forked-session-agent-prompt.ts
@@ -32518,6 +32764,32 @@ async function fetchCloudSessionMeta(params) {
32518
32764
  });
32519
32765
  }
32520
32766
 
32767
+ // ../e2ee/src/constants.ts
32768
+ var E2EE_NONCE_BYTES = 12;
32769
+
32770
+ // ../e2ee/src/types.ts
32771
+ function isE2eeEnvelope(value) {
32772
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
32773
+ const o = value;
32774
+ return typeof o.k === "string" && typeof o.n === "string" && typeof o.c === "string";
32775
+ }
32776
+
32777
+ // ../e2ee/src/encoding.ts
32778
+ function base64UrlEncode(bytes) {
32779
+ let binary = "";
32780
+ for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
32781
+ const b64 = btoa(binary);
32782
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
32783
+ }
32784
+ function base64UrlDecode(value) {
32785
+ const b64 = value.replace(/-/g, "+").replace(/_/g, "/");
32786
+ const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
32787
+ const binary = atob(padded);
32788
+ const out = new Uint8Array(binary.length);
32789
+ for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
32790
+ return out;
32791
+ }
32792
+
32521
32793
  // src/lib/e2ee/decrypt-stored-e2ee-content.ts
32522
32794
  function parseJsonObject(raw) {
32523
32795
  try {
@@ -32715,125 +32987,278 @@ function enrichIntegrationContentPromptForAgent(params) {
32715
32987
  return injectIntegrationContentAgentPromptNote(params.agentPromptText, params.sessionId);
32716
32988
  }
32717
32989
 
32718
- // src/agents/acp/send-prompt-to-agent.ts
32719
- async function sendPromptToAgent(options) {
32720
- const {
32721
- handle,
32722
- promptText,
32723
- promptId,
32724
- sessionId,
32725
- runId,
32726
- agentType,
32727
- agentCwd,
32728
- sendResult,
32729
- sendSessionUpdate,
32730
- log: log2,
32731
- followUpCatalogPromptId,
32732
- sessionChangeSummaryFilePaths,
32733
- cloudApiBaseUrl,
32734
- getCloudAccessToken,
32735
- e2ee,
32736
- attachments
32737
- } = options;
32738
- try {
32739
- let agentPromptText = promptText;
32740
- if (sessionId && cloudApiBaseUrl && getCloudAccessToken) {
32741
- agentPromptText = await enrichForkedSessionPromptForAgent({
32742
- sessionId,
32743
- promptText,
32744
- cloudApiBaseUrl,
32745
- getCloudAccessToken
32746
- });
32747
- }
32748
- if (sessionId) {
32749
- agentPromptText = enrichIntegrationContentPromptForAgent({
32750
- sessionId,
32751
- originalPromptText: promptText,
32752
- agentPromptText
32753
- });
32754
- }
32755
- let sendOpts = {};
32756
- if (attachments && attachments.length > 0) {
32757
- if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
32758
- sendResult({
32759
- type: "prompt_result",
32760
- id: promptId,
32761
- ...sessionId ? { sessionId } : {},
32762
- ...runId ? { runId } : {},
32763
- success: false,
32764
- error: "Prompt includes images but the bridge is not configured with a cloud API URL and token to fetch them.",
32765
- ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
32766
- ...augmentPromptResultAuthFields(agentType, "missing cloud for images download")
32767
- });
32768
- return;
32769
- }
32770
- const resolved = await fetchSessionAttachmentPayloadsForAgent({
32771
- attachments,
32772
- sessionId,
32773
- cloudApiBaseUrl,
32774
- getCloudAccessToken,
32775
- e2ee,
32776
- log: log2
32777
- });
32778
- if (!resolved.ok) {
32779
- sendResult({
32780
- type: "prompt_result",
32781
- id: promptId,
32782
- ...sessionId ? { sessionId } : {},
32783
- ...runId ? { runId } : {},
32784
- success: false,
32785
- error: resolved.error,
32786
- ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
32787
- ...augmentPromptResultAuthFields(agentType, resolved.error)
32788
- });
32789
- return;
32790
- }
32791
- sendOpts = { images: resolved.images };
32990
+ // src/agents/planning/enrich-planning-session-prompt-for-agent.ts
32991
+ async function enrichPlanningSessionPromptForAgent(params) {
32992
+ const meta = await fetchCloudSessionMeta({
32993
+ sessionId: params.sessionId,
32994
+ cloudApiBaseUrl: params.cloudApiBaseUrl,
32995
+ getCloudAccessToken: params.getCloudAccessToken
32996
+ });
32997
+ if (!meta.ok) return { promptText: params.promptText, isPlanningSession: false };
32998
+ const sessionKind = parseSessionKind(meta.data.sessionKind);
32999
+ if (sessionKind !== "planning") return { promptText: params.promptText, isPlanningSession: false };
33000
+ const todos = Array.isArray(meta.data.planningTodos) ? meta.data.planningTodos : [];
33001
+ if (params.isNewSession || todos.length === 0) {
33002
+ return {
33003
+ promptText: buildPlanningSessionAgentPrompt(params.promptText),
33004
+ isPlanningSession: true
33005
+ };
33006
+ }
33007
+ return {
33008
+ promptText: buildPlanningSessionFollowUpAgentPrompt(params.promptText, todos),
33009
+ isPlanningSession: true
33010
+ };
33011
+ }
33012
+
33013
+ // src/agents/acp/prompts/prepare-agent-prompt-text.ts
33014
+ async function prepareAgentPromptText(params) {
33015
+ const { promptText, sessionId, runId, isNewSession, cloudApiBaseUrl, getCloudAccessToken } = params;
33016
+ let agentPromptText = promptText;
33017
+ let isPlanningSession = false;
33018
+ if (sessionId && cloudApiBaseUrl && getCloudAccessToken) {
33019
+ agentPromptText = await enrichForkedSessionPromptForAgent({
33020
+ sessionId,
33021
+ promptText,
33022
+ cloudApiBaseUrl,
33023
+ getCloudAccessToken
33024
+ });
33025
+ const planningEnriched = await enrichPlanningSessionPromptForAgent({
33026
+ sessionId,
33027
+ promptText: agentPromptText,
33028
+ isNewSession,
33029
+ cloudApiBaseUrl,
33030
+ getCloudAccessToken
33031
+ });
33032
+ agentPromptText = planningEnriched.promptText;
33033
+ isPlanningSession = planningEnriched.isPlanningSession;
33034
+ }
33035
+ if (isPlanningSession && runId) {
33036
+ registerPlanningSessionTurn(runId);
33037
+ }
33038
+ if (sessionId) {
33039
+ agentPromptText = enrichIntegrationContentPromptForAgent({
33040
+ sessionId,
33041
+ originalPromptText: promptText,
33042
+ agentPromptText
33043
+ });
33044
+ }
33045
+ return { agentPromptText, isPlanningSession };
33046
+ }
33047
+
33048
+ // src/agents/acp/fetch-session-attachments.ts
33049
+ function metaSaysEncrypted(meta) {
33050
+ if (!meta) return false;
33051
+ const e = meta.encrypted;
33052
+ return e === true || e === "true" || e === 1;
33053
+ }
33054
+ function warnIfDecodedImageMagicUnexpected(buf, mimeType, idShort) {
33055
+ const m = mimeType.toLowerCase();
33056
+ if (!m.startsWith("image/") || buf.length < 4) return;
33057
+ const b0 = buf[0];
33058
+ const b1 = buf[1];
33059
+ const b2 = buf[2];
33060
+ const b3 = buf[3];
33061
+ let looksOk = false;
33062
+ if (m.includes("png") && b0 === 137 && b1 === 80 && b2 === 78 && b3 === 71) looksOk = true;
33063
+ else if ((m.includes("jpeg") || m.includes("jpg")) && b0 === 255 && b1 === 216 && b2 === 255) looksOk = true;
33064
+ else if (m.includes("gif") && b0 === 71 && b1 === 73 && b2 === 70) looksOk = true;
33065
+ else if (m.includes("webp") && buf.length >= 12 && buf.subarray(0, 4).toString("ascii") === "RIFF" && buf.subarray(8, 12).toString("ascii") === "WEBP")
33066
+ looksOk = true;
33067
+ else if (!/(png|jpe?g|gif|webp)/i.test(m)) looksOk = true;
33068
+ if (!looksOk) {
33069
+ logDebug(
33070
+ `[Agent] Attachment ${idShort} (${mimeType}): decoded bytes do not match common image signatures \u2014 wrong E2EE key, corrupt blob, or metadata mismatch. First 12 bytes (hex): ${buf.subarray(0, Math.min(12, buf.length)).toString("hex")}`
33071
+ );
33072
+ }
33073
+ }
33074
+ async function fetchSessionAttachmentPayloadsForAgent(params) {
33075
+ const { attachments, sessionId, cloudApiBaseUrl, getCloudAccessToken, e2ee, log: log2 } = params;
33076
+ const token = getCloudAccessToken();
33077
+ if (!token) {
33078
+ return { ok: false, error: "Missing cloud access token; cannot download attachments." };
33079
+ }
33080
+ const wantedCount = attachments.filter((a) => typeof a.attachmentId === "string" && a.attachmentId.trim() !== "").length;
33081
+ if (wantedCount === 0) {
33082
+ return { ok: false, error: "No valid attachment ids in prompt." };
33083
+ }
33084
+ const base = cloudApiBaseUrl.replace(/\/+$/, "");
33085
+ const out = [];
33086
+ for (const a of attachments) {
33087
+ const id = typeof a.attachmentId === "string" ? a.attachmentId.trim() : "";
33088
+ if (!id) continue;
33089
+ const metaUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/meta`;
33090
+ const blobUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/blob`;
33091
+ const metaRes = await fetch(metaUrl, { headers: { Authorization: `Bearer ${token}` } });
33092
+ if (!metaRes.ok) {
33093
+ const t = await metaRes.text().catch(() => "");
33094
+ log2(`[Agent] Attachment meta fetch failed ${metaRes.status}: ${t.slice(0, 200)}`);
33095
+ return { ok: false, error: `Could not load attachment (${id.slice(0, 8)}\u2026): ${metaRes.status}` };
32792
33096
  }
32793
- const result = await handle.sendPrompt(agentPromptText, sendOpts);
32794
- if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
32795
- await collectTurnGitDiffFromPreTurnSnapshot({
32796
- sessionId,
32797
- runId,
32798
- agentCwd,
32799
- sendSessionUpdate,
32800
- log: log2
32801
- });
33097
+ const meta = await metaRes.json().catch(() => null);
33098
+ const mimeType = typeof meta?.mimeType === "string" && meta.mimeType.trim() ? meta.mimeType.trim() : a.mimeType;
33099
+ const blobRes = await fetch(blobUrl, { headers: { Authorization: `Bearer ${token}` } });
33100
+ if (!blobRes.ok) {
33101
+ const t = await blobRes.text().catch(() => "");
33102
+ log2(`[Agent] Attachment blob fetch failed ${blobRes.status}: ${t.slice(0, 200)}`);
33103
+ return { ok: false, error: `Could not load attachment data (${id.slice(0, 8)}\u2026): ${blobRes.status}` };
32802
33104
  }
32803
- await maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess({
33105
+ const buf = Buffer.from(await blobRes.arrayBuffer());
33106
+ let imageBytes;
33107
+ const encrypted = metaSaysEncrypted(meta);
33108
+ if (encrypted) {
33109
+ if (!e2ee) {
33110
+ return { ok: false, error: "Encrypted attachments require E2EE keys on this bridge." };
33111
+ }
33112
+ const k = typeof meta?.k === "string" ? meta.k : "";
33113
+ const n = typeof meta?.n === "string" ? meta.n : "";
33114
+ if (!k || !n) {
33115
+ return { ok: false, error: "Invalid encrypted attachment metadata (missing key id or nonce)." };
33116
+ }
33117
+ const c = base64UrlEncode(buf);
33118
+ imageBytes = e2ee.decryptEnvelopeToBuffer({ k, n, c });
33119
+ } else {
33120
+ imageBytes = buf;
33121
+ }
33122
+ warnIfDecodedImageMagicUnexpected(imageBytes, mimeType, id.slice(0, 8));
33123
+ logDebug(
33124
+ `[Agent] Loaded prompt image ${id.slice(0, 8)}\u2026: ${imageBytes.length} bytes, mime=${mimeType}, ${encrypted ? "E2EE decrypted" : "plaintext from storage"}`
33125
+ );
33126
+ const dataBase64 = imageBytes.toString("base64");
33127
+ out.push({ mimeType, dataBase64 });
33128
+ }
33129
+ if (out.length !== wantedCount) {
33130
+ return {
33131
+ ok: false,
33132
+ error: `Expected ${wantedCount} image attachment(s) but only loaded ${out.length} (check attachment ids and empty rows).`
33133
+ };
33134
+ }
33135
+ return { ok: true, images: out };
33136
+ }
33137
+
33138
+ // src/agents/acp/prompts/resolve-send-prompt-images.ts
33139
+ async function resolveSendPromptImages(params) {
33140
+ const {
33141
+ attachments,
33142
+ sessionId,
33143
+ promptId,
33144
+ runId,
33145
+ agentType,
33146
+ followUpCatalogPromptId,
33147
+ cloudApiBaseUrl,
33148
+ getCloudAccessToken,
33149
+ e2ee,
33150
+ log: log2
33151
+ } = params;
33152
+ if (!attachments || attachments.length === 0) {
33153
+ return { ok: true, sendOpts: {} };
33154
+ }
33155
+ if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
33156
+ return {
33157
+ ok: false,
33158
+ errorResult: {
33159
+ type: "prompt_result",
33160
+ id: promptId,
33161
+ ...sessionId ? { sessionId } : {},
33162
+ ...runId ? { runId } : {},
33163
+ success: false,
33164
+ error: "Prompt includes images but the bridge is not configured with a cloud API URL and token to fetch them.",
33165
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
33166
+ ...augmentPromptResultAuthFields(agentType, "missing cloud for images download")
33167
+ }
33168
+ };
33169
+ }
33170
+ const resolved = await fetchSessionAttachmentPayloadsForAgent({
33171
+ attachments,
33172
+ sessionId,
33173
+ cloudApiBaseUrl,
33174
+ getCloudAccessToken,
33175
+ e2ee,
33176
+ log: log2
33177
+ });
33178
+ if (!resolved.ok) {
33179
+ return {
33180
+ ok: false,
33181
+ errorResult: {
33182
+ type: "prompt_result",
33183
+ id: promptId,
33184
+ ...sessionId ? { sessionId } : {},
33185
+ ...runId ? { runId } : {},
33186
+ success: false,
33187
+ error: resolved.error,
33188
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
33189
+ ...augmentPromptResultAuthFields(agentType, resolved.error)
33190
+ }
33191
+ };
33192
+ }
33193
+ return { ok: true, sendOpts: { images: resolved.images } };
33194
+ }
33195
+
33196
+ // src/agents/acp/prompts/send-prompt-to-agent.ts
33197
+ async function sendPromptToAgent(options) {
33198
+ const {
33199
+ handle,
33200
+ promptText,
33201
+ promptId,
33202
+ sessionId,
33203
+ runId,
33204
+ agentType,
33205
+ agentCwd,
33206
+ sendResult,
33207
+ sendSessionUpdate,
33208
+ log: log2,
33209
+ followUpCatalogPromptId,
33210
+ sessionChangeSummaryFilePaths,
33211
+ cloudApiBaseUrl,
33212
+ getCloudAccessToken,
33213
+ e2ee,
33214
+ attachments,
33215
+ isNewSession = false
33216
+ } = options;
33217
+ let isPlanningSession = false;
33218
+ try {
33219
+ const prepared = await prepareAgentPromptText({
33220
+ promptText,
32804
33221
  sessionId,
32805
33222
  runId,
32806
- resultSuccess: result.success === true,
32807
- output: result.output,
32808
- e2ee,
33223
+ isNewSession,
33224
+ cloudApiBaseUrl,
33225
+ getCloudAccessToken
33226
+ });
33227
+ isPlanningSession = prepared.isPlanningSession;
33228
+ const imagesResolved = await resolveSendPromptImages({
33229
+ attachments,
33230
+ sessionId,
33231
+ promptId,
33232
+ runId,
33233
+ agentType,
33234
+ followUpCatalogPromptId,
32809
33235
  cloudApiBaseUrl,
32810
33236
  getCloudAccessToken,
33237
+ e2ee,
33238
+ log: log2
33239
+ });
33240
+ if (!imagesResolved.ok) {
33241
+ sendResult(imagesResolved.errorResult);
33242
+ return;
33243
+ }
33244
+ const result = await handle.sendPrompt(prepared.agentPromptText, imagesResolved.sendOpts);
33245
+ await finalizeAndSendPromptResult({
33246
+ result,
33247
+ sessionId,
33248
+ runId,
33249
+ promptId,
33250
+ agentType,
33251
+ agentCwd,
33252
+ isPlanningSession,
32811
33253
  followUpCatalogPromptId,
32812
33254
  sessionChangeSummaryFilePaths,
33255
+ cloudApiBaseUrl,
33256
+ getCloudAccessToken,
33257
+ e2ee,
33258
+ sendResult,
33259
+ sendSessionUpdate,
32813
33260
  log: log2
32814
33261
  });
32815
- const errStr = typeof result.error === "string" ? result.error : void 0;
32816
- const resultStop = typeof result.stopReason === "string" ? result.stopReason.trim() : "";
32817
- const cancelledByAgent = resultStop.toLowerCase() === "cancelled" || errStr != null && isUserEndedSessionTurnErrorText(errStr);
32818
- sendResult({
32819
- type: "prompt_result",
32820
- id: promptId,
32821
- ...sessionId ? { sessionId } : {},
32822
- ...runId ? { runId } : {},
32823
- ...result,
32824
- ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
32825
- ...augmentPromptResultAuthFields(agentType, errStr),
32826
- ...!result.success && cancelledByAgent ? { stopReason: "cancelled" } : {}
32827
- });
32828
- if (!result.success) {
32829
- if (cancelledByAgent) {
32830
- log2("[Agent] Run ended after stop request (stopped by user).");
32831
- } else {
32832
- log2(
32833
- `[Agent] Prompt did not run successfully on the agent (no successful start/completion): ${result.error ?? "Unknown error"}`
32834
- );
32835
- }
32836
- }
32837
33262
  } catch (err) {
32838
33263
  const errMsg = err instanceof Error ? err.message : String(err);
32839
33264
  log2(`[Agent] Send failed: ${errMsg}`);
@@ -32847,6 +33272,8 @@ async function sendPromptToAgent(options) {
32847
33272
  ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
32848
33273
  ...augmentPromptResultAuthFields(agentType, errMsg)
32849
33274
  });
33275
+ } finally {
33276
+ if (runId && isPlanningSession) clearPlanningSessionTurn(runId);
32850
33277
  }
32851
33278
  }
32852
33279
 
@@ -32860,15 +33287,36 @@ function getAcpSessionAgentState(ctx, acpSessionAgentKey) {
32860
33287
  return state;
32861
33288
  }
32862
33289
 
32863
- // src/agents/acp/manager/handle-prompt.ts
32864
- function handlePrompt(ctx, opts) {
33290
+ // src/agents/acp/manager/handle-pending-prompt-cancel.ts
33291
+ async function handlePendingPromptCancel(params) {
33292
+ const { ctx, activeRunId, promptId, sessionId, handle, sendResult } = params;
33293
+ if (!ctx.pendingCancelRunIds.has(activeRunId)) {
33294
+ return false;
33295
+ }
33296
+ ctx.pendingCancelRunIds.delete(activeRunId);
33297
+ try {
33298
+ await handle.cancel?.();
33299
+ } catch {
33300
+ }
33301
+ sendResult({
33302
+ type: "prompt_result",
33303
+ id: promptId,
33304
+ ...sessionId ? { sessionId } : {},
33305
+ runId: activeRunId,
33306
+ success: false,
33307
+ error: "Stopped by user",
33308
+ stopReason: "cancelled"
33309
+ });
33310
+ return true;
33311
+ }
33312
+
33313
+ // src/agents/acp/manager/run-acp-prompt.ts
33314
+ async function runAcpPrompt(ctx, runCtx, opts) {
32865
33315
  const {
32866
33316
  promptText,
32867
33317
  promptId,
32868
33318
  sessionId,
32869
- runId,
32870
33319
  mode,
32871
- agentType,
32872
33320
  agentConfig,
32873
33321
  sessionParentPath,
32874
33322
  sendResult,
@@ -32878,109 +33326,99 @@ function handlePrompt(ctx, opts) {
32878
33326
  cloudApiBaseUrl,
32879
33327
  getCloudAccessToken,
32880
33328
  e2ee,
32881
- attachments
33329
+ attachments,
33330
+ isNewSession
32882
33331
  } = opts;
32883
- const preferredForPrompt = agentType ?? ctx.backendFallbackAgentType ?? null;
32884
- if (!runId) {
32885
- ctx.log("[Agent] Prompt ignored: missing runId (cannot route session updates).");
32886
- return;
32887
- }
32888
- const activeRunId = runId;
32889
- const acpAgentKey = computeAcpAgentKey(preferredForPrompt, mode, agentConfig ?? null);
32890
- if (!acpAgentKey) {
33332
+ const { activeRunId, activeAcpAgentKey, activeAcpSessionAgentKey, preferredForPrompt } = runCtx;
33333
+ const acpAgentState = getAcpSessionAgentState(ctx, activeAcpSessionAgentKey);
33334
+ const handle = await ensureAcpClient({
33335
+ state: acpAgentState,
33336
+ acpAgentKey: activeAcpAgentKey,
33337
+ preferredAgentType: preferredForPrompt,
33338
+ mode,
33339
+ agentConfig: agentConfig ?? null,
33340
+ sessionParentPath,
33341
+ resolveRouting: () => ctx.promptRouting.resolveRouting(activeAcpSessionAgentKey),
33342
+ cloudSessionId: sessionId,
33343
+ bridgeAccessPort: ctx.getBridgeAccessPort(),
33344
+ sendSessionUpdate,
33345
+ sendRequest: sendSessionUpdate,
33346
+ log: ctx.log,
33347
+ reportAgentCapabilities: ctx.reportAgentCapabilities
33348
+ });
33349
+ if (!handle) {
33350
+ const errMsg = acpAgentState.lastAcpStartError || "No agent configured. Register local agents on this bridge in the app.";
33351
+ const evaluated = Boolean(preferredForPrompt && errMsg.trim());
33352
+ const suggestsAuth = evaluated ? localAgentErrorSuggestsAuth(preferredForPrompt, errMsg) : false;
33353
+ const auth = suggestsAuth && preferredForPrompt ? { agentAuthRequired: true, agentType: preferredForPrompt } : {};
32891
33354
  sendResult({
32892
33355
  type: "prompt_result",
32893
33356
  id: promptId,
32894
33357
  ...sessionId ? { sessionId } : {},
32895
33358
  runId: activeRunId,
32896
33359
  success: false,
32897
- error: "No agent type: ensure the app sends agentType on prompts or agent_config for this bridge."
33360
+ error: errMsg,
33361
+ ...auth
32898
33362
  });
32899
33363
  return;
32900
33364
  }
32901
- const activeAcpAgentKey = acpAgentKey;
32902
- const activeAcpSessionAgentKey = computeAcpSessionAgentKey(sessionId, activeAcpAgentKey);
32903
- ctx.pendingCancelRunIds.delete(activeRunId);
32904
- ctx.promptRouting.registerRun({ sessionId, runId: activeRunId });
32905
- ctx.runDispatch.set(activeRunId, { acpSessionAgentKey: activeAcpSessionAgentKey });
32906
- async function run() {
32907
- const acpAgentState = getAcpSessionAgentState(ctx, activeAcpSessionAgentKey);
32908
- const handle = await ensureAcpClient({
32909
- state: acpAgentState,
32910
- acpAgentKey: activeAcpAgentKey,
32911
- preferredAgentType: preferredForPrompt,
32912
- mode,
32913
- agentConfig: agentConfig ?? null,
32914
- sessionParentPath,
32915
- resolveRouting: () => ctx.promptRouting.resolveRouting(activeAcpSessionAgentKey),
32916
- cloudSessionId: sessionId,
32917
- bridgeAccessPort: ctx.getBridgeAccessPort(),
33365
+ if (!ctx.promptRouting.isRegisteredRun(activeRunId)) {
33366
+ return;
33367
+ }
33368
+ const cancelled = await handlePendingPromptCancel({
33369
+ ctx,
33370
+ activeRunId,
33371
+ promptId,
33372
+ sessionId,
33373
+ handle,
33374
+ sendResult
33375
+ });
33376
+ if (cancelled) return;
33377
+ ctx.promptRouting.setStreamingRunId(activeAcpSessionAgentKey, activeRunId);
33378
+ try {
33379
+ await sendPromptToAgent({
33380
+ handle,
33381
+ promptText,
33382
+ promptId,
33383
+ sessionId,
33384
+ runId: activeRunId,
33385
+ agentType: preferredForPrompt,
33386
+ agentCwd: resolveSessionParentPathForAgentProcess(sessionParentPath),
33387
+ sendResult,
32918
33388
  sendSessionUpdate,
32919
- sendRequest: sendSessionUpdate,
32920
33389
  log: ctx.log,
32921
- reportAgentCapabilities: ctx.reportAgentCapabilities
33390
+ followUpCatalogPromptId,
33391
+ sessionChangeSummaryFilePaths,
33392
+ cloudApiBaseUrl,
33393
+ getCloudAccessToken,
33394
+ e2ee,
33395
+ attachments,
33396
+ isNewSession
32922
33397
  });
32923
- if (!handle) {
32924
- const errMsg = acpAgentState.lastAcpStartError || "No agent configured. Register local agents on this bridge in the app.";
32925
- const evaluated = Boolean(preferredForPrompt && errMsg.trim());
32926
- const suggestsAuth = evaluated ? localAgentErrorSuggestsAuth(preferredForPrompt, errMsg) : false;
32927
- const auth = suggestsAuth && preferredForPrompt ? { agentAuthRequired: true, agentType: preferredForPrompt } : {};
32928
- sendResult({
32929
- type: "prompt_result",
32930
- id: promptId,
32931
- ...sessionId ? { sessionId } : {},
32932
- runId: activeRunId,
32933
- success: false,
32934
- error: errMsg,
32935
- ...auth
32936
- });
32937
- return;
32938
- }
32939
- if (!ctx.promptRouting.isRegisteredRun(activeRunId)) {
32940
- return;
32941
- }
32942
- if (ctx.pendingCancelRunIds.has(activeRunId)) {
32943
- ctx.pendingCancelRunIds.delete(activeRunId);
32944
- try {
32945
- await handle.cancel?.();
32946
- } catch {
32947
- }
33398
+ } finally {
33399
+ ctx.promptRouting.clearStreamingRunId(activeAcpSessionAgentKey, activeRunId);
33400
+ }
33401
+ }
33402
+
33403
+ // src/agents/acp/manager/handle-prompt.ts
33404
+ function handlePrompt(ctx, opts) {
33405
+ const { promptId, sessionId, runId, mode, agentType, agentConfig, sendResult } = opts;
33406
+ const runCtx = resolvePromptRunContext(ctx, { runId, mode, agentType, agentConfig, sessionId });
33407
+ if (!runCtx) {
33408
+ if (runId) {
32948
33409
  sendResult({
32949
33410
  type: "prompt_result",
32950
33411
  id: promptId,
32951
33412
  ...sessionId ? { sessionId } : {},
32952
- runId: activeRunId,
33413
+ runId,
32953
33414
  success: false,
32954
- error: "Stopped by user",
32955
- stopReason: "cancelled"
33415
+ error: "No agent type: ensure the app sends agentType on prompts or agent_config for this bridge."
32956
33416
  });
32957
- return;
32958
- }
32959
- ctx.promptRouting.setStreamingRunId(activeAcpSessionAgentKey, activeRunId);
32960
- try {
32961
- await sendPromptToAgent({
32962
- handle,
32963
- promptText,
32964
- promptId,
32965
- sessionId,
32966
- runId: activeRunId,
32967
- agentType: preferredForPrompt,
32968
- agentCwd: resolveSessionParentPathForAgentProcess(sessionParentPath),
32969
- sendResult,
32970
- sendSessionUpdate,
32971
- log: ctx.log,
32972
- followUpCatalogPromptId,
32973
- sessionChangeSummaryFilePaths,
32974
- cloudApiBaseUrl,
32975
- getCloudAccessToken,
32976
- e2ee,
32977
- attachments
32978
- });
32979
- } finally {
32980
- ctx.promptRouting.clearStreamingRunId(activeAcpSessionAgentKey, activeRunId);
32981
33417
  }
33418
+ return;
32982
33419
  }
32983
- void run().finally(() => {
33420
+ const { activeRunId } = runCtx;
33421
+ void runAcpPrompt(ctx, runCtx, opts).finally(() => {
32984
33422
  ctx.promptRouting.unregisterRun(activeRunId);
32985
33423
  ctx.runDispatch.delete(activeRunId);
32986
33424
  ctx.pendingCancelRunIds.delete(activeRunId);
@@ -42278,7 +42716,8 @@ async function runPreambleAndPrompt(params) {
42278
42716
  cloudApiBaseUrl: deps.cloudApiBaseUrl,
42279
42717
  getCloudAccessToken: deps.getCloudAccessToken,
42280
42718
  e2ee: deps.e2ee,
42281
- ...attachments.length > 0 ? { attachments } : {}
42719
+ ...attachments.length > 0 ? { attachments } : {},
42720
+ isNewSession: msg.isNewSession === true
42282
42721
  });
42283
42722
  }
42284
42723