@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/cli.js CHANGED
@@ -25631,7 +25631,7 @@ var {
25631
25631
  } = import_index.default;
25632
25632
 
25633
25633
  // src/cli-version.ts
25634
- var CLI_VERSION = "0.1.57".length > 0 ? "0.1.57" : "0.0.0-dev";
25634
+ var CLI_VERSION = "0.1.58".length > 0 ? "0.1.58" : "0.0.0-dev";
25635
25635
 
25636
25636
  // src/cli/defaults.ts
25637
25637
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
@@ -28757,6 +28757,81 @@ function isUserEndedSessionTurnErrorText(errorText) {
28757
28757
  // ../types/src/session-kind.ts
28758
28758
  var SESSION_KINDS = ["regular", "planning", "planned"];
28759
28759
  var SESSION_KIND_SET = new Set(SESSION_KINDS);
28760
+ function parseSessionKind(raw) {
28761
+ if (raw === "todo") return "planned";
28762
+ if (typeof raw === "string" && SESSION_KIND_SET.has(raw)) return raw;
28763
+ return "regular";
28764
+ }
28765
+
28766
+ // ../types/src/planning-todo.ts
28767
+ function planningTodoTitle(item) {
28768
+ return item.content.title;
28769
+ }
28770
+
28771
+ // ../types/src/parse-planning-todo-agent-json.ts
28772
+ function parsePlanningTodoAgentJson(raw) {
28773
+ if (raw == null || raw.trim() === "") return [];
28774
+ let text = raw.trim();
28775
+ const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
28776
+ if (fence?.[1]) text = fence[1].trim();
28777
+ let parsed;
28778
+ try {
28779
+ parsed = JSON.parse(text);
28780
+ } catch {
28781
+ const start = text.indexOf("[");
28782
+ const end = text.lastIndexOf("]");
28783
+ if (start < 0 || end <= start) return [];
28784
+ try {
28785
+ parsed = JSON.parse(text.slice(start, end + 1));
28786
+ } catch {
28787
+ return [];
28788
+ }
28789
+ }
28790
+ if (!Array.isArray(parsed)) return [];
28791
+ const rows = [];
28792
+ for (const item of parsed) {
28793
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
28794
+ const o = item;
28795
+ const title = typeof o.title === "string" ? o.title.trim() : "";
28796
+ const prompt = typeof o.prompt === "string" ? o.prompt.trim() : typeof o.promptText === "string" ? o.promptText.trim() : "";
28797
+ if (!title || !prompt) continue;
28798
+ rows.push({ title, prompt });
28799
+ }
28800
+ return rows;
28801
+ }
28802
+
28803
+ // ../types/src/build-planning-session-agent-prompt.ts
28804
+ var PLANNING_INSTRUCTIONS = [
28805
+ "PLANNING MODE: You must NOT make any changes to the codebase, filesystem, or environment.",
28806
+ "Do not run tools that modify files or execute commands that change state.",
28807
+ "Your only task is to analyze and produce a plan.",
28808
+ "",
28809
+ "Respond with ONLY a single JSON array \u2014 no markdown fences, no other text before or after.",
28810
+ "Each array element must be an object with exactly:",
28811
+ '- "title": a one-line summary of the task',
28812
+ '- "prompt": the full prompt to use when creating a new draft session from this todo',
28813
+ "",
28814
+ "Example:",
28815
+ '[{"title":"Add user login","prompt":"Implement a login form with email and password..."}]'
28816
+ ].join("\n");
28817
+ function buildPlanningSessionAgentPrompt(userPrompt) {
28818
+ return [PLANNING_INSTRUCTIONS, "", "User request:", userPrompt.trim()].join("\n");
28819
+ }
28820
+ function buildPlanningSessionFollowUpAgentPrompt(userPrompt, existingTodos) {
28821
+ const todoLines = existingTodos.length > 0 ? existingTodos.map((t) => {
28822
+ const status = t.plannedSessionId ? "DONE (draft session already created)" : "OPEN";
28823
+ return `- [${status}] ${planningTodoTitle(t)}`;
28824
+ }).join("\n") : "(none yet)";
28825
+ return [
28826
+ PLANNING_INSTRUCTIONS,
28827
+ "",
28828
+ "Existing todo items from this planning session (consider these when responding; do not duplicate completed items):",
28829
+ todoLines,
28830
+ "",
28831
+ "User follow-up:",
28832
+ userPrompt.trim()
28833
+ ].join("\n");
28834
+ }
28760
28835
 
28761
28836
  // ../types/src/change-summary-path.ts
28762
28837
  function normalizeRepoRelativePath(p) {
@@ -29140,6 +29215,19 @@ function buildCliAutoApprovedPermissionRpcResult(requestParams) {
29140
29215
  };
29141
29216
  }
29142
29217
 
29218
+ // ../types/src/acp-agent-text-stream-bucket.ts
29219
+ function resolveAgentTextStreamBucket(params) {
29220
+ const k = params.transcriptRowKind;
29221
+ const su = params.sessionUpdate;
29222
+ if (k === "agent_thought_chunk" || su === "agent_thought_chunk" || k === "thinking" || su === "thinking") {
29223
+ return "thought";
29224
+ }
29225
+ if (k === "agent_message_chunk" || su === "agent_message_chunk" || k === "update" || su === "update") {
29226
+ return "message";
29227
+ }
29228
+ return null;
29229
+ }
29230
+
29143
29231
  // ../types/src/agent-config.ts
29144
29232
  var AGENT_CONFIG_CLAUDE_PERMISSION_MODE_KEY = "claude_permission_mode";
29145
29233
  var AGENT_CONFIG_CLI_PERMISSION_MODE_KEY = "cli_permission_mode";
@@ -34109,6 +34197,185 @@ function createBridgeOnRequest(opts) {
34109
34197
  };
34110
34198
  }
34111
34199
 
34200
+ // src/agents/acp/hooks/bridge-on-session-update/constants.ts
34201
+ var PATH_SNAPSHOT_DEBOUNCE_MS = 500;
34202
+
34203
+ // src/agents/acp/hooks/bridge-on-session-update/tool-key.ts
34204
+ function getToolCallId(params) {
34205
+ if (!params || typeof params !== "object") return "";
34206
+ const u = params;
34207
+ const id = u.toolCallId ?? u.tool_call_id;
34208
+ return typeof id === "string" ? id : "";
34209
+ }
34210
+ function isCompletedToolStatus(status) {
34211
+ if (typeof status !== "string") return false;
34212
+ const s = status.toLowerCase();
34213
+ return s === "completed" || s === "complete" || s === "succeeded" || s === "success";
34214
+ }
34215
+ function accumulateToolPaths(toolKey, paths, acc) {
34216
+ if (!toolKey || paths.length === 0) return;
34217
+ let s = acc.get(toolKey);
34218
+ if (!s) {
34219
+ s = /* @__PURE__ */ new Set();
34220
+ acc.set(toolKey, s);
34221
+ }
34222
+ for (const p of paths) s.add(p);
34223
+ }
34224
+
34225
+ // src/agents/acp/hooks/bridge-on-session-update/path-snapshot-tracker.ts
34226
+ var PathSnapshotTracker = class {
34227
+ lastSeenRunId;
34228
+ anonToolSeq = 0;
34229
+ lastAnonymousToolKey = null;
34230
+ beforeByToolKey = /* @__PURE__ */ new Map();
34231
+ debouncers = /* @__PURE__ */ new Map();
34232
+ accumulatedPathsByToolKey = /* @__PURE__ */ new Map();
34233
+ clearRunState() {
34234
+ for (const t of this.debouncers.values()) clearTimeout(t);
34235
+ this.debouncers.clear();
34236
+ this.beforeByToolKey.clear();
34237
+ this.accumulatedPathsByToolKey.clear();
34238
+ this.lastAnonymousToolKey = null;
34239
+ }
34240
+ onRunIdChanged(runId) {
34241
+ if (runId && runId !== this.lastSeenRunId) {
34242
+ this.lastSeenRunId = runId;
34243
+ this.clearRunState();
34244
+ }
34245
+ }
34246
+ resolveToolKey(params, updateKind) {
34247
+ const id = getToolCallId(params);
34248
+ if (id) return id;
34249
+ if (updateKind === "tool_call") {
34250
+ this.lastAnonymousToolKey = `anon:${++this.anonToolSeq}`;
34251
+ return this.lastAnonymousToolKey;
34252
+ }
34253
+ return this.lastAnonymousToolKey ?? "_unknown";
34254
+ }
34255
+ resetToolSnapshots(toolKey) {
34256
+ const t = this.debouncers.get(toolKey);
34257
+ if (t) clearTimeout(t);
34258
+ this.debouncers.delete(toolKey);
34259
+ this.beforeByToolKey.delete(toolKey);
34260
+ this.accumulatedPathsByToolKey.delete(toolKey);
34261
+ }
34262
+ captureBeforeFromDisk(toolKey, paths, sessionParentPath) {
34263
+ if (paths.length === 0) return;
34264
+ let m = this.beforeByToolKey.get(toolKey);
34265
+ if (!m) {
34266
+ m = /* @__PURE__ */ new Map();
34267
+ this.beforeByToolKey.set(toolKey, m);
34268
+ }
34269
+ for (const p of paths) {
34270
+ if (m.has(p)) continue;
34271
+ m.set(p, readUtf8WorkspaceFile(sessionParentPath, p));
34272
+ }
34273
+ }
34274
+ ensureBeforeFromHeadForMissing(toolKey, paths, sessionParentPath) {
34275
+ let m = this.beforeByToolKey.get(toolKey);
34276
+ if (!m) {
34277
+ m = /* @__PURE__ */ new Map();
34278
+ this.beforeByToolKey.set(toolKey, m);
34279
+ }
34280
+ for (const p of paths) {
34281
+ if (m.has(p)) continue;
34282
+ m.set(p, readGitHeadBlob(sessionParentPath, p));
34283
+ }
34284
+ }
34285
+ flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
34286
+ const t = this.debouncers.get(toolKey);
34287
+ if (t) clearTimeout(t);
34288
+ this.debouncers.delete(toolKey);
34289
+ const beforeMap = this.beforeByToolKey.get(toolKey);
34290
+ if (!beforeMap || beforeMap.size === 0) return;
34291
+ this.beforeByToolKey.delete(toolKey);
34292
+ if (!send || !runId || !sessionId) return;
34293
+ for (const [displayPath, oldText] of beforeMap) {
34294
+ const newText = readUtf8WorkspaceFile(sessionParentPath, displayPath);
34295
+ if (oldText === newText) continue;
34296
+ const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
34297
+ const dirFlags = getSessionFileChangeDirectoryFlags(sessionParentPath, displayPath);
34298
+ try {
34299
+ send({
34300
+ type: "session_file_change",
34301
+ sessionId,
34302
+ runId,
34303
+ path: displayPath,
34304
+ oldText,
34305
+ newText,
34306
+ patchContent,
34307
+ isDirectory: dirFlags.isDirectory,
34308
+ directoryRemoved: dirFlags.directoryRemoved
34309
+ });
34310
+ sentPaths.add(displayPath);
34311
+ } catch (err) {
34312
+ log2(`[Bridge service] Session file change failed for ${displayPath}: ${errorMessage(err)}`);
34313
+ }
34314
+ }
34315
+ }
34316
+ scheduleDebouncedFlush(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
34317
+ const prev = this.debouncers.get(toolKey);
34318
+ if (prev) clearTimeout(prev);
34319
+ this.debouncers.set(
34320
+ toolKey,
34321
+ setTimeout(() => {
34322
+ this.debouncers.delete(toolKey);
34323
+ this.flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
34324
+ }, PATH_SNAPSHOT_DEBOUNCE_MS)
34325
+ );
34326
+ }
34327
+ handleToolCallLifecycle(updateKind, toolKey, toolPaths, status, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
34328
+ if (updateKind === "tool_call") {
34329
+ this.resetToolSnapshots(toolKey);
34330
+ }
34331
+ if (updateKind === "tool_call") {
34332
+ this.captureBeforeFromDisk(toolKey, toolPaths, sessionParentPath);
34333
+ } else if (updateKind === "tool_call_update") {
34334
+ if (isCompletedToolStatus(status)) {
34335
+ this.ensureBeforeFromHeadForMissing(toolKey, toolPaths, sessionParentPath);
34336
+ this.flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
34337
+ } else {
34338
+ this.captureBeforeFromDisk(toolKey, toolPaths, sessionParentPath);
34339
+ if (this.beforeByToolKey.has(toolKey)) {
34340
+ this.scheduleDebouncedFlush(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
34341
+ }
34342
+ }
34343
+ }
34344
+ }
34345
+ };
34346
+
34347
+ // src/agents/acp/hooks/bridge-on-session-update/send-session-info-title-update.ts
34348
+ function extractSessionInfoTitle(params) {
34349
+ if (!params || typeof params !== "object") return null;
34350
+ const p = params;
34351
+ const title = typeof p.title === "string" ? p.title.trim() : "";
34352
+ return title ? title : null;
34353
+ }
34354
+ function sendSessionInfoTitleUpdate(params) {
34355
+ const title = extractSessionInfoTitle(params.payload);
34356
+ if (!title || !params.runId || !params.send) return;
34357
+ try {
34358
+ params.send({
34359
+ type: "session_title_update",
34360
+ ...params.sessionId ? { sessionId: params.sessionId } : {},
34361
+ runId: params.runId,
34362
+ title
34363
+ });
34364
+ } catch (err) {
34365
+ params.log(`[Bridge service] Session title update send failed: ${errorMessage(err)}`);
34366
+ }
34367
+ }
34368
+
34369
+ // src/agents/acp/hooks/bridge-on-session-update/parse-bridge-session-update.ts
34370
+ function parseBridgeSessionUpdate(params) {
34371
+ const p = params;
34372
+ const updateKind = p.sessionUpdate ?? p.session_update ?? p.type ?? "update";
34373
+ const isCompletedToolCallUpdate = updateKind === "tool_call_update" && isCompletedToolStatus(p.status);
34374
+ const toolName = p.toolCall?.name ?? p.tool_call?.name ?? "";
34375
+ const isToolUpdate = updateKind === "tool_call" || updateKind === "tool_call_update" || typeof toolName === "string" && toolName.length > 0;
34376
+ return { updateKind, toolName, isToolUpdate, isCompletedToolCallUpdate };
34377
+ }
34378
+
34112
34379
  // src/agents/acp/hooks/extract-acp-file-diffs-from-update/paths-and-text.ts
34113
34380
  import { fileURLToPath as fileURLToPath5 } from "node:url";
34114
34381
  function readOptionalTextField(v) {
@@ -34279,174 +34546,27 @@ function extractToolTargetDisplayPaths(update, sessionParentPath) {
34279
34546
  return [...out];
34280
34547
  }
34281
34548
 
34282
- // src/agents/acp/hooks/bridge-on-session-update/constants.ts
34283
- var PATH_SNAPSHOT_DEBOUNCE_MS = 500;
34284
-
34285
- // src/agents/acp/hooks/bridge-on-session-update/tool-key.ts
34286
- function getToolCallId(params) {
34287
- if (!params || typeof params !== "object") return "";
34288
- const u = params;
34289
- const id = u.toolCallId ?? u.tool_call_id;
34290
- return typeof id === "string" ? id : "";
34291
- }
34292
- function isCompletedToolStatus(status) {
34293
- if (typeof status !== "string") return false;
34294
- const s = status.toLowerCase();
34295
- return s === "completed" || s === "complete" || s === "succeeded" || s === "success";
34296
- }
34297
- function accumulateToolPaths(toolKey, paths, acc) {
34298
- if (!toolKey || paths.length === 0) return;
34299
- let s = acc.get(toolKey);
34300
- if (!s) {
34301
- s = /* @__PURE__ */ new Set();
34302
- acc.set(toolKey, s);
34303
- }
34304
- for (const p of paths) s.add(p);
34305
- }
34306
-
34307
- // src/agents/acp/hooks/bridge-on-session-update/path-snapshot-tracker.ts
34308
- var PathSnapshotTracker = class {
34309
- lastSeenRunId;
34310
- anonToolSeq = 0;
34311
- lastAnonymousToolKey = null;
34312
- beforeByToolKey = /* @__PURE__ */ new Map();
34313
- debouncers = /* @__PURE__ */ new Map();
34314
- accumulatedPathsByToolKey = /* @__PURE__ */ new Map();
34315
- clearRunState() {
34316
- for (const t of this.debouncers.values()) clearTimeout(t);
34317
- this.debouncers.clear();
34318
- this.beforeByToolKey.clear();
34319
- this.accumulatedPathsByToolKey.clear();
34320
- this.lastAnonymousToolKey = null;
34321
- }
34322
- onRunIdChanged(runId) {
34323
- if (runId && runId !== this.lastSeenRunId) {
34324
- this.lastSeenRunId = runId;
34325
- this.clearRunState();
34326
- }
34327
- }
34328
- resolveToolKey(params, updateKind) {
34329
- const id = getToolCallId(params);
34330
- if (id) return id;
34331
- if (updateKind === "tool_call") {
34332
- this.lastAnonymousToolKey = `anon:${++this.anonToolSeq}`;
34333
- return this.lastAnonymousToolKey;
34334
- }
34335
- return this.lastAnonymousToolKey ?? "_unknown";
34336
- }
34337
- resetToolSnapshots(toolKey) {
34338
- const t = this.debouncers.get(toolKey);
34339
- if (t) clearTimeout(t);
34340
- this.debouncers.delete(toolKey);
34341
- this.beforeByToolKey.delete(toolKey);
34342
- this.accumulatedPathsByToolKey.delete(toolKey);
34343
- }
34344
- captureBeforeFromDisk(toolKey, paths, sessionParentPath) {
34345
- if (paths.length === 0) return;
34346
- let m = this.beforeByToolKey.get(toolKey);
34347
- if (!m) {
34348
- m = /* @__PURE__ */ new Map();
34349
- this.beforeByToolKey.set(toolKey, m);
34350
- }
34351
- for (const p of paths) {
34352
- if (m.has(p)) continue;
34353
- m.set(p, readUtf8WorkspaceFile(sessionParentPath, p));
34354
- }
34355
- }
34356
- ensureBeforeFromHeadForMissing(toolKey, paths, sessionParentPath) {
34357
- let m = this.beforeByToolKey.get(toolKey);
34358
- if (!m) {
34359
- m = /* @__PURE__ */ new Map();
34360
- this.beforeByToolKey.set(toolKey, m);
34361
- }
34362
- for (const p of paths) {
34363
- if (m.has(p)) continue;
34364
- m.set(p, readGitHeadBlob(sessionParentPath, p));
34365
- }
34366
- }
34367
- flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
34368
- const t = this.debouncers.get(toolKey);
34369
- if (t) clearTimeout(t);
34370
- this.debouncers.delete(toolKey);
34371
- const beforeMap = this.beforeByToolKey.get(toolKey);
34372
- if (!beforeMap || beforeMap.size === 0) return;
34373
- this.beforeByToolKey.delete(toolKey);
34374
- if (!send || !runId || !sessionId) return;
34375
- for (const [displayPath, oldText] of beforeMap) {
34376
- const newText = readUtf8WorkspaceFile(sessionParentPath, displayPath);
34377
- if (oldText === newText) continue;
34378
- const patchContent = editSnippetToUnifiedDiff(displayPath, oldText, newText);
34379
- const dirFlags = getSessionFileChangeDirectoryFlags(sessionParentPath, displayPath);
34380
- try {
34381
- send({
34382
- type: "session_file_change",
34383
- sessionId,
34384
- runId,
34385
- path: displayPath,
34386
- oldText,
34387
- newText,
34388
- patchContent,
34389
- isDirectory: dirFlags.isDirectory,
34390
- directoryRemoved: dirFlags.directoryRemoved
34391
- });
34392
- sentPaths.add(displayPath);
34393
- } catch (err) {
34394
- log2(`[Bridge service] Session file change failed for ${displayPath}: ${errorMessage(err)}`);
34395
- }
34396
- }
34397
- }
34398
- scheduleDebouncedFlush(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
34399
- const prev = this.debouncers.get(toolKey);
34400
- if (prev) clearTimeout(prev);
34401
- this.debouncers.set(
34402
- toolKey,
34403
- setTimeout(() => {
34404
- this.debouncers.delete(toolKey);
34405
- this.flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
34406
- }, PATH_SNAPSHOT_DEBOUNCE_MS)
34407
- );
34408
- }
34409
- handleToolCallLifecycle(updateKind, toolKey, toolPaths, status, sessionParentPath, sentPaths, send, runId, sessionId, log2) {
34410
- if (updateKind === "tool_call") {
34411
- this.resetToolSnapshots(toolKey);
34412
- }
34413
- if (updateKind === "tool_call") {
34414
- this.captureBeforeFromDisk(toolKey, toolPaths, sessionParentPath);
34415
- } else if (updateKind === "tool_call_update") {
34416
- if (isCompletedToolStatus(status)) {
34417
- this.ensureBeforeFromHeadForMissing(toolKey, toolPaths, sessionParentPath);
34418
- this.flushPathSnapshots(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
34419
- } else {
34420
- this.captureBeforeFromDisk(toolKey, toolPaths, sessionParentPath);
34421
- if (this.beforeByToolKey.has(toolKey)) {
34422
- this.scheduleDebouncedFlush(toolKey, sessionParentPath, sentPaths, send, runId, sessionId, log2);
34423
- }
34424
- }
34425
- }
34426
- }
34427
- };
34428
-
34429
- // src/agents/acp/hooks/bridge-on-session-update/send-structured-file-changes.ts
34430
- function sendExtractedDiffsAsSessionFileChanges(diffs, send, sessionParentPath, sessionId, runId, sentPaths, log2) {
34431
- for (const d of diffs) {
34432
- try {
34433
- const patchContent = editSnippetToUnifiedDiff(d.path, d.oldText, d.newText);
34434
- const dirFlags = getSessionFileChangeDirectoryFlags(sessionParentPath, d.path);
34435
- send({
34436
- type: "session_file_change",
34437
- sessionId,
34438
- runId,
34439
- path: d.path,
34440
- oldText: d.oldText,
34441
- newText: d.newText,
34442
- patchContent,
34443
- isDirectory: dirFlags.isDirectory,
34444
- directoryRemoved: dirFlags.directoryRemoved
34445
- });
34446
- sentPaths.add(d.path);
34447
- } catch (err) {
34448
- log2(`[Bridge service] Session file change failed for ${d.path}: ${errorMessage(err)}`);
34449
- }
34549
+ // src/agents/acp/hooks/bridge-on-session-update/send-structured-file-changes.ts
34550
+ function sendExtractedDiffsAsSessionFileChanges(diffs, send, sessionParentPath, sessionId, runId, sentPaths, log2) {
34551
+ for (const d of diffs) {
34552
+ try {
34553
+ const patchContent = editSnippetToUnifiedDiff(d.path, d.oldText, d.newText);
34554
+ const dirFlags = getSessionFileChangeDirectoryFlags(sessionParentPath, d.path);
34555
+ send({
34556
+ type: "session_file_change",
34557
+ sessionId,
34558
+ runId,
34559
+ path: d.path,
34560
+ oldText: d.oldText,
34561
+ newText: d.newText,
34562
+ patchContent,
34563
+ isDirectory: dirFlags.isDirectory,
34564
+ directoryRemoved: dirFlags.directoryRemoved
34565
+ });
34566
+ sentPaths.add(d.path);
34567
+ } catch (err) {
34568
+ log2(`[Bridge service] Session file change failed for ${d.path}: ${errorMessage(err)}`);
34569
+ }
34450
34570
  }
34451
34571
  }
34452
34572
  function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send, sessionParentPath, sessionId, runId, log2) {
@@ -34476,25 +34596,158 @@ function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send, sessio
34476
34596
  }
34477
34597
  }
34478
34598
 
34479
- // src/agents/acp/hooks/bridge-on-session-update/send-session-info-title-update.ts
34480
- function extractSessionInfoTitle(params) {
34481
- if (!params || typeof params !== "object") return null;
34482
- const p = params;
34483
- const title = typeof p.title === "string" ? p.title.trim() : "";
34484
- return title ? title : null;
34599
+ // src/agents/acp/hooks/bridge-on-session-update/handle-bridge-tool-session-update.ts
34600
+ function handleBridgeToolSessionUpdate(params) {
34601
+ const {
34602
+ params: updateParams,
34603
+ updateKind,
34604
+ isToolUpdate,
34605
+ isCompletedToolCallUpdate,
34606
+ toolKey,
34607
+ toolPaths,
34608
+ status,
34609
+ sessionParentPath,
34610
+ pathTracker,
34611
+ sentFileChangePaths,
34612
+ send,
34613
+ runId,
34614
+ sessionId,
34615
+ log: log2
34616
+ } = params;
34617
+ if (updateKind === "tool_call") {
34618
+ pathTracker.resetToolSnapshots(toolKey);
34619
+ }
34620
+ if (isToolUpdate) {
34621
+ accumulateToolPaths(toolKey, toolPaths, pathTracker.accumulatedPathsByToolKey);
34622
+ }
34623
+ if (isToolUpdate) {
34624
+ const deliver = send && runId && sessionId ? (msg) => send(msg) : null;
34625
+ pathTracker.handleToolCallLifecycle(
34626
+ updateKind,
34627
+ toolKey,
34628
+ toolPaths,
34629
+ status,
34630
+ sessionParentPath,
34631
+ sentFileChangePaths,
34632
+ deliver,
34633
+ runId,
34634
+ sessionId,
34635
+ log2
34636
+ );
34637
+ }
34638
+ const diffs = extractAcpFileDiffsFromUpdate(updateParams, sessionParentPath);
34639
+ if (diffs.length > 0 && send && runId && sessionId) {
34640
+ sendExtractedDiffsAsSessionFileChanges(
34641
+ diffs,
34642
+ send,
34643
+ sessionParentPath,
34644
+ sessionId,
34645
+ runId,
34646
+ sentFileChangePaths,
34647
+ log2
34648
+ );
34649
+ } else if (diffs.length > 0) {
34650
+ log2(
34651
+ `[Bridge service] Agent file diff(s) not forwarded (${diffs.length}): session or run not wired to the bridge.`
34652
+ );
34653
+ }
34654
+ if (isCompletedToolCallUpdate && send && runId && sessionId) {
34655
+ const acc = pathTracker.accumulatedPathsByToolKey.get(toolKey);
34656
+ const merged = [.../* @__PURE__ */ new Set([...acc ? [...acc] : [], ...toolPaths])];
34657
+ sendGitHeadVsWorkspaceForToolPaths(
34658
+ merged,
34659
+ sentFileChangePaths,
34660
+ send,
34661
+ sessionParentPath,
34662
+ sessionId,
34663
+ runId,
34664
+ log2
34665
+ );
34666
+ pathTracker.accumulatedPathsByToolKey.delete(toolKey);
34667
+ }
34485
34668
  }
34486
- function sendSessionInfoTitleUpdate(params) {
34487
- const title = extractSessionInfoTitle(params.payload);
34488
- if (!title || !params.runId || !params.send) return;
34669
+ function resolveBridgeToolPaths(params, isToolUpdate, sessionParentPath) {
34670
+ return isToolUpdate ? extractToolTargetDisplayPaths(params, sessionParentPath) : [];
34671
+ }
34672
+
34673
+ // src/agents/planning/extract-session-update-message-text.ts
34674
+ function extractSessionUpdateMessageText(params) {
34675
+ if (params == null || typeof params !== "object" || Array.isArray(params)) return null;
34676
+ const o = params;
34677
+ if (typeof o.text === "string" && o.text.length > 0) return o.text;
34678
+ const content = o.content;
34679
+ if (typeof content === "string" && content.length > 0) return content;
34680
+ if (content != null && typeof content === "object" && !Array.isArray(content)) {
34681
+ const c = content;
34682
+ if (typeof c.text === "string" && c.text.length > 0) return c.text;
34683
+ }
34684
+ const update = o.update;
34685
+ if (typeof update === "string" && update.length > 0) return update;
34686
+ if (update != null && typeof update === "object" && !Array.isArray(update)) {
34687
+ const u = update;
34688
+ const innerContent = u.content;
34689
+ if (typeof innerContent === "string" && innerContent.length > 0) return innerContent;
34690
+ if (innerContent != null && typeof innerContent === "object" && !Array.isArray(innerContent)) {
34691
+ const ic = innerContent;
34692
+ if (typeof ic.text === "string" && ic.text.length > 0) return ic.text;
34693
+ }
34694
+ }
34695
+ return null;
34696
+ }
34697
+
34698
+ // src/agents/planning/planning-session-turn-buffer.ts
34699
+ var buffersByRunId = /* @__PURE__ */ new Map();
34700
+ function registerPlanningSessionTurn(runId) {
34701
+ buffersByRunId.set(runId, { messageBuffer: "" });
34702
+ }
34703
+ function clearPlanningSessionTurn(runId) {
34704
+ buffersByRunId.delete(runId);
34705
+ }
34706
+ function capturePlanningSessionUpdate(params) {
34707
+ const state = buffersByRunId.get(params.runId);
34708
+ if (!state) return false;
34709
+ const sessionUpdate = params.payload != null && typeof params.payload === "object" && !Array.isArray(params.payload) ? params.payload.sessionUpdate ?? params.payload.session_update : void 0;
34710
+ const bucket = resolveAgentTextStreamBucket({
34711
+ transcriptRowKind: params.updateKind,
34712
+ sessionUpdate: typeof sessionUpdate === "string" ? sessionUpdate : void 0
34713
+ });
34714
+ if (bucket !== "message") return false;
34715
+ const text = extractSessionUpdateMessageText(params.payload);
34716
+ if (text) state.messageBuffer += text;
34717
+ return true;
34718
+ }
34719
+ function getPlanningSessionTurnOutput(runId) {
34720
+ return buffersByRunId.get(runId)?.messageBuffer ?? "";
34721
+ }
34722
+ function consumePlanningSessionTurnOutput(runId) {
34723
+ const state = buffersByRunId.get(runId);
34724
+ buffersByRunId.delete(runId);
34725
+ return state?.messageBuffer ?? "";
34726
+ }
34727
+
34728
+ // src/agents/acp/hooks/bridge-on-session-update/forward-bridge-session-update.ts
34729
+ function forwardBridgeSessionUpdate(params) {
34730
+ const { runId, sessionId, updateKind, payload, send, nextTranscriptStreamSeq, log: log2 } = params;
34731
+ if (updateKind === "permission") {
34732
+ return;
34733
+ }
34734
+ if (capturePlanningSessionUpdate({ runId, updateKind, payload })) {
34735
+ return;
34736
+ }
34489
34737
  try {
34490
- params.send({
34491
- type: "session_title_update",
34492
- ...params.sessionId ? { sessionId: params.sessionId } : {},
34493
- runId: params.runId,
34494
- title
34738
+ const transcriptStreamSeq = nextTranscriptStreamSeq?.(runId);
34739
+ send({
34740
+ type: "session_update",
34741
+ ...sessionId ? { sessionId } : {},
34742
+ runId,
34743
+ kind: updateKind,
34744
+ payload,
34745
+ ...transcriptStreamSeq != null ? { transcriptStreamSeq } : {}
34495
34746
  });
34496
34747
  } catch (err) {
34497
- params.log(`[Bridge service] Session title update send failed: ${errorMessage(err)}`);
34748
+ log2(
34749
+ `[Bridge service] Session update send failed (${formatSessionUpdateKindForLog(updateKind)}): ${errorMessage(err)}`
34750
+ );
34498
34751
  }
34499
34752
  }
34500
34753
 
@@ -34509,8 +34762,7 @@ function createBridgeOnSessionUpdate(opts) {
34509
34762
  pathTracker.onRunIdChanged(runId);
34510
34763
  const send = getSendSessionUpdate();
34511
34764
  const sentFileChangePaths = /* @__PURE__ */ new Set();
34512
- const p = params;
34513
- const updateKind = p.sessionUpdate ?? p.session_update ?? p.type ?? "update";
34765
+ const { updateKind, isToolUpdate, isCompletedToolCallUpdate } = parseBridgeSessionUpdate(params);
34514
34766
  if (updateKind === "config_option_update") {
34515
34767
  return;
34516
34768
  }
@@ -34518,81 +34770,35 @@ function createBridgeOnSessionUpdate(opts) {
34518
34770
  sendSessionInfoTitleUpdate({ payload: params, runId, sessionId, send, log: log2 });
34519
34771
  return;
34520
34772
  }
34521
- const isCompletedToolCallUpdate = updateKind === "tool_call_update" && isCompletedToolStatus(p.status);
34522
- const toolName = p.toolCall?.name ?? p.tool_call?.name ?? "";
34523
- const isToolUpdate = updateKind === "tool_call" || updateKind === "tool_call_update" || typeof toolName === "string" && toolName.length > 0;
34524
- const toolPaths = isToolUpdate ? extractToolTargetDisplayPaths(params, sessionParentPath) : [];
34773
+ const toolPaths = resolveBridgeToolPaths(params, isToolUpdate, sessionParentPath);
34525
34774
  const toolKey = isToolUpdate ? pathTracker.resolveToolKey(params, updateKind) : "";
34526
- if (updateKind === "tool_call") {
34527
- pathTracker.resetToolSnapshots(toolKey);
34528
- }
34529
- if (isToolUpdate) {
34530
- accumulateToolPaths(toolKey, toolPaths, pathTracker.accumulatedPathsByToolKey);
34531
- }
34532
- if (isToolUpdate) {
34533
- const deliver = send && runId && sessionId ? (msg) => send(msg) : null;
34534
- pathTracker.handleToolCallLifecycle(
34535
- updateKind,
34536
- toolKey,
34537
- toolPaths,
34538
- p.status,
34539
- sessionParentPath,
34540
- sentFileChangePaths,
34541
- deliver,
34542
- runId ?? "",
34543
- sessionId ?? "",
34544
- log2
34545
- );
34546
- }
34547
- const diffs = extractAcpFileDiffsFromUpdate(params, sessionParentPath);
34548
- if (diffs.length > 0 && send && runId && sessionId) {
34549
- sendExtractedDiffsAsSessionFileChanges(
34550
- diffs,
34551
- send,
34552
- sessionParentPath,
34553
- sessionId,
34775
+ const status = params.status;
34776
+ handleBridgeToolSessionUpdate({
34777
+ params,
34778
+ updateKind,
34779
+ isToolUpdate,
34780
+ isCompletedToolCallUpdate,
34781
+ toolKey,
34782
+ toolPaths,
34783
+ status,
34784
+ sessionParentPath,
34785
+ pathTracker,
34786
+ sentFileChangePaths,
34787
+ send,
34788
+ runId: runId ?? "",
34789
+ sessionId: sessionId ?? "",
34790
+ log: log2
34791
+ });
34792
+ if (runId && send) {
34793
+ forwardBridgeSessionUpdate({
34554
34794
  runId,
34555
- sentFileChangePaths,
34556
- log2
34557
- );
34558
- } else if (diffs.length > 0) {
34559
- log2(
34560
- `[Bridge service] Agent file diff(s) not forwarded (${diffs.length}): session or run not wired to the bridge.`
34561
- );
34562
- }
34563
- if (isCompletedToolCallUpdate && send && runId && sessionId) {
34564
- const acc = pathTracker.accumulatedPathsByToolKey.get(toolKey);
34565
- const merged = [.../* @__PURE__ */ new Set([...acc ? [...acc] : [], ...toolPaths])];
34566
- sendGitHeadVsWorkspaceForToolPaths(
34567
- merged,
34568
- sentFileChangePaths,
34569
- send,
34570
- sessionParentPath,
34571
34795
  sessionId,
34572
- runId,
34573
- log2
34574
- );
34575
- pathTracker.accumulatedPathsByToolKey.delete(toolKey);
34576
- }
34577
- if (runId && send) {
34578
- if (updateKind === "permission") {
34579
- return;
34580
- }
34581
- try {
34582
- const transcriptStreamSeq = nextTranscriptStreamSeq?.(runId);
34583
- send({
34584
- type: "session_update",
34585
- ...sessionId ? { sessionId } : {},
34586
- runId,
34587
- kind: updateKind,
34588
- payload: params,
34589
- ...transcriptStreamSeq != null ? { transcriptStreamSeq } : {}
34590
- });
34591
- } catch (err) {
34592
- log2(
34593
- `[Bridge service] Session update send failed (${formatSessionUpdateKindForLog(updateKind)}): ${errorMessage(err)}`
34594
- );
34595
- }
34796
+ updateKind,
34797
+ payload: params,
34798
+ send,
34799
+ nextTranscriptStreamSeq,
34800
+ log: log2
34801
+ });
34596
34802
  }
34597
34803
  };
34598
34804
  }
@@ -34932,14 +35138,48 @@ async function disconnectAll(ctx) {
34932
35138
  ctx.pendingCancelRunIds.clear();
34933
35139
  }
34934
35140
 
34935
- // src/git/git-runtime.ts
34936
- var activeGitChildProcesses = /* @__PURE__ */ new Set();
34937
- function abortActiveGitChildProcesses() {
34938
- for (const child of activeGitChildProcesses) {
34939
- try {
34940
- child.kill("SIGTERM");
34941
- } catch {
34942
- }
35141
+ // src/agents/acp/manager/resolve-prompt-run-context.ts
35142
+ function resolvePromptRunContext(ctx, opts) {
35143
+ const { runId, mode, agentType, agentConfig, sessionId } = opts;
35144
+ const preferredForPrompt = agentType ?? ctx.backendFallbackAgentType ?? null;
35145
+ if (!runId) {
35146
+ ctx.log("[Agent] Prompt ignored: missing runId (cannot route session updates).");
35147
+ return null;
35148
+ }
35149
+ const acpAgentKey = computeAcpAgentKey(preferredForPrompt, mode, agentConfig ?? null);
35150
+ if (!acpAgentKey) {
35151
+ return null;
35152
+ }
35153
+ const activeAcpSessionAgentKey = computeAcpSessionAgentKey(sessionId, acpAgentKey);
35154
+ ctx.pendingCancelRunIds.delete(runId);
35155
+ ctx.promptRouting.registerRun({ sessionId, runId });
35156
+ ctx.runDispatch.set(runId, { acpSessionAgentKey: activeAcpSessionAgentKey });
35157
+ return {
35158
+ activeRunId: runId,
35159
+ activeAcpAgentKey: acpAgentKey,
35160
+ activeAcpSessionAgentKey,
35161
+ preferredForPrompt
35162
+ };
35163
+ }
35164
+
35165
+ // src/agents/acp/send-prompt-result-augment.ts
35166
+ function augmentPromptResultAuthFields(agentType, errorText) {
35167
+ const err = errorText ?? "";
35168
+ const at = agentType ?? null;
35169
+ const evaluated = Boolean(at && err.trim());
35170
+ const suggestsAuth = evaluated && at ? localAgentErrorSuggestsAuth(at, err) : false;
35171
+ if (!suggestsAuth || !agentType) return {};
35172
+ return { agentAuthRequired: true, agentType };
35173
+ }
35174
+
35175
+ // src/git/git-runtime.ts
35176
+ var activeGitChildProcesses = /* @__PURE__ */ new Set();
35177
+ function abortActiveGitChildProcesses() {
35178
+ for (const child of activeGitChildProcesses) {
35179
+ try {
35180
+ child.kill("SIGTERM");
35181
+ } catch {
35182
+ }
34943
35183
  }
34944
35184
  }
34945
35185
  var GitOperationAbortedError = class extends Error {
@@ -35387,104 +35627,136 @@ async function maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess(params) {
35387
35627
  }
35388
35628
  }
35389
35629
 
35390
- // src/agents/acp/send-prompt-result-augment.ts
35391
- function augmentPromptResultAuthFields(agentType, errorText) {
35392
- const err = errorText ?? "";
35393
- const at = agentType ?? null;
35394
- const evaluated = Boolean(at && err.trim());
35395
- const suggestsAuth = evaluated && at ? localAgentErrorSuggestsAuth(at, err) : false;
35396
- if (!suggestsAuth || !agentType) return {};
35397
- return { agentAuthRequired: true, agentType };
35630
+ // src/agents/planning/submit-planning-todos-for-turn.ts
35631
+ async function submitPlanningTodosForTurn(params) {
35632
+ const token = params.getCloudAccessToken();
35633
+ if (!token.trim()) return { ok: false, error: "Missing cloud access token" };
35634
+ const base = params.cloudApiBaseUrl.replace(/\/$/, "");
35635
+ const url2 = `${base}/internal/sessions/todos/submit`;
35636
+ const res = await fetch(url2, {
35637
+ method: "POST",
35638
+ headers: {
35639
+ Authorization: `Bearer ${token}`,
35640
+ "Content-Type": "application/json"
35641
+ },
35642
+ body: JSON.stringify({
35643
+ sessionId: params.sessionId,
35644
+ turnId: params.turnId,
35645
+ items: params.items
35646
+ })
35647
+ });
35648
+ if (!res.ok) {
35649
+ const body = await res.json().catch(() => null);
35650
+ return { ok: false, error: body?.error ?? `Planning todo submit failed (${res.status})` };
35651
+ }
35652
+ return { ok: true };
35398
35653
  }
35399
35654
 
35400
- // src/agents/acp/fetch-session-attachments.ts
35401
- function metaSaysEncrypted(meta) {
35402
- if (!meta) return false;
35403
- const e = meta.encrypted;
35404
- return e === true || e === "true" || e === 1;
35405
- }
35406
- function warnIfDecodedImageMagicUnexpected(buf, mimeType, idShort) {
35407
- const m = mimeType.toLowerCase();
35408
- if (!m.startsWith("image/") || buf.length < 4) return;
35409
- const b0 = buf[0];
35410
- const b1 = buf[1];
35411
- const b2 = buf[2];
35412
- const b3 = buf[3];
35413
- let looksOk = false;
35414
- if (m.includes("png") && b0 === 137 && b1 === 80 && b2 === 78 && b3 === 71) looksOk = true;
35415
- else if ((m.includes("jpeg") || m.includes("jpg")) && b0 === 255 && b1 === 216 && b2 === 255) looksOk = true;
35416
- else if (m.includes("gif") && b0 === 71 && b1 === 73 && b2 === 70) looksOk = true;
35417
- else if (m.includes("webp") && buf.length >= 12 && buf.subarray(0, 4).toString("ascii") === "RIFF" && buf.subarray(8, 12).toString("ascii") === "WEBP")
35418
- looksOk = true;
35419
- else if (!/(png|jpe?g|gif|webp)/i.test(m)) looksOk = true;
35420
- if (!looksOk) {
35421
- logDebug(
35422
- `[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")}`
35423
- );
35655
+ // src/agents/planning/maybe-submit-planning-todos-after-agent-success.ts
35656
+ async function maybeSubmitPlanningTodosAfterAgentSuccess(params) {
35657
+ const { sessionId, runId, resultSuccess, output, cloudApiBaseUrl, getCloudAccessToken, log: log2 } = params;
35658
+ const buffered = runId ? getPlanningSessionTurnOutput(runId) : "";
35659
+ const outputFromResult = typeof output === "string" ? output : "";
35660
+ const outputStr = buffered.trim() !== "" ? buffered : outputFromResult;
35661
+ if (!sessionId || !runId || !resultSuccess || outputStr.trim() === "") {
35662
+ return { submitted: false, suppressOutput: false };
35424
35663
  }
35425
- }
35426
- async function fetchSessionAttachmentPayloadsForAgent(params) {
35427
- const { attachments, sessionId, cloudApiBaseUrl, getCloudAccessToken, e2ee, log: log2 } = params;
35428
- const token = getCloudAccessToken();
35429
- if (!token) {
35430
- return { ok: false, error: "Missing cloud access token; cannot download attachments." };
35664
+ if (!cloudApiBaseUrl || !getCloudAccessToken) {
35665
+ return { submitted: false, suppressOutput: false };
35431
35666
  }
35432
- const wantedCount = attachments.filter((a) => typeof a.attachmentId === "string" && a.attachmentId.trim() !== "").length;
35433
- if (wantedCount === 0) {
35434
- return { ok: false, error: "No valid attachment ids in prompt." };
35667
+ const items = parsePlanningTodoAgentJson(outputStr);
35668
+ if (items.length === 0) {
35669
+ return { submitted: false, suppressOutput: false };
35435
35670
  }
35436
- const base = cloudApiBaseUrl.replace(/\/+$/, "");
35437
- const out = [];
35438
- for (const a of attachments) {
35439
- const id = typeof a.attachmentId === "string" ? a.attachmentId.trim() : "";
35440
- if (!id) continue;
35441
- const metaUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/meta`;
35442
- const blobUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/blob`;
35443
- const metaRes = await fetch(metaUrl, { headers: { Authorization: `Bearer ${token}` } });
35444
- if (!metaRes.ok) {
35445
- const t = await metaRes.text().catch(() => "");
35446
- log2(`[Agent] Attachment meta fetch failed ${metaRes.status}: ${t.slice(0, 200)}`);
35447
- return { ok: false, error: `Could not load attachment (${id.slice(0, 8)}\u2026): ${metaRes.status}` };
35448
- }
35449
- const meta = await metaRes.json().catch(() => null);
35450
- const mimeType = typeof meta?.mimeType === "string" && meta.mimeType.trim() ? meta.mimeType.trim() : a.mimeType;
35451
- const blobRes = await fetch(blobUrl, { headers: { Authorization: `Bearer ${token}` } });
35452
- if (!blobRes.ok) {
35453
- const t = await blobRes.text().catch(() => "");
35454
- log2(`[Agent] Attachment blob fetch failed ${blobRes.status}: ${t.slice(0, 200)}`);
35455
- return { ok: false, error: `Could not load attachment data (${id.slice(0, 8)}\u2026): ${blobRes.status}` };
35456
- }
35457
- const buf = Buffer.from(await blobRes.arrayBuffer());
35458
- let imageBytes;
35459
- const encrypted = metaSaysEncrypted(meta);
35460
- if (encrypted) {
35461
- if (!e2ee) {
35462
- return { ok: false, error: "Encrypted attachments require E2EE keys on this bridge." };
35463
- }
35464
- const k = typeof meta?.k === "string" ? meta.k : "";
35465
- const n = typeof meta?.n === "string" ? meta.n : "";
35466
- if (!k || !n) {
35467
- return { ok: false, error: "Invalid encrypted attachment metadata (missing key id or nonce)." };
35468
- }
35469
- const c = base64UrlEncode(buf);
35470
- imageBytes = e2ee.decryptEnvelopeToBuffer({ k, n, c });
35671
+ const result = await submitPlanningTodosForTurn({
35672
+ sessionId,
35673
+ turnId: runId,
35674
+ items,
35675
+ cloudApiBaseUrl,
35676
+ getCloudAccessToken
35677
+ });
35678
+ if (!result.ok) {
35679
+ log2(`[Agent] Planning todo submit failed: ${result.error}`);
35680
+ return { submitted: false, suppressOutput: false };
35681
+ }
35682
+ if (runId) consumePlanningSessionTurnOutput(runId);
35683
+ log2(`[Agent] Planning session: stored ${items.length} todo(s) via internal API`);
35684
+ return { submitted: true, suppressOutput: true };
35685
+ }
35686
+
35687
+ // src/agents/acp/prompts/finalize-and-send-prompt-result.ts
35688
+ async function finalizeAndSendPromptResult(params) {
35689
+ const {
35690
+ result,
35691
+ sessionId,
35692
+ runId,
35693
+ promptId,
35694
+ agentType,
35695
+ agentCwd,
35696
+ isPlanningSession,
35697
+ followUpCatalogPromptId,
35698
+ sessionChangeSummaryFilePaths,
35699
+ cloudApiBaseUrl,
35700
+ getCloudAccessToken,
35701
+ e2ee,
35702
+ sendResult,
35703
+ sendSessionUpdate,
35704
+ log: log2
35705
+ } = params;
35706
+ if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
35707
+ await collectTurnGitDiffFromPreTurnSnapshot({
35708
+ sessionId,
35709
+ runId,
35710
+ agentCwd,
35711
+ sendSessionUpdate,
35712
+ log: log2
35713
+ });
35714
+ }
35715
+ await maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess({
35716
+ sessionId,
35717
+ runId,
35718
+ resultSuccess: result.success === true,
35719
+ output: result.output,
35720
+ e2ee,
35721
+ cloudApiBaseUrl,
35722
+ getCloudAccessToken,
35723
+ followUpCatalogPromptId,
35724
+ sessionChangeSummaryFilePaths,
35725
+ log: log2
35726
+ });
35727
+ const planningTodosSubmit = await maybeSubmitPlanningTodosAfterAgentSuccess({
35728
+ sessionId,
35729
+ runId,
35730
+ resultSuccess: result.success === true,
35731
+ output: result.output,
35732
+ cloudApiBaseUrl,
35733
+ getCloudAccessToken,
35734
+ log: log2
35735
+ });
35736
+ const errStr = typeof result.error === "string" ? result.error : void 0;
35737
+ const resultStop = typeof result.stopReason === "string" ? result.stopReason.trim() : "";
35738
+ const cancelledByAgent = resultStop.toLowerCase() === "cancelled" || errStr != null && isUserEndedSessionTurnErrorText(errStr);
35739
+ const planningFallbackOutput = !planningTodosSubmit.suppressOutput && isPlanningSession && runId ? consumePlanningSessionTurnOutput(runId) : "";
35740
+ sendResult({
35741
+ type: "prompt_result",
35742
+ id: promptId,
35743
+ ...sessionId ? { sessionId } : {},
35744
+ ...runId ? { runId } : {},
35745
+ ...result,
35746
+ ...planningTodosSubmit.suppressOutput ? { output: void 0 } : planningFallbackOutput.trim() !== "" ? { output: planningFallbackOutput } : {},
35747
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
35748
+ ...augmentPromptResultAuthFields(agentType, errStr),
35749
+ ...!result.success && cancelledByAgent ? { stopReason: "cancelled" } : {}
35750
+ });
35751
+ if (!result.success) {
35752
+ if (cancelledByAgent) {
35753
+ log2("[Agent] Run ended after stop request (stopped by user).");
35471
35754
  } else {
35472
- imageBytes = buf;
35755
+ log2(
35756
+ `[Agent] Prompt did not run successfully on the agent (no successful start/completion): ${result.error ?? "Unknown error"}`
35757
+ );
35473
35758
  }
35474
- warnIfDecodedImageMagicUnexpected(imageBytes, mimeType, id.slice(0, 8));
35475
- logDebug(
35476
- `[Agent] Loaded prompt image ${id.slice(0, 8)}\u2026: ${imageBytes.length} bytes, mime=${mimeType}, ${encrypted ? "E2EE decrypted" : "plaintext from storage"}`
35477
- );
35478
- const dataBase64 = imageBytes.toString("base64");
35479
- out.push({ mimeType, dataBase64 });
35480
35759
  }
35481
- if (out.length !== wantedCount) {
35482
- return {
35483
- ok: false,
35484
- error: `Expected ${wantedCount} image attachment(s) but only loaded ${out.length} (check attachment ids and empty rows).`
35485
- };
35486
- }
35487
- return { ok: true, images: out };
35488
35760
  }
35489
35761
 
35490
35762
  // src/agents/acp/build-forked-session-agent-prompt.ts
@@ -35675,7 +35947,213 @@ function enrichIntegrationContentPromptForAgent(params) {
35675
35947
  return injectIntegrationContentAgentPromptNote(params.agentPromptText, params.sessionId);
35676
35948
  }
35677
35949
 
35678
- // src/agents/acp/send-prompt-to-agent.ts
35950
+ // src/agents/planning/enrich-planning-session-prompt-for-agent.ts
35951
+ async function enrichPlanningSessionPromptForAgent(params) {
35952
+ const meta = await fetchCloudSessionMeta({
35953
+ sessionId: params.sessionId,
35954
+ cloudApiBaseUrl: params.cloudApiBaseUrl,
35955
+ getCloudAccessToken: params.getCloudAccessToken
35956
+ });
35957
+ if (!meta.ok) return { promptText: params.promptText, isPlanningSession: false };
35958
+ const sessionKind = parseSessionKind(meta.data.sessionKind);
35959
+ if (sessionKind !== "planning") return { promptText: params.promptText, isPlanningSession: false };
35960
+ const todos = Array.isArray(meta.data.planningTodos) ? meta.data.planningTodos : [];
35961
+ if (params.isNewSession || todos.length === 0) {
35962
+ return {
35963
+ promptText: buildPlanningSessionAgentPrompt(params.promptText),
35964
+ isPlanningSession: true
35965
+ };
35966
+ }
35967
+ return {
35968
+ promptText: buildPlanningSessionFollowUpAgentPrompt(params.promptText, todos),
35969
+ isPlanningSession: true
35970
+ };
35971
+ }
35972
+
35973
+ // src/agents/acp/prompts/prepare-agent-prompt-text.ts
35974
+ async function prepareAgentPromptText(params) {
35975
+ const { promptText, sessionId, runId, isNewSession, cloudApiBaseUrl, getCloudAccessToken } = params;
35976
+ let agentPromptText = promptText;
35977
+ let isPlanningSession = false;
35978
+ if (sessionId && cloudApiBaseUrl && getCloudAccessToken) {
35979
+ agentPromptText = await enrichForkedSessionPromptForAgent({
35980
+ sessionId,
35981
+ promptText,
35982
+ cloudApiBaseUrl,
35983
+ getCloudAccessToken
35984
+ });
35985
+ const planningEnriched = await enrichPlanningSessionPromptForAgent({
35986
+ sessionId,
35987
+ promptText: agentPromptText,
35988
+ isNewSession,
35989
+ cloudApiBaseUrl,
35990
+ getCloudAccessToken
35991
+ });
35992
+ agentPromptText = planningEnriched.promptText;
35993
+ isPlanningSession = planningEnriched.isPlanningSession;
35994
+ }
35995
+ if (isPlanningSession && runId) {
35996
+ registerPlanningSessionTurn(runId);
35997
+ }
35998
+ if (sessionId) {
35999
+ agentPromptText = enrichIntegrationContentPromptForAgent({
36000
+ sessionId,
36001
+ originalPromptText: promptText,
36002
+ agentPromptText
36003
+ });
36004
+ }
36005
+ return { agentPromptText, isPlanningSession };
36006
+ }
36007
+
36008
+ // src/agents/acp/fetch-session-attachments.ts
36009
+ function metaSaysEncrypted(meta) {
36010
+ if (!meta) return false;
36011
+ const e = meta.encrypted;
36012
+ return e === true || e === "true" || e === 1;
36013
+ }
36014
+ function warnIfDecodedImageMagicUnexpected(buf, mimeType, idShort) {
36015
+ const m = mimeType.toLowerCase();
36016
+ if (!m.startsWith("image/") || buf.length < 4) return;
36017
+ const b0 = buf[0];
36018
+ const b1 = buf[1];
36019
+ const b2 = buf[2];
36020
+ const b3 = buf[3];
36021
+ let looksOk = false;
36022
+ if (m.includes("png") && b0 === 137 && b1 === 80 && b2 === 78 && b3 === 71) looksOk = true;
36023
+ else if ((m.includes("jpeg") || m.includes("jpg")) && b0 === 255 && b1 === 216 && b2 === 255) looksOk = true;
36024
+ else if (m.includes("gif") && b0 === 71 && b1 === 73 && b2 === 70) looksOk = true;
36025
+ else if (m.includes("webp") && buf.length >= 12 && buf.subarray(0, 4).toString("ascii") === "RIFF" && buf.subarray(8, 12).toString("ascii") === "WEBP")
36026
+ looksOk = true;
36027
+ else if (!/(png|jpe?g|gif|webp)/i.test(m)) looksOk = true;
36028
+ if (!looksOk) {
36029
+ logDebug(
36030
+ `[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")}`
36031
+ );
36032
+ }
36033
+ }
36034
+ async function fetchSessionAttachmentPayloadsForAgent(params) {
36035
+ const { attachments, sessionId, cloudApiBaseUrl, getCloudAccessToken, e2ee, log: log2 } = params;
36036
+ const token = getCloudAccessToken();
36037
+ if (!token) {
36038
+ return { ok: false, error: "Missing cloud access token; cannot download attachments." };
36039
+ }
36040
+ const wantedCount = attachments.filter((a) => typeof a.attachmentId === "string" && a.attachmentId.trim() !== "").length;
36041
+ if (wantedCount === 0) {
36042
+ return { ok: false, error: "No valid attachment ids in prompt." };
36043
+ }
36044
+ const base = cloudApiBaseUrl.replace(/\/+$/, "");
36045
+ const out = [];
36046
+ for (const a of attachments) {
36047
+ const id = typeof a.attachmentId === "string" ? a.attachmentId.trim() : "";
36048
+ if (!id) continue;
36049
+ const metaUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/meta`;
36050
+ const blobUrl = `${base}/internal/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/blob`;
36051
+ const metaRes = await fetch(metaUrl, { headers: { Authorization: `Bearer ${token}` } });
36052
+ if (!metaRes.ok) {
36053
+ const t = await metaRes.text().catch(() => "");
36054
+ log2(`[Agent] Attachment meta fetch failed ${metaRes.status}: ${t.slice(0, 200)}`);
36055
+ return { ok: false, error: `Could not load attachment (${id.slice(0, 8)}\u2026): ${metaRes.status}` };
36056
+ }
36057
+ const meta = await metaRes.json().catch(() => null);
36058
+ const mimeType = typeof meta?.mimeType === "string" && meta.mimeType.trim() ? meta.mimeType.trim() : a.mimeType;
36059
+ const blobRes = await fetch(blobUrl, { headers: { Authorization: `Bearer ${token}` } });
36060
+ if (!blobRes.ok) {
36061
+ const t = await blobRes.text().catch(() => "");
36062
+ log2(`[Agent] Attachment blob fetch failed ${blobRes.status}: ${t.slice(0, 200)}`);
36063
+ return { ok: false, error: `Could not load attachment data (${id.slice(0, 8)}\u2026): ${blobRes.status}` };
36064
+ }
36065
+ const buf = Buffer.from(await blobRes.arrayBuffer());
36066
+ let imageBytes;
36067
+ const encrypted = metaSaysEncrypted(meta);
36068
+ if (encrypted) {
36069
+ if (!e2ee) {
36070
+ return { ok: false, error: "Encrypted attachments require E2EE keys on this bridge." };
36071
+ }
36072
+ const k = typeof meta?.k === "string" ? meta.k : "";
36073
+ const n = typeof meta?.n === "string" ? meta.n : "";
36074
+ if (!k || !n) {
36075
+ return { ok: false, error: "Invalid encrypted attachment metadata (missing key id or nonce)." };
36076
+ }
36077
+ const c = base64UrlEncode(buf);
36078
+ imageBytes = e2ee.decryptEnvelopeToBuffer({ k, n, c });
36079
+ } else {
36080
+ imageBytes = buf;
36081
+ }
36082
+ warnIfDecodedImageMagicUnexpected(imageBytes, mimeType, id.slice(0, 8));
36083
+ logDebug(
36084
+ `[Agent] Loaded prompt image ${id.slice(0, 8)}\u2026: ${imageBytes.length} bytes, mime=${mimeType}, ${encrypted ? "E2EE decrypted" : "plaintext from storage"}`
36085
+ );
36086
+ const dataBase64 = imageBytes.toString("base64");
36087
+ out.push({ mimeType, dataBase64 });
36088
+ }
36089
+ if (out.length !== wantedCount) {
36090
+ return {
36091
+ ok: false,
36092
+ error: `Expected ${wantedCount} image attachment(s) but only loaded ${out.length} (check attachment ids and empty rows).`
36093
+ };
36094
+ }
36095
+ return { ok: true, images: out };
36096
+ }
36097
+
36098
+ // src/agents/acp/prompts/resolve-send-prompt-images.ts
36099
+ async function resolveSendPromptImages(params) {
36100
+ const {
36101
+ attachments,
36102
+ sessionId,
36103
+ promptId,
36104
+ runId,
36105
+ agentType,
36106
+ followUpCatalogPromptId,
36107
+ cloudApiBaseUrl,
36108
+ getCloudAccessToken,
36109
+ e2ee,
36110
+ log: log2
36111
+ } = params;
36112
+ if (!attachments || attachments.length === 0) {
36113
+ return { ok: true, sendOpts: {} };
36114
+ }
36115
+ if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
36116
+ return {
36117
+ ok: false,
36118
+ errorResult: {
36119
+ type: "prompt_result",
36120
+ id: promptId,
36121
+ ...sessionId ? { sessionId } : {},
36122
+ ...runId ? { runId } : {},
36123
+ success: false,
36124
+ error: "Prompt includes images but the bridge is not configured with a cloud API URL and token to fetch them.",
36125
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
36126
+ ...augmentPromptResultAuthFields(agentType, "missing cloud for images download")
36127
+ }
36128
+ };
36129
+ }
36130
+ const resolved = await fetchSessionAttachmentPayloadsForAgent({
36131
+ attachments,
36132
+ sessionId,
36133
+ cloudApiBaseUrl,
36134
+ getCloudAccessToken,
36135
+ e2ee,
36136
+ log: log2
36137
+ });
36138
+ if (!resolved.ok) {
36139
+ return {
36140
+ ok: false,
36141
+ errorResult: {
36142
+ type: "prompt_result",
36143
+ id: promptId,
36144
+ ...sessionId ? { sessionId } : {},
36145
+ ...runId ? { runId } : {},
36146
+ success: false,
36147
+ error: resolved.error,
36148
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
36149
+ ...augmentPromptResultAuthFields(agentType, resolved.error)
36150
+ }
36151
+ };
36152
+ }
36153
+ return { ok: true, sendOpts: { images: resolved.images } };
36154
+ }
36155
+
36156
+ // src/agents/acp/prompts/send-prompt-to-agent.ts
35679
36157
  async function sendPromptToAgent(options) {
35680
36158
  const {
35681
36159
  handle,
@@ -35693,107 +36171,54 @@ async function sendPromptToAgent(options) {
35693
36171
  cloudApiBaseUrl,
35694
36172
  getCloudAccessToken,
35695
36173
  e2ee,
35696
- attachments
36174
+ attachments,
36175
+ isNewSession = false
35697
36176
  } = options;
36177
+ let isPlanningSession = false;
35698
36178
  try {
35699
- let agentPromptText = promptText;
35700
- if (sessionId && cloudApiBaseUrl && getCloudAccessToken) {
35701
- agentPromptText = await enrichForkedSessionPromptForAgent({
35702
- sessionId,
35703
- promptText,
35704
- cloudApiBaseUrl,
35705
- getCloudAccessToken
35706
- });
35707
- }
35708
- if (sessionId) {
35709
- agentPromptText = enrichIntegrationContentPromptForAgent({
35710
- sessionId,
35711
- originalPromptText: promptText,
35712
- agentPromptText
35713
- });
35714
- }
35715
- let sendOpts = {};
35716
- if (attachments && attachments.length > 0) {
35717
- if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
35718
- sendResult({
35719
- type: "prompt_result",
35720
- id: promptId,
35721
- ...sessionId ? { sessionId } : {},
35722
- ...runId ? { runId } : {},
35723
- success: false,
35724
- error: "Prompt includes images but the bridge is not configured with a cloud API URL and token to fetch them.",
35725
- ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
35726
- ...augmentPromptResultAuthFields(agentType, "missing cloud for images download")
35727
- });
35728
- return;
35729
- }
35730
- const resolved = await fetchSessionAttachmentPayloadsForAgent({
35731
- attachments,
35732
- sessionId,
35733
- cloudApiBaseUrl,
35734
- getCloudAccessToken,
35735
- e2ee,
35736
- log: log2
35737
- });
35738
- if (!resolved.ok) {
35739
- sendResult({
35740
- type: "prompt_result",
35741
- id: promptId,
35742
- ...sessionId ? { sessionId } : {},
35743
- ...runId ? { runId } : {},
35744
- success: false,
35745
- error: resolved.error,
35746
- ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
35747
- ...augmentPromptResultAuthFields(agentType, resolved.error)
35748
- });
35749
- return;
35750
- }
35751
- sendOpts = { images: resolved.images };
35752
- }
35753
- const result = await handle.sendPrompt(agentPromptText, sendOpts);
35754
- if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
35755
- await collectTurnGitDiffFromPreTurnSnapshot({
35756
- sessionId,
35757
- runId,
35758
- agentCwd,
35759
- sendSessionUpdate,
35760
- log: log2
35761
- });
35762
- }
35763
- await maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess({
36179
+ const prepared = await prepareAgentPromptText({
36180
+ promptText,
35764
36181
  sessionId,
35765
36182
  runId,
35766
- resultSuccess: result.success === true,
35767
- output: result.output,
35768
- e2ee,
36183
+ isNewSession,
36184
+ cloudApiBaseUrl,
36185
+ getCloudAccessToken
36186
+ });
36187
+ isPlanningSession = prepared.isPlanningSession;
36188
+ const imagesResolved = await resolveSendPromptImages({
36189
+ attachments,
36190
+ sessionId,
36191
+ promptId,
36192
+ runId,
36193
+ agentType,
36194
+ followUpCatalogPromptId,
35769
36195
  cloudApiBaseUrl,
35770
36196
  getCloudAccessToken,
36197
+ e2ee,
36198
+ log: log2
36199
+ });
36200
+ if (!imagesResolved.ok) {
36201
+ sendResult(imagesResolved.errorResult);
36202
+ return;
36203
+ }
36204
+ const result = await handle.sendPrompt(prepared.agentPromptText, imagesResolved.sendOpts);
36205
+ await finalizeAndSendPromptResult({
36206
+ result,
36207
+ sessionId,
36208
+ runId,
36209
+ promptId,
36210
+ agentType,
36211
+ agentCwd,
36212
+ isPlanningSession,
35771
36213
  followUpCatalogPromptId,
35772
36214
  sessionChangeSummaryFilePaths,
36215
+ cloudApiBaseUrl,
36216
+ getCloudAccessToken,
36217
+ e2ee,
36218
+ sendResult,
36219
+ sendSessionUpdate,
35773
36220
  log: log2
35774
36221
  });
35775
- const errStr = typeof result.error === "string" ? result.error : void 0;
35776
- const resultStop = typeof result.stopReason === "string" ? result.stopReason.trim() : "";
35777
- const cancelledByAgent = resultStop.toLowerCase() === "cancelled" || errStr != null && isUserEndedSessionTurnErrorText(errStr);
35778
- sendResult({
35779
- type: "prompt_result",
35780
- id: promptId,
35781
- ...sessionId ? { sessionId } : {},
35782
- ...runId ? { runId } : {},
35783
- ...result,
35784
- ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
35785
- ...augmentPromptResultAuthFields(agentType, errStr),
35786
- ...!result.success && cancelledByAgent ? { stopReason: "cancelled" } : {}
35787
- });
35788
- if (!result.success) {
35789
- if (cancelledByAgent) {
35790
- log2("[Agent] Run ended after stop request (stopped by user).");
35791
- } else {
35792
- log2(
35793
- `[Agent] Prompt did not run successfully on the agent (no successful start/completion): ${result.error ?? "Unknown error"}`
35794
- );
35795
- }
35796
- }
35797
36222
  } catch (err) {
35798
36223
  const errMsg = err instanceof Error ? err.message : String(err);
35799
36224
  log2(`[Agent] Send failed: ${errMsg}`);
@@ -35807,6 +36232,8 @@ async function sendPromptToAgent(options) {
35807
36232
  ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
35808
36233
  ...augmentPromptResultAuthFields(agentType, errMsg)
35809
36234
  });
36235
+ } finally {
36236
+ if (runId && isPlanningSession) clearPlanningSessionTurn(runId);
35810
36237
  }
35811
36238
  }
35812
36239
 
@@ -35820,15 +36247,36 @@ function getAcpSessionAgentState(ctx, acpSessionAgentKey) {
35820
36247
  return state;
35821
36248
  }
35822
36249
 
35823
- // src/agents/acp/manager/handle-prompt.ts
35824
- function handlePrompt(ctx, opts) {
36250
+ // src/agents/acp/manager/handle-pending-prompt-cancel.ts
36251
+ async function handlePendingPromptCancel(params) {
36252
+ const { ctx, activeRunId, promptId, sessionId, handle, sendResult } = params;
36253
+ if (!ctx.pendingCancelRunIds.has(activeRunId)) {
36254
+ return false;
36255
+ }
36256
+ ctx.pendingCancelRunIds.delete(activeRunId);
36257
+ try {
36258
+ await handle.cancel?.();
36259
+ } catch {
36260
+ }
36261
+ sendResult({
36262
+ type: "prompt_result",
36263
+ id: promptId,
36264
+ ...sessionId ? { sessionId } : {},
36265
+ runId: activeRunId,
36266
+ success: false,
36267
+ error: "Stopped by user",
36268
+ stopReason: "cancelled"
36269
+ });
36270
+ return true;
36271
+ }
36272
+
36273
+ // src/agents/acp/manager/run-acp-prompt.ts
36274
+ async function runAcpPrompt(ctx, runCtx, opts) {
35825
36275
  const {
35826
36276
  promptText,
35827
36277
  promptId,
35828
36278
  sessionId,
35829
- runId,
35830
36279
  mode,
35831
- agentType,
35832
36280
  agentConfig,
35833
36281
  sessionParentPath,
35834
36282
  sendResult,
@@ -35838,109 +36286,99 @@ function handlePrompt(ctx, opts) {
35838
36286
  cloudApiBaseUrl,
35839
36287
  getCloudAccessToken,
35840
36288
  e2ee,
35841
- attachments
36289
+ attachments,
36290
+ isNewSession
35842
36291
  } = opts;
35843
- const preferredForPrompt = agentType ?? ctx.backendFallbackAgentType ?? null;
35844
- if (!runId) {
35845
- ctx.log("[Agent] Prompt ignored: missing runId (cannot route session updates).");
35846
- return;
35847
- }
35848
- const activeRunId = runId;
35849
- const acpAgentKey = computeAcpAgentKey(preferredForPrompt, mode, agentConfig ?? null);
35850
- if (!acpAgentKey) {
36292
+ const { activeRunId, activeAcpAgentKey, activeAcpSessionAgentKey, preferredForPrompt } = runCtx;
36293
+ const acpAgentState = getAcpSessionAgentState(ctx, activeAcpSessionAgentKey);
36294
+ const handle = await ensureAcpClient({
36295
+ state: acpAgentState,
36296
+ acpAgentKey: activeAcpAgentKey,
36297
+ preferredAgentType: preferredForPrompt,
36298
+ mode,
36299
+ agentConfig: agentConfig ?? null,
36300
+ sessionParentPath,
36301
+ resolveRouting: () => ctx.promptRouting.resolveRouting(activeAcpSessionAgentKey),
36302
+ cloudSessionId: sessionId,
36303
+ bridgeAccessPort: ctx.getBridgeAccessPort(),
36304
+ sendSessionUpdate,
36305
+ sendRequest: sendSessionUpdate,
36306
+ log: ctx.log,
36307
+ reportAgentCapabilities: ctx.reportAgentCapabilities
36308
+ });
36309
+ if (!handle) {
36310
+ const errMsg = acpAgentState.lastAcpStartError || "No agent configured. Register local agents on this bridge in the app.";
36311
+ const evaluated = Boolean(preferredForPrompt && errMsg.trim());
36312
+ const suggestsAuth = evaluated ? localAgentErrorSuggestsAuth(preferredForPrompt, errMsg) : false;
36313
+ const auth = suggestsAuth && preferredForPrompt ? { agentAuthRequired: true, agentType: preferredForPrompt } : {};
35851
36314
  sendResult({
35852
36315
  type: "prompt_result",
35853
36316
  id: promptId,
35854
36317
  ...sessionId ? { sessionId } : {},
35855
36318
  runId: activeRunId,
35856
36319
  success: false,
35857
- error: "No agent type: ensure the app sends agentType on prompts or agent_config for this bridge."
36320
+ error: errMsg,
36321
+ ...auth
35858
36322
  });
35859
36323
  return;
35860
36324
  }
35861
- const activeAcpAgentKey = acpAgentKey;
35862
- const activeAcpSessionAgentKey = computeAcpSessionAgentKey(sessionId, activeAcpAgentKey);
35863
- ctx.pendingCancelRunIds.delete(activeRunId);
35864
- ctx.promptRouting.registerRun({ sessionId, runId: activeRunId });
35865
- ctx.runDispatch.set(activeRunId, { acpSessionAgentKey: activeAcpSessionAgentKey });
35866
- async function run() {
35867
- const acpAgentState = getAcpSessionAgentState(ctx, activeAcpSessionAgentKey);
35868
- const handle = await ensureAcpClient({
35869
- state: acpAgentState,
35870
- acpAgentKey: activeAcpAgentKey,
35871
- preferredAgentType: preferredForPrompt,
35872
- mode,
35873
- agentConfig: agentConfig ?? null,
35874
- sessionParentPath,
35875
- resolveRouting: () => ctx.promptRouting.resolveRouting(activeAcpSessionAgentKey),
35876
- cloudSessionId: sessionId,
35877
- bridgeAccessPort: ctx.getBridgeAccessPort(),
36325
+ if (!ctx.promptRouting.isRegisteredRun(activeRunId)) {
36326
+ return;
36327
+ }
36328
+ const cancelled = await handlePendingPromptCancel({
36329
+ ctx,
36330
+ activeRunId,
36331
+ promptId,
36332
+ sessionId,
36333
+ handle,
36334
+ sendResult
36335
+ });
36336
+ if (cancelled) return;
36337
+ ctx.promptRouting.setStreamingRunId(activeAcpSessionAgentKey, activeRunId);
36338
+ try {
36339
+ await sendPromptToAgent({
36340
+ handle,
36341
+ promptText,
36342
+ promptId,
36343
+ sessionId,
36344
+ runId: activeRunId,
36345
+ agentType: preferredForPrompt,
36346
+ agentCwd: resolveSessionParentPathForAgentProcess(sessionParentPath),
36347
+ sendResult,
35878
36348
  sendSessionUpdate,
35879
- sendRequest: sendSessionUpdate,
35880
36349
  log: ctx.log,
35881
- reportAgentCapabilities: ctx.reportAgentCapabilities
36350
+ followUpCatalogPromptId,
36351
+ sessionChangeSummaryFilePaths,
36352
+ cloudApiBaseUrl,
36353
+ getCloudAccessToken,
36354
+ e2ee,
36355
+ attachments,
36356
+ isNewSession
35882
36357
  });
35883
- if (!handle) {
35884
- const errMsg = acpAgentState.lastAcpStartError || "No agent configured. Register local agents on this bridge in the app.";
35885
- const evaluated = Boolean(preferredForPrompt && errMsg.trim());
35886
- const suggestsAuth = evaluated ? localAgentErrorSuggestsAuth(preferredForPrompt, errMsg) : false;
35887
- const auth = suggestsAuth && preferredForPrompt ? { agentAuthRequired: true, agentType: preferredForPrompt } : {};
35888
- sendResult({
35889
- type: "prompt_result",
35890
- id: promptId,
35891
- ...sessionId ? { sessionId } : {},
35892
- runId: activeRunId,
35893
- success: false,
35894
- error: errMsg,
35895
- ...auth
35896
- });
35897
- return;
35898
- }
35899
- if (!ctx.promptRouting.isRegisteredRun(activeRunId)) {
35900
- return;
35901
- }
35902
- if (ctx.pendingCancelRunIds.has(activeRunId)) {
35903
- ctx.pendingCancelRunIds.delete(activeRunId);
35904
- try {
35905
- await handle.cancel?.();
35906
- } catch {
35907
- }
36358
+ } finally {
36359
+ ctx.promptRouting.clearStreamingRunId(activeAcpSessionAgentKey, activeRunId);
36360
+ }
36361
+ }
36362
+
36363
+ // src/agents/acp/manager/handle-prompt.ts
36364
+ function handlePrompt(ctx, opts) {
36365
+ const { promptId, sessionId, runId, mode, agentType, agentConfig, sendResult } = opts;
36366
+ const runCtx = resolvePromptRunContext(ctx, { runId, mode, agentType, agentConfig, sessionId });
36367
+ if (!runCtx) {
36368
+ if (runId) {
35908
36369
  sendResult({
35909
36370
  type: "prompt_result",
35910
36371
  id: promptId,
35911
36372
  ...sessionId ? { sessionId } : {},
35912
- runId: activeRunId,
36373
+ runId,
35913
36374
  success: false,
35914
- error: "Stopped by user",
35915
- stopReason: "cancelled"
35916
- });
35917
- return;
35918
- }
35919
- ctx.promptRouting.setStreamingRunId(activeAcpSessionAgentKey, activeRunId);
35920
- try {
35921
- await sendPromptToAgent({
35922
- handle,
35923
- promptText,
35924
- promptId,
35925
- sessionId,
35926
- runId: activeRunId,
35927
- agentType: preferredForPrompt,
35928
- agentCwd: resolveSessionParentPathForAgentProcess(sessionParentPath),
35929
- sendResult,
35930
- sendSessionUpdate,
35931
- log: ctx.log,
35932
- followUpCatalogPromptId,
35933
- sessionChangeSummaryFilePaths,
35934
- cloudApiBaseUrl,
35935
- getCloudAccessToken,
35936
- e2ee,
35937
- attachments
36375
+ error: "No agent type: ensure the app sends agentType on prompts or agent_config for this bridge."
35938
36376
  });
35939
- } finally {
35940
- ctx.promptRouting.clearStreamingRunId(activeAcpSessionAgentKey, activeRunId);
35941
36377
  }
36378
+ return;
35942
36379
  }
35943
- void run().finally(() => {
36380
+ const { activeRunId } = runCtx;
36381
+ void runAcpPrompt(ctx, runCtx, opts).finally(() => {
35944
36382
  ctx.promptRouting.unregisterRun(activeRunId);
35945
36383
  ctx.runDispatch.delete(activeRunId);
35946
36384
  ctx.pendingCancelRunIds.delete(activeRunId);
@@ -45406,7 +45844,8 @@ async function runPreambleAndPrompt(params) {
45406
45844
  cloudApiBaseUrl: deps.cloudApiBaseUrl,
45407
45845
  getCloudAccessToken: deps.getCloudAccessToken,
45408
45846
  e2ee: deps.e2ee,
45409
- ...attachments.length > 0 ? { attachments } : {}
45847
+ ...attachments.length > 0 ? { attachments } : {},
45848
+ isNewSession: msg.isNewSession === true
45410
45849
  });
45411
45850
  }
45412
45851