@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/cli.js CHANGED
@@ -8413,7 +8413,11 @@ var init_migrate_cli_sqlite = __esm({
8413
8413
  });
8414
8414
 
8415
8415
  // src/sqlite/cli-database.ts
8416
- var cliSqlite, safeCloseCliSqliteDatabase, closeAllCliSqliteConnections, openCliSqliteConnection, withCliSqliteSync, withCliSqlite, ensureCliSqliteInitialized;
8416
+ function closeAllCliSqliteConnections() {
8417
+ legacyImportDoneForPath = null;
8418
+ cliSqlite.closeAllConnections();
8419
+ }
8420
+ var legacyImportDoneForPath, cliSqlite, safeCloseCliSqliteDatabase, openCliSqliteConnection, withCliSqliteSync, withCliSqlite, ensureCliSqliteInitialized;
8417
8421
  var init_cli_database = __esm({
8418
8422
  "src/sqlite/cli-database.ts"() {
8419
8423
  "use strict";
@@ -8423,16 +8427,19 @@ var init_cli_database = __esm({
8423
8427
  init_import_cli_legacy_disk_data();
8424
8428
  init_migrate_cli_sqlite();
8425
8429
  init_sqlite_errors();
8430
+ legacyImportDoneForPath = null;
8426
8431
  cliSqlite = createSqliteDatabaseAccess({
8427
8432
  getPath: getCliSqlitePath,
8428
8433
  ensureParentDir: ensureCliSqliteParentDir,
8429
8434
  migrate: migrateCliSqlite,
8430
8435
  afterOpen: (db, options) => {
8436
+ const sqlitePath = getCliSqlitePath();
8437
+ if (legacyImportDoneForPath === sqlitePath) return;
8431
8438
  importCliSqliteLegacyDiskData(db, options?.logLegacyMigration);
8439
+ legacyImportDoneForPath = sqlitePath;
8432
8440
  }
8433
8441
  });
8434
8442
  safeCloseCliSqliteDatabase = cliSqlite.safeClose;
8435
- closeAllCliSqliteConnections = cliSqlite.closeAllConnections;
8436
8443
  openCliSqliteConnection = cliSqlite.openConnection;
8437
8444
  withCliSqliteSync = cliSqlite.withSync;
8438
8445
  withCliSqlite = cliSqlite.withAsync;
@@ -31064,7 +31071,7 @@ var {
31064
31071
  } = import_index.default;
31065
31072
 
31066
31073
  // src/cli-version.ts
31067
- var CLI_VERSION = "0.1.76".length > 0 ? "0.1.76" : "0.0.0-dev";
31074
+ var CLI_VERSION = "0.1.78".length > 0 ? "0.1.78" : "0.0.0-dev";
31068
31075
 
31069
31076
  // src/cli/defaults.ts
31070
31077
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
@@ -36205,9 +36212,12 @@ function cancelPendingCursorPermissionRequests(pendingRequests2, respond) {
36205
36212
  // src/agents/acp/clients/cursor/create-cursor-acp-handle.ts
36206
36213
  function createCursorAcpHandle(options) {
36207
36214
  let teardownStarted = false;
36215
+ let cancelFallback = null;
36208
36216
  async function disconnectGracefully() {
36209
36217
  if (teardownStarted) return;
36210
36218
  teardownStarted = true;
36219
+ if (cancelFallback) clearTimeout(cancelFallback);
36220
+ cancelFallback = null;
36211
36221
  cancelPendingCursorPermissionRequests(options.pendingRequests, options.wire.respond);
36212
36222
  try {
36213
36223
  await options.transport.cancelSession(options.sessionId);
@@ -36233,7 +36243,17 @@ function createCursorAcpHandle(options) {
36233
36243
  },
36234
36244
  async cancel() {
36235
36245
  cancelPendingCursorPermissionRequests(options.pendingRequests, options.wire.respond);
36236
- await options.transport.cancelSession(options.sessionId);
36246
+ try {
36247
+ await options.transport.cancelSession(options.sessionId);
36248
+ } catch {
36249
+ }
36250
+ if (cancelFallback) return;
36251
+ cancelFallback = setTimeout(() => {
36252
+ if (options.child.exitCode == null && options.child.signalCode == null) {
36253
+ void disconnectGracefully();
36254
+ }
36255
+ }, 5e3);
36256
+ cancelFallback.unref?.();
36237
36257
  },
36238
36258
  resolveRequest(requestId, result) {
36239
36259
  if (options.pendingRequests.has(requestId)) {
@@ -36788,13 +36808,15 @@ import { existsSync as existsSync4, statSync } from "node:fs";
36788
36808
  // src/git/get-git-repo-root-sync.ts
36789
36809
  import { execFileSync as execFileSync3 } from "node:child_process";
36790
36810
  import * as path28 from "node:path";
36811
+ var GIT_LOOKUP_TIMEOUT_MS = 2e3;
36791
36812
  function getGitRepoRootSync(startDir) {
36792
36813
  try {
36793
36814
  const out = execFileSync3("git", ["rev-parse", "--show-toplevel"], {
36794
36815
  cwd: path28.resolve(startDir),
36795
36816
  encoding: "utf8",
36796
36817
  stdio: ["ignore", "pipe", "ignore"],
36797
- maxBuffer: 1024 * 1024
36818
+ maxBuffer: 1024 * 1024,
36819
+ timeout: GIT_LOOKUP_TIMEOUT_MS
36798
36820
  }).trim();
36799
36821
  return out ? path28.resolve(out) : null;
36800
36822
  } catch {
@@ -36806,6 +36828,7 @@ function getGitRepoRootSync(startDir) {
36806
36828
  import { execFileSync as execFileSync4 } from "node:child_process";
36807
36829
  import { readFileSync as readFileSync4 } from "node:fs";
36808
36830
  import * as path29 from "node:path";
36831
+ var GIT_FILE_READ_TIMEOUT_MS = 2e3;
36809
36832
  function resolveWorkspaceFilePath(sessionParentPath, rawPath) {
36810
36833
  const trimmed2 = rawPath.trim();
36811
36834
  if (!trimmed2) return null;
@@ -36867,7 +36890,8 @@ function readGitHeadBlob(sessionParentPath, displayPath) {
36867
36890
  return execFileSync4("git", ["show", `HEAD:${displayPath}`], {
36868
36891
  cwd: execCwd,
36869
36892
  encoding: "utf8",
36870
- maxBuffer: 50 * 1024 * 1024
36893
+ maxBuffer: 50 * 1024 * 1024,
36894
+ timeout: GIT_FILE_READ_TIMEOUT_MS
36871
36895
  });
36872
36896
  } catch {
36873
36897
  return "";
@@ -36875,6 +36899,7 @@ function readGitHeadBlob(sessionParentPath, displayPath) {
36875
36899
  }
36876
36900
 
36877
36901
  // src/agents/acp/session-file-change-path-kind.ts
36902
+ var GIT_PATH_KIND_TIMEOUT_MS = 2e3;
36878
36903
  function gitHeadPathObjectType(sessionParentPath, displayPath) {
36879
36904
  if (!displayPath || displayPath.includes("..")) return null;
36880
36905
  const gitRoot = getGitRepoRootSync(sessionParentPath);
@@ -36882,7 +36907,8 @@ function gitHeadPathObjectType(sessionParentPath, displayPath) {
36882
36907
  try {
36883
36908
  return execFileSync5("git", ["cat-file", "-t", `HEAD:${displayPath}`], {
36884
36909
  cwd: gitRoot,
36885
- encoding: "utf8"
36910
+ encoding: "utf8",
36911
+ timeout: GIT_PATH_KIND_TIMEOUT_MS
36886
36912
  }).trim();
36887
36913
  } catch {
36888
36914
  return null;
@@ -37989,6 +38015,73 @@ function augmentPromptResultAuthFields(agentType, errorText) {
37989
38015
  return { agentAuthRequired: true, agentType };
37990
38016
  }
37991
38017
 
38018
+ // src/agents/planning/submit-planning-todos-for-turn.ts
38019
+ var PLANNING_TODO_SUBMIT_TIMEOUT_MS = 3e3;
38020
+ async function submitPlanningTodosForTurn(params) {
38021
+ const token = params.getCloudAccessToken();
38022
+ if (!token.trim()) return { ok: false, error: "Missing cloud access token" };
38023
+ const base = params.cloudApiBaseUrl.replace(/\/$/, "");
38024
+ const url2 = `${base}/internal/sessions/todos/submit`;
38025
+ let res;
38026
+ try {
38027
+ res = await fetch(url2, {
38028
+ method: "POST",
38029
+ signal: AbortSignal.timeout(PLANNING_TODO_SUBMIT_TIMEOUT_MS),
38030
+ headers: {
38031
+ Authorization: `Bearer ${token}`,
38032
+ "Content-Type": "application/json"
38033
+ },
38034
+ body: JSON.stringify({
38035
+ sessionId: params.sessionId,
38036
+ turnId: params.turnId,
38037
+ items: params.items
38038
+ })
38039
+ });
38040
+ } catch (error40) {
38041
+ return { ok: false, error: error40 instanceof Error ? error40.message : String(error40) };
38042
+ }
38043
+ if (!res.ok) {
38044
+ const body = await res.json().catch(() => null);
38045
+ return { ok: false, error: body?.error ?? `Planning todo submit failed (${res.status})` };
38046
+ }
38047
+ return { ok: true };
38048
+ }
38049
+
38050
+ // src/agents/planning/maybe-submit-planning-todos-after-agent-success.ts
38051
+ async function maybeSubmitPlanningTodosAfterAgentSuccess(params) {
38052
+ const { sessionId, runId, resultSuccess, output, cloudApiBaseUrl, getCloudAccessToken, log: log2 } = params;
38053
+ const buffered = runId ? getPlanningSessionTurnOutput(runId) : "";
38054
+ const outputFromResult = typeof output === "string" ? output : "";
38055
+ const outputStr = buffered.trim() !== "" ? buffered : outputFromResult;
38056
+ if (!sessionId || !runId || !resultSuccess || outputStr.trim() === "") {
38057
+ return { submitted: false, suppressOutput: false };
38058
+ }
38059
+ if (!cloudApiBaseUrl || !getCloudAccessToken) {
38060
+ return { submitted: false, suppressOutput: false };
38061
+ }
38062
+ const items = parsePlanningTodoAgentJson(outputStr);
38063
+ if (items.length === 0) {
38064
+ return { submitted: false, suppressOutput: false };
38065
+ }
38066
+ const result = await submitPlanningTodosForTurn({
38067
+ sessionId,
38068
+ turnId: runId,
38069
+ items,
38070
+ cloudApiBaseUrl,
38071
+ getCloudAccessToken
38072
+ });
38073
+ if (!result.ok) {
38074
+ log2(`[Agent] Planning todo submit failed: ${result.error}`);
38075
+ return { submitted: false, suppressOutput: false };
38076
+ }
38077
+ if (runId) consumePlanningSessionTurnOutput(runId);
38078
+ log2(`[Agent] Planning session: stored ${items.length} todo(s) via internal API`);
38079
+ return { submitted: true, suppressOutput: true };
38080
+ }
38081
+
38082
+ // src/agents/acp/prompts/report-post-turn-enrichment.ts
38083
+ init_yield_to_event_loop();
38084
+
37992
38085
  // src/git/git-runtime.ts
37993
38086
  init_cli_process_interrupt();
37994
38087
  var activeGitChildProcesses = /* @__PURE__ */ new Set();
@@ -38351,61 +38444,30 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
38351
38444
  });
38352
38445
  }
38353
38446
 
38354
- // src/agents/planning/submit-planning-todos-for-turn.ts
38355
- async function submitPlanningTodosForTurn(params) {
38356
- const token = params.getCloudAccessToken();
38357
- if (!token.trim()) return { ok: false, error: "Missing cloud access token" };
38358
- const base = params.cloudApiBaseUrl.replace(/\/$/, "");
38359
- const url2 = `${base}/internal/sessions/todos/submit`;
38360
- const res = await fetch(url2, {
38361
- method: "POST",
38362
- headers: {
38363
- Authorization: `Bearer ${token}`,
38364
- "Content-Type": "application/json"
38365
- },
38366
- body: JSON.stringify({
38367
- sessionId: params.sessionId,
38368
- turnId: params.turnId,
38369
- items: params.items
38370
- })
38371
- });
38372
- if (!res.ok) {
38373
- const body = await res.json().catch(() => null);
38374
- return { ok: false, error: body?.error ?? `Planning todo submit failed (${res.status})` };
38375
- }
38376
- return { ok: true };
38377
- }
38378
-
38379
- // src/agents/planning/maybe-submit-planning-todos-after-agent-success.ts
38380
- async function maybeSubmitPlanningTodosAfterAgentSuccess(params) {
38381
- const { sessionId, runId, resultSuccess, output, cloudApiBaseUrl, getCloudAccessToken, log: log2 } = params;
38382
- const buffered = runId ? getPlanningSessionTurnOutput(runId) : "";
38383
- const outputFromResult = typeof output === "string" ? output : "";
38384
- const outputStr = buffered.trim() !== "" ? buffered : outputFromResult;
38385
- if (!sessionId || !runId || !resultSuccess || outputStr.trim() === "") {
38386
- return { submitted: false, suppressOutput: false };
38387
- }
38388
- if (!cloudApiBaseUrl || !getCloudAccessToken) {
38389
- return { submitted: false, suppressOutput: false };
38390
- }
38391
- const items = parsePlanningTodoAgentJson(outputStr);
38392
- if (items.length === 0) {
38393
- return { submitted: false, suppressOutput: false };
38394
- }
38395
- const result = await submitPlanningTodosForTurn({
38396
- sessionId,
38397
- turnId: runId,
38398
- items,
38399
- cloudApiBaseUrl,
38400
- getCloudAccessToken
38447
+ // src/agents/acp/prompts/report-post-turn-enrichment.ts
38448
+ function reportPostTurnEnrichment(options) {
38449
+ void (async () => {
38450
+ const {
38451
+ sessionId,
38452
+ runId,
38453
+ agentCwd,
38454
+ result,
38455
+ sendSessionUpdate,
38456
+ log: log2
38457
+ } = options;
38458
+ await yieldToEventLoop();
38459
+ if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
38460
+ await collectTurnGitDiffFromPreTurnSnapshot({
38461
+ sessionId,
38462
+ runId,
38463
+ agentCwd,
38464
+ sendSessionUpdate,
38465
+ log: log2
38466
+ });
38467
+ }
38468
+ })().catch((error40) => {
38469
+ options.log(`[Agent] Post-turn enrichment failed: ${error40 instanceof Error ? error40.message : String(error40)}`);
38401
38470
  });
38402
- if (!result.ok) {
38403
- log2(`[Agent] Planning todo submit failed: ${result.error}`);
38404
- return { submitted: false, suppressOutput: false };
38405
- }
38406
- if (runId) consumePlanningSessionTurnOutput(runId);
38407
- log2(`[Agent] Planning session: stored ${items.length} todo(s) via internal API`);
38408
- return { submitted: true, suppressOutput: true };
38409
38471
  }
38410
38472
 
38411
38473
  // src/agents/acp/prompts/finalize-and-send-prompt-result.ts
@@ -38425,16 +38487,7 @@ async function finalizeAndSendPromptResult(params) {
38425
38487
  sendSessionUpdate,
38426
38488
  log: log2
38427
38489
  } = params;
38428
- if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
38429
- await collectTurnGitDiffFromPreTurnSnapshot({
38430
- sessionId,
38431
- runId,
38432
- agentCwd,
38433
- sendSessionUpdate,
38434
- log: log2
38435
- });
38436
- }
38437
- const planningTodosSubmit = await maybeSubmitPlanningTodosAfterAgentSuccess({
38490
+ const planningTodosSubmit = isPlanningSession ? await maybeSubmitPlanningTodosAfterAgentSuccess({
38438
38491
  sessionId,
38439
38492
  runId,
38440
38493
  resultSuccess: result.success === true,
@@ -38442,11 +38495,11 @@ async function finalizeAndSendPromptResult(params) {
38442
38495
  cloudApiBaseUrl,
38443
38496
  getCloudAccessToken,
38444
38497
  log: log2
38445
- });
38498
+ }) : { suppressOutput: false };
38499
+ const planningFallbackOutput = !planningTodosSubmit.suppressOutput && isPlanningSession && runId ? consumePlanningSessionTurnOutput(runId) : "";
38446
38500
  const errStr = typeof result.error === "string" ? result.error : void 0;
38447
38501
  const resultStop = typeof result.stopReason === "string" ? result.stopReason.trim() : "";
38448
38502
  const cancelledByAgent = resultStop.toLowerCase() === "cancelled" || errStr != null && isUserEndedSessionTurnErrorText(errStr);
38449
- const planningFallbackOutput = !planningTodosSubmit.suppressOutput && isPlanningSession && runId ? consumePlanningSessionTurnOutput(runId) : "";
38450
38503
  sendResult({
38451
38504
  type: "prompt_result",
38452
38505
  id: promptId,
@@ -38467,6 +38520,14 @@ async function finalizeAndSendPromptResult(params) {
38467
38520
  );
38468
38521
  }
38469
38522
  }
38523
+ void reportPostTurnEnrichment({
38524
+ sessionId,
38525
+ runId,
38526
+ agentCwd,
38527
+ result,
38528
+ sendSessionUpdate,
38529
+ log: log2
38530
+ });
38470
38531
  }
38471
38532
 
38472
38533
  // src/agents/acp/build-forked-session-agent-prompt.ts
@@ -38865,6 +38926,7 @@ async function resolveSendPromptImages(params) {
38865
38926
  }
38866
38927
 
38867
38928
  // src/agents/acp/prompts/send-prompt-to-agent.ts
38929
+ var SLOW_ACP_PROMPT_MS = 5e3;
38868
38930
  async function sendPromptToAgent(options) {
38869
38931
  const {
38870
38932
  handle,
@@ -38911,7 +38973,12 @@ async function sendPromptToAgent(options) {
38911
38973
  sendResult(imagesResolved.errorResult);
38912
38974
  return;
38913
38975
  }
38976
+ const promptStartedAt = Date.now();
38914
38977
  const result = await handle.sendPrompt(prepared.agentPromptText, imagesResolved.sendOpts);
38978
+ const promptDurationMs = Date.now() - promptStartedAt;
38979
+ if (promptDurationMs >= SLOW_ACP_PROMPT_MS) {
38980
+ log2(`[Agent] ACP session/prompt completed after ${promptDurationMs}ms.`);
38981
+ }
38915
38982
  await finalizeAndSendPromptResult({
38916
38983
  result,
38917
38984
  sessionId,
@@ -38923,7 +38990,6 @@ async function sendPromptToAgent(options) {
38923
38990
  followUpCatalogPromptId,
38924
38991
  cloudApiBaseUrl,
38925
38992
  getCloudAccessToken,
38926
- e2ee,
38927
38993
  sendResult,
38928
38994
  sendSessionUpdate,
38929
38995
  log: log2
@@ -38946,16 +39012,6 @@ async function sendPromptToAgent(options) {
38946
39012
  }
38947
39013
  }
38948
39014
 
38949
- // src/agents/acp/manager/get-acp-agent-state.ts
38950
- function getAcpSessionAgentState(ctx, acpSessionAgentKey) {
38951
- let state = ctx.acpAgents.get(acpSessionAgentKey);
38952
- if (!state) {
38953
- state = createEmptyAcpClientState();
38954
- ctx.acpAgents.set(acpSessionAgentKey, state);
38955
- }
38956
- return state;
38957
- }
38958
-
38959
39015
  // src/agents/acp/manager/handle-pending-prompt-cancel.ts
38960
39016
  async function handlePendingPromptCancel(params) {
38961
39017
  const { ctx, activeRunId, promptId, sessionId, handle, sendResult } = params;
@@ -38979,94 +39035,104 @@ async function handlePendingPromptCancel(params) {
38979
39035
  return true;
38980
39036
  }
38981
39037
 
38982
- // src/agents/acp/manager/run-acp-prompt.ts
38983
- async function runAcpPrompt(ctx, runCtx, opts) {
38984
- const {
38985
- promptText,
38986
- promptId,
38987
- sessionId,
38988
- mode,
38989
- agentConfig,
38990
- sessionParentPath,
38991
- sendResult,
38992
- sendSessionUpdate,
38993
- followUpCatalogPromptId,
38994
- cloudApiBaseUrl,
38995
- getCloudAccessToken,
38996
- e2ee,
38997
- attachments,
38998
- isNewSession
38999
- } = opts;
39000
- const { activeRunId, activeAcpAgentKey, activeAcpSessionAgentKey, preferredForPrompt } = runCtx;
39001
- const acpAgentState = getAcpSessionAgentState(ctx, activeAcpSessionAgentKey);
39002
- const handle = await ensureAcpClient({
39003
- state: acpAgentState,
39004
- acpAgentKey: activeAcpAgentKey,
39005
- preferredAgentType: preferredForPrompt,
39006
- mode,
39007
- agentConfig: agentConfig ?? null,
39008
- sessionParentPath,
39009
- resolveRouting: () => ctx.promptRouting.resolveRouting(activeAcpSessionAgentKey),
39010
- cloudSessionId: sessionId,
39011
- bridgeAccessPort: ctx.getBridgeAccessPort(),
39012
- sendSessionUpdate,
39013
- sendRequest: sendSessionUpdate,
39014
- log: ctx.log,
39015
- reportAgentCapabilities: ctx.reportAgentCapabilities
39016
- });
39017
- if (!handle) {
39018
- const errMsg = acpAgentState.lastAcpStartError || "No agent configured. Register local agents on this bridge in the app.";
39019
- const evaluated = Boolean(preferredForPrompt && errMsg.trim());
39020
- const suggestsAuth = evaluated ? localAgentErrorSuggestsAuth(preferredForPrompt, errMsg) : false;
39021
- const auth = suggestsAuth && preferredForPrompt ? { agentAuthRequired: true, agentType: preferredForPrompt } : {};
39022
- sendResult({
39038
+ // src/agents/acp/manager/dispatch-acp-prompt.ts
39039
+ async function dispatchAcpPrompt(ctx, runCtx, opts, handle) {
39040
+ const { activeRunId, activeAcpSessionAgentKey, preferredForPrompt } = runCtx;
39041
+ if (!ctx.promptRouting.isRegisteredRun(activeRunId)) {
39042
+ opts.sendResult({
39023
39043
  type: "prompt_result",
39024
- id: promptId,
39025
- ...sessionId ? { sessionId } : {},
39044
+ id: opts.promptId,
39045
+ ...opts.sessionId ? { sessionId: opts.sessionId } : {},
39026
39046
  runId: activeRunId,
39027
39047
  success: false,
39028
- error: errMsg,
39029
- ...auth
39048
+ error: "Prompt dispatch was superseded before the agent started."
39030
39049
  });
39031
39050
  return;
39032
39051
  }
39033
- if (!ctx.promptRouting.isRegisteredRun(activeRunId)) {
39034
- return;
39035
- }
39036
39052
  const cancelled = await handlePendingPromptCancel({
39037
39053
  ctx,
39038
39054
  activeRunId,
39039
- promptId,
39040
- sessionId,
39055
+ promptId: opts.promptId,
39056
+ sessionId: opts.sessionId,
39041
39057
  handle,
39042
- sendResult
39058
+ sendResult: opts.sendResult
39043
39059
  });
39044
39060
  if (cancelled) return;
39045
39061
  ctx.promptRouting.setStreamingRunId(activeAcpSessionAgentKey, activeRunId);
39046
39062
  try {
39047
39063
  await sendPromptToAgent({
39048
39064
  handle,
39049
- promptText,
39050
- promptId,
39051
- sessionId,
39065
+ promptText: opts.promptText,
39066
+ promptId: opts.promptId,
39067
+ sessionId: opts.sessionId,
39052
39068
  runId: activeRunId,
39053
39069
  agentType: preferredForPrompt,
39054
- agentCwd: resolveSessionParentPathForAgentProcess(sessionParentPath),
39055
- sendResult,
39056
- sendSessionUpdate,
39070
+ agentCwd: resolveSessionParentPathForAgentProcess(opts.sessionParentPath),
39071
+ sendResult: opts.sendResult,
39072
+ sendSessionUpdate: opts.sendSessionUpdate,
39057
39073
  log: ctx.log,
39058
- followUpCatalogPromptId,
39059
- cloudApiBaseUrl,
39060
- getCloudAccessToken,
39061
- e2ee,
39062
- attachments,
39063
- isNewSession
39074
+ followUpCatalogPromptId: opts.followUpCatalogPromptId,
39075
+ cloudApiBaseUrl: opts.cloudApiBaseUrl,
39076
+ getCloudAccessToken: opts.getCloudAccessToken,
39077
+ e2ee: opts.e2ee,
39078
+ attachments: opts.attachments,
39079
+ isNewSession: opts.isNewSession
39064
39080
  });
39065
39081
  } finally {
39066
39082
  ctx.promptRouting.clearStreamingRunId(activeAcpSessionAgentKey, activeRunId);
39067
39083
  }
39068
39084
  }
39069
39085
 
39086
+ // src/agents/acp/manager/get-acp-agent-state.ts
39087
+ function getAcpSessionAgentState(ctx, acpSessionAgentKey) {
39088
+ let state = ctx.acpAgents.get(acpSessionAgentKey);
39089
+ if (!state) {
39090
+ state = createEmptyAcpClientState();
39091
+ ctx.acpAgents.set(acpSessionAgentKey, state);
39092
+ }
39093
+ return state;
39094
+ }
39095
+
39096
+ // src/agents/acp/manager/ensure-acp-prompt-client.ts
39097
+ async function ensureAcpPromptClient(ctx, runCtx, opts) {
39098
+ const state = getAcpSessionAgentState(ctx, runCtx.activeAcpSessionAgentKey);
39099
+ const handle = await ensureAcpClient({
39100
+ state,
39101
+ acpAgentKey: runCtx.activeAcpAgentKey,
39102
+ preferredAgentType: runCtx.preferredForPrompt,
39103
+ mode: opts.mode,
39104
+ agentConfig: opts.agentConfig ?? null,
39105
+ sessionParentPath: opts.sessionParentPath,
39106
+ resolveRouting: () => ctx.promptRouting.resolveRouting(runCtx.activeAcpSessionAgentKey),
39107
+ cloudSessionId: opts.sessionId,
39108
+ bridgeAccessPort: ctx.getBridgeAccessPort(),
39109
+ sendSessionUpdate: opts.sendSessionUpdate,
39110
+ sendRequest: opts.sendSessionUpdate,
39111
+ log: ctx.log,
39112
+ reportAgentCapabilities: ctx.reportAgentCapabilities
39113
+ });
39114
+ if (handle) return handle;
39115
+ const error40 = state.lastAcpStartError || "No agent configured. Register local agents on this bridge in the app.";
39116
+ const agentType = runCtx.preferredForPrompt;
39117
+ const requiresAuth = Boolean(agentType && localAgentErrorSuggestsAuth(agentType, error40));
39118
+ opts.sendResult({
39119
+ type: "prompt_result",
39120
+ id: opts.promptId,
39121
+ ...opts.sessionId ? { sessionId: opts.sessionId } : {},
39122
+ runId: runCtx.activeRunId,
39123
+ success: false,
39124
+ error: error40,
39125
+ ...requiresAuth && agentType ? { agentAuthRequired: true, agentType } : {}
39126
+ });
39127
+ return null;
39128
+ }
39129
+
39130
+ // src/agents/acp/manager/run-acp-prompt.ts
39131
+ async function runAcpPrompt(ctx, runCtx, opts) {
39132
+ const handle = await ensureAcpPromptClient(ctx, runCtx, opts);
39133
+ if (handle) await dispatchAcpPrompt(ctx, runCtx, opts, handle);
39134
+ }
39135
+
39070
39136
  // src/agents/acp/manager/handle-prompt.ts
39071
39137
  function handlePrompt(ctx, opts) {
39072
39138
  const { promptId, sessionId, runId, mode, agentType, agentConfig, sendResult } = opts;
@@ -49485,9 +49551,13 @@ import * as fs50 from "node:fs";
49485
49551
  import * as path71 from "node:path";
49486
49552
 
49487
49553
  // src/git/snapshot/git.ts
49554
+ var SNAPSHOT_GIT_TIMEOUT_MS = 5e3;
49488
49555
  async function gitStashCreate(repoRoot, log2) {
49489
49556
  try {
49490
- const { stdout } = await execGitFile(["stash", "create"], { cwd: repoRoot });
49557
+ const { stdout } = await execGitFile(["stash", "create"], {
49558
+ cwd: repoRoot,
49559
+ timeout: SNAPSHOT_GIT_TIMEOUT_MS
49560
+ });
49491
49561
  return stdout.trim();
49492
49562
  } catch (e) {
49493
49563
  log2(
@@ -49498,7 +49568,10 @@ async function gitStashCreate(repoRoot, log2) {
49498
49568
  }
49499
49569
  async function gitUntrackedPaths(repoRoot, log2) {
49500
49570
  try {
49501
- const { stdout } = await execGitFile(["ls-files", "--others", "--exclude-standard"], { cwd: repoRoot });
49571
+ const { stdout } = await execGitFile(["ls-files", "--others", "--exclude-standard"], {
49572
+ cwd: repoRoot,
49573
+ timeout: SNAPSHOT_GIT_TIMEOUT_MS
49574
+ });
49502
49575
  return stdout.split("\n").map((l) => l.trim()).filter(Boolean);
49503
49576
  } catch (e) {
49504
49577
  log2(
@@ -51924,9 +51997,6 @@ function setRunIdQueueKey(runId, queueKey) {
51924
51997
  runIdToQueueKey.set(runId, queueKey);
51925
51998
  locallyDispatchedRunIds.add(runId);
51926
51999
  }
51927
- function isLocallyDispatchedRun(runId) {
51928
- return locallyDispatchedRunIds.has(runId);
51929
- }
51930
52000
  function deleteRunIdQueueKey(runId) {
51931
52001
  const queueKey = runIdToQueueKey.get(runId);
51932
52002
  runIdToQueueKey.delete(runId);
@@ -51953,11 +52023,39 @@ async function dispatchStartedPromptQueueRuns(entries, started, deps) {
51953
52023
 
51954
52024
  // src/prompt-turn-queue/runner/handle-prompt-queue-cancellations.ts
51955
52025
  init_yield_to_event_loop();
52026
+
52027
+ // src/prompt-turn-queue/client-report.ts
52028
+ function sendPromptQueueClientReport(ws, queues) {
52029
+ if (!ws) return false;
52030
+ const wireQueues = {};
52031
+ for (const [queueKey, rows] of Object.entries(queues)) {
52032
+ wireQueues[queueKey] = rows.map((r) => ({ turnId: r.turnId, clientState: r.cliState }));
52033
+ }
52034
+ sendWsMessage(ws, { type: "prompt_queue_client_report", queues: wireQueues });
52035
+ return true;
52036
+ }
52037
+
52038
+ // src/prompt-turn-queue/runner/finalize-prompt-turn-on-bridge.ts
52039
+ async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
52040
+ if (!runId) return false;
52041
+ const queueKey = deleteRunIdQueueKey(runId);
52042
+ if (!queueKey) return false;
52043
+ const f = await readPersistedQueue(queueKey);
52044
+ if (!f) return false;
52045
+ const t = f.turns.find((x) => x.turnId === runId);
52046
+ if (!t) return false;
52047
+ t.lastCliState = opts?.terminalCliState ?? (success2 ? "stopped" : "failed");
52048
+ await writePersistedQueue(f);
52049
+ sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, cliState: t.lastCliState }] });
52050
+ return true;
52051
+ }
52052
+
52053
+ // src/prompt-turn-queue/runner/handle-prompt-queue-cancellations.ts
51956
52054
  async function handlePromptQueueCancellations(entries, deps) {
51957
52055
  for (const [queueKey] of entries) {
51958
52056
  const file2 = await readPersistedQueue(queueKey);
51959
52057
  const turn = file2?.turns.find((row) => row.bridgeServerState === "cancel_requested" && row.lastCliState === "running");
51960
- if (!turn || getRunIdQueueKey(turn.turnId) !== queueKey || !isLocallyDispatchedRun(turn.turnId)) {
52058
+ if (!turn || getRunIdQueueKey(turn.turnId) !== queueKey) {
51961
52059
  await yieldToEventLoop();
51962
52060
  continue;
51963
52061
  }
@@ -51966,7 +52064,8 @@ async function handlePromptQueueCancellations(entries, deps) {
51966
52064
  await yieldToEventLoop();
51967
52065
  continue;
51968
52066
  }
51969
- deps.log(`[Queue] cancel_requested for ${turn.turnId.slice(0, 8)}\u2026 but no local ACP run remains.`);
52067
+ deps.log(`[Queue] No local ACP run remains for ${turn.turnId.slice(0, 8)}\u2026; reconciling cancelled turn.`);
52068
+ await finalizePromptTurnOnBridge(deps.getWs, turn.turnId, false, { terminalCliState: "cancelled" });
51970
52069
  await yieldToEventLoop();
51971
52070
  }
51972
52071
  }
@@ -51994,17 +52093,6 @@ function promptQueueSnapshotEntries(queues) {
51994
52093
  // src/prompt-turn-queue/runner/start-prompt-queue-runs.ts
51995
52094
  init_yield_to_event_loop();
51996
52095
 
51997
- // src/prompt-turn-queue/client-report.ts
51998
- function sendPromptQueueClientReport(ws, queues) {
51999
- if (!ws) return false;
52000
- const wireQueues = {};
52001
- for (const [queueKey, rows] of Object.entries(queues)) {
52002
- wireQueues[queueKey] = rows.map((r) => ({ turnId: r.turnId, clientState: r.cliState }));
52003
- }
52004
- sendWsMessage(ws, { type: "prompt_queue_client_report", queues: wireQueues });
52005
- return true;
52006
- }
52007
-
52008
52096
  // src/prompt-turn-queue/runner/queue-selection.ts
52009
52097
  function pickNextRunnableTurn(turns) {
52010
52098
  for (const t of turns) {
@@ -52168,29 +52256,20 @@ async function startPromptQueueRuns(entries, deps) {
52168
52256
  }
52169
52257
 
52170
52258
  // src/prompt-turn-queue/runner/apply-prompt-queue-state-from-server.ts
52259
+ var SLOW_QUEUE_SYNC_MS = 2e3;
52171
52260
  async function applyPromptQueueStateFromServer(msg, deps) {
52172
52261
  const raw = msg.queues;
52173
52262
  if (!raw || typeof raw !== "object") return;
52263
+ const startedAt = Date.now();
52174
52264
  const entries = promptQueueSnapshotEntries(raw);
52175
52265
  await persistPromptQueueSnapshot(entries);
52176
52266
  await handlePromptQueueCancellations(entries, deps);
52177
52267
  const started = await startPromptQueueRuns(entries, deps);
52178
52268
  await dispatchStartedPromptQueueRuns(entries, started, deps);
52179
- }
52180
-
52181
- // src/prompt-turn-queue/runner/finalize-prompt-turn-on-bridge.ts
52182
- async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
52183
- if (!runId) return false;
52184
- const queueKey = deleteRunIdQueueKey(runId);
52185
- if (!queueKey) return false;
52186
- const f = await readPersistedQueue(queueKey);
52187
- if (!f) return false;
52188
- const t = f.turns.find((x) => x.turnId === runId);
52189
- if (!t) return false;
52190
- t.lastCliState = opts?.terminalCliState ?? (success2 ? "stopped" : "failed");
52191
- await writePersistedQueue(f);
52192
- sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, cliState: t.lastCliState }] });
52193
- return true;
52269
+ const durationMs = Date.now() - startedAt;
52270
+ if (durationMs >= SLOW_QUEUE_SYNC_MS) {
52271
+ deps.log(`[Queue] Queue state sync and dispatch took ${durationMs}ms.`);
52272
+ }
52194
52273
  }
52195
52274
 
52196
52275
  // src/agents/acp/from-bridge/bridge-prompt-wiring.ts
@@ -52267,59 +52346,93 @@ function parseWorktreeBaseBranches(msg) {
52267
52346
  return Object.keys(out).length > 0 ? out : void 0;
52268
52347
  }
52269
52348
 
52270
- // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
52349
+ // src/agents/acp/from-bridge/parse-follow-up-prompt-fields.ts
52350
+ function parseFollowUpFieldsFromPromptMessage(msg) {
52351
+ const value = msg.followUpCatalogPromptId;
52352
+ return {
52353
+ followUpCatalogPromptId: typeof value === "string" && value.trim() !== "" ? value.trim() : null
52354
+ };
52355
+ }
52356
+
52357
+ // src/agents/acp/from-bridge/prepare-bridge-prompt-git-state.ts
52271
52358
  import { execFile as execFile8 } from "node:child_process";
52272
52359
  import { promisify as promisify9 } from "node:util";
52360
+
52361
+ // src/agents/acp/from-bridge/git-state-preparation-timeout.ts
52362
+ async function awaitGitStatePreparation(options) {
52363
+ const { label, timeoutMs, work, fallback, log: log2 } = options;
52364
+ let timer;
52365
+ try {
52366
+ return await Promise.race([
52367
+ work,
52368
+ new Promise((resolve37) => {
52369
+ timer = setTimeout(() => {
52370
+ log2(`[Agent] ${label} exceeded ${timeoutMs}ms; continuing without it.`);
52371
+ resolve37(fallback);
52372
+ }, timeoutMs);
52373
+ timer.unref?.();
52374
+ })
52375
+ ]);
52376
+ } finally {
52377
+ if (timer) clearTimeout(timer);
52378
+ }
52379
+ }
52380
+
52381
+ // src/agents/acp/from-bridge/prepare-bridge-prompt-git-state.ts
52273
52382
  var execFileAsync7 = promisify9(execFile8);
52383
+ var GIT_STATE_DISCOVERY_TIMEOUT_MS = 2e3;
52274
52384
  async function readGitBranch(cwd) {
52275
52385
  try {
52276
- const { stdout } = await execFileAsync7("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
52277
- const b = stdout.trim();
52278
- return b || null;
52386
+ const { stdout } = await execFileAsync7("git", ["branch", "--show-current"], {
52387
+ cwd,
52388
+ maxBuffer: 64 * 1024,
52389
+ timeout: GIT_STATE_DISCOVERY_TIMEOUT_MS
52390
+ });
52391
+ return stdout.trim() || null;
52279
52392
  } catch {
52280
52393
  return null;
52281
52394
  }
52282
52395
  }
52283
- async function runBridgePromptPreamble(params) {
52396
+ async function prepareBridgePromptGitState(params) {
52284
52397
  const { getWs, log: log2, sessionWorktreeManager, sessionId, runId, effectiveCwd } = params;
52285
- const s = getWs();
52286
- const repoCheckoutPaths = await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sessionId);
52398
+ const repoCheckoutPaths = await awaitGitStatePreparation({
52399
+ label: "Session worktree discovery",
52400
+ timeoutMs: GIT_STATE_DISCOVERY_TIMEOUT_MS,
52401
+ work: sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sessionId),
52402
+ fallback: void 0,
52403
+ log: log2
52404
+ });
52287
52405
  const repoRoots = await resolveSnapshotRepoRoots({
52288
52406
  worktreePaths: repoCheckoutPaths,
52289
52407
  fallbackCwd: effectiveCwd,
52290
52408
  sessionId: sessionId?.trim() || void 0,
52291
52409
  log: log2
52292
52410
  });
52293
- if (s && sessionId) {
52294
- const usesWt = sessionWorktreeManager.usesWorktreeSession(sessionId);
52295
- const cliGitBranch = repoRoots.length > 0 ? await readGitBranch(effectiveCwd) : null;
52296
- const isolatedSessionParentPath = sessionWorktreeManager.getIsolatedSessionParentPathForSession(sessionId);
52297
- sendWsMessage(s, {
52411
+ const socket = getWs();
52412
+ if (socket && sessionId) {
52413
+ const usesWorktree = sessionWorktreeManager.usesWorktreeSession(sessionId);
52414
+ sendWsMessage(socket, {
52298
52415
  type: "session_git_context_report",
52299
52416
  sessionId,
52300
- cliGitBranch,
52301
- agentUsesWorktree: usesWt,
52302
- sessionParent: usesWt ? "worktrees_root" : "bridge_root",
52303
- sessionParentPath: usesWt ? isolatedSessionParentPath ?? effectiveCwd : getBridgeRoot()
52417
+ cliGitBranch: repoRoots.length > 0 ? await readGitBranch(effectiveCwd) : null,
52418
+ agentUsesWorktree: usesWorktree,
52419
+ sessionParent: usesWorktree ? "worktrees_root" : "bridge_root",
52420
+ sessionParentPath: usesWorktree ? sessionWorktreeManager.getIsolatedSessionParentPathForSession(sessionId) ?? effectiveCwd : getBridgeRoot()
52304
52421
  });
52305
52422
  }
52306
- if (s && sessionId && runId) {
52307
- const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
52308
- sendWsMessage(s, {
52423
+ if (socket && sessionId && runId) {
52424
+ const snapshot = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false };
52425
+ sendWsMessage(socket, {
52309
52426
  type: "pre_turn_snapshot_report",
52310
52427
  sessionId,
52311
52428
  turnId: runId,
52312
- captured: cap.ok
52429
+ captured: snapshot.ok
52313
52430
  });
52314
52431
  }
52315
52432
  }
52316
- function parseFollowUpFieldsFromPromptMessage(msg) {
52317
- const followUpCatalogPromptId = typeof msg.followUpCatalogPromptId === "string" && msg.followUpCatalogPromptId.trim() !== "" ? msg.followUpCatalogPromptId.trim() : null;
52318
- return { followUpCatalogPromptId };
52319
- }
52320
52433
 
52321
- // src/agents/acp/from-bridge/handle-bridge-prompt/run-preamble-and-prompt.ts
52322
- async function runPreambleAndPrompt(params) {
52434
+ // src/agents/acp/from-bridge/handle-bridge-prompt/prepare-git-state-and-run-prompt.ts
52435
+ async function prepareGitStateAndRunPrompt(params) {
52323
52436
  const {
52324
52437
  deps,
52325
52438
  msg,
@@ -52338,7 +52451,7 @@ async function runPreambleAndPrompt(params) {
52338
52451
  senders: { sendResult, sendSessionUpdate }
52339
52452
  } = params;
52340
52453
  const effectiveCwd = resolveSessionParentPathForAgentProcess(resolvedCwd);
52341
- await runBridgePromptPreamble({
52454
+ await prepareBridgePromptGitState({
52342
52455
  getWs,
52343
52456
  log: log2,
52344
52457
  sessionWorktreeManager,
@@ -52382,17 +52495,14 @@ function handleBridgePrompt(msg, deps) {
52382
52495
  log2(
52383
52496
  `[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).`
52384
52497
  );
52385
- senders.sendBridgeMessage(
52386
- {
52387
- type: "prompt_result",
52388
- ...promptId ? { id: promptId } : {},
52389
- ...sessionId ? { sessionId } : {},
52390
- ...runId ? { runId } : {},
52391
- success: false,
52392
- error: "Empty or missing prompt text from the bridge; this turn was not sent to the agent."
52393
- },
52394
- ["error"]
52395
- );
52498
+ senders.sendResult({
52499
+ type: "prompt_result",
52500
+ ...promptId ? { id: promptId } : {},
52501
+ ...sessionId ? { sessionId } : {},
52502
+ ...runId ? { runId } : {},
52503
+ success: false,
52504
+ error: "Empty or missing prompt text from the bridge; this turn was not sent to the agent."
52505
+ });
52396
52506
  return;
52397
52507
  }
52398
52508
  const isNewSession = msg.isNewSession === true;
@@ -52412,7 +52522,7 @@ function handleBridgePrompt(msg, deps) {
52412
52522
  sessionParentPath,
52413
52523
  ...worktreeBaseBranches ? { worktreeBaseBranches } : {}
52414
52524
  }).then(
52415
- (cwd) => runPreambleAndPrompt({
52525
+ (cwd) => prepareGitStateAndRunPrompt({
52416
52526
  deps,
52417
52527
  msg,
52418
52528
  getWs,
@@ -52431,7 +52541,7 @@ function handleBridgePrompt(msg, deps) {
52431
52541
  })
52432
52542
  ).catch((err) => {
52433
52543
  log2(`[Agent] Session parent path resolve failed: ${err instanceof Error ? err.message : String(err)}`);
52434
- void runPreambleAndPrompt({
52544
+ void prepareGitStateAndRunPrompt({
52435
52545
  deps,
52436
52546
  msg,
52437
52547
  getWs,