@buildautomaton/cli 0.1.76 → 0.1.78

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
@@ -23060,7 +23060,11 @@ var init_migrate_cli_sqlite = __esm({
23060
23060
  });
23061
23061
 
23062
23062
  // src/sqlite/cli-database.ts
23063
- var cliSqlite, safeCloseCliSqliteDatabase, closeAllCliSqliteConnections, openCliSqliteConnection, withCliSqliteSync, withCliSqlite, ensureCliSqliteInitialized;
23063
+ function closeAllCliSqliteConnections() {
23064
+ legacyImportDoneForPath = null;
23065
+ cliSqlite.closeAllConnections();
23066
+ }
23067
+ var legacyImportDoneForPath, cliSqlite, safeCloseCliSqliteDatabase, openCliSqliteConnection, withCliSqliteSync, withCliSqlite, ensureCliSqliteInitialized;
23064
23068
  var init_cli_database = __esm({
23065
23069
  "src/sqlite/cli-database.ts"() {
23066
23070
  "use strict";
@@ -23070,16 +23074,19 @@ var init_cli_database = __esm({
23070
23074
  init_import_cli_legacy_disk_data();
23071
23075
  init_migrate_cli_sqlite();
23072
23076
  init_sqlite_errors();
23077
+ legacyImportDoneForPath = null;
23073
23078
  cliSqlite = createSqliteDatabaseAccess({
23074
23079
  getPath: getCliSqlitePath,
23075
23080
  ensureParentDir: ensureCliSqliteParentDir,
23076
23081
  migrate: migrateCliSqlite,
23077
23082
  afterOpen: (db, options) => {
23083
+ const sqlitePath = getCliSqlitePath();
23084
+ if (legacyImportDoneForPath === sqlitePath) return;
23078
23085
  importCliSqliteLegacyDiskData(db, options?.logLegacyMigration);
23086
+ legacyImportDoneForPath = sqlitePath;
23079
23087
  }
23080
23088
  });
23081
23089
  safeCloseCliSqliteDatabase = cliSqlite.safeClose;
23082
- closeAllCliSqliteConnections = cliSqlite.closeAllConnections;
23083
23090
  openCliSqliteConnection = cliSqlite.openConnection;
23084
23091
  withCliSqliteSync = cliSqlite.withSync;
23085
23092
  withCliSqlite = cliSqlite.withAsync;
@@ -30606,7 +30613,7 @@ function installBridgeProcessResilience() {
30606
30613
  }
30607
30614
 
30608
30615
  // src/cli-version.ts
30609
- var CLI_VERSION = "0.1.76".length > 0 ? "0.1.76" : "0.0.0-dev";
30616
+ var CLI_VERSION = "0.1.78".length > 0 ? "0.1.78" : "0.0.0-dev";
30610
30617
 
30611
30618
  // src/connection/heartbeat/constants.ts
30612
30619
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -33224,9 +33231,12 @@ function cancelPendingCursorPermissionRequests(pendingRequests2, respond) {
33224
33231
  // src/agents/acp/clients/cursor/create-cursor-acp-handle.ts
33225
33232
  function createCursorAcpHandle(options) {
33226
33233
  let teardownStarted = false;
33234
+ let cancelFallback = null;
33227
33235
  async function disconnectGracefully() {
33228
33236
  if (teardownStarted) return;
33229
33237
  teardownStarted = true;
33238
+ if (cancelFallback) clearTimeout(cancelFallback);
33239
+ cancelFallback = null;
33230
33240
  cancelPendingCursorPermissionRequests(options.pendingRequests, options.wire.respond);
33231
33241
  try {
33232
33242
  await options.transport.cancelSession(options.sessionId);
@@ -33252,7 +33262,17 @@ function createCursorAcpHandle(options) {
33252
33262
  },
33253
33263
  async cancel() {
33254
33264
  cancelPendingCursorPermissionRequests(options.pendingRequests, options.wire.respond);
33255
- await options.transport.cancelSession(options.sessionId);
33265
+ try {
33266
+ await options.transport.cancelSession(options.sessionId);
33267
+ } catch {
33268
+ }
33269
+ if (cancelFallback) return;
33270
+ cancelFallback = setTimeout(() => {
33271
+ if (options.child.exitCode == null && options.child.signalCode == null) {
33272
+ void disconnectGracefully();
33273
+ }
33274
+ }, 5e3);
33275
+ cancelFallback.unref?.();
33256
33276
  },
33257
33277
  resolveRequest(requestId, result) {
33258
33278
  if (options.pendingRequests.has(requestId)) {
@@ -33807,13 +33827,15 @@ import { existsSync as existsSync4, statSync } from "node:fs";
33807
33827
  // src/git/get-git-repo-root-sync.ts
33808
33828
  import { execFileSync as execFileSync3 } from "node:child_process";
33809
33829
  import * as path27 from "node:path";
33830
+ var GIT_LOOKUP_TIMEOUT_MS = 2e3;
33810
33831
  function getGitRepoRootSync(startDir) {
33811
33832
  try {
33812
33833
  const out = execFileSync3("git", ["rev-parse", "--show-toplevel"], {
33813
33834
  cwd: path27.resolve(startDir),
33814
33835
  encoding: "utf8",
33815
33836
  stdio: ["ignore", "pipe", "ignore"],
33816
- maxBuffer: 1024 * 1024
33837
+ maxBuffer: 1024 * 1024,
33838
+ timeout: GIT_LOOKUP_TIMEOUT_MS
33817
33839
  }).trim();
33818
33840
  return out ? path27.resolve(out) : null;
33819
33841
  } catch {
@@ -33825,6 +33847,7 @@ function getGitRepoRootSync(startDir) {
33825
33847
  import { execFileSync as execFileSync4 } from "node:child_process";
33826
33848
  import { readFileSync as readFileSync4 } from "node:fs";
33827
33849
  import * as path28 from "node:path";
33850
+ var GIT_FILE_READ_TIMEOUT_MS = 2e3;
33828
33851
  function resolveWorkspaceFilePath(sessionParentPath, rawPath) {
33829
33852
  const trimmed2 = rawPath.trim();
33830
33853
  if (!trimmed2) return null;
@@ -33886,7 +33909,8 @@ function readGitHeadBlob(sessionParentPath, displayPath) {
33886
33909
  return execFileSync4("git", ["show", `HEAD:${displayPath}`], {
33887
33910
  cwd: execCwd,
33888
33911
  encoding: "utf8",
33889
- maxBuffer: 50 * 1024 * 1024
33912
+ maxBuffer: 50 * 1024 * 1024,
33913
+ timeout: GIT_FILE_READ_TIMEOUT_MS
33890
33914
  });
33891
33915
  } catch {
33892
33916
  return "";
@@ -33894,6 +33918,7 @@ function readGitHeadBlob(sessionParentPath, displayPath) {
33894
33918
  }
33895
33919
 
33896
33920
  // src/agents/acp/session-file-change-path-kind.ts
33921
+ var GIT_PATH_KIND_TIMEOUT_MS = 2e3;
33897
33922
  function gitHeadPathObjectType(sessionParentPath, displayPath) {
33898
33923
  if (!displayPath || displayPath.includes("..")) return null;
33899
33924
  const gitRoot = getGitRepoRootSync(sessionParentPath);
@@ -33901,7 +33926,8 @@ function gitHeadPathObjectType(sessionParentPath, displayPath) {
33901
33926
  try {
33902
33927
  return execFileSync5("git", ["cat-file", "-t", `HEAD:${displayPath}`], {
33903
33928
  cwd: gitRoot,
33904
- encoding: "utf8"
33929
+ encoding: "utf8",
33930
+ timeout: GIT_PATH_KIND_TIMEOUT_MS
33905
33931
  }).trim();
33906
33932
  } catch {
33907
33933
  return null;
@@ -35008,6 +35034,73 @@ function augmentPromptResultAuthFields(agentType, errorText) {
35008
35034
  return { agentAuthRequired: true, agentType };
35009
35035
  }
35010
35036
 
35037
+ // src/agents/planning/submit-planning-todos-for-turn.ts
35038
+ var PLANNING_TODO_SUBMIT_TIMEOUT_MS = 3e3;
35039
+ async function submitPlanningTodosForTurn(params) {
35040
+ const token = params.getCloudAccessToken();
35041
+ if (!token.trim()) return { ok: false, error: "Missing cloud access token" };
35042
+ const base = params.cloudApiBaseUrl.replace(/\/$/, "");
35043
+ const url2 = `${base}/internal/sessions/todos/submit`;
35044
+ let res;
35045
+ try {
35046
+ res = await fetch(url2, {
35047
+ method: "POST",
35048
+ signal: AbortSignal.timeout(PLANNING_TODO_SUBMIT_TIMEOUT_MS),
35049
+ headers: {
35050
+ Authorization: `Bearer ${token}`,
35051
+ "Content-Type": "application/json"
35052
+ },
35053
+ body: JSON.stringify({
35054
+ sessionId: params.sessionId,
35055
+ turnId: params.turnId,
35056
+ items: params.items
35057
+ })
35058
+ });
35059
+ } catch (error40) {
35060
+ return { ok: false, error: error40 instanceof Error ? error40.message : String(error40) };
35061
+ }
35062
+ if (!res.ok) {
35063
+ const body = await res.json().catch(() => null);
35064
+ return { ok: false, error: body?.error ?? `Planning todo submit failed (${res.status})` };
35065
+ }
35066
+ return { ok: true };
35067
+ }
35068
+
35069
+ // src/agents/planning/maybe-submit-planning-todos-after-agent-success.ts
35070
+ async function maybeSubmitPlanningTodosAfterAgentSuccess(params) {
35071
+ const { sessionId, runId, resultSuccess, output, cloudApiBaseUrl, getCloudAccessToken, log: log2 } = params;
35072
+ const buffered = runId ? getPlanningSessionTurnOutput(runId) : "";
35073
+ const outputFromResult = typeof output === "string" ? output : "";
35074
+ const outputStr = buffered.trim() !== "" ? buffered : outputFromResult;
35075
+ if (!sessionId || !runId || !resultSuccess || outputStr.trim() === "") {
35076
+ return { submitted: false, suppressOutput: false };
35077
+ }
35078
+ if (!cloudApiBaseUrl || !getCloudAccessToken) {
35079
+ return { submitted: false, suppressOutput: false };
35080
+ }
35081
+ const items = parsePlanningTodoAgentJson(outputStr);
35082
+ if (items.length === 0) {
35083
+ return { submitted: false, suppressOutput: false };
35084
+ }
35085
+ const result = await submitPlanningTodosForTurn({
35086
+ sessionId,
35087
+ turnId: runId,
35088
+ items,
35089
+ cloudApiBaseUrl,
35090
+ getCloudAccessToken
35091
+ });
35092
+ if (!result.ok) {
35093
+ log2(`[Agent] Planning todo submit failed: ${result.error}`);
35094
+ return { submitted: false, suppressOutput: false };
35095
+ }
35096
+ if (runId) consumePlanningSessionTurnOutput(runId);
35097
+ log2(`[Agent] Planning session: stored ${items.length} todo(s) via internal API`);
35098
+ return { submitted: true, suppressOutput: true };
35099
+ }
35100
+
35101
+ // src/agents/acp/prompts/report-post-turn-enrichment.ts
35102
+ init_yield_to_event_loop();
35103
+
35011
35104
  // src/git/git-runtime.ts
35012
35105
  init_cli_process_interrupt();
35013
35106
  var activeGitChildProcesses = /* @__PURE__ */ new Set();
@@ -35370,61 +35463,30 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
35370
35463
  });
35371
35464
  }
35372
35465
 
35373
- // src/agents/planning/submit-planning-todos-for-turn.ts
35374
- async function submitPlanningTodosForTurn(params) {
35375
- const token = params.getCloudAccessToken();
35376
- if (!token.trim()) return { ok: false, error: "Missing cloud access token" };
35377
- const base = params.cloudApiBaseUrl.replace(/\/$/, "");
35378
- const url2 = `${base}/internal/sessions/todos/submit`;
35379
- const res = await fetch(url2, {
35380
- method: "POST",
35381
- headers: {
35382
- Authorization: `Bearer ${token}`,
35383
- "Content-Type": "application/json"
35384
- },
35385
- body: JSON.stringify({
35386
- sessionId: params.sessionId,
35387
- turnId: params.turnId,
35388
- items: params.items
35389
- })
35390
- });
35391
- if (!res.ok) {
35392
- const body = await res.json().catch(() => null);
35393
- return { ok: false, error: body?.error ?? `Planning todo submit failed (${res.status})` };
35394
- }
35395
- return { ok: true };
35396
- }
35397
-
35398
- // src/agents/planning/maybe-submit-planning-todos-after-agent-success.ts
35399
- async function maybeSubmitPlanningTodosAfterAgentSuccess(params) {
35400
- const { sessionId, runId, resultSuccess, output, cloudApiBaseUrl, getCloudAccessToken, log: log2 } = params;
35401
- const buffered = runId ? getPlanningSessionTurnOutput(runId) : "";
35402
- const outputFromResult = typeof output === "string" ? output : "";
35403
- const outputStr = buffered.trim() !== "" ? buffered : outputFromResult;
35404
- if (!sessionId || !runId || !resultSuccess || outputStr.trim() === "") {
35405
- return { submitted: false, suppressOutput: false };
35406
- }
35407
- if (!cloudApiBaseUrl || !getCloudAccessToken) {
35408
- return { submitted: false, suppressOutput: false };
35409
- }
35410
- const items = parsePlanningTodoAgentJson(outputStr);
35411
- if (items.length === 0) {
35412
- return { submitted: false, suppressOutput: false };
35413
- }
35414
- const result = await submitPlanningTodosForTurn({
35415
- sessionId,
35416
- turnId: runId,
35417
- items,
35418
- cloudApiBaseUrl,
35419
- getCloudAccessToken
35466
+ // src/agents/acp/prompts/report-post-turn-enrichment.ts
35467
+ function reportPostTurnEnrichment(options) {
35468
+ void (async () => {
35469
+ const {
35470
+ sessionId,
35471
+ runId,
35472
+ agentCwd,
35473
+ result,
35474
+ sendSessionUpdate,
35475
+ log: log2
35476
+ } = options;
35477
+ await yieldToEventLoop();
35478
+ if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
35479
+ await collectTurnGitDiffFromPreTurnSnapshot({
35480
+ sessionId,
35481
+ runId,
35482
+ agentCwd,
35483
+ sendSessionUpdate,
35484
+ log: log2
35485
+ });
35486
+ }
35487
+ })().catch((error40) => {
35488
+ options.log(`[Agent] Post-turn enrichment failed: ${error40 instanceof Error ? error40.message : String(error40)}`);
35420
35489
  });
35421
- if (!result.ok) {
35422
- log2(`[Agent] Planning todo submit failed: ${result.error}`);
35423
- return { submitted: false, suppressOutput: false };
35424
- }
35425
- if (runId) consumePlanningSessionTurnOutput(runId);
35426
- log2(`[Agent] Planning session: stored ${items.length} todo(s) via internal API`);
35427
- return { submitted: true, suppressOutput: true };
35428
35490
  }
35429
35491
 
35430
35492
  // src/agents/acp/prompts/finalize-and-send-prompt-result.ts
@@ -35444,16 +35506,7 @@ async function finalizeAndSendPromptResult(params) {
35444
35506
  sendSessionUpdate,
35445
35507
  log: log2
35446
35508
  } = params;
35447
- if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
35448
- await collectTurnGitDiffFromPreTurnSnapshot({
35449
- sessionId,
35450
- runId,
35451
- agentCwd,
35452
- sendSessionUpdate,
35453
- log: log2
35454
- });
35455
- }
35456
- const planningTodosSubmit = await maybeSubmitPlanningTodosAfterAgentSuccess({
35509
+ const planningTodosSubmit = isPlanningSession ? await maybeSubmitPlanningTodosAfterAgentSuccess({
35457
35510
  sessionId,
35458
35511
  runId,
35459
35512
  resultSuccess: result.success === true,
@@ -35461,11 +35514,11 @@ async function finalizeAndSendPromptResult(params) {
35461
35514
  cloudApiBaseUrl,
35462
35515
  getCloudAccessToken,
35463
35516
  log: log2
35464
- });
35517
+ }) : { suppressOutput: false };
35518
+ const planningFallbackOutput = !planningTodosSubmit.suppressOutput && isPlanningSession && runId ? consumePlanningSessionTurnOutput(runId) : "";
35465
35519
  const errStr = typeof result.error === "string" ? result.error : void 0;
35466
35520
  const resultStop = typeof result.stopReason === "string" ? result.stopReason.trim() : "";
35467
35521
  const cancelledByAgent = resultStop.toLowerCase() === "cancelled" || errStr != null && isUserEndedSessionTurnErrorText(errStr);
35468
- const planningFallbackOutput = !planningTodosSubmit.suppressOutput && isPlanningSession && runId ? consumePlanningSessionTurnOutput(runId) : "";
35469
35522
  sendResult({
35470
35523
  type: "prompt_result",
35471
35524
  id: promptId,
@@ -35486,6 +35539,14 @@ async function finalizeAndSendPromptResult(params) {
35486
35539
  );
35487
35540
  }
35488
35541
  }
35542
+ void reportPostTurnEnrichment({
35543
+ sessionId,
35544
+ runId,
35545
+ agentCwd,
35546
+ result,
35547
+ sendSessionUpdate,
35548
+ log: log2
35549
+ });
35489
35550
  }
35490
35551
 
35491
35552
  // src/agents/acp/build-forked-session-agent-prompt.ts
@@ -35976,6 +36037,7 @@ async function resolveSendPromptImages(params) {
35976
36037
  }
35977
36038
 
35978
36039
  // src/agents/acp/prompts/send-prompt-to-agent.ts
36040
+ var SLOW_ACP_PROMPT_MS = 5e3;
35979
36041
  async function sendPromptToAgent(options) {
35980
36042
  const {
35981
36043
  handle,
@@ -36022,7 +36084,12 @@ async function sendPromptToAgent(options) {
36022
36084
  sendResult(imagesResolved.errorResult);
36023
36085
  return;
36024
36086
  }
36087
+ const promptStartedAt = Date.now();
36025
36088
  const result = await handle.sendPrompt(prepared.agentPromptText, imagesResolved.sendOpts);
36089
+ const promptDurationMs = Date.now() - promptStartedAt;
36090
+ if (promptDurationMs >= SLOW_ACP_PROMPT_MS) {
36091
+ log2(`[Agent] ACP session/prompt completed after ${promptDurationMs}ms.`);
36092
+ }
36026
36093
  await finalizeAndSendPromptResult({
36027
36094
  result,
36028
36095
  sessionId,
@@ -36034,7 +36101,6 @@ async function sendPromptToAgent(options) {
36034
36101
  followUpCatalogPromptId,
36035
36102
  cloudApiBaseUrl,
36036
36103
  getCloudAccessToken,
36037
- e2ee,
36038
36104
  sendResult,
36039
36105
  sendSessionUpdate,
36040
36106
  log: log2
@@ -36057,16 +36123,6 @@ async function sendPromptToAgent(options) {
36057
36123
  }
36058
36124
  }
36059
36125
 
36060
- // src/agents/acp/manager/get-acp-agent-state.ts
36061
- function getAcpSessionAgentState(ctx, acpSessionAgentKey) {
36062
- let state = ctx.acpAgents.get(acpSessionAgentKey);
36063
- if (!state) {
36064
- state = createEmptyAcpClientState();
36065
- ctx.acpAgents.set(acpSessionAgentKey, state);
36066
- }
36067
- return state;
36068
- }
36069
-
36070
36126
  // src/agents/acp/manager/handle-pending-prompt-cancel.ts
36071
36127
  async function handlePendingPromptCancel(params) {
36072
36128
  const { ctx, activeRunId, promptId, sessionId, handle, sendResult } = params;
@@ -36090,94 +36146,104 @@ async function handlePendingPromptCancel(params) {
36090
36146
  return true;
36091
36147
  }
36092
36148
 
36093
- // src/agents/acp/manager/run-acp-prompt.ts
36094
- async function runAcpPrompt(ctx, runCtx, opts) {
36095
- const {
36096
- promptText,
36097
- promptId,
36098
- sessionId,
36099
- mode,
36100
- agentConfig,
36101
- sessionParentPath,
36102
- sendResult,
36103
- sendSessionUpdate,
36104
- followUpCatalogPromptId,
36105
- cloudApiBaseUrl,
36106
- getCloudAccessToken,
36107
- e2ee,
36108
- attachments,
36109
- isNewSession
36110
- } = opts;
36111
- const { activeRunId, activeAcpAgentKey, activeAcpSessionAgentKey, preferredForPrompt } = runCtx;
36112
- const acpAgentState = getAcpSessionAgentState(ctx, activeAcpSessionAgentKey);
36113
- const handle = await ensureAcpClient({
36114
- state: acpAgentState,
36115
- acpAgentKey: activeAcpAgentKey,
36116
- preferredAgentType: preferredForPrompt,
36117
- mode,
36118
- agentConfig: agentConfig ?? null,
36119
- sessionParentPath,
36120
- resolveRouting: () => ctx.promptRouting.resolveRouting(activeAcpSessionAgentKey),
36121
- cloudSessionId: sessionId,
36122
- bridgeAccessPort: ctx.getBridgeAccessPort(),
36123
- sendSessionUpdate,
36124
- sendRequest: sendSessionUpdate,
36125
- log: ctx.log,
36126
- reportAgentCapabilities: ctx.reportAgentCapabilities
36127
- });
36128
- if (!handle) {
36129
- const errMsg = acpAgentState.lastAcpStartError || "No agent configured. Register local agents on this bridge in the app.";
36130
- const evaluated = Boolean(preferredForPrompt && errMsg.trim());
36131
- const suggestsAuth = evaluated ? localAgentErrorSuggestsAuth(preferredForPrompt, errMsg) : false;
36132
- const auth = suggestsAuth && preferredForPrompt ? { agentAuthRequired: true, agentType: preferredForPrompt } : {};
36133
- sendResult({
36149
+ // src/agents/acp/manager/dispatch-acp-prompt.ts
36150
+ async function dispatchAcpPrompt(ctx, runCtx, opts, handle) {
36151
+ const { activeRunId, activeAcpSessionAgentKey, preferredForPrompt } = runCtx;
36152
+ if (!ctx.promptRouting.isRegisteredRun(activeRunId)) {
36153
+ opts.sendResult({
36134
36154
  type: "prompt_result",
36135
- id: promptId,
36136
- ...sessionId ? { sessionId } : {},
36155
+ id: opts.promptId,
36156
+ ...opts.sessionId ? { sessionId: opts.sessionId } : {},
36137
36157
  runId: activeRunId,
36138
36158
  success: false,
36139
- error: errMsg,
36140
- ...auth
36159
+ error: "Prompt dispatch was superseded before the agent started."
36141
36160
  });
36142
36161
  return;
36143
36162
  }
36144
- if (!ctx.promptRouting.isRegisteredRun(activeRunId)) {
36145
- return;
36146
- }
36147
36163
  const cancelled = await handlePendingPromptCancel({
36148
36164
  ctx,
36149
36165
  activeRunId,
36150
- promptId,
36151
- sessionId,
36166
+ promptId: opts.promptId,
36167
+ sessionId: opts.sessionId,
36152
36168
  handle,
36153
- sendResult
36169
+ sendResult: opts.sendResult
36154
36170
  });
36155
36171
  if (cancelled) return;
36156
36172
  ctx.promptRouting.setStreamingRunId(activeAcpSessionAgentKey, activeRunId);
36157
36173
  try {
36158
36174
  await sendPromptToAgent({
36159
36175
  handle,
36160
- promptText,
36161
- promptId,
36162
- sessionId,
36176
+ promptText: opts.promptText,
36177
+ promptId: opts.promptId,
36178
+ sessionId: opts.sessionId,
36163
36179
  runId: activeRunId,
36164
36180
  agentType: preferredForPrompt,
36165
- agentCwd: resolveSessionParentPathForAgentProcess(sessionParentPath),
36166
- sendResult,
36167
- sendSessionUpdate,
36181
+ agentCwd: resolveSessionParentPathForAgentProcess(opts.sessionParentPath),
36182
+ sendResult: opts.sendResult,
36183
+ sendSessionUpdate: opts.sendSessionUpdate,
36168
36184
  log: ctx.log,
36169
- followUpCatalogPromptId,
36170
- cloudApiBaseUrl,
36171
- getCloudAccessToken,
36172
- e2ee,
36173
- attachments,
36174
- isNewSession
36185
+ followUpCatalogPromptId: opts.followUpCatalogPromptId,
36186
+ cloudApiBaseUrl: opts.cloudApiBaseUrl,
36187
+ getCloudAccessToken: opts.getCloudAccessToken,
36188
+ e2ee: opts.e2ee,
36189
+ attachments: opts.attachments,
36190
+ isNewSession: opts.isNewSession
36175
36191
  });
36176
36192
  } finally {
36177
36193
  ctx.promptRouting.clearStreamingRunId(activeAcpSessionAgentKey, activeRunId);
36178
36194
  }
36179
36195
  }
36180
36196
 
36197
+ // src/agents/acp/manager/get-acp-agent-state.ts
36198
+ function getAcpSessionAgentState(ctx, acpSessionAgentKey) {
36199
+ let state = ctx.acpAgents.get(acpSessionAgentKey);
36200
+ if (!state) {
36201
+ state = createEmptyAcpClientState();
36202
+ ctx.acpAgents.set(acpSessionAgentKey, state);
36203
+ }
36204
+ return state;
36205
+ }
36206
+
36207
+ // src/agents/acp/manager/ensure-acp-prompt-client.ts
36208
+ async function ensureAcpPromptClient(ctx, runCtx, opts) {
36209
+ const state = getAcpSessionAgentState(ctx, runCtx.activeAcpSessionAgentKey);
36210
+ const handle = await ensureAcpClient({
36211
+ state,
36212
+ acpAgentKey: runCtx.activeAcpAgentKey,
36213
+ preferredAgentType: runCtx.preferredForPrompt,
36214
+ mode: opts.mode,
36215
+ agentConfig: opts.agentConfig ?? null,
36216
+ sessionParentPath: opts.sessionParentPath,
36217
+ resolveRouting: () => ctx.promptRouting.resolveRouting(runCtx.activeAcpSessionAgentKey),
36218
+ cloudSessionId: opts.sessionId,
36219
+ bridgeAccessPort: ctx.getBridgeAccessPort(),
36220
+ sendSessionUpdate: opts.sendSessionUpdate,
36221
+ sendRequest: opts.sendSessionUpdate,
36222
+ log: ctx.log,
36223
+ reportAgentCapabilities: ctx.reportAgentCapabilities
36224
+ });
36225
+ if (handle) return handle;
36226
+ const error40 = state.lastAcpStartError || "No agent configured. Register local agents on this bridge in the app.";
36227
+ const agentType = runCtx.preferredForPrompt;
36228
+ const requiresAuth = Boolean(agentType && localAgentErrorSuggestsAuth(agentType, error40));
36229
+ opts.sendResult({
36230
+ type: "prompt_result",
36231
+ id: opts.promptId,
36232
+ ...opts.sessionId ? { sessionId: opts.sessionId } : {},
36233
+ runId: runCtx.activeRunId,
36234
+ success: false,
36235
+ error: error40,
36236
+ ...requiresAuth && agentType ? { agentAuthRequired: true, agentType } : {}
36237
+ });
36238
+ return null;
36239
+ }
36240
+
36241
+ // src/agents/acp/manager/run-acp-prompt.ts
36242
+ async function runAcpPrompt(ctx, runCtx, opts) {
36243
+ const handle = await ensureAcpPromptClient(ctx, runCtx, opts);
36244
+ if (handle) await dispatchAcpPrompt(ctx, runCtx, opts, handle);
36245
+ }
36246
+
36181
36247
  // src/agents/acp/manager/handle-prompt.ts
36182
36248
  function handlePrompt(ctx, opts) {
36183
36249
  const { promptId, sessionId, runId, mode, agentType, agentConfig, sendResult } = opts;
@@ -46439,9 +46505,13 @@ import * as fs49 from "node:fs";
46439
46505
  import * as path70 from "node:path";
46440
46506
 
46441
46507
  // src/git/snapshot/git.ts
46508
+ var SNAPSHOT_GIT_TIMEOUT_MS = 5e3;
46442
46509
  async function gitStashCreate(repoRoot, log2) {
46443
46510
  try {
46444
- const { stdout } = await execGitFile(["stash", "create"], { cwd: repoRoot });
46511
+ const { stdout } = await execGitFile(["stash", "create"], {
46512
+ cwd: repoRoot,
46513
+ timeout: SNAPSHOT_GIT_TIMEOUT_MS
46514
+ });
46445
46515
  return stdout.trim();
46446
46516
  } catch (e) {
46447
46517
  log2(
@@ -46452,7 +46522,10 @@ async function gitStashCreate(repoRoot, log2) {
46452
46522
  }
46453
46523
  async function gitUntrackedPaths(repoRoot, log2) {
46454
46524
  try {
46455
- const { stdout } = await execGitFile(["ls-files", "--others", "--exclude-standard"], { cwd: repoRoot });
46525
+ const { stdout } = await execGitFile(["ls-files", "--others", "--exclude-standard"], {
46526
+ cwd: repoRoot,
46527
+ timeout: SNAPSHOT_GIT_TIMEOUT_MS
46528
+ });
46456
46529
  return stdout.split("\n").map((l) => l.trim()).filter(Boolean);
46457
46530
  } catch (e) {
46458
46531
  log2(
@@ -48878,9 +48951,6 @@ function setRunIdQueueKey(runId, queueKey) {
48878
48951
  runIdToQueueKey.set(runId, queueKey);
48879
48952
  locallyDispatchedRunIds.add(runId);
48880
48953
  }
48881
- function isLocallyDispatchedRun(runId) {
48882
- return locallyDispatchedRunIds.has(runId);
48883
- }
48884
48954
  function deleteRunIdQueueKey(runId) {
48885
48955
  const queueKey = runIdToQueueKey.get(runId);
48886
48956
  runIdToQueueKey.delete(runId);
@@ -48907,11 +48977,39 @@ async function dispatchStartedPromptQueueRuns(entries, started, deps) {
48907
48977
 
48908
48978
  // src/prompt-turn-queue/runner/handle-prompt-queue-cancellations.ts
48909
48979
  init_yield_to_event_loop();
48980
+
48981
+ // src/prompt-turn-queue/client-report.ts
48982
+ function sendPromptQueueClientReport(ws, queues) {
48983
+ if (!ws) return false;
48984
+ const wireQueues = {};
48985
+ for (const [queueKey, rows] of Object.entries(queues)) {
48986
+ wireQueues[queueKey] = rows.map((r) => ({ turnId: r.turnId, clientState: r.cliState }));
48987
+ }
48988
+ sendWsMessage(ws, { type: "prompt_queue_client_report", queues: wireQueues });
48989
+ return true;
48990
+ }
48991
+
48992
+ // src/prompt-turn-queue/runner/finalize-prompt-turn-on-bridge.ts
48993
+ async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
48994
+ if (!runId) return false;
48995
+ const queueKey = deleteRunIdQueueKey(runId);
48996
+ if (!queueKey) return false;
48997
+ const f = await readPersistedQueue(queueKey);
48998
+ if (!f) return false;
48999
+ const t = f.turns.find((x) => x.turnId === runId);
49000
+ if (!t) return false;
49001
+ t.lastCliState = opts?.terminalCliState ?? (success2 ? "stopped" : "failed");
49002
+ await writePersistedQueue(f);
49003
+ sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, cliState: t.lastCliState }] });
49004
+ return true;
49005
+ }
49006
+
49007
+ // src/prompt-turn-queue/runner/handle-prompt-queue-cancellations.ts
48910
49008
  async function handlePromptQueueCancellations(entries, deps) {
48911
49009
  for (const [queueKey] of entries) {
48912
49010
  const file2 = await readPersistedQueue(queueKey);
48913
49011
  const turn = file2?.turns.find((row) => row.bridgeServerState === "cancel_requested" && row.lastCliState === "running");
48914
- if (!turn || getRunIdQueueKey(turn.turnId) !== queueKey || !isLocallyDispatchedRun(turn.turnId)) {
49012
+ if (!turn || getRunIdQueueKey(turn.turnId) !== queueKey) {
48915
49013
  await yieldToEventLoop();
48916
49014
  continue;
48917
49015
  }
@@ -48920,7 +49018,8 @@ async function handlePromptQueueCancellations(entries, deps) {
48920
49018
  await yieldToEventLoop();
48921
49019
  continue;
48922
49020
  }
48923
- deps.log(`[Queue] cancel_requested for ${turn.turnId.slice(0, 8)}\u2026 but no local ACP run remains.`);
49021
+ deps.log(`[Queue] No local ACP run remains for ${turn.turnId.slice(0, 8)}\u2026; reconciling cancelled turn.`);
49022
+ await finalizePromptTurnOnBridge(deps.getWs, turn.turnId, false, { terminalCliState: "cancelled" });
48924
49023
  await yieldToEventLoop();
48925
49024
  }
48926
49025
  }
@@ -48948,17 +49047,6 @@ function promptQueueSnapshotEntries(queues) {
48948
49047
  // src/prompt-turn-queue/runner/start-prompt-queue-runs.ts
48949
49048
  init_yield_to_event_loop();
48950
49049
 
48951
- // src/prompt-turn-queue/client-report.ts
48952
- function sendPromptQueueClientReport(ws, queues) {
48953
- if (!ws) return false;
48954
- const wireQueues = {};
48955
- for (const [queueKey, rows] of Object.entries(queues)) {
48956
- wireQueues[queueKey] = rows.map((r) => ({ turnId: r.turnId, clientState: r.cliState }));
48957
- }
48958
- sendWsMessage(ws, { type: "prompt_queue_client_report", queues: wireQueues });
48959
- return true;
48960
- }
48961
-
48962
49050
  // src/prompt-turn-queue/runner/queue-selection.ts
48963
49051
  function pickNextRunnableTurn(turns) {
48964
49052
  for (const t of turns) {
@@ -49122,29 +49210,20 @@ async function startPromptQueueRuns(entries, deps) {
49122
49210
  }
49123
49211
 
49124
49212
  // src/prompt-turn-queue/runner/apply-prompt-queue-state-from-server.ts
49213
+ var SLOW_QUEUE_SYNC_MS = 2e3;
49125
49214
  async function applyPromptQueueStateFromServer(msg, deps) {
49126
49215
  const raw = msg.queues;
49127
49216
  if (!raw || typeof raw !== "object") return;
49217
+ const startedAt = Date.now();
49128
49218
  const entries = promptQueueSnapshotEntries(raw);
49129
49219
  await persistPromptQueueSnapshot(entries);
49130
49220
  await handlePromptQueueCancellations(entries, deps);
49131
49221
  const started = await startPromptQueueRuns(entries, deps);
49132
49222
  await dispatchStartedPromptQueueRuns(entries, started, deps);
49133
- }
49134
-
49135
- // src/prompt-turn-queue/runner/finalize-prompt-turn-on-bridge.ts
49136
- async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
49137
- if (!runId) return false;
49138
- const queueKey = deleteRunIdQueueKey(runId);
49139
- if (!queueKey) return false;
49140
- const f = await readPersistedQueue(queueKey);
49141
- if (!f) return false;
49142
- const t = f.turns.find((x) => x.turnId === runId);
49143
- if (!t) return false;
49144
- t.lastCliState = opts?.terminalCliState ?? (success2 ? "stopped" : "failed");
49145
- await writePersistedQueue(f);
49146
- sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, cliState: t.lastCliState }] });
49147
- return true;
49223
+ const durationMs = Date.now() - startedAt;
49224
+ if (durationMs >= SLOW_QUEUE_SYNC_MS) {
49225
+ deps.log(`[Queue] Queue state sync and dispatch took ${durationMs}ms.`);
49226
+ }
49148
49227
  }
49149
49228
 
49150
49229
  // src/agents/acp/from-bridge/bridge-prompt-wiring.ts
@@ -49221,59 +49300,93 @@ function parseWorktreeBaseBranches(msg) {
49221
49300
  return Object.keys(out).length > 0 ? out : void 0;
49222
49301
  }
49223
49302
 
49224
- // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
49303
+ // src/agents/acp/from-bridge/parse-follow-up-prompt-fields.ts
49304
+ function parseFollowUpFieldsFromPromptMessage(msg) {
49305
+ const value = msg.followUpCatalogPromptId;
49306
+ return {
49307
+ followUpCatalogPromptId: typeof value === "string" && value.trim() !== "" ? value.trim() : null
49308
+ };
49309
+ }
49310
+
49311
+ // src/agents/acp/from-bridge/prepare-bridge-prompt-git-state.ts
49225
49312
  import { execFile as execFile8 } from "node:child_process";
49226
49313
  import { promisify as promisify9 } from "node:util";
49314
+
49315
+ // src/agents/acp/from-bridge/git-state-preparation-timeout.ts
49316
+ async function awaitGitStatePreparation(options) {
49317
+ const { label, timeoutMs, work, fallback, log: log2 } = options;
49318
+ let timer;
49319
+ try {
49320
+ return await Promise.race([
49321
+ work,
49322
+ new Promise((resolve35) => {
49323
+ timer = setTimeout(() => {
49324
+ log2(`[Agent] ${label} exceeded ${timeoutMs}ms; continuing without it.`);
49325
+ resolve35(fallback);
49326
+ }, timeoutMs);
49327
+ timer.unref?.();
49328
+ })
49329
+ ]);
49330
+ } finally {
49331
+ if (timer) clearTimeout(timer);
49332
+ }
49333
+ }
49334
+
49335
+ // src/agents/acp/from-bridge/prepare-bridge-prompt-git-state.ts
49227
49336
  var execFileAsync7 = promisify9(execFile8);
49337
+ var GIT_STATE_DISCOVERY_TIMEOUT_MS = 2e3;
49228
49338
  async function readGitBranch(cwd) {
49229
49339
  try {
49230
- const { stdout } = await execFileAsync7("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
49231
- const b = stdout.trim();
49232
- return b || null;
49340
+ const { stdout } = await execFileAsync7("git", ["branch", "--show-current"], {
49341
+ cwd,
49342
+ maxBuffer: 64 * 1024,
49343
+ timeout: GIT_STATE_DISCOVERY_TIMEOUT_MS
49344
+ });
49345
+ return stdout.trim() || null;
49233
49346
  } catch {
49234
49347
  return null;
49235
49348
  }
49236
49349
  }
49237
- async function runBridgePromptPreamble(params) {
49350
+ async function prepareBridgePromptGitState(params) {
49238
49351
  const { getWs, log: log2, sessionWorktreeManager, sessionId, runId, effectiveCwd } = params;
49239
- const s = getWs();
49240
- const repoCheckoutPaths = await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sessionId);
49352
+ const repoCheckoutPaths = await awaitGitStatePreparation({
49353
+ label: "Session worktree discovery",
49354
+ timeoutMs: GIT_STATE_DISCOVERY_TIMEOUT_MS,
49355
+ work: sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sessionId),
49356
+ fallback: void 0,
49357
+ log: log2
49358
+ });
49241
49359
  const repoRoots = await resolveSnapshotRepoRoots({
49242
49360
  worktreePaths: repoCheckoutPaths,
49243
49361
  fallbackCwd: effectiveCwd,
49244
49362
  sessionId: sessionId?.trim() || void 0,
49245
49363
  log: log2
49246
49364
  });
49247
- if (s && sessionId) {
49248
- const usesWt = sessionWorktreeManager.usesWorktreeSession(sessionId);
49249
- const cliGitBranch = repoRoots.length > 0 ? await readGitBranch(effectiveCwd) : null;
49250
- const isolatedSessionParentPath = sessionWorktreeManager.getIsolatedSessionParentPathForSession(sessionId);
49251
- sendWsMessage(s, {
49365
+ const socket = getWs();
49366
+ if (socket && sessionId) {
49367
+ const usesWorktree = sessionWorktreeManager.usesWorktreeSession(sessionId);
49368
+ sendWsMessage(socket, {
49252
49369
  type: "session_git_context_report",
49253
49370
  sessionId,
49254
- cliGitBranch,
49255
- agentUsesWorktree: usesWt,
49256
- sessionParent: usesWt ? "worktrees_root" : "bridge_root",
49257
- sessionParentPath: usesWt ? isolatedSessionParentPath ?? effectiveCwd : getBridgeRoot()
49371
+ cliGitBranch: repoRoots.length > 0 ? await readGitBranch(effectiveCwd) : null,
49372
+ agentUsesWorktree: usesWorktree,
49373
+ sessionParent: usesWorktree ? "worktrees_root" : "bridge_root",
49374
+ sessionParentPath: usesWorktree ? sessionWorktreeManager.getIsolatedSessionParentPathForSession(sessionId) ?? effectiveCwd : getBridgeRoot()
49258
49375
  });
49259
49376
  }
49260
- if (s && sessionId && runId) {
49261
- const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
49262
- sendWsMessage(s, {
49377
+ if (socket && sessionId && runId) {
49378
+ const snapshot = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false };
49379
+ sendWsMessage(socket, {
49263
49380
  type: "pre_turn_snapshot_report",
49264
49381
  sessionId,
49265
49382
  turnId: runId,
49266
- captured: cap.ok
49383
+ captured: snapshot.ok
49267
49384
  });
49268
49385
  }
49269
49386
  }
49270
- function parseFollowUpFieldsFromPromptMessage(msg) {
49271
- const followUpCatalogPromptId = typeof msg.followUpCatalogPromptId === "string" && msg.followUpCatalogPromptId.trim() !== "" ? msg.followUpCatalogPromptId.trim() : null;
49272
- return { followUpCatalogPromptId };
49273
- }
49274
49387
 
49275
- // src/agents/acp/from-bridge/handle-bridge-prompt/run-preamble-and-prompt.ts
49276
- async function runPreambleAndPrompt(params) {
49388
+ // src/agents/acp/from-bridge/handle-bridge-prompt/prepare-git-state-and-run-prompt.ts
49389
+ async function prepareGitStateAndRunPrompt(params) {
49277
49390
  const {
49278
49391
  deps,
49279
49392
  msg,
@@ -49292,7 +49405,7 @@ async function runPreambleAndPrompt(params) {
49292
49405
  senders: { sendResult, sendSessionUpdate }
49293
49406
  } = params;
49294
49407
  const effectiveCwd = resolveSessionParentPathForAgentProcess(resolvedCwd);
49295
- await runBridgePromptPreamble({
49408
+ await prepareBridgePromptGitState({
49296
49409
  getWs,
49297
49410
  log: log2,
49298
49411
  sessionWorktreeManager,
@@ -49336,17 +49449,14 @@ function handleBridgePrompt(msg, deps) {
49336
49449
  log2(
49337
49450
  `[Bridge service] Prompt ignored: empty or missing prompt text (session ${typeof msg.sessionId === "string" ? msg.sessionId.slice(0, 8) : "\u2014"}\u2026, run ${typeof msg.runId === "string" ? msg.runId.slice(0, 8) : "\u2014"}\u2026).`
49338
49451
  );
49339
- senders.sendBridgeMessage(
49340
- {
49341
- type: "prompt_result",
49342
- ...promptId ? { id: promptId } : {},
49343
- ...sessionId ? { sessionId } : {},
49344
- ...runId ? { runId } : {},
49345
- success: false,
49346
- error: "Empty or missing prompt text from the bridge; this turn was not sent to the agent."
49347
- },
49348
- ["error"]
49349
- );
49452
+ senders.sendResult({
49453
+ type: "prompt_result",
49454
+ ...promptId ? { id: promptId } : {},
49455
+ ...sessionId ? { sessionId } : {},
49456
+ ...runId ? { runId } : {},
49457
+ success: false,
49458
+ error: "Empty or missing prompt text from the bridge; this turn was not sent to the agent."
49459
+ });
49350
49460
  return;
49351
49461
  }
49352
49462
  const isNewSession = msg.isNewSession === true;
@@ -49366,7 +49476,7 @@ function handleBridgePrompt(msg, deps) {
49366
49476
  sessionParentPath,
49367
49477
  ...worktreeBaseBranches ? { worktreeBaseBranches } : {}
49368
49478
  }).then(
49369
- (cwd) => runPreambleAndPrompt({
49479
+ (cwd) => prepareGitStateAndRunPrompt({
49370
49480
  deps,
49371
49481
  msg,
49372
49482
  getWs,
@@ -49385,7 +49495,7 @@ function handleBridgePrompt(msg, deps) {
49385
49495
  })
49386
49496
  ).catch((err) => {
49387
49497
  log2(`[Agent] Session parent path resolve failed: ${err instanceof Error ? err.message : String(err)}`);
49388
- void runPreambleAndPrompt({
49498
+ void prepareGitStateAndRunPrompt({
49389
49499
  deps,
49390
49500
  msg,
49391
49501
  getWs,