@alan-ai-hq/agent-manager 0.1.122 → 0.1.123

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.
Files changed (2) hide show
  1. package/dist/index.cjs +1204 -566
  2. package/package.json +3 -3
package/dist/index.cjs CHANGED
@@ -33429,6 +33429,13 @@ var activeRunSlotSchema = external_exports.object({
33429
33429
  awaitingUser: external_exports.boolean().optional(),
33430
33430
  stale: external_exports.boolean().optional()
33431
33431
  });
33432
+ var MAX_REPORTED_TRANSCRIPT_SYNC_RUNS = 50;
33433
+ var transcriptSyncPendingRunSchema = external_exports.object({
33434
+ runId: external_exports.uuid(),
33435
+ conversationId: external_exports.uuid(),
33436
+ problemStatus: external_exports.number().int().optional(),
33437
+ problemPermanent: external_exports.boolean().optional()
33438
+ });
33432
33439
  var RUNNER_CAPACITY_SOURCES = ["default", "config", "provider_limit"];
33433
33440
  var DEFAULT_MAX_ACTIVE_RUNS = 4;
33434
33441
  var MIN_MAX_ACTIVE_RUNS = 1;
@@ -33444,6 +33451,8 @@ var runtimeRunnerStatusSchema = external_exports.object({
33444
33451
  restartRecommended: external_exports.boolean(),
33445
33452
  activeRuns: external_exports.array(activeRunSlotSchema),
33446
33453
  interruptedRunIds: external_exports.array(external_exports.uuid()).optional(),
33454
+ // A malformed backlog must not discard the rest of the heartbeat.
33455
+ transcriptSyncPending: external_exports.array(transcriptSyncPendingRunSchema).max(MAX_REPORTED_TRANSCRIPT_SYNC_RUNS).optional().catch(void 0),
33447
33456
  activeRunCount: external_exports.number().int().nonnegative(),
33448
33457
  staleRunCount: external_exports.number().int().nonnegative(),
33449
33458
  /**
@@ -34913,10 +34922,28 @@ function resolveWindowsCodexNativeExecutable(executable) {
34913
34922
  }
34914
34923
  return executable;
34915
34924
  }
34925
+ function promoteAheadOfVersionManagers(path2, promoted, versionManagerDirs) {
34926
+ const managed = new Set(versionManagerDirs);
34927
+ const firstManaged = path2.findIndex((dir) => managed.has(dir));
34928
+ if (firstManaged === -1) return path2;
34929
+ const before = path2.slice(0, firstManaged);
34930
+ const tail = path2.slice(firstManaged);
34931
+ const moved = tail.filter((dir) => promoted.has(dir));
34932
+ if (moved.length === 0) return path2;
34933
+ return [...before, ...moved, ...tail.filter((dir) => !promoted.has(dir))];
34934
+ }
34935
+ function promotedUserBinDirs(home) {
34936
+ return /* @__PURE__ */ new Set([
34937
+ (0, import_node_path5.join)(home, ".local", "bin"),
34938
+ (0, import_node_path5.join)(home, ".bun", "bin"),
34939
+ (0, import_node_path5.join)(home, ".cargo", "bin")
34940
+ ]);
34941
+ }
34916
34942
  function augmentCliPath2(env, includeSystemPaths = true) {
34917
34943
  const home = env.HOME || env.USERPROFILE || (0, import_node_os4.homedir)();
34918
34944
  const isWin = (0, import_node_os4.platform)() === "win32";
34919
34945
  const windowsLocalAppData = env.LOCALAPPDATA || (0, import_node_path5.join)(home, "AppData", "Local");
34946
+ const versionManagerDirs = isWin ? [] : resolveVersionManagerBinDirs(home);
34920
34947
  const userPaths = isWin ? [
34921
34948
  (0, import_node_path5.join)(home, "AppData", "Roaming", "npm"),
34922
34949
  (0, import_node_path5.join)(windowsLocalAppData, "Programs", "OpenAI", "Codex", "bin"),
@@ -34930,18 +34957,17 @@ function augmentCliPath2(env, includeSystemPaths = true) {
34930
34957
  // nvm/fnm/volta/asdf/mise live behind shell functions and leave nothing
34931
34958
  // on a non-interactive PATH, so resolve them from disk. Without this a
34932
34959
  // degraded shell probe hides node entirely and `env node` hooks die 127.
34933
- ...resolveVersionManagerBinDirs(home)
34960
+ ...versionManagerDirs
34934
34961
  ];
34935
34962
  const systemPaths = isWin ? ["C:\\Program Files\\nodejs", "C:\\Program Files\\Git\\cmd"] : ["/opt/homebrew/bin", "/usr/local/bin"];
34936
34963
  const extraPaths = includeSystemPaths ? [...userPaths, ...systemPaths] : userPaths;
34937
34964
  const basePath = env.PATH ?? "";
34938
34965
  const existing = new Set(splitPath3(basePath));
34939
34966
  const missing = extraPaths.filter((dir) => !existing.has(dir));
34940
- if (missing.length === 0) return env;
34941
- return {
34942
- ...env,
34943
- PATH: missing.length > 0 ? `${missing.join(pathSeparator())}${pathSeparator()}${basePath}` : basePath
34944
- };
34967
+ const combined = missing.length > 0 ? [...missing, ...splitPath3(basePath)] : splitPath3(basePath);
34968
+ const ordered = isWin ? combined : promoteAheadOfVersionManagers(combined, promotedUserBinDirs(home), versionManagerDirs);
34969
+ if (ordered === combined && missing.length === 0) return env;
34970
+ return { ...env, PATH: ordered.join(pathSeparator()) };
34945
34971
  }
34946
34972
  function resolveCliExecutable(command, env) {
34947
34973
  const trimmed4 = command.trim();
@@ -35245,7 +35271,7 @@ function buildComputerReadiness(input2) {
35245
35271
  }
35246
35272
 
35247
35273
  // src/version.ts
35248
- var AGENT_VERSION = "0.1.122";
35274
+ var AGENT_VERSION = "0.1.123";
35249
35275
 
35250
35276
  // src/daemon/status.ts
35251
35277
  var PENDING_RUN_START_TTL_MS = 10 * 6e4;
@@ -35348,6 +35374,10 @@ function buildAbortResult(reason) {
35348
35374
  errorKind: reason
35349
35375
  };
35350
35376
  }
35377
+ function abortResultFor(reason, sessionId) {
35378
+ const result = buildAbortResult(reason);
35379
+ return sessionId ? { ...result, providerSessionId: sessionId, runtimeSessionId: sessionId } : result;
35380
+ }
35351
35381
  function abortActiveAgent(entry, options) {
35352
35382
  const awaitingAsk = entry.runHost.pendingAskUserToolIds.size > 0;
35353
35383
  const reason = options?.reason ?? (options?.userInitiated ? "user" : "runner_stalled");
@@ -35355,7 +35385,7 @@ function abortActiveAgent(entry, options) {
35355
35385
  return;
35356
35386
  }
35357
35387
  entry.cleanupState = "stopping";
35358
- entry.runHost.onComplete(buildAbortResult(reason));
35388
+ entry.runHost.onComplete(abortResultFor(reason, entry.runHost.sessionId));
35359
35389
  entry.agent.kill();
35360
35390
  }
35361
35391
  function isProviderProcessAlive(pid) {
@@ -35444,6 +35474,7 @@ function buildRunnerStatusPayload(input2) {
35444
35474
  restartRecommended: false,
35445
35475
  activeRuns,
35446
35476
  interruptedRunIds: input2.interruptedRunIds ?? [],
35477
+ transcriptSyncPending: input2.transcriptSyncPending ?? [],
35447
35478
  activeRunCount,
35448
35479
  staleRunCount,
35449
35480
  maxActiveRuns,
@@ -35512,7 +35543,7 @@ function reconcileActiveRuns(input2) {
35512
35543
  const verdict = reconcileVerdict(entry, nowMs);
35513
35544
  if (verdict.action === "keep") continue;
35514
35545
  if (verdict.action === "finalize_for_resume") {
35515
- entry.runHost.onComplete(buildAbortResult("provider_exited"));
35546
+ entry.runHost.onComplete(abortResultFor("provider_exited", entry.runHost.sessionId));
35516
35547
  entry.agent.kill();
35517
35548
  releaseAfterCleanup(input2.activeAgents, runId, entry);
35518
35549
  input2.pushLog?.(
@@ -35570,7 +35601,8 @@ function runnerStateFingerprint(summary) {
35570
35601
  summary.staleRunCount,
35571
35602
  summary.runnerStatus.primaryMessage ?? "",
35572
35603
  readiness,
35573
- [...summary.activeRunIds].sort().join(",")
35604
+ [...summary.activeRunIds].sort().join(","),
35605
+ (summary.runnerStatus.transcriptSyncPending ?? []).map((run) => `${run.runId}:${run.problemStatus ?? ""}`).sort().join(",")
35574
35606
  ].join("|");
35575
35607
  }
35576
35608
 
@@ -40153,20 +40185,36 @@ function providerModelCacheKey(provider, executable) {
40153
40185
  return `${provider}:${executable}`;
40154
40186
  }
40155
40187
  var providerVersionCache = /* @__PURE__ */ new Map();
40188
+ var providerRunCache = /* @__PURE__ */ new Map();
40156
40189
  var providerModelCache = /* @__PURE__ */ new Map();
40157
40190
  var providerAvailabilitySeen = /* @__PURE__ */ new Set();
40158
40191
  var providerProbesInFlight = /* @__PURE__ */ new Map();
40159
40192
  var providerModelProbesInFlight = /* @__PURE__ */ new Map();
40160
40193
  var VERSION_REFRESH_INTERVAL_MS = 20 * 60 * 1e3;
40194
+ function firstNonEmptyLine(text2) {
40195
+ return text2.split("\n").map((line) => line.trim()).find((line) => line.length > 0);
40196
+ }
40161
40197
  function refreshSingleProviderVersion(provider, executable, env) {
40162
40198
  const inFlight = providerProbesInFlight.get(provider);
40163
40199
  if (inFlight) return inFlight;
40164
40200
  const probe = (async () => {
40165
40201
  try {
40166
40202
  const result = await runCliVersionProbeAsync(executable, env);
40167
- if (result.error || result.status !== 0) return;
40168
- const version2 = (result.stdout || result.stderr).split("\n")[0]?.trim();
40169
- if (version2) providerVersionCache.set(provider, version2);
40203
+ if (result.error || result.signal || typeof result.status !== "number") {
40204
+ providerRunCache.delete(provider);
40205
+ return;
40206
+ }
40207
+ const firstLine3 = firstNonEmptyLine(result.stdout) ?? firstNonEmptyLine(result.stderr);
40208
+ if (result.status !== 0) {
40209
+ providerRunCache.set(provider, {
40210
+ status: "cannot_start",
40211
+ ...firstLine3 ? { message: firstLine3 } : {},
40212
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
40213
+ });
40214
+ return;
40215
+ }
40216
+ if (firstLine3) providerVersionCache.set(provider, firstLine3);
40217
+ providerRunCache.set(provider, { status: "ready", checkedAt: (/* @__PURE__ */ new Date()).toISOString() });
40170
40218
  } finally {
40171
40219
  providerProbesInFlight.delete(provider);
40172
40220
  }
@@ -40278,10 +40326,12 @@ function discoverCapabilities() {
40278
40326
  void refreshSingleProviderVersion(provider, executable, env);
40279
40327
  } else if (!available) {
40280
40328
  providerAvailabilitySeen.delete(provider);
40329
+ providerRunCache.delete(provider);
40281
40330
  }
40282
40331
  const version2 = providerVersionCache.get(provider);
40283
40332
  const modelDiscovery = executable ? providerModelCache.get(providerModelCacheKey(provider, executable)) : void 0;
40284
40333
  const auth = !available ? void 0 : provider === ALAN_CODE_PROVIDER_KIND ? { status: "ready", checkedAt: now } : providerAuthCache.get(provider);
40334
+ const run = available ? providerRunCache.get(provider) : void 0;
40285
40335
  return {
40286
40336
  provider,
40287
40337
  available,
@@ -40297,6 +40347,11 @@ function discoverCapabilities() {
40297
40347
  authStatus: auth.status,
40298
40348
  ...auth.message ? { authMessage: auth.message } : {},
40299
40349
  authCheckedAt: auth.checkedAt
40350
+ } : {},
40351
+ ...run ? {
40352
+ runStatus: run.status,
40353
+ ...run.message ? { runMessage: run.message } : {},
40354
+ runCheckedAt: run.checkedAt
40300
40355
  } : {}
40301
40356
  };
40302
40357
  }),
@@ -43410,21 +43465,23 @@ var import_os3 = require("os");
43410
43465
  var import_path7 = require("path");
43411
43466
  var import_promises3 = require("fs/promises");
43412
43467
  var import_path8 = require("path");
43468
+ var import_promises4 = require("fs/promises");
43469
+ var import_path9 = require("path");
43413
43470
  var import_crypto4 = require("crypto");
43414
43471
  var import_crypto5 = require("crypto");
43415
43472
  var import_fs9 = require("fs");
43416
43473
  var import_os4 = require("os");
43417
- var import_path9 = require("path");
43474
+ var import_path10 = require("path");
43418
43475
  var import_url2 = require("url");
43419
43476
  var import_child_process6 = require("child_process");
43420
43477
  var import_util14 = require("util");
43421
43478
  var import_os5 = require("os");
43422
- var import_path10 = require("path");
43479
+ var import_path11 = require("path");
43423
43480
  var import_url3 = require("url");
43424
43481
  var import_fs10 = require("fs");
43425
43482
  var import_fs11 = require("fs");
43426
43483
  var import_fs12 = require("fs");
43427
- var import_path11 = require("path");
43484
+ var import_path12 = require("path");
43428
43485
  var import_crypto6 = require("crypto");
43429
43486
 
43430
43487
  // ../shared/dist/constants/local-workspace.js
@@ -43437,16 +43494,19 @@ function isAlanDefaultWorkspace(value2) {
43437
43494
  var import_crypto7 = require("crypto");
43438
43495
  var import_fs13 = require("fs");
43439
43496
  var import_os6 = require("os");
43440
- var import_path12 = require("path");
43441
- var import_fs14 = require("fs");
43442
43497
  var import_path13 = require("path");
43498
+ var import_fs14 = require("fs");
43499
+ var import_path14 = require("path");
43443
43500
  var import_fs15 = require("fs");
43444
43501
  var import_os7 = require("os");
43445
- var import_path14 = require("path");
43446
- var import_child_process7 = require("child_process");
43502
+ var import_path15 = require("path");
43447
43503
  var import_fs16 = require("fs");
43448
43504
  var import_os8 = require("os");
43449
- var import_path15 = require("path");
43505
+ var import_path16 = require("path");
43506
+ var import_child_process7 = require("child_process");
43507
+ var import_fs17 = require("fs");
43508
+ var import_os9 = require("os");
43509
+ var import_path17 = require("path");
43450
43510
  function createGrammarState() {
43451
43511
  return { seenToolCallIds: /* @__PURE__ */ new Set(), counter: 0, scratch: {} };
43452
43512
  }
@@ -43891,6 +43951,7 @@ var CLI_PROBLEM_CODE_BY_ERROR_KIND = {
43891
43951
  network: "cli.unknown",
43892
43952
  provider_timeout: "cli.process.timeout",
43893
43953
  resume_failed: "cli.resume_failed",
43954
+ history_rejected: "cli.resume_failed",
43894
43955
  permission_denied: "cli.tool.failed",
43895
43956
  interpreter_missing: "cli.process.not_found",
43896
43957
  out_of_memory: "cli.process.nonzero_exit",
@@ -43986,6 +44047,10 @@ var ERROR_SPECS = {
43986
44047
  message: "The previous session could not be resumed \u2014 its stored session was missing or expired. The next message starts a fresh session without earlier context.",
43987
44048
  recoveryClass: "retry"
43988
44049
  },
44050
+ history_rejected: {
44051
+ message: "The provider rejected this session's stored history, so it could not be continued. The next message starts a fresh session with the recent conversation re-included.",
44052
+ recoveryClass: "retry"
44053
+ },
43989
44054
  permission_denied: {
43990
44055
  message: "Permission denied. Check file or command permissions on the runtime.",
43991
44056
  recoveryClass: "needs_env"
@@ -44060,6 +44125,7 @@ function normalizeCodexCliErrorMessage(message) {
44060
44125
  }
44061
44126
  return message;
44062
44127
  }
44128
+ var NAMED_SESSION_NOT_FOUND = /\b(?:session|chat|conversation)\s+(?:"[^"\n]{1,160}"|'[^'\n]{1,160}'|[\w-]{6,})\s+not found\b/;
44063
44129
  function classifyCliErrorDetailed(stderr, exitCode, opts) {
44064
44130
  const normalizedCodexError = normalizeCodexCliErrorMessage(stderr);
44065
44131
  if (normalizedCodexError !== stderr) {
@@ -44088,7 +44154,28 @@ function classifyCliErrorDetailed(stderr, exitCode, opts) {
44088
44154
  return specForErrorKind("usage_limit");
44089
44155
  if (has("creditserror", "no payment method")) return specForErrorKind("subscription_required");
44090
44156
  if (has("datapolicyerror", "requires explicit opt in")) return specForErrorKind("model_mismatch");
44091
- if (has("no chat found", "chat not found", "session not found", "could not resume"))
44157
+ if (has(
44158
+ "text content blocks must be non-empty",
44159
+ "content field in the message object",
44160
+ "ids were found without `tool_result` blocks",
44161
+ "ids were found without tool_result blocks",
44162
+ "unexpected `tool_use_id`",
44163
+ "unexpected tool_use_id",
44164
+ "tool use concurrency issues",
44165
+ "no tool output found for",
44166
+ "does not support assistant message prefill"
44167
+ ))
44168
+ return specForErrorKind("history_rejected");
44169
+ if (has(
44170
+ "no chat found",
44171
+ "chat not found",
44172
+ "session not found",
44173
+ "could not resume",
44174
+ "no conversation found",
44175
+ "no rollout found",
44176
+ "no session, task, or name matched",
44177
+ "conversation not found"
44178
+ ) || NAMED_SESSION_NOT_FOUND.test(haystack))
44092
44179
  return specForErrorKind("resume_failed");
44093
44180
  if (has("rate_limit", "rate limit", "resource_exhausted", "resource exhausted") || /\b429\b/.test(haystack))
44094
44181
  return specForErrorKind("rate_limited");
@@ -44714,14 +44801,6 @@ function settleControlRequest(kind, state, requestId) {
44714
44801
  }
44715
44802
  }
44716
44803
  function deferOrFinalizeStructuredResult(kind, _context, state) {
44717
- const pendingAsks = state.pendingAskUserToolIds?.size ?? 0;
44718
- if (pendingAsks > 0) {
44719
- console.info(
44720
- `[${kind}] Keeping stdin open \u2014 ${pendingAsks} interactive question(s) awaiting user response`
44721
- );
44722
- state.resultDeferredOnPendingAnswer = true;
44723
- return "deferred_pending_answer";
44724
- }
44725
44804
  const activeBackgroundTasks = state.activeBackgroundTaskIds?.size ?? 0;
44726
44805
  if (activeBackgroundTasks > 0) {
44727
44806
  console.info(
@@ -44734,17 +44813,12 @@ function deferOrFinalizeStructuredResult(kind, _context, state) {
44734
44813
  return "finalized";
44735
44814
  }
44736
44815
  function maybeFinalizeDeferredResult(kind, _context, state) {
44737
- if (!state.resultDeferredOnBackgroundWork && !state.resultDeferredOnPendingAnswer) return;
44738
- if (state.pendingAskUserToolIds?.size || state.activeBackgroundTaskIds?.size) return;
44739
- if (state.resultDeferredOnBackgroundWork) {
44740
- state.awaitingPostBackgroundSynthesis = true;
44741
- console.info(
44742
- `[${kind}] Background work drained \u2014 awaiting post-subagent synthesis before stdin close`
44743
- );
44744
- return;
44745
- }
44746
- state.resultDeferredOnPendingAnswer = false;
44747
- finalizeStructuredResult(kind, state);
44816
+ if (!state.resultDeferredOnBackgroundWork) return;
44817
+ if (state.activeBackgroundTaskIds?.size) return;
44818
+ state.awaitingPostBackgroundSynthesis = true;
44819
+ console.info(
44820
+ `[${kind}] Background work drained \u2014 awaiting post-subagent synthesis before stdin close`
44821
+ );
44748
44822
  }
44749
44823
  function logResumeObservability(event, fields) {
44750
44824
  console.info(`[agent-resume] ${event}`, { event, ...fields });
@@ -44771,12 +44845,6 @@ function buildCliResult(facts, identity, state) {
44771
44845
  function cliProblemScope(context) {
44772
44846
  return context.config.conversationId ? { conversationId: context.config.conversationId } : {};
44773
44847
  }
44774
- var IDLE_TIMEOUT_MESSAGES = {
44775
- background_task: "Agent timed out waiting for a background task to finish \u2014 no terminal event arrived, so the run was stopped. Send your message again to continue.",
44776
- post_subagent_synthesis: "Agent timed out waiting for a post-subagent summary \u2014 synthesis did not finish, so the run was stopped. Send your message again to continue.",
44777
- post_startup_silence: "The agent went silent and stopped responding, so the run was stopped. Send your message again to retry.",
44778
- pending_user_answer: "Agent timed out waiting for your answer to its question \u2014 no response arrived, so the run was stopped. Send your reply as a new message to continue."
44779
- };
44780
44848
  function timeoutProblem(closing, message, phase) {
44781
44849
  const { options, context, observed } = closing;
44782
44850
  return createCliProblem(
@@ -44874,44 +44942,6 @@ function startupTimeoutResult(closing) {
44874
44942
  state
44875
44943
  );
44876
44944
  }
44877
- async function idleTimeoutResult(closing) {
44878
- const { options, context, state, observed } = closing;
44879
- if (!observed.idleTimedOut) return null;
44880
- console.error(`[${options.kind}] Process force-killed after idle timeout`, {
44881
- stderrLines: observed.recentStderrLines,
44882
- summaryPreview: state.summary.slice(0, 200),
44883
- runtimeSessionId: state.runtimeSessionId
44884
- });
44885
- await options.afterExit?.(context, state);
44886
- if (hasRenderableTurn(state)) emitTurnEnd(context, state);
44887
- const deferred = deferredBackgroundSummary(closing);
44888
- if (deferred) {
44889
- console.warn(
44890
- `[${options.kind}] Finalizing deferred result after background-task idle timeout \u2014 turn content was already delivered`
44891
- );
44892
- return buildCliResult({ success: true, summary: deferred }, options, state);
44893
- }
44894
- const message = observed.idleTimeoutReason ? IDLE_TIMEOUT_MESSAGES[observed.idleTimeoutReason] : IDLE_TIMEOUT_MESSAGES.pending_user_answer;
44895
- return buildCliResult(
44896
- {
44897
- success: false,
44898
- summary: state.summary.trim() || "Agent execution timed out",
44899
- error: message,
44900
- errorKind: "provider_timeout",
44901
- recoveryClass: "retry",
44902
- problem: timeoutProblem(closing, message, "stream")
44903
- },
44904
- options,
44905
- state
44906
- );
44907
- }
44908
- function deferredBackgroundSummary(closing) {
44909
- const { state, observed } = closing;
44910
- const waitingOnBackground = observed.idleTimeoutReason === "background_task" || observed.idleTimeoutReason === "post_subagent_synthesis";
44911
- if (!waitingOnBackground) return "";
44912
- if (!state.resultDeferredOnBackgroundWork && !state.awaitingPostBackgroundSynthesis) return "";
44913
- return state.summary.trim();
44914
- }
44915
44945
  function hasRenderableTurn(state) {
44916
44946
  return state.summary.trim().length > 0 || state.iterations > 0;
44917
44947
  }
@@ -44920,8 +44950,13 @@ function backgroundStillActive(state) {
44920
44950
  }
44921
44951
  function observeClose(closing) {
44922
44952
  const { state, observed } = closing;
44953
+ const backgroundActive = backgroundStillActive(state);
44923
44954
  return {
44924
- midSubagentParentExit: !observed.idleTimedOut && !observed.startupTimedOut && backgroundStillActive(state),
44955
+ // A turn we ended because its result had already been delivered is cleanup, not a
44956
+ // parent that died on its subagents. The content reached the reader, so only a
44957
+ // provider that left on its own with work outstanding fails the run.
44958
+ midSubagentParentExit: backgroundActive && !observed.startupTimedOut && state.exitGraceKillTriggered !== true,
44959
+ abandonedBackgroundWork: backgroundActive,
44925
44960
  activeBackgroundTaskCount: state.activeBackgroundTaskIds?.size ?? 0
44926
44961
  };
44927
44962
  }
@@ -44980,8 +45015,6 @@ function logProcessClose(closing, facts) {
44980
45015
  activeBackgroundTaskCount: facts.activeBackgroundTaskCount,
44981
45016
  activeBackgroundTaskIds: [...state.activeBackgroundTaskIds ?? []].slice(0, 8),
44982
45017
  pendingAskCount: state.pendingAskUserToolIds?.size ?? 0,
44983
- idleTimedOut: observed.idleTimedOut,
44984
- idleTimeoutReason: observed.idleTimeoutReason,
44985
45018
  startupTimedOut: observed.startupTimedOut,
44986
45019
  midSubagentParentExit: facts.midSubagentParentExit,
44987
45020
  runtimeSessionId: state.runtimeSessionId
@@ -45112,28 +45145,30 @@ function failureProblem(closing, verdict, failure2) {
45112
45145
  }
45113
45146
  );
45114
45147
  }
45115
- function summaryFor(state, verdict) {
45148
+ function summaryFor(state, verdict, failureMessage) {
45116
45149
  const reported = state.summary.trim() || state.error?.trim();
45117
45150
  if (reported) return reported;
45118
45151
  if (!verdict.failed) return "Task completed";
45152
+ if (failureMessage?.trim()) return failureMessage.trim();
45119
45153
  return verdict.midSubagentParentExit ? "Background agents interrupted" : "Task failed";
45120
45154
  }
45121
45155
  async function closedRunResult(closing, silentResumeFork) {
45122
45156
  const { options, context, state } = closing;
45123
45157
  const facts = observeClose(closing);
45124
45158
  const diagnostics = logProcessClose(closing, facts);
45125
- if (facts.midSubagentParentExit) settleAbandonedSubagents(closing, diagnostics);
45159
+ if (facts.abandonedBackgroundWork) settleAbandonedSubagents(closing, diagnostics);
45126
45160
  await options.afterExit?.(context, state);
45127
45161
  if (hasRenderableTurn(state)) emitTurnEnd(context, state);
45128
45162
  const verdict = judge(closing, facts);
45129
45163
  if (silentResumeFork && !verdict.failed) return resumeFailedResult(closing);
45130
- const summary = summaryFor(state, verdict);
45131
- if (!verdict.failed) return buildCliResult({ success: true, summary }, options, state);
45164
+ if (!verdict.failed) {
45165
+ return buildCliResult({ success: true, summary: summaryFor(state, verdict) }, options, state);
45166
+ }
45132
45167
  const failure2 = withRequiredResumeId(explainFailure(closing, verdict), context);
45133
45168
  return buildCliResult(
45134
45169
  {
45135
45170
  success: false,
45136
- summary,
45171
+ summary: summaryFor(state, verdict, failure2.error),
45137
45172
  error: failure2.error,
45138
45173
  errorKind: failure2.errorKind,
45139
45174
  recoveryClass: failure2.recoveryClass,
@@ -45525,9 +45560,7 @@ var TEXT_TIER_MESSAGE_ID = "cli-stdout";
45525
45560
  var CLI_STDERR_TAIL_CHARS = 256 * 1024;
45526
45561
  var SPAWN_OUTCOME_TIMEOUT_MS = 2e3;
45527
45562
  var RESULT_EXIT_GRACE_MS = 5e3;
45528
- var IDLE_KILL_EXIT_WAIT_MS = 5e3;
45529
45563
  var DESCENDANT_CLEANUP_MS = 1e3;
45530
- var DEFAULT_RESULT_IDLE_MS = 10 * 60 * 1e3;
45531
45564
  var SPAWN_RETRY_DELAYS_BY_CODE = {
45532
45565
  ETXTBSY: [250, 750, 1500, 3e3, 5e3],
45533
45566
  ENOENT: [250, 750]
@@ -45668,24 +45701,6 @@ var StreamCapture = class {
45668
45701
  emitSessionUpdate(logUpdate(this.options.kind, line), this.context, this.state);
45669
45702
  }
45670
45703
  };
45671
- function idleTimeoutReasonNow(options, state, sawStdoutLine) {
45672
- if (state.pendingAskUserToolIds?.size) return "pending_user_answer";
45673
- if (state.awaitingPostBackgroundSynthesis) return "post_subagent_synthesis";
45674
- if (state.resultDeferredOnBackgroundWork && state.activeBackgroundTaskIds?.size) {
45675
- return "background_task";
45676
- }
45677
- const silenceMs = options.postStartupSilenceTimeoutMs;
45678
- if (silenceMs && sawStdoutLine && typeof state.lastRawOutputAtMs === "number" && Date.now() - state.lastRawOutputAtMs >= silenceMs) {
45679
- return "post_startup_silence";
45680
- }
45681
- return null;
45682
- }
45683
- var IDLE_WAIT_LABELS = {
45684
- background_task: "a background task terminal event",
45685
- post_subagent_synthesis: "post-subagent synthesis result",
45686
- post_startup_silence: "any further output (silent hang)",
45687
- pending_user_answer: "a pending user answer"
45688
- };
45689
45704
  var CliProcessSupervisor = class {
45690
45705
  constructor(options, context, state) {
45691
45706
  this.options = options;
@@ -45698,8 +45713,6 @@ var CliProcessSupervisor = class {
45698
45713
  postExitTimer;
45699
45714
  onAbort;
45700
45715
  startupTimedOut = false;
45701
- idleTimedOut = false;
45702
- idleTimeoutReason = null;
45703
45716
  async run(prompt, args) {
45704
45717
  const child = await this.spawn(args);
45705
45718
  this.streams = new StreamCapture(this.options, child, this.context, this.state);
@@ -45709,8 +45722,6 @@ var CliProcessSupervisor = class {
45709
45722
  return {
45710
45723
  exit: await this.awaitExit(child),
45711
45724
  startupTimedOut: this.startupTimedOut,
45712
- idleTimedOut: this.idleTimedOut,
45713
- idleTimeoutReason: this.idleTimeoutReason,
45714
45725
  stderrTail: this.streams.stderrTail,
45715
45726
  recentStderrLines: this.streams.recentStderrLines
45716
45727
  };
@@ -45795,51 +45806,26 @@ var CliProcessSupervisor = class {
45795
45806
  const exit = this.exitPromise(child);
45796
45807
  return this.options.keepStdinOpen ? this.raceExitAgainstResult(child, exit) : exit;
45797
45808
  }
45809
+ /**
45810
+ * A structured CLI that holds stdin open ends its turn with a `result` frame and exits
45811
+ * once stdin closes. The process exit still decides the run's terminal state, so a
45812
+ * result only sets how long that exit is worth waiting for.
45813
+ */
45798
45814
  async raceExitAgainstResult(child, exit) {
45799
45815
  const resultPromise = new Promise((resolve52) => {
45800
45816
  this.state.onResultReceived = () => resolve52("result_received");
45801
45817
  if (this.state.resultReceived) resolve52("result_received");
45802
45818
  });
45803
- const idleMs = this.options.resultIdleTimeoutMs ?? DEFAULT_RESULT_IDLE_MS;
45804
- let idleTimer = null;
45805
- const idlePromise = new Promise((resolve52) => {
45806
- const armIdleTimer = () => {
45807
- if (idleTimer) clearTimeout(idleTimer);
45808
- idleTimer = setTimeout(() => {
45809
- const reason = idleTimeoutReasonNow(
45810
- this.options,
45811
- this.state,
45812
- this.streams?.sawStdoutLine === true
45813
- );
45814
- if (!reason) return armIdleTimer();
45815
- this.idleTimeoutReason = reason;
45816
- resolve52("idle_timeout");
45817
- }, idleMs);
45818
- };
45819
- this.state.onNewOutput = armIdleTimer;
45820
- armIdleTimer();
45821
- });
45822
- const outcome = await Promise.race([exit, resultPromise, idlePromise]);
45823
- if (idleTimer) clearTimeout(idleTimer);
45824
- if (outcome === "idle_timeout") return this.killAfterIdleTimeout(child, exit, idleMs);
45825
- if (outcome === "result_received") return this.killAfterResult(child, exit);
45826
- return outcome;
45827
- }
45828
- async killAfterIdleTimeout(child, exit, idleMs) {
45829
- this.idleTimedOut = true;
45830
- const waiting = this.idleTimeoutReason ? IDLE_WAIT_LABELS[this.idleTimeoutReason] : "the CLI";
45831
- console.warn(
45832
- `[${this.options.kind}] Idle timeout (${idleMs}ms) waiting for ${waiting} \u2014 force-killing orphaned process`
45833
- );
45834
- killProcessTree(child.pid, { signal: "SIGKILL", child });
45835
- return Promise.race([
45836
- exit,
45837
- new Promise(
45838
- (resolve52) => setTimeout(() => resolve52({ exitCode: null, signal: "SIGKILL" }), IDLE_KILL_EXIT_WAIT_MS)
45839
- )
45840
- ]);
45819
+ const outcome = await Promise.race([exit, resultPromise]);
45820
+ if (outcome !== "result_received") return outcome;
45821
+ return this.closeAfterResult(child, exit);
45841
45822
  }
45842
- async killAfterResult(child, exit) {
45823
+ /**
45824
+ * The turn is already delivered by the time this runs, so a process that will not leave
45825
+ * is cleanup rather than a verdict. Termination is awaited and verified, and recorded as
45826
+ * an expected grace kill so it cannot turn a delivered turn into a failure.
45827
+ */
45828
+ async closeAfterResult(child, exit) {
45843
45829
  const gracedExit = await Promise.race([
45844
45830
  exit,
45845
45831
  new Promise((resolve52) => setTimeout(() => resolve52(null), RESULT_EXIT_GRACE_MS))
@@ -45847,9 +45833,14 @@ var CliProcessSupervisor = class {
45847
45833
  if (gracedExit) return gracedExit;
45848
45834
  if (child.exitCode === null && !child.killed) {
45849
45835
  console.warn(
45850
- `[${this.options.kind}] Process still alive ${RESULT_EXIT_GRACE_MS}ms after result event, killing`
45836
+ `[${this.options.kind}] Process still alive ${RESULT_EXIT_GRACE_MS}ms after result event \u2014 terminating the process tree`
45851
45837
  );
45852
- killProcessTree(child.pid, { signal: "SIGKILL", child });
45838
+ this.state.exitGraceKillTriggered = true;
45839
+ if (!await terminateProcessTree(child)) {
45840
+ console.error(
45841
+ `[${this.options.kind}] Process tree survived termination after result event`
45842
+ );
45843
+ }
45853
45844
  }
45854
45845
  return { exitCode: null, signal: null };
45855
45846
  }
@@ -45891,7 +45882,7 @@ function createCliBackend(options) {
45891
45882
  const observed = await supervisor.run(prompt, resolveArgs(options, context, prompt));
45892
45883
  const closing = { options, context, state, observed };
45893
45884
  const silentResumeFork = detectSilentResumeFork(closing);
45894
- return abortedResult(closing) ?? startupTimeoutResult(closing) ?? await idleTimeoutResult(closing) ?? await closedRunResult(closing, silentResumeFork);
45885
+ return abortedResult(closing) ?? startupTimeoutResult(closing) ?? await closedRunResult(closing, silentResumeFork);
45895
45886
  }
45896
45887
  };
45897
45888
  }
@@ -46501,20 +46492,21 @@ function requireManagedBearerToken(registration) {
46501
46492
  function requireManagedRunId(registration) {
46502
46493
  return requiredHeader(registration, "x-alan-run-id");
46503
46494
  }
46504
- async function withCursorProjectMcpEntry(cwd, url3, run) {
46505
- const path2 = (0, import_path8.join)(cwd, ".cursor", "mcp.json");
46495
+ async function withCommandCodeProjectMcpEntry(cwd, url3, run) {
46496
+ const path2 = (0, import_path8.join)(cwd, ".mcp.json");
46506
46497
  const original = await (0, import_promises3.readFile)(path2, "utf8").catch(() => null);
46507
46498
  const config4 = parseConfig(original);
46508
- const servers = config4.mcpServers ?? {};
46509
- config4.mcpServers = servers;
46499
+ const servers = asRecord2(config4.mcpServers);
46510
46500
  servers.alan = {
46511
- type: "http",
46501
+ transport: "http",
46502
+ enabled: true,
46512
46503
  url: url3,
46513
46504
  headers: {
46514
- Authorization: `\${env:${ALAN_MCP_AUTHORIZATION_ENV}}`,
46515
- "x-alan-run-id": `\${env:${ALAN_RUN_ID_ENV}}`
46505
+ Authorization: `\${${ALAN_MCP_AUTHORIZATION_ENV}}`,
46506
+ "x-alan-run-id": `\${${ALAN_RUN_ID_ENV}}`
46516
46507
  }
46517
46508
  };
46509
+ config4.mcpServers = servers;
46518
46510
  await (0, import_promises3.mkdir)((0, import_path8.dirname)(path2), { recursive: true });
46519
46511
  await (0, import_promises3.writeFile)(path2, `${JSON.stringify(config4, null, 2)}
46520
46512
  `);
@@ -46526,6 +46518,42 @@ async function withCursorProjectMcpEntry(cwd, url3, run) {
46526
46518
  }
46527
46519
  }
46528
46520
  function parseConfig(raw) {
46521
+ if (!raw) return {};
46522
+ try {
46523
+ const parsed = JSON.parse(raw);
46524
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
46525
+ } catch {
46526
+ return {};
46527
+ }
46528
+ }
46529
+ function asRecord2(value2) {
46530
+ return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
46531
+ }
46532
+ async function withCursorProjectMcpEntry(cwd, url3, run) {
46533
+ const path2 = (0, import_path9.join)(cwd, ".cursor", "mcp.json");
46534
+ const original = await (0, import_promises4.readFile)(path2, "utf8").catch(() => null);
46535
+ const config4 = parseConfig2(original);
46536
+ const servers = config4.mcpServers ?? {};
46537
+ config4.mcpServers = servers;
46538
+ servers.alan = {
46539
+ type: "http",
46540
+ url: url3,
46541
+ headers: {
46542
+ Authorization: `\${env:${ALAN_MCP_AUTHORIZATION_ENV}}`,
46543
+ "x-alan-run-id": `\${env:${ALAN_RUN_ID_ENV}}`
46544
+ }
46545
+ };
46546
+ await (0, import_promises4.mkdir)((0, import_path9.dirname)(path2), { recursive: true });
46547
+ await (0, import_promises4.writeFile)(path2, `${JSON.stringify(config4, null, 2)}
46548
+ `);
46549
+ try {
46550
+ return await run();
46551
+ } finally {
46552
+ if (original === null) await (0, import_promises4.rm)(path2, { force: true });
46553
+ else await (0, import_promises4.writeFile)(path2, original);
46554
+ }
46555
+ }
46556
+ function parseConfig2(raw) {
46529
46557
  if (!raw) return {};
46530
46558
  try {
46531
46559
  const parsed = JSON.parse(raw);
@@ -47713,13 +47741,78 @@ async function createSession(baseUrl, context, signal) {
47713
47741
  throw new Error(`${openCodeEngineLabel(context)} session create response had no id`);
47714
47742
  return info.id;
47715
47743
  }
47744
+ function storedPartOf(value2) {
47745
+ const record4 = asRecord(value2);
47746
+ const id = asString(record4?.id);
47747
+ const type = asString(record4?.type);
47748
+ return id && type ? { id, type, text: asString(record4?.text) } : null;
47749
+ }
47750
+ function storedMessageOf(value2) {
47751
+ const record4 = asRecord(value2);
47752
+ const info = asRecord(record4?.info);
47753
+ const id = asString(info?.id);
47754
+ if (!id || !Array.isArray(record4?.parts)) return null;
47755
+ const parts2 = record4.parts.flatMap((part) => storedPartOf(part) ?? []);
47756
+ return { id, role: asString(info?.role), parts: parts2 };
47757
+ }
47758
+ var TEXT_PART_TYPES = /* @__PURE__ */ new Set(["text", "reasoning"]);
47759
+ var STEP_PART_TYPES = /* @__PURE__ */ new Set(["step-start", "step-finish"]);
47760
+ function isBlankTextPart(part) {
47761
+ return TEXT_PART_TYPES.has(part.type) && !part.text.trim();
47762
+ }
47763
+ async function removeBlankAssistantContent(baseUrl, sessionId, signal) {
47764
+ const sessionUrl = `${baseUrl}/session/${encodeURIComponent(sessionId)}`;
47765
+ const response = await fetch(`${sessionUrl}/message`, { signal }).catch(() => null);
47766
+ if (!response?.ok) return 0;
47767
+ const body = await response.json().catch(() => null);
47768
+ if (!Array.isArray(body)) return 0;
47769
+ const deletions = [];
47770
+ for (const message of body.flatMap((entry) => storedMessageOf(entry) ?? [])) {
47771
+ if (message.role !== "assistant") continue;
47772
+ const messageUrl = `${sessionUrl}/message/${encodeURIComponent(message.id)}`;
47773
+ const holdsNothing = message.parts.every(
47774
+ (part) => isBlankTextPart(part) || STEP_PART_TYPES.has(part.type)
47775
+ );
47776
+ if (holdsNothing) {
47777
+ deletions.push(messageUrl);
47778
+ continue;
47779
+ }
47780
+ for (const part of message.parts.filter(isBlankTextPart)) {
47781
+ deletions.push(`${messageUrl}/part/${encodeURIComponent(part.id)}`);
47782
+ }
47783
+ }
47784
+ let removed = 0;
47785
+ for (const url3 of deletions) {
47786
+ const deleted = await fetch(url3, { method: "DELETE", signal }).catch(() => null);
47787
+ if (deleted?.ok) removed += 1;
47788
+ }
47789
+ return removed;
47790
+ }
47791
+ var SESSION_ABORT_TIMEOUT_MS = 1500;
47792
+ async function requestSessionAbort(baseUrl, sessionId) {
47793
+ if (!baseUrl || !sessionId) return;
47794
+ await fetch(`${baseUrl}/session/${encodeURIComponent(sessionId)}/abort`, {
47795
+ method: "POST",
47796
+ signal: AbortSignal.timeout(SESSION_ABORT_TIMEOUT_MS)
47797
+ }).catch(() => void 0);
47798
+ }
47716
47799
  async function resolveSession(baseUrl, context, signal) {
47717
47800
  const resumeId3 = context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim();
47718
47801
  if (!resumeId3) {
47719
47802
  return { id: await createSession(baseUrl, context, signal), resumed: false };
47720
47803
  }
47721
47804
  const existing = await fetch(`${baseUrl}/session/${encodeURIComponent(resumeId3)}`, { signal });
47722
- if (existing.ok) return { id: resumeId3, resumed: true };
47805
+ if (existing.ok) {
47806
+ const removed = await removeBlankAssistantContent(baseUrl, resumeId3, signal);
47807
+ if (removed > 0) {
47808
+ logResumeObservability("agent_resume_history_repaired", {
47809
+ backendKind: context.config.backendKind ?? "opencode_serve",
47810
+ requestedResumeId: resumeId3,
47811
+ removedBlankEntries: removed
47812
+ });
47813
+ }
47814
+ return { id: resumeId3, resumed: true };
47815
+ }
47723
47816
  if (existing.status !== 404) {
47724
47817
  throw new Error(
47725
47818
  `Failed to look up ${openCodeEngineLabel(context)} session ${resumeId3}: HTTP ${existing.status}`
@@ -47932,8 +48025,8 @@ function buildAgentResult(state, sessionId, aborted2, backendKind = "opencode_se
47932
48025
  usage: state.usage
47933
48026
  };
47934
48027
  }
48028
+ const producedOutput = Boolean(state.summary.trim()) || [...state.opencodePartTypeById?.values() ?? []].some((type) => OUTPUT_PART_TYPES.has(type));
47935
48029
  if (!state.error?.trim()) {
47936
- const producedOutput = Boolean(state.summary.trim()) || [...state.opencodePartTypeById?.values() ?? []].some((type) => OUTPUT_PART_TYPES.has(type));
47937
48030
  if (state.opencodeFinishReason === "length") {
47938
48031
  state.error = "The model's response was cut off because it hit the output token limit.";
47939
48032
  state.errorKind = "context_length";
@@ -47973,7 +48066,8 @@ function buildAgentResult(state, sessionId, aborted2, backendKind = "opencode_se
47973
48066
  // Failures arrive via the SSE stream, never a CLI exit.
47974
48067
  phase: "stream",
47975
48068
  finality: "terminal",
47976
- outcomeCertainty: "effect_unknown"
48069
+ // A rejected history fails the first request, so with no output nothing ran.
48070
+ outcomeCertainty: errorKind === "history_rejected" && !producedOutput ? "no_effect" : "effect_unknown"
47977
48071
  }
47978
48072
  );
47979
48073
  }
@@ -48045,17 +48139,19 @@ function createOpencodeServeBackend(command = "opencode", options = PLAIN_OPENCO
48045
48139
  }));
48046
48140
  let sseClose = null;
48047
48141
  let sessionId = "";
48142
+ let serverBaseUrl = null;
48048
48143
  let aborted2 = false;
48049
48144
  const onAbort = () => {
48050
48145
  aborted2 = true;
48051
48146
  sseClose?.();
48052
48147
  if (leaseKey) serveLeases.delete(leaseKey);
48053
- void terminateProcessTree(child);
48148
+ void requestSessionAbort(serverBaseUrl, sessionId).then(() => terminateProcessTree(child));
48054
48149
  };
48055
48150
  context.abortController.signal.addEventListener("abort", onAbort, { once: true });
48056
48151
  if (context.abortController.signal.aborted) onAbort();
48057
48152
  try {
48058
48153
  const server = await ready;
48154
+ serverBaseUrl = server.baseUrl;
48059
48155
  state.lastRawOutputAtMs = Date.now();
48060
48156
  context.registerBackgroundTaskStopper?.(
48061
48157
  (taskId) => stopBackgroundTaskInRun(
@@ -48184,6 +48280,7 @@ var ALAN_CODE_ENGINE_PROVIDER_ID = "alan";
48184
48280
  var ALAN_CODE_API_KEY_ENV = "ALAN_CODE_API_KEY";
48185
48281
  var ALAN_CODE_RUN_ID_ENV = "ALAN_CODE_RUN_ID";
48186
48282
  var ALAN_CODE_PROXY_PATH = "/public/harness/v1";
48283
+ var ALAN_CODE_WEBSEARCH_PATH = "/public/harness/v1/websearch";
48187
48284
  var ALAN_CODE_CATALOG_PATH = "/public/harness/models";
48188
48285
  var CATALOG_TIMEOUT_MS = 5e3;
48189
48286
  function seedAlanCodeModels() {
@@ -48243,21 +48340,133 @@ function resolveAlanCodeModelId(selectedModel2, models) {
48243
48340
  if (requested && models.some((model2) => model2.id === requested)) return requested;
48244
48341
  return models.find((model2) => model2.isDefault)?.id ?? models[0]?.id ?? (requested || DEFAULT_HARNESS_MODEL_ID);
48245
48342
  }
48246
- var OPERATION_KEY_PLUGIN_SOURCE = `
48247
- export const AlanOperationKeyPlugin = async () => ({
48343
+ function enginePluginSource(apiOrigin) {
48344
+ return `
48345
+ const WEB_SEARCH_URL = ${JSON.stringify(`${apiOrigin}${ALAN_CODE_WEBSEARCH_PATH}`)};
48346
+ const API_KEY_ENV = ${JSON.stringify(ALAN_CODE_API_KEY_ENV)};
48347
+ const RUN_ID_ENV = ${JSON.stringify(ALAN_CODE_RUN_ID_ENV)};
48348
+ const PROVIDER_ID = ${JSON.stringify(ALAN_CODE_ENGINE_PROVIDER_ID)};
48349
+ const INCLUDED_RESULTS = 8;
48350
+ const MAX_AUTO_RETRIES = 2;
48351
+ const MAX_AUTO_RETRY_WAIT_MS = 10_000;
48352
+
48353
+ function truncate(text, max) {
48354
+ const trimmed = String(text == null ? "" : text).trim();
48355
+ return trimmed.length <= max ? trimmed : trimmed.slice(0, max - 1) + "\u2026";
48356
+ }
48357
+
48358
+ function formatResults(results) {
48359
+ const lines = [];
48360
+ results.slice(0, INCLUDED_RESULTS).forEach(function (item, index) {
48361
+ const title = (item.title || "").trim() || (item.url || "").trim() || "Result " + (index + 1);
48362
+ lines.push(index + 1 + ". " + title);
48363
+ if (item.url) lines.push(" " + item.url);
48364
+ const excerpt = Array.isArray(item.excerpts)
48365
+ ? item.excerpts.find(function (part) { return part && part.trim(); })
48366
+ : "";
48367
+ if (excerpt) lines.push(" " + truncate(excerpt, 400));
48368
+ });
48369
+ return lines.join("\\n");
48370
+ }
48371
+
48372
+ /** Mirrors the harness CLI: say what the agent can do next instead of just failing. */
48373
+ function describeRefusal(refusal, status, text) {
48374
+ const code = refusal && typeof refusal.error === "string" ? refusal.error : "";
48375
+ const message = refusal && typeof refusal.message === "string" ? refusal.message : truncate(text, 300);
48376
+ const reset = refusal && typeof refusal.resetsAt === "string" ? refusal.resetsAt.slice(0, 10) : "";
48377
+ if (code === "quota_exceeded") {
48378
+ return "Web search allowance exhausted" + (reset ? "; it resets " + reset : "") + ". Do not retry \u2014 continue without search or tell the user.";
48379
+ }
48380
+ if (code === "search_daily_limit") {
48381
+ return "Daily web search limit reached; it resets at 00:00 UTC. Continue without search rather than retrying.";
48382
+ }
48383
+ if (code === "billing_review_required" || code === "not_a_member" || code === "seat_required" || code.indexOf("entitlement") === 0) {
48384
+ return "Web search is not available for this account (" + message + "). Do not retry \u2014 report it to the user.";
48385
+ }
48386
+ if (refusal && refusal.retryable === true) {
48387
+ return "Web search is temporarily unavailable (" + message + "). Retry shortly.";
48388
+ }
48389
+ return "Web search failed (" + status + "): " + message;
48390
+ }
48391
+
48392
+ async function callWebSearch(query, signal) {
48393
+ const apiKey = process.env[API_KEY_ENV];
48394
+ if (!apiKey) {
48395
+ return { title: "web_search unavailable", output: "Web search is unavailable: this run has no Alan credential." };
48396
+ }
48397
+ const headers = { Authorization: "Bearer " + apiKey, "Content-Type": "application/json" };
48398
+ if (process.env[RUN_ID_ENV]) headers["x-alan-run-id"] = process.env[RUN_ID_ENV];
48399
+
48400
+ for (let attempt = 0; ; attempt += 1) {
48401
+ const response = await fetch(WEB_SEARCH_URL, {
48402
+ method: "POST",
48403
+ headers: headers,
48404
+ body: JSON.stringify({ query: query }),
48405
+ signal: signal,
48406
+ });
48407
+ const text = await response.text();
48408
+ if (response.ok) {
48409
+ let parsed;
48410
+ try {
48411
+ parsed = JSON.parse(text);
48412
+ } catch {
48413
+ return { title: "web_search", output: "Web search returned an unreadable response." };
48414
+ }
48415
+ const results = Array.isArray(parsed.results) ? parsed.results : [];
48416
+ if (results.length === 0) {
48417
+ return { title: "web_search: " + query, output: 'No results for "' + query + '". Try a tighter query, then webfetch promising URLs.' };
48418
+ }
48419
+ return {
48420
+ title: "web_search: " + query,
48421
+ output: [
48422
+ 'Web search (Alan) for "' + query + '":',
48423
+ formatResults(results),
48424
+ "",
48425
+ "Next: pick the best URL(s) and call webfetch to read the page. Cite title + URL.",
48426
+ ].join("\\n"),
48427
+ metadata: { resultCount: results.length, searchId: parsed.search_id },
48428
+ };
48429
+ }
48430
+
48431
+ let refusal;
48432
+ try { refusal = JSON.parse(text); } catch { refusal = undefined; }
48433
+ const waitMs = refusal && typeof refusal.retryAfterSeconds === "number" ? refusal.retryAfterSeconds * 1000 : 0;
48434
+ if (refusal && refusal.retryable === true && waitMs > 0 && waitMs <= MAX_AUTO_RETRY_WAIT_MS && attempt < MAX_AUTO_RETRIES) {
48435
+ await new Promise(function (resolve) { setTimeout(resolve, waitMs); });
48436
+ continue;
48437
+ }
48438
+ return { title: "web_search " + response.status, output: describeRefusal(refusal, response.status, text) };
48439
+ }
48440
+ }
48441
+
48442
+ export const AlanEnginePlugin = async () => ({
48248
48443
  "chat.headers": async (input, output) => {
48249
- if (input.model.providerID === "${ALAN_CODE_ENGINE_PROVIDER_ID}") {
48444
+ if (input.model.providerID === PROVIDER_ID) {
48250
48445
  output.headers["x-alan-operation-key"] = Date.now() + "-" + crypto.randomUUID()
48251
48446
  }
48252
48447
  },
48448
+ tool: {
48449
+ web_search: {
48450
+ description: "web_search: Search the public web via Alan's Parallel proxy. Use when you do not already have a URL. Follow up with webfetch on the best result(s).",
48451
+ args: {
48452
+ query: { type: "string", description: "Search query / objective" },
48453
+ },
48454
+ async execute(args, context) {
48455
+ const query = String((args && args.query) || "").trim();
48456
+ if (!query) return { title: "web_search", output: "web_search requires a non-empty query." };
48457
+ return callWebSearch(query, context && context.abort);
48458
+ },
48459
+ },
48460
+ },
48253
48461
  })
48254
48462
  `;
48255
- function materializeOperationKeyPlugin(directory = (0, import_os4.tmpdir)()) {
48256
- const dir = (0, import_path9.join)(directory, "alan-code");
48257
- const target = (0, import_path9.join)(dir, "operation-key-v1.mjs");
48463
+ }
48464
+ function materializeEnginePlugin(directory = (0, import_os4.tmpdir)(), apiOrigin) {
48465
+ const dir = (0, import_path10.join)(directory, "alan-code");
48466
+ const target = (0, import_path10.join)(dir, "engine-plugin-v1.mjs");
48258
48467
  const temporary = `${target}.${process.pid}.${(0, import_crypto5.randomUUID)()}.tmp`;
48259
48468
  (0, import_fs9.mkdirSync)(dir, { recursive: true, mode: 448 });
48260
- (0, import_fs9.writeFileSync)(temporary, OPERATION_KEY_PLUGIN_SOURCE, { mode: 384 });
48469
+ (0, import_fs9.writeFileSync)(temporary, enginePluginSource(apiOrigin), { mode: 384 });
48261
48470
  (0, import_fs9.renameSync)(temporary, target);
48262
48471
  return (0, import_url2.pathToFileURL)(target).href;
48263
48472
  }
@@ -48321,15 +48530,15 @@ function buildAlanCodeConfigContent(existing, input2) {
48321
48530
  }
48322
48531
  function alanCodeEngineDirectories(root) {
48323
48532
  const directories = {
48324
- XDG_DATA_HOME: (0, import_path9.join)(root, "share"),
48325
- XDG_STATE_HOME: (0, import_path9.join)(root, "state"),
48326
- XDG_CACHE_HOME: (0, import_path9.join)(root, "cache")
48533
+ XDG_DATA_HOME: (0, import_path10.join)(root, "share"),
48534
+ XDG_STATE_HOME: (0, import_path10.join)(root, "state"),
48535
+ XDG_CACHE_HOME: (0, import_path10.join)(root, "cache")
48327
48536
  };
48328
48537
  for (const directory of Object.values(directories)) (0, import_fs9.mkdirSync)(directory, { recursive: true });
48329
48538
  return directories;
48330
48539
  }
48331
48540
  function defaultEngineRoot(env) {
48332
- return (0, import_path9.join)(env.HOME?.trim() || (0, import_os4.homedir)(), ".alan", "alan-code", "engine");
48541
+ return (0, import_path10.join)(env.HOME?.trim() || (0, import_os4.homedir)(), ".alan", "alan-code", "engine");
48333
48542
  }
48334
48543
  async function withAlanCodeEngine(context, dependencies = {}) {
48335
48544
  const registration = context.config.alanMcp;
@@ -48356,7 +48565,7 @@ async function withAlanCodeEngine(context, dependencies = {}) {
48356
48565
  baseURL: `${apiOrigin}${ALAN_CODE_PROXY_PATH}`,
48357
48566
  modelId,
48358
48567
  models,
48359
- pluginUrl: materializeOperationKeyPlugin(dependencies.pluginDirectory)
48568
+ pluginUrl: materializeEnginePlugin(dependencies.pluginDirectory, apiOrigin)
48360
48569
  })
48361
48570
  }
48362
48571
  };
@@ -49170,7 +49379,7 @@ var TranscriptTail = class {
49170
49379
  const wanted = /* @__PURE__ */ new Set();
49171
49380
  for (const path2 of this.options.transcriptPaths()) {
49172
49381
  if (!path2) continue;
49173
- wanted.add(this.armFile(path2) ? path2 : (0, import_path11.dirname)(path2));
49382
+ wanted.add(this.armFile(path2) ? path2 : (0, import_path12.dirname)(path2));
49174
49383
  }
49175
49384
  for (const target of wanted) this.arm(target, false);
49176
49385
  for (const target of this.watchers.keys()) {
@@ -49223,10 +49432,10 @@ var ANTIGRAVITY_TOOL_RESULT_TYPES = /* @__PURE__ */ new Set([
49223
49432
  ]);
49224
49433
  function antigravityTranscriptRoot(context) {
49225
49434
  const home = context.env.HOME || process.env.HOME || (0, import_os5.homedir)();
49226
- return (0, import_path10.resolve)(home, ".gemini", "antigravity-cli", "brain");
49435
+ return (0, import_path11.resolve)(home, ".gemini", "antigravity-cli", "brain");
49227
49436
  }
49228
49437
  function parentTranscriptPath(conversationId, context) {
49229
- return (0, import_path10.resolve)(
49438
+ return (0, import_path11.resolve)(
49230
49439
  antigravityTranscriptRoot(context),
49231
49440
  conversationId,
49232
49441
  ".system_generated",
@@ -49241,10 +49450,10 @@ function childTranscriptPath(event, context) {
49241
49450
  try {
49242
49451
  const url3 = new URL(logUri);
49243
49452
  if (url3.protocol !== "file:") return void 0;
49244
- const transcriptPath2 = (0, import_path10.resolve)((0, import_url3.fileURLToPath)(url3));
49245
- const pathFromRoot = (0, import_path10.relative)(antigravityTranscriptRoot(context), transcriptPath2);
49246
- const segments = pathFromRoot.split(import_path10.sep);
49247
- if (!pathFromRoot || pathFromRoot === ".." || pathFromRoot.startsWith(`..${import_path10.sep}`) || (0, import_path10.isAbsolute)(pathFromRoot) || segments[0] !== conversationId || segments.at(-1) !== "transcript.jsonl" || segments.at(-2) !== "logs" || segments.at(-3) !== ".system_generated") {
49453
+ const transcriptPath2 = (0, import_path11.resolve)((0, import_url3.fileURLToPath)(url3));
49454
+ const pathFromRoot = (0, import_path11.relative)(antigravityTranscriptRoot(context), transcriptPath2);
49455
+ const segments = pathFromRoot.split(import_path11.sep);
49456
+ if (!pathFromRoot || pathFromRoot === ".." || pathFromRoot.startsWith(`..${import_path11.sep}`) || (0, import_path11.isAbsolute)(pathFromRoot) || segments[0] !== conversationId || segments.at(-1) !== "transcript.jsonl" || segments.at(-2) !== "logs" || segments.at(-3) !== ".system_generated") {
49248
49457
  return void 0;
49249
49458
  }
49250
49459
  return transcriptPath2;
@@ -49260,7 +49469,7 @@ function parseTranscriptEntry(text2) {
49260
49469
  }
49261
49470
  }
49262
49471
  function fullTranscriptPath(transcriptPath2) {
49263
- return (0, import_path10.resolve)((0, import_path10.dirname)(transcriptPath2), FULL_TRANSCRIPT_FILENAME);
49472
+ return (0, import_path11.resolve)((0, import_path11.dirname)(transcriptPath2), FULL_TRANSCRIPT_FILENAME);
49264
49473
  }
49265
49474
  function resolveTruncatedEntry(entry, lineIndex, transcriptPath2, full) {
49266
49475
  const truncated = Array.isArray(entry.truncated_fields) ? entry.truncated_fields : [];
@@ -51244,18 +51453,18 @@ function recoverCodexFileChange(state, item) {
51244
51453
  var DAY_MS = 24 * 60 * 60 * 1e3;
51245
51454
  var MAX_HEADER_BYTES = 1024 * 1024;
51246
51455
  function workerDirectories(rootLog) {
51247
- const directory = (0, import_path13.dirname)(rootLog);
51248
- const monthDirectory = (0, import_path13.dirname)(directory);
51249
- const yearDirectory = (0, import_path13.dirname)(monthDirectory);
51250
- const date5 = `${(0, import_path13.basename)(yearDirectory)}-${(0, import_path13.basename)(monthDirectory)}-${(0, import_path13.basename)(directory)}`;
51456
+ const directory = (0, import_path14.dirname)(rootLog);
51457
+ const monthDirectory = (0, import_path14.dirname)(directory);
51458
+ const yearDirectory = (0, import_path14.dirname)(monthDirectory);
51459
+ const date5 = `${(0, import_path14.basename)(yearDirectory)}-${(0, import_path14.basename)(monthDirectory)}-${(0, import_path14.basename)(directory)}`;
51251
51460
  if (!/^\d{4}-\d{2}-\d{2}$/.test(date5)) return [directory];
51252
51461
  const timestamp = Date.parse(`${date5}T00:00:00Z`);
51253
51462
  if (!Number.isFinite(timestamp) || new Date(timestamp).toISOString().slice(0, 10) !== date5)
51254
51463
  return [directory];
51255
- const sessionsDirectory2 = (0, import_path13.dirname)(yearDirectory);
51464
+ const sessionsDirectory2 = (0, import_path14.dirname)(yearDirectory);
51256
51465
  return [0, 1, -1].map((offset2) => {
51257
51466
  const day = new Date(timestamp + offset2 * DAY_MS).toISOString().slice(0, 10);
51258
- return (0, import_path13.join)(sessionsDirectory2, ...day.split("-"));
51467
+ return (0, import_path14.join)(sessionsDirectory2, ...day.split("-"));
51259
51468
  });
51260
51469
  }
51261
51470
  function firstLine(path2) {
@@ -51292,7 +51501,7 @@ function findCodexWorkerLogs(rootLog, parentThreadId) {
51292
51501
  }
51293
51502
  for (const entry of entries) {
51294
51503
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
51295
- const path2 = (0, import_path13.join)(directory, entry.name);
51504
+ const path2 = (0, import_path14.join)(directory, entry.name);
51296
51505
  if (path2 === rootLog) continue;
51297
51506
  try {
51298
51507
  const line = firstLine(path2);
@@ -51320,7 +51529,7 @@ function findCodexWorkerLogs(rootLog, parentThreadId) {
51320
51529
  return workers;
51321
51530
  }
51322
51531
  function resolveCodexHome(env) {
51323
- return env.CODEX_HOME || process.env.CODEX_HOME || (0, import_path12.join)((0, import_os6.homedir)(), ".codex");
51532
+ return env.CODEX_HOME || process.env.CODEX_HOME || (0, import_path13.join)((0, import_os6.homedir)(), ".codex");
51324
51533
  }
51325
51534
  function mtimeOf(path2) {
51326
51535
  try {
@@ -51334,11 +51543,11 @@ function newestMatchIn(dir, entries, sessionId) {
51334
51543
  (entry) => entry.isFile() && entry.name.endsWith(".jsonl") && entry.name.includes(sessionId)
51335
51544
  );
51336
51545
  if (matches.length === 0) return null;
51337
- if (matches.length === 1) return (0, import_path12.join)(dir, matches[0].name);
51546
+ if (matches.length === 1) return (0, import_path13.join)(dir, matches[0].name);
51338
51547
  let best = "";
51339
51548
  let bestMtime = -1;
51340
51549
  for (const entry of matches) {
51341
- const path2 = (0, import_path12.join)(dir, entry.name);
51550
+ const path2 = (0, import_path13.join)(dir, entry.name);
51342
51551
  const mtime = mtimeOf(path2);
51343
51552
  if (mtime > bestMtime) {
51344
51553
  best = path2;
@@ -51349,7 +51558,7 @@ function newestMatchIn(dir, entries, sessionId) {
51349
51558
  }
51350
51559
  function findCodexSessionLog(runtimeSessionId, codexHome) {
51351
51560
  if (!runtimeSessionId) return null;
51352
- const stack = [(0, import_path12.join)(codexHome, "sessions")];
51561
+ const stack = [(0, import_path13.join)(codexHome, "sessions")];
51353
51562
  while (stack.length > 0) {
51354
51563
  const dir = stack.pop();
51355
51564
  let entries;
@@ -51362,7 +51571,7 @@ function findCodexSessionLog(runtimeSessionId, codexHome) {
51362
51571
  if (match) return match;
51363
51572
  const subdirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
51364
51573
  subdirs.sort();
51365
- for (const name of subdirs) stack.push((0, import_path12.join)(dir, name));
51574
+ for (const name of subdirs) stack.push((0, import_path13.join)(dir, name));
51366
51575
  }
51367
51576
  return null;
51368
51577
  }
@@ -53028,6 +53237,7 @@ var COMMAND_CODE_GRAMMAR_NAME = "command_code_cli";
53028
53237
  var TEXT_MESSAGE_KEY = "commandCode.textMessageId";
53029
53238
  var THINKING_MESSAGE_KEY = "commandCode.thinkingMessageId";
53030
53239
  var ANONYMOUS_TOOL_KEY2 = "commandCode.lastAnonymousToolCallId";
53240
+ var ANSWER_MESSAGE_KEY = "commandCode.answerMessageId";
53031
53241
  function commandCodeString(value2) {
53032
53242
  return typeof value2 === "string" && value2.trim().length > 0 ? value2.trim() : "";
53033
53243
  }
@@ -53116,10 +53326,12 @@ function parseEvent2(event, state) {
53116
53326
  case "text_delta": {
53117
53327
  const delta = typeof event.delta === "string" ? event.delta : "";
53118
53328
  if (!delta) return [];
53329
+ const messageId = latchedMessageId(state, TEXT_MESSAGE_KEY, "command-code-msg");
53330
+ state.scratch[ANSWER_MESSAGE_KEY] = messageId;
53119
53331
  return [
53120
53332
  {
53121
53333
  sessionUpdate: "agent_message_chunk",
53122
- messageId: latchedMessageId(state, TEXT_MESSAGE_KEY, "command-code-msg"),
53334
+ messageId,
53123
53335
  content: textBlock(delta)
53124
53336
  }
53125
53337
  ];
@@ -53127,6 +53339,7 @@ function parseEvent2(event, state) {
53127
53339
  case "message_update":
53128
53340
  return parseMessageUpdate(event, state);
53129
53341
  case "tool_running":
53342
+ delete state.scratch[ANSWER_MESSAGE_KEY];
53130
53343
  return [
53131
53344
  toolCallStart(
53132
53345
  resolveToolCallId(commandCodeString(event.toolCallId), state),
@@ -53209,13 +53422,22 @@ function parseResult2(event, state) {
53209
53422
  const sessionId = commandCodeString(event.sessionId);
53210
53423
  if (sessionId) state.sessionId = sessionId;
53211
53424
  const subtype = commandCodeString(event.subtype);
53212
- return [
53213
- {
53214
- sessionUpdate: "state_update",
53215
- state: "idle",
53216
- ...subtype === "error" ? {} : { stopReason: "end_turn" }
53217
- }
53218
- ];
53425
+ const updates = [];
53426
+ const finalText = commandCodeString(event.finalText);
53427
+ const answerId = state.scratch[ANSWER_MESSAGE_KEY];
53428
+ if (finalText && typeof answerId === "string" && answerId) {
53429
+ updates.push({
53430
+ sessionUpdate: "agent_message",
53431
+ messageId: answerId,
53432
+ content: [textBlock(finalText)]
53433
+ });
53434
+ }
53435
+ updates.push({
53436
+ sessionUpdate: "state_update",
53437
+ state: "idle",
53438
+ ...subtype === "error" ? {} : { stopReason: "end_turn" }
53439
+ });
53440
+ return updates;
53219
53441
  }
53220
53442
  var backgroundStates = /* @__PURE__ */ new WeakMap();
53221
53443
  function backgroundStateFor(state) {
@@ -53483,39 +53705,49 @@ function createCommandCodeCliBackend(command = "command-code", defaultArgs = [])
53483
53705
  context.config.images,
53484
53706
  context.cwd
53485
53707
  );
53708
+ const managed = context.config.alanMcp;
53709
+ const runContext = managed ? {
53710
+ ...context,
53711
+ env: {
53712
+ ...context.env,
53713
+ [ALAN_MCP_AUTHORIZATION_ENV]: requireManagedAuthorization(managed),
53714
+ [ALAN_RUN_ID_ENV]: requireManagedRunId(managed)
53715
+ }
53716
+ } : context;
53717
+ const runBackend = () => createCliBackend({
53718
+ kind: "command_code_cli",
53719
+ supportTier: "structured",
53720
+ command,
53721
+ args: [],
53722
+ buildArgs: (ctx, prompt) => {
53723
+ const args = [
53724
+ "-p",
53725
+ "--output-format",
53726
+ "json",
53727
+ "--skip-onboarding",
53728
+ "--trust",
53729
+ "--no-auto-update",
53730
+ ...commandCodePermissionArgs(ctx.config),
53731
+ ...defaultArgs
53732
+ ];
53733
+ const resumeId3 = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
53734
+ if (resumeId3) args.push("--resume", resumeId3);
53735
+ const model2 = ctx.config.selectedModel?.trim();
53736
+ if (model2) args.push("--model", model2);
53737
+ const effort3 = ctx.config.selectedEffortLevel?.trim();
53738
+ if (effort3) args.push("--effort", effort3);
53739
+ args.push(prompt);
53740
+ return args;
53741
+ },
53742
+ augmentPrompt: (ctx) => buildPromptWithImagePathReferences(ctx, imageFiles),
53743
+ parseStructuredLine: grammarAsStructuredLineParser(
53744
+ commandCodeGrammar,
53745
+ commandCodeEffects
53746
+ ),
53747
+ keepStdinOpen: true
53748
+ }).run(runContext);
53486
53749
  try {
53487
- return await createCliBackend({
53488
- kind: "command_code_cli",
53489
- supportTier: "structured",
53490
- command,
53491
- args: [],
53492
- buildArgs: (ctx, prompt) => {
53493
- const args = [
53494
- "-p",
53495
- "--output-format",
53496
- "json",
53497
- "--skip-onboarding",
53498
- "--trust",
53499
- "--no-auto-update",
53500
- ...commandCodePermissionArgs(ctx.config),
53501
- ...defaultArgs
53502
- ];
53503
- const resumeId3 = ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim();
53504
- if (resumeId3) args.push("--resume", resumeId3);
53505
- const model2 = ctx.config.selectedModel?.trim();
53506
- if (model2) args.push("--model", model2);
53507
- const effort3 = ctx.config.selectedEffortLevel?.trim();
53508
- if (effort3) args.push("--effort", effort3);
53509
- args.push(prompt);
53510
- return args;
53511
- },
53512
- augmentPrompt: (ctx) => buildPromptWithImagePathReferences(ctx, imageFiles),
53513
- parseStructuredLine: grammarAsStructuredLineParser(
53514
- commandCodeGrammar,
53515
- commandCodeEffects
53516
- ),
53517
- keepStdinOpen: true
53518
- }).run(context);
53750
+ return await (managed ? withCommandCodeProjectMcpEntry(context.cwd, managed.url, runBackend) : runBackend());
53519
53751
  } finally {
53520
53752
  cleanup();
53521
53753
  }
@@ -54001,7 +54233,27 @@ function activeTaskIdNamedIn(state, content) {
54001
54233
  }
54002
54234
  return void 0;
54003
54235
  }
54004
- function buildCopilotCliArgs(context, prompt, defaultArgs = []) {
54236
+ function writeCopilotMcpConfig(registration) {
54237
+ if (!registration) return null;
54238
+ const dir = (0, import_fs15.mkdtempSync)((0, import_path15.join)((0, import_os7.tmpdir)(), "alan-copilot-mcp-"));
54239
+ const path2 = (0, import_path15.join)(dir, "mcp-config.json");
54240
+ (0, import_fs15.writeFileSync)(
54241
+ path2,
54242
+ JSON.stringify({
54243
+ mcpServers: {
54244
+ [MANAGED_MCP_SERVER_NAME]: {
54245
+ type: "http",
54246
+ url: registration.url,
54247
+ tools: ["*"],
54248
+ headers: registration.headers
54249
+ }
54250
+ }
54251
+ }),
54252
+ { mode: 384 }
54253
+ );
54254
+ return { path: path2, cleanup: () => (0, import_fs15.rmSync)(dir, { recursive: true, force: true }) };
54255
+ }
54256
+ function buildCopilotCliArgs(context, prompt, defaultArgs = [], options = {}) {
54005
54257
  const args = [
54006
54258
  "--yolo",
54007
54259
  "-s",
@@ -54029,6 +54281,9 @@ function buildCopilotCliArgs(context, prompt, defaultArgs = []) {
54029
54281
  args.push("--effort", effort3);
54030
54282
  }
54031
54283
  }
54284
+ if (options.additionalMcpConfigPath) {
54285
+ args.push("--additional-mcp-config", `@${options.additionalMcpConfigPath}`);
54286
+ }
54032
54287
  args.push("-p", prompt);
54033
54288
  return [...args, ...defaultArgs];
54034
54289
  }
@@ -54041,19 +54296,23 @@ function createCopilotCliBackend(command = "copilot", defaultArgs = []) {
54041
54296
  context.config.images,
54042
54297
  context.cwd
54043
54298
  );
54299
+ const mcpConfig = writeCopilotMcpConfig(context.config.alanMcp);
54044
54300
  try {
54045
54301
  return await createCliBackend({
54046
54302
  kind: "copilot_cli",
54047
54303
  supportTier: "structured",
54048
54304
  command,
54049
54305
  args: [],
54050
- buildArgs: (ctx, prompt) => buildCopilotCliArgs(ctx, prompt, defaultArgs),
54306
+ buildArgs: (ctx, prompt) => buildCopilotCliArgs(ctx, prompt, defaultArgs, {
54307
+ additionalMcpConfigPath: mcpConfig?.path
54308
+ }),
54051
54309
  augmentPrompt: (ctx) => buildPromptWithImagePathReferences(ctx, imageFiles),
54052
54310
  parseStructuredLine: grammarAsStructuredLineParser(copilotGrammar, copilotEffects),
54053
54311
  // Held open so a deferred `result` can wait for background work to drain.
54054
54312
  keepStdinOpen: true
54055
54313
  }).run(context);
54056
54314
  } finally {
54315
+ mcpConfig?.cleanup();
54057
54316
  cleanup();
54058
54317
  }
54059
54318
  }
@@ -54600,11 +54859,11 @@ function cursorConversationStepText(steps) {
54600
54859
  }
54601
54860
  function cursorTranscriptsRoot(context) {
54602
54861
  const projectKey = context.cwd.replace(/^[/\\]+/, "").replace(/[/\\]+/g, "-");
54603
- const home = context.env.HOME || process.env.HOME || (0, import_os7.homedir)();
54604
- return (0, import_path14.join)(home, ".cursor", "projects", projectKey, "agent-transcripts");
54862
+ const home = context.env.HOME || process.env.HOME || (0, import_os8.homedir)();
54863
+ return (0, import_path16.join)(home, ".cursor", "projects", projectKey, "agent-transcripts");
54605
54864
  }
54606
54865
  function cursorTranscriptPathForAgent(context, agentId) {
54607
- return (0, import_path14.join)(cursorTranscriptsRoot(context), agentId, `${agentId}.jsonl`);
54866
+ return (0, import_path16.join)(cursorTranscriptsRoot(context), agentId, `${agentId}.jsonl`);
54608
54867
  }
54609
54868
  function resolveCursorSubagentTranscriptPath(success2, context) {
54610
54869
  const explicit = success2?.transcriptPath ?? success2?.transcript_path;
@@ -54629,13 +54888,13 @@ function cursorTranscriptUserText(transcriptPath2) {
54629
54888
  }
54630
54889
  function discoverCursorSubagentTranscript(context, state, task) {
54631
54890
  const root = cursorTranscriptsRoot(context);
54632
- if (!(0, import_fs15.existsSync)(root)) return void 0;
54891
+ if (!(0, import_fs16.existsSync)(root)) return void 0;
54633
54892
  const prompt = task.prompt?.trim();
54634
54893
  const description = task.description?.trim().toLowerCase();
54635
54894
  const candidates = [];
54636
54895
  let entries;
54637
54896
  try {
54638
- entries = (0, import_fs15.readdirSync)(root, { withFileTypes: true });
54897
+ entries = (0, import_fs16.readdirSync)(root, { withFileTypes: true });
54639
54898
  } catch {
54640
54899
  return void 0;
54641
54900
  }
@@ -54644,7 +54903,7 @@ function discoverCursorSubagentTranscript(context, state, task) {
54644
54903
  const path2 = cursorTranscriptPathForAgent(context, entry.name);
54645
54904
  let mtimeMs;
54646
54905
  try {
54647
- mtimeMs = (0, import_fs15.statSync)(path2).mtimeMs;
54906
+ mtimeMs = (0, import_fs16.statSync)(path2).mtimeMs;
54648
54907
  } catch {
54649
54908
  continue;
54650
54909
  }
@@ -55072,10 +55331,6 @@ function createCursorAgentCliBackend(command = "cursor-agent", defaultArgs = [])
55072
55331
  closeStdinAfterPrompt: true,
55073
55332
  // cursor-agent headless can hang at startup with zero output.
55074
55333
  startupTimeoutMs: 18e4,
55075
- // Its asks never fire the idle timer's conditions, so silence after output
55076
- // is otherwise bounded only by the ~30-min external reaper.
55077
- resultIdleTimeoutMs: 5 * 6e4,
55078
- postStartupSilenceTimeoutMs: 15 * 6e4,
55079
55334
  augmentPrompt: (ctx) => {
55080
55335
  const isResume = Boolean(
55081
55336
  ctx.config.runtimeSessionId?.trim() || ctx.config.providerSessionId?.trim()
@@ -55893,6 +56148,17 @@ var GENERIC_GROK_RPC_ERRORS = /* @__PURE__ */ new Set([
55893
56148
  "grok agent error",
55894
56149
  "grok build run failed"
55895
56150
  ]);
56151
+ function buildGrokAcpMcpServers(registration) {
56152
+ if (!registration) return [];
56153
+ return [
56154
+ {
56155
+ name: MANAGED_MCP_SERVER_NAME,
56156
+ type: "http",
56157
+ url: registration.url,
56158
+ headers: Object.entries(registration.headers).map(([name, value2]) => ({ name, value: value2 }))
56159
+ }
56160
+ ];
56161
+ }
55896
56162
  function resumeSessionId(context) {
55897
56163
  return context.config.runtimeSessionId?.trim() || context.config.providerSessionId?.trim() || "";
55898
56164
  }
@@ -55941,8 +56207,9 @@ function toolSettlement(toolCallId, state) {
55941
56207
  return hasUntrackedBackgroundWork(state) ? "drain" : "self";
55942
56208
  }
55943
56209
  var GrokAcpSession = class {
55944
- constructor(imageFiles) {
56210
+ constructor(imageFiles, alanMcp) {
55945
56211
  this.imageFiles = imageFiles;
56212
+ this.alanMcp = alanMcp;
55946
56213
  }
55947
56214
  stdin = null;
55948
56215
  nextId = 0;
@@ -55983,7 +56250,10 @@ var GrokAcpSession = class {
55983
56250
  }
55984
56251
  newSession(context) {
55985
56252
  this.attemptedNewSession = true;
55986
- this.sessionOpId = this.request("session/new", { cwd: context.cwd, mcpServers: [] });
56253
+ this.sessionOpId = this.request("session/new", {
56254
+ cwd: context.cwd,
56255
+ mcpServers: buildGrokAcpMcpServers(this.alanMcp)
56256
+ });
55987
56257
  }
55988
56258
  beginSession(context, state) {
55989
56259
  const resume = resumeSessionId(context);
@@ -55995,7 +56265,7 @@ var GrokAcpSession = class {
55995
56265
  this.sessionOpId = this.request("session/load", {
55996
56266
  sessionId: resume,
55997
56267
  cwd: context.cwd,
55998
- mcpServers: []
56268
+ mcpServers: buildGrokAcpMcpServers(this.alanMcp)
55999
56269
  });
56000
56270
  }
56001
56271
  sendPrompt(context, state) {
@@ -56316,8 +56586,8 @@ var GrokAcpSession = class {
56316
56586
  });
56317
56587
  }
56318
56588
  };
56319
- function createGrokAcpRuntime(imageFiles) {
56320
- const session = new GrokAcpSession(imageFiles);
56589
+ function createGrokAcpRuntime(imageFiles, alanMcp) {
56590
+ const session = new GrokAcpSession(imageFiles, alanMcp);
56321
56591
  const effects = { lifecycle: session.lifecycle };
56322
56592
  return {
56323
56593
  start: (stdinStream) => session.start(stdinStream),
@@ -56353,7 +56623,7 @@ function createGrokCliBackend(command = "grok", defaultArgs = []) {
56353
56623
  context.config.images,
56354
56624
  context.cwd
56355
56625
  );
56356
- const runtime = createGrokAcpRuntime(imageFiles);
56626
+ const runtime = createGrokAcpRuntime(imageFiles, context.config.alanMcp);
56357
56627
  try {
56358
56628
  return await createCliBackend({
56359
56629
  kind: "grok_cli",
@@ -56922,10 +57192,10 @@ function createKimiCliBackend(command = "kimi", defaultArgs = []) {
56922
57192
  async run(context) {
56923
57193
  const dialect = await detectKimiCliDialect(command, context);
56924
57194
  const registration = dialect === "kimi-cli" ? context.config.alanMcp : void 0;
56925
- const mcpDirectory = registration ? (0, import_fs16.mkdtempSync)((0, import_path15.join)((0, import_os8.tmpdir)(), "alan-kimi-mcp-")) : null;
56926
- const mcpConfigPath = mcpDirectory ? (0, import_path15.join)(mcpDirectory, "mcp.json") : null;
57195
+ const mcpDirectory = registration ? (0, import_fs17.mkdtempSync)((0, import_path17.join)((0, import_os9.tmpdir)(), "alan-kimi-mcp-")) : null;
57196
+ const mcpConfigPath = mcpDirectory ? (0, import_path17.join)(mcpDirectory, "mcp.json") : null;
56927
57197
  if (registration && mcpConfigPath) {
56928
- (0, import_fs16.writeFileSync)(
57198
+ (0, import_fs17.writeFileSync)(
56929
57199
  mcpConfigPath,
56930
57200
  JSON.stringify(
56931
57201
  buildKimiMcpConfig(registration, selectExternalMcpServers(context.config))
@@ -56960,7 +57230,7 @@ function createKimiCliBackend(command = "kimi", defaultArgs = []) {
56960
57230
  }).run(context);
56961
57231
  } finally {
56962
57232
  cleanup();
56963
- if (mcpDirectory) (0, import_fs16.rmSync)(mcpDirectory, { recursive: true, force: true });
57233
+ if (mcpDirectory) (0, import_fs17.rmSync)(mcpDirectory, { recursive: true, force: true });
56964
57234
  }
56965
57235
  }
56966
57236
  };
@@ -58723,6 +58993,56 @@ var RunLedger = class {
58723
58993
  }
58724
58994
  };
58725
58995
 
58996
+ // src/events/inline-media.ts
58997
+ var MIN_INLINE_MEDIA_CHARS = 8 * 1024;
58998
+ var DATA_URL_PREFIX = /^data:([\w.+-]+\/[\w.+-]+);base64,/;
58999
+ var BASE64_BODY = /^[A-Za-z0-9+/]+={0,2}$/;
59000
+ function omittedMedia(value2) {
59001
+ if (value2.length < MIN_INLINE_MEDIA_CHARS) return null;
59002
+ const dataUrl = DATA_URL_PREFIX.exec(value2);
59003
+ if (dataUrl) {
59004
+ return `[${dataUrl[1]} omitted: ${value2.length - dataUrl[0].length} base64 chars]`;
59005
+ }
59006
+ return BASE64_BODY.test(value2) ? `[base64 omitted: ${value2.length} chars]` : null;
59007
+ }
59008
+ function withoutMedia(value2) {
59009
+ if (typeof value2 === "string") return omittedMedia(value2) ?? value2;
59010
+ if (Array.isArray(value2)) {
59011
+ let changed = false;
59012
+ const items = value2.map((item) => {
59013
+ const next = withoutMedia(item);
59014
+ if (next !== item) changed = true;
59015
+ return next;
59016
+ });
59017
+ return changed ? items : value2;
59018
+ }
59019
+ if (value2 !== null && typeof value2 === "object") {
59020
+ let changed = false;
59021
+ const entries = Object.entries(value2).map(([key, item]) => {
59022
+ const next = withoutMedia(item);
59023
+ if (next !== item) changed = true;
59024
+ return [key, next];
59025
+ });
59026
+ return changed ? Object.fromEntries(entries) : value2;
59027
+ }
59028
+ return value2;
59029
+ }
59030
+ function isMeta(value2) {
59031
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
59032
+ }
59033
+ function withoutInlineMedia(event) {
59034
+ const { update } = event;
59035
+ const currentMeta = isMeta(update._meta) ? update._meta : void 0;
59036
+ const payload = currentMeta?.[PROVIDER_PAYLOAD_META];
59037
+ const nextPayload = withoutMedia(payload);
59038
+ const rawOutput = update.sessionUpdate === "tool_call_update" ? update.rawOutput : void 0;
59039
+ const nextRawOutput = withoutMedia(rawOutput);
59040
+ if (nextPayload === payload && nextRawOutput === rawOutput) return event;
59041
+ const meta4 = currentMeta && nextPayload !== payload ? { ...currentMeta, [PROVIDER_PAYLOAD_META]: nextPayload } : currentMeta;
59042
+ const next = update.sessionUpdate === "tool_call_update" ? { ...update, _meta: meta4, rawOutput: nextRawOutput } : { ...update, _meta: meta4 };
59043
+ return { ...event, update: next };
59044
+ }
59045
+
58726
59046
  // src/events/stream.ts
58727
59047
  var listeners = /* @__PURE__ */ new Map();
58728
59048
  var FINISHED_LIMIT = 256;
@@ -58741,9 +59061,10 @@ function subscribeSessionEvents(key, listener, options = {}) {
58741
59061
  function publishSessionEvent(runId, event, conversationId) {
58742
59062
  const sets = [listeners.get(runId), conversationId ? listeners.get(conversationId) : void 0];
58743
59063
  if (!sets.some((set2) => set2 && set2.size > 0)) return;
59064
+ const outgoing = withoutInlineMedia(event);
58744
59065
  let serialized;
58745
59066
  try {
58746
- serialized = JSON.stringify(event);
59067
+ serialized = JSON.stringify(outgoing);
58747
59068
  } catch {
58748
59069
  return;
58749
59070
  }
@@ -58754,7 +59075,7 @@ function publishSessionEvent(runId, event, conversationId) {
58754
59075
  if (delivered.has(subscriber)) continue;
58755
59076
  delivered.add(subscriber);
58756
59077
  try {
58757
- subscriber.listener(event, serialized);
59078
+ subscriber.listener(outgoing, serialized);
58758
59079
  } catch {
58759
59080
  set2.delete(subscriber);
58760
59081
  }
@@ -59949,7 +60270,7 @@ var Polling = class extends Transport {
59949
60270
  */
59950
60271
  pause(onPause) {
59951
60272
  this.readyState = "pausing";
59952
- const pause = () => {
60273
+ const pause2 = () => {
59953
60274
  debug2("paused");
59954
60275
  this.readyState = "paused";
59955
60276
  onPause();
@@ -59961,7 +60282,7 @@ var Polling = class extends Transport {
59961
60282
  total++;
59962
60283
  this.once("pollComplete", function() {
59963
60284
  debug2("pre-pause polling complete");
59964
- --total || pause();
60285
+ --total || pause2();
59965
60286
  });
59966
60287
  }
59967
60288
  if (!this.writable) {
@@ -59969,11 +60290,11 @@ var Polling = class extends Transport {
59969
60290
  total++;
59970
60291
  this.once("drain", function() {
59971
60292
  debug2("pre-pause writing complete");
59972
- --total || pause();
60293
+ --total || pause2();
59973
60294
  });
59974
60295
  }
59975
60296
  } else {
59976
- pause();
60297
+ pause2();
59977
60298
  }
59978
60299
  }
59979
60300
  /**
@@ -63040,11 +63361,11 @@ function removeNativeHooks(homeDir = (0, import_node_os13.homedir)()) {
63040
63361
  }
63041
63362
 
63042
63363
  // src/events/runtime-command-reply.ts
63043
- var import_promises4 = require("timers/promises");
63364
+ var import_promises5 = require("timers/promises");
63044
63365
  async function publishRuntimeCommandReply(path2, reply, credentials) {
63045
63366
  const body = JSON.stringify(reply);
63046
63367
  for (let attempt = 0; attempt < 2; attempt += 1) {
63047
- if (attempt > 0) await (0, import_promises4.setTimeout)(250, void 0, { signal: credentials.signal });
63368
+ if (attempt > 0) await (0, import_promises5.setTimeout)(250, void 0, { signal: credentials.signal });
63048
63369
  credentials.signal.throwIfAborted();
63049
63370
  let response;
63050
63371
  try {
@@ -63076,18 +63397,17 @@ async function publishModelScanReply(reply, credentials) {
63076
63397
  }
63077
63398
 
63078
63399
  // src/events/replay.ts
63079
- var import_node_fs18 = require("fs");
63080
- var import_promises8 = require("fs/promises");
63400
+ var import_node_fs19 = require("fs");
63081
63401
 
63082
63402
  // src/events/live.ts
63083
63403
  var import_node_crypto8 = require("crypto");
63084
63404
  var import_node_fs17 = require("fs");
63085
- var import_promises7 = require("fs/promises");
63405
+ var import_promises8 = require("fs/promises");
63086
63406
 
63087
63407
  // src/events/http-retry.ts
63088
63408
  var TranscriptRequestError = class extends Error {
63089
- retryAfterMs;
63090
63409
  status;
63410
+ retryAfterMs;
63091
63411
  constructor(response) {
63092
63412
  super(`Transcript request failed (${response.status})`);
63093
63413
  this.status = response.status;
@@ -63097,18 +63417,53 @@ var TranscriptRequestError = class extends Error {
63097
63417
  this.retryAfterMs = Number.isFinite(seconds) ? Math.max(0, seconds * 1e3) : Number.isFinite(date5) ? Math.max(0, date5 - Date.now()) : 0;
63098
63418
  }
63099
63419
  };
63420
+ var PERMANENT_STATUSES = /* @__PURE__ */ new Set([400, 401, 403, 404, 410, 413, 415]);
63421
+ var TRANSIENT_BACKOFF_CAP_MS = 3e4;
63422
+ var PERMANENT_BACKOFF_BASE_MS = 6e4;
63423
+ var PERMANENT_BACKOFF_CAP_MS = 5 * 6e4;
63424
+ function isPermanentTranscriptError(error61) {
63425
+ return error61 instanceof TranscriptRequestError && PERMANENT_STATUSES.has(error61.status);
63426
+ }
63427
+ function transcriptSyncProblem(error61, at = Date.now()) {
63428
+ return {
63429
+ ...error61 instanceof TranscriptRequestError ? { status: error61.status } : {},
63430
+ message: error61 instanceof Error ? error61.message : String(error61),
63431
+ permanent: isPermanentTranscriptError(error61),
63432
+ at
63433
+ };
63434
+ }
63100
63435
  function transcriptRetryDeadline(failures, error61) {
63101
- const backoff = Math.min(1e3 * 2 ** Math.min(failures - 1, 5), 3e4);
63436
+ const exponent = Math.min(failures - 1, 5);
63437
+ const backoff = isPermanentTranscriptError(error61) ? Math.min(PERMANENT_BACKOFF_BASE_MS * 2 ** exponent, PERMANENT_BACKOFF_CAP_MS) : Math.min(1e3 * 2 ** exponent, TRANSIENT_BACKOFF_CAP_MS);
63102
63438
  return Date.now() + Math.max(
63103
63439
  backoff + Math.floor(Math.random() * backoff * 0.2),
63104
63440
  error61 instanceof TranscriptRequestError ? error61.retryAfterMs : 0
63105
63441
  );
63106
63442
  }
63443
+ function requestDeadline(timeoutMs, parent) {
63444
+ const controller = new AbortController();
63445
+ const timer = setTimeout(
63446
+ () => controller.abort(
63447
+ new DOMException("The operation was aborted due to timeout", "TimeoutError")
63448
+ ),
63449
+ timeoutMs
63450
+ );
63451
+ const onAbort = () => controller.abort(parent?.reason);
63452
+ if (parent?.aborted) onAbort();
63453
+ else parent?.addEventListener("abort", onAbort, { once: true });
63454
+ return {
63455
+ signal: controller.signal,
63456
+ dispose() {
63457
+ clearTimeout(timer);
63458
+ parent?.removeEventListener("abort", onAbort);
63459
+ }
63460
+ };
63461
+ }
63107
63462
 
63108
63463
  // src/events/push.ts
63109
63464
  var import_node_crypto7 = require("crypto");
63110
63465
  var import_node_fs16 = require("fs");
63111
- var import_promises6 = require("fs/promises");
63466
+ var import_promises7 = require("fs/promises");
63112
63467
 
63113
63468
  // src/events/session.ts
63114
63469
  var import_node_fs15 = require("fs");
@@ -63203,6 +63558,7 @@ function runState(value2) {
63203
63558
  ...offset(value2.liveFailures) ? { liveFailures: value2.liveFailures } : {},
63204
63559
  ...offset(value2.pushRetryAfter) ? { pushRetryAfter: value2.pushRetryAfter } : {},
63205
63560
  ...offset(value2.pushFailures) ? { pushFailures: value2.pushFailures } : {},
63561
+ ...isSyncProblem(value2.syncProblem) ? { syncProblem: value2.syncProblem } : {},
63206
63562
  ...record2(sandbox) && typeof sandbox.sandboxId === "string" && typeof sandbox.taskId === "string" && typeof sandbox.agentSessionId === "string" ? {
63207
63563
  sandbox: {
63208
63564
  sandboxId: sandbox.sandboxId,
@@ -63212,6 +63568,9 @@ function runState(value2) {
63212
63568
  } : {}
63213
63569
  };
63214
63570
  }
63571
+ function isSyncProblem(value2) {
63572
+ return record2(value2) && typeof value2.message === "string" && typeof value2.permanent === "boolean" && offset(value2.at) && (value2.status === void 0 || offset(value2.status));
63573
+ }
63215
63574
  function readJson(path2) {
63216
63575
  try {
63217
63576
  return JSON.parse((0, import_node_fs14.readFileSync)(path2, "utf8"));
@@ -63313,7 +63672,7 @@ function writeRunTranscriptState(configPath, conversationId, runId, state) {
63313
63672
  }
63314
63673
 
63315
63674
  // src/events/transcript-buffer.ts
63316
- var import_promises5 = require("fs/promises");
63675
+ var import_promises6 = require("fs/promises");
63317
63676
  var RETAINED_BYTES = 256 * 1024;
63318
63677
  var MAX_PENDING_TRANSCRIPT_BYTES = 8 * 1024 * 1024;
63319
63678
  var TranscriptBuffer = class {
@@ -63373,7 +63732,7 @@ function closeTranscriptBuffer(path2, buffer) {
63373
63732
  async function retryTranscriptWrites(path2, runId) {
63374
63733
  const buffer = active.get(path2);
63375
63734
  if (!buffer || buffer.runId !== runId) return void 0;
63376
- const file2 = await (0, import_promises5.open)(path2, "r+");
63735
+ const file2 = await (0, import_promises6.open)(path2, "r+");
63377
63736
  try {
63378
63737
  const stat4 = await file2.stat();
63379
63738
  if (`${stat4.dev}.${stat4.ino}.${stat4.birthtimeMs}` !== buffer.generation)
@@ -63410,6 +63769,13 @@ var openTranscripts = /* @__PURE__ */ new Set();
63410
63769
  function isSessionTranscriptOpen(runId) {
63411
63770
  return openTranscripts.has(runId);
63412
63771
  }
63772
+ function canonicalPromptMeta(message) {
63773
+ const meta4 = {
63774
+ ...message.clientMessageId ? { "alan.dev/clientMessageId": message.clientMessageId } : {},
63775
+ ...message.attachmentIds?.length ? { "alan.dev/attachmentIds": message.attachmentIds } : {}
63776
+ };
63777
+ return Object.keys(meta4).length > 0 ? { _meta: meta4 } : {};
63778
+ }
63413
63779
  function requestTranscriptFinalization(configPath, conversationId, runId) {
63414
63780
  const state = readRunTranscriptState(configPath, conversationId, runId);
63415
63781
  if (state && state.endOffset !== void 0 && !state.interrupted)
@@ -63480,7 +63846,7 @@ function openSessionTranscript(input2) {
63480
63846
  sessionUpdate: "user_message",
63481
63847
  messageId: message.id,
63482
63848
  content: [{ type: "text", text: message.content }],
63483
- ...message.clientMessageId ? { _meta: { "alan.dev/clientMessageId": message.clientMessageId } } : {}
63849
+ ...canonicalPromptMeta(message)
63484
63850
  }
63485
63851
  };
63486
63852
  const bytes = Buffer.from(`${JSON.stringify(event)}
@@ -63644,6 +64010,11 @@ function sizeOf(path2) {
63644
64010
  var CHECKPOINT_MS = 5e3;
63645
64011
  var MAX_RECEIPT_POLL_MS = 15e3;
63646
64012
  var MAX_PUSH_BYTES = 512 * 1024;
64013
+ var MIN_PUSH_BYTES = 64 * 1024;
64014
+ var adaptivePushBytes = MAX_PUSH_BYTES;
64015
+ function isTimeout(error61) {
64016
+ return error61 instanceof Error && error61.name === "TimeoutError";
64017
+ }
63647
64018
  var REQUEST_TIMEOUT_MS = 1e4;
63648
64019
  function transcriptPushUrl(apiUrl, conversationId) {
63649
64020
  return `${apiUrl.replace(/\/+$/, "")}/public/conversations/${encodeURIComponent(conversationId)}/transcript`;
@@ -63656,34 +64027,39 @@ function isReceipt(value2) {
63656
64027
  async function exchange(target, body, offset2, final = false) {
63657
64028
  const token = target.getRuntimeToken();
63658
64029
  if (!token) throw new Error("Transcript token unavailable");
63659
- const response = await fetch(transcriptPushUrl(target.apiUrl, target.conversationId), {
63660
- method: body ? "POST" : "GET",
63661
- signal: target.signal ? AbortSignal.any([target.signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]) : AbortSignal.timeout(REQUEST_TIMEOUT_MS),
63662
- headers: {
63663
- authorization: `Bearer ${token}`,
63664
- "x-alan-run-id": target.runId,
63665
- ...target.sandbox ? {
63666
- "x-alan-sandbox-id": target.sandbox.sandboxId,
63667
- "x-alan-task-id": target.sandbox.taskId,
63668
- "x-alan-agent-session-id": target.sandbox.agentSessionId
63669
- } : {},
63670
- ...body ? {
63671
- "content-type": "application/octet-stream",
63672
- "x-alan-offset": String(offset2),
63673
- "x-alan-revision": "0",
63674
- "x-alan-sha256": (0, import_node_crypto7.createHash)("sha256").update(body).digest("hex"),
63675
- ...final ? { "x-alan-final": "1" } : {}
63676
- } : {}
63677
- },
63678
- ...body ? { body: new Uint8Array(body) } : {}
63679
- });
63680
- if (!response.ok) throw new TranscriptRequestError(response);
63681
- const result = await response.json();
63682
- if (!result || typeof result !== "object" || !("receipt" in result))
63683
- throw new Error("Invalid transcript receipt");
63684
- if (result.receipt === null && !body) return null;
63685
- if (!isReceipt(result.receipt)) throw new Error("Invalid transcript receipt");
63686
- return result.receipt;
64030
+ const deadline = requestDeadline(REQUEST_TIMEOUT_MS, target.signal);
64031
+ try {
64032
+ const response = await fetch(transcriptPushUrl(target.apiUrl, target.conversationId), {
64033
+ method: body ? "POST" : "GET",
64034
+ signal: deadline.signal,
64035
+ headers: {
64036
+ authorization: `Bearer ${token}`,
64037
+ "x-alan-run-id": target.runId,
64038
+ ...target.sandbox ? {
64039
+ "x-alan-sandbox-id": target.sandbox.sandboxId,
64040
+ "x-alan-task-id": target.sandbox.taskId,
64041
+ "x-alan-agent-session-id": target.sandbox.agentSessionId
64042
+ } : {},
64043
+ ...body ? {
64044
+ "content-type": "application/octet-stream",
64045
+ "x-alan-offset": String(offset2),
64046
+ "x-alan-revision": "0",
64047
+ "x-alan-sha256": (0, import_node_crypto7.createHash)("sha256").update(body).digest("hex"),
64048
+ ...final ? { "x-alan-final": "1" } : {}
64049
+ } : {}
64050
+ },
64051
+ ...body ? { body: new Uint8Array(body) } : {}
64052
+ });
64053
+ if (!response.ok) throw new TranscriptRequestError(response);
64054
+ const result = await response.json();
64055
+ if (!result || typeof result !== "object" || !("receipt" in result))
64056
+ throw new Error("Invalid transcript receipt");
64057
+ if (result.receipt === null && !body) return null;
64058
+ if (!isReceipt(result.receipt)) throw new Error("Invalid transcript receipt");
64059
+ return result.receipt;
64060
+ } finally {
64061
+ deadline.dispose();
64062
+ }
63687
64063
  }
63688
64064
  function readTranscriptReceipt(target) {
63689
64065
  return exchange(target);
@@ -63755,8 +64131,8 @@ function startTranscriptPush(target, onProblem) {
63755
64131
  while (!stopped && uploadDue && admitted < boundary) {
63756
64132
  const current = state();
63757
64133
  if (!current) throw new Error("Transcript run range disappeared");
63758
- const end = Math.min(boundary, admitted + MAX_PUSH_BYTES);
63759
- const file2 = await (0, import_promises6.open)(path2, "r");
64134
+ const end = Math.min(boundary, admitted + adaptivePushBytes);
64135
+ const file2 = await (0, import_promises7.open)(path2, "r");
63760
64136
  let bytes;
63761
64137
  try {
63762
64138
  bytes = Buffer.alloc(end - admitted);
@@ -63770,6 +64146,7 @@ function startTranscriptPush(target, onProblem) {
63770
64146
  const next = accept(receipt);
63771
64147
  if (next <= admitted) throw new Error("Transcript admission did not advance");
63772
64148
  admitted = next;
64149
+ adaptivePushBytes = Math.min(MAX_PUSH_BYTES, adaptivePushBytes * 2);
63773
64150
  checkpointAt = Date.now() + CHECKPOINT_MS;
63774
64151
  }
63775
64152
  if (stopped) return;
@@ -63795,15 +64172,17 @@ function startTranscriptPush(target, onProblem) {
63795
64172
  failures = 0;
63796
64173
  retryNotBefore = 0;
63797
64174
  const current = state();
63798
- if (current?.pushRetryAfter !== void 0 || current?.pushFailures !== void 0) {
64175
+ if (current?.pushRetryAfter !== void 0 || current?.pushFailures !== void 0 || current?.syncProblem !== void 0) {
63799
64176
  writeRunTranscriptState(target.configPath, target.conversationId, target.runId, {
63800
64177
  ...current,
63801
64178
  pushRetryAfter: void 0,
63802
- pushFailures: void 0
64179
+ pushFailures: void 0,
64180
+ syncProblem: void 0
63803
64181
  });
63804
64182
  }
63805
64183
  }).catch((error61) => {
63806
64184
  if (target.signal?.aborted) return;
64185
+ if (isTimeout(error61)) adaptivePushBytes = Math.max(MIN_PUSH_BYTES, adaptivePushBytes / 2);
63807
64186
  failures += 1;
63808
64187
  pending = true;
63809
64188
  cached2 = void 0;
@@ -63814,7 +64193,8 @@ function startTranscriptPush(target, onProblem) {
63814
64193
  writeRunTranscriptState(target.configPath, target.conversationId, target.runId, {
63815
64194
  ...current,
63816
64195
  pushRetryAfter: retryNotBefore,
63817
- pushFailures: failures
64196
+ pushFailures: failures,
64197
+ syncProblem: transcriptSyncProblem(error61)
63818
64198
  });
63819
64199
  } catch (stateError) {
63820
64200
  onProblem(
@@ -63934,30 +64314,37 @@ function startTranscriptLive(target, onProblem) {
63934
64314
  const exchange2 = async (body, offset2, epoch) => {
63935
64315
  const token = target.getRuntimeToken();
63936
64316
  if (!token) throw new Error("Live transcript token unavailable");
63937
- const response = await fetch(transcriptPushUrl(target.apiUrl, target.conversationId), {
63938
- method: body ? "POST" : "GET",
63939
- signal: AbortSignal.any([signal, AbortSignal.timeout(5e3)]),
63940
- headers: {
63941
- authorization: `Bearer ${token}`,
63942
- "x-alan-run-id": target.runId,
63943
- [TRANSCRIPT_LIVE_DELIVERY_HEADER]: "live",
63944
- ...target.sandbox ? {
63945
- "x-alan-sandbox-id": target.sandbox.sandboxId,
63946
- "x-alan-task-id": target.sandbox.taskId,
63947
- "x-alan-agent-session-id": target.sandbox.agentSessionId
63948
- } : {},
63949
- ...body ? {
63950
- "content-type": "application/octet-stream",
63951
- "x-alan-offset": String(offset2),
63952
- "x-alan-revision": "0",
63953
- "x-alan-sha256": (0, import_node_crypto8.createHash)("sha256").update(body).digest("hex"),
63954
- "x-alan-live-epoch": epoch ?? ""
63955
- } : {}
63956
- },
63957
- ...body ? { body: new Uint8Array(body) } : {}
63958
- });
63959
- if (!response.ok) throw new TranscriptRequestError(response);
63960
- const result = await response.json();
64317
+ const deadline = requestDeadline(5e3, signal);
64318
+ let response;
64319
+ let result;
64320
+ try {
64321
+ response = await fetch(transcriptPushUrl(target.apiUrl, target.conversationId), {
64322
+ method: body ? "POST" : "GET",
64323
+ signal: deadline.signal,
64324
+ headers: {
64325
+ authorization: `Bearer ${token}`,
64326
+ "x-alan-run-id": target.runId,
64327
+ [TRANSCRIPT_LIVE_DELIVERY_HEADER]: "live",
64328
+ ...target.sandbox ? {
64329
+ "x-alan-sandbox-id": target.sandbox.sandboxId,
64330
+ "x-alan-task-id": target.sandbox.taskId,
64331
+ "x-alan-agent-session-id": target.sandbox.agentSessionId
64332
+ } : {},
64333
+ ...body ? {
64334
+ "content-type": "application/octet-stream",
64335
+ "x-alan-offset": String(offset2),
64336
+ "x-alan-revision": "0",
64337
+ "x-alan-sha256": (0, import_node_crypto8.createHash)("sha256").update(body).digest("hex"),
64338
+ "x-alan-live-epoch": epoch ?? ""
64339
+ } : {}
64340
+ },
64341
+ ...body ? { body: new Uint8Array(body) } : {}
64342
+ });
64343
+ if (!response.ok) throw new TranscriptRequestError(response);
64344
+ result = await response.json();
64345
+ } finally {
64346
+ deadline.dispose();
64347
+ }
63961
64348
  if (!record3(result) || !isReceipt2(result.receipt))
63962
64349
  throw new Error("Invalid live transcript receipt");
63963
64350
  const receipt = result.receipt;
@@ -63988,7 +64375,7 @@ function startTranscriptLive(target, onProblem) {
63988
64375
  if (active2 && from < active2.retainedFrom)
63989
64376
  length = Math.min(length, active2.retainedFrom - from);
63990
64377
  bytes = Buffer.alloc(length);
63991
- const file2 = await (0, import_promises7.open)(path2, "r");
64378
+ const file2 = await (0, import_promises8.open)(path2, "r");
63992
64379
  try {
63993
64380
  const read = await file2.read(bytes, 0, length, from);
63994
64381
  if (read.bytesRead !== length) throw new Error("Live transcript changed during read");
@@ -64094,19 +64481,104 @@ function startTranscriptLive(target, onProblem) {
64094
64481
  };
64095
64482
  }
64096
64483
 
64484
+ // src/events/sync-backlog.ts
64485
+ var import_node_fs18 = require("fs");
64486
+ function isTranscriptDelivered(state) {
64487
+ if (!state || state.endOffset === void 0) return true;
64488
+ const size = state.endOffset - state.startOffset;
64489
+ return state.syncedOffset === size && state.finalizedOffset === size;
64490
+ }
64491
+ function decodeConversationId(name) {
64492
+ try {
64493
+ const id = decodeURIComponent(name);
64494
+ return id.length > 0 ? id : void 0;
64495
+ } catch {
64496
+ return void 0;
64497
+ }
64498
+ }
64499
+ function listStoredConversationIds(configPath) {
64500
+ let entries;
64501
+ try {
64502
+ entries = (0, import_node_fs18.readdirSync)(sessionsDirectory(configPath), { withFileTypes: true });
64503
+ } catch (error61) {
64504
+ if (error61.code === "ENOENT") return [];
64505
+ throw error61;
64506
+ }
64507
+ const ids = /* @__PURE__ */ new Set();
64508
+ for (const entry of entries) {
64509
+ const name = entry.isDirectory() ? entry.name.replace(/\.runs$/, "") : entry.isFile() && entry.name.endsWith(".jsonl") ? entry.name.slice(0, -6) : void 0;
64510
+ const id = name === void 0 ? void 0 : decodeConversationId(name);
64511
+ if (id) ids.add(id);
64512
+ }
64513
+ return [...ids];
64514
+ }
64515
+ function readTranscriptSyncBacklog(configPath, isRunActive) {
64516
+ const backlog = [];
64517
+ for (const conversationId of listStoredConversationIds(configPath)) {
64518
+ let runs;
64519
+ try {
64520
+ runs = readConversationSyncState(configPath, conversationId).runs;
64521
+ } catch {
64522
+ continue;
64523
+ }
64524
+ for (const [runId, state] of Object.entries(runs)) {
64525
+ if (isRunActive(runId) || isTranscriptDelivered(state) || state.endOffset === void 0)
64526
+ continue;
64527
+ backlog.push({
64528
+ runId,
64529
+ conversationId,
64530
+ pendingBytes: Math.max(0, state.endOffset - state.startOffset - state.syncedOffset),
64531
+ failures: state.pushFailures ?? 0,
64532
+ ...state.syncProblem ? { problem: state.syncProblem } : {}
64533
+ });
64534
+ }
64535
+ }
64536
+ return backlog.sort((a, b) => b.pendingBytes - a.pendingBytes);
64537
+ }
64538
+ function releasePermanentTranscriptCooldowns(configPath) {
64539
+ let released = 0;
64540
+ for (const conversationId of listStoredConversationIds(configPath)) {
64541
+ let runs;
64542
+ try {
64543
+ runs = readConversationSyncState(configPath, conversationId).runs;
64544
+ } catch {
64545
+ continue;
64546
+ }
64547
+ for (const [runId, state] of Object.entries(runs)) {
64548
+ if (!state.syncProblem?.permanent) continue;
64549
+ writeRunTranscriptState(configPath, conversationId, runId, {
64550
+ ...state,
64551
+ pushRetryAfter: void 0,
64552
+ pushFailures: void 0,
64553
+ liveRetryAfter: void 0,
64554
+ liveFailures: void 0
64555
+ });
64556
+ released += 1;
64557
+ }
64558
+ }
64559
+ return released;
64560
+ }
64561
+
64097
64562
  // src/events/replay.ts
64098
64563
  function startTranscriptReplay(options) {
64099
64564
  const controller = new AbortController();
64100
64565
  const signal = AbortSignal.any([options.signal, controller.signal]);
64101
64566
  let running;
64102
64567
  let notifiedDuringReplay = false;
64568
+ let cooldownTimer;
64569
+ const retryAfterCooldown = (retryAt) => {
64570
+ if (retryAt === void 0 || signal.aborted) return;
64571
+ if (cooldownTimer) clearTimeout(cooldownTimer);
64572
+ cooldownTimer = setTimeout(notify, Math.max(0, retryAt - Date.now()));
64573
+ cooldownTimer.unref();
64574
+ };
64103
64575
  const notify = () => {
64104
64576
  if (signal.aborted) return;
64105
64577
  if (running) {
64106
64578
  notifiedDuringReplay = true;
64107
64579
  return;
64108
64580
  }
64109
- running = replayClosedTranscripts({ ...options, signal }).catch((error61) => options.onProblem(`transcript-replay-failed ${String(error61)}`)).finally(() => {
64581
+ running = replayClosedTranscripts({ ...options, signal }).then(retryAfterCooldown).catch((error61) => options.onProblem(`transcript-replay-failed ${String(error61)}`)).finally(() => {
64110
64582
  running = void 0;
64111
64583
  if (notifiedDuringReplay) {
64112
64584
  notifiedDuringReplay = false;
@@ -64120,6 +64592,7 @@ function startTranscriptReplay(options) {
64120
64592
  notify,
64121
64593
  async stop() {
64122
64594
  clearInterval(timer);
64595
+ if (cooldownTimer) clearTimeout(cooldownTimer);
64123
64596
  controller.abort();
64124
64597
  await running;
64125
64598
  }
@@ -64131,7 +64604,7 @@ function recoverInterruptedRange(options, conversationId, runId) {
64131
64604
  if (!state) throw new Error("Transcript range disappeared");
64132
64605
  if ((state.finalRequested || state.interrupted) && state.endOffset !== void 0) return state;
64133
64606
  const path2 = transcriptPath(options.configPath, conversationId);
64134
- const length = (0, import_node_fs18.statSync)(path2).size;
64607
+ const length = (0, import_node_fs19.statSync)(path2).size;
64135
64608
  const nextStart = Object.values(conversation.runs).reduce(
64136
64609
  (end2, range) => range.startOffset > state.startOffset ? Math.min(end2, range.startOffset) : end2,
64137
64610
  length
@@ -64139,12 +64612,12 @@ function recoverInterruptedRange(options, conversationId, runId) {
64139
64612
  let end = state.endOffset ?? nextStart;
64140
64613
  if (end > length || end < state.startOffset)
64141
64614
  throw new Error("Interrupted transcript range is outside its journal");
64142
- const file2 = (0, import_node_fs18.openSync)(path2, "r");
64615
+ const file2 = (0, import_node_fs19.openSync)(path2, "r");
64143
64616
  try {
64144
64617
  const buffer = Buffer.alloc(64 * 1024);
64145
64618
  while (end > state.startOffset) {
64146
64619
  const start = Math.max(state.startOffset, end - buffer.length);
64147
- const count2 = (0, import_node_fs18.readSync)(file2, buffer, 0, end - start, start);
64620
+ const count2 = (0, import_node_fs19.readSync)(file2, buffer, 0, end - start, start);
64148
64621
  if (count2 !== end - start) throw new Error("Interrupted transcript changed during recovery");
64149
64622
  const newline = buffer.subarray(0, count2).lastIndexOf(10);
64150
64623
  if (newline >= 0) {
@@ -64154,7 +64627,7 @@ function recoverInterruptedRange(options, conversationId, runId) {
64154
64627
  end = start;
64155
64628
  }
64156
64629
  } finally {
64157
- (0, import_node_fs18.closeSync)(file2);
64630
+ (0, import_node_fs19.closeSync)(file2);
64158
64631
  }
64159
64632
  if (end < state.startOffset + state.syncedOffset)
64160
64633
  throw new Error("Interrupted transcript has committed incomplete bytes; repair required");
@@ -64170,114 +64643,121 @@ function recoverInterruptedRange(options, conversationId, runId) {
64170
64643
  );
64171
64644
  return recovered;
64172
64645
  }
64173
- function waitUntil(at, signal) {
64174
- return new Promise((resolve16) => {
64175
- const done = () => {
64176
- signal.removeEventListener("abort", done);
64177
- clearTimeout(timer);
64178
- resolve16();
64179
- };
64180
- const timer = setTimeout(done, Math.max(0, at - Date.now()));
64181
- signal.addEventListener("abort", done, { once: true });
64182
- });
64183
- }
64184
- function isDelivered(state) {
64185
- if (!state || state.endOffset === void 0) return true;
64186
- const size = state.endOffset - state.startOffset;
64187
- return state.syncedOffset === size && state.finalizedOffset === size;
64188
- }
64189
- function decodeId(name) {
64646
+ var REPLAY_CONCURRENCY = 3;
64647
+ function lastActivityMs(configPath, conversationId) {
64190
64648
  try {
64191
- const id = decodeURIComponent(name);
64192
- return id.length > 0 ? id : void 0;
64649
+ return (0, import_node_fs19.statSync)(syncStatePath(configPath, conversationId)).mtimeMs;
64193
64650
  } catch {
64194
- return void 0;
64651
+ return 0;
64195
64652
  }
64196
64653
  }
64197
64654
  async function replayClosedTranscripts(options) {
64198
- if (options.signal.aborted || !options.canReplay()) return;
64199
- const root = sessionsDirectory(options.configPath);
64200
- const directories = await (0, import_promises8.opendir)(root).catch((error61) => {
64201
- if (error61.code === "ENOENT") return void 0;
64202
- throw error61;
64203
- });
64204
- if (!directories) return;
64205
- const conversations = /* @__PURE__ */ new Set();
64206
- for await (const directory of directories) {
64207
- const name = directory.isDirectory() ? directory.name.replace(/\.runs$/, "") : directory.isFile() && directory.name.endsWith(".jsonl") ? directory.name.slice(0, -6) : void 0;
64208
- const conversationId = name === void 0 ? void 0 : decodeId(name);
64209
- if (!conversationId) continue;
64210
- conversations.add(conversationId);
64211
- }
64212
- for (const conversationId of conversations) {
64213
- if (options.conversationId && options.conversationId !== conversationId) continue;
64655
+ if (options.signal.aborted || !options.canReplay()) return void 0;
64656
+ const conversations = listStoredConversationIds(options.configPath).filter((id) => !options.conversationId || options.conversationId === id).map((id) => ({ id, activity: lastActivityMs(options.configPath, id) })).sort((a, b) => b.activity - a.activity).map((entry) => entry.id);
64657
+ const cooldowns = { earliest: void 0 };
64658
+ let next = 0;
64659
+ const worker = async () => {
64660
+ while (next < conversations.length) {
64661
+ if (options.signal.aborted || !options.canReplay()) return;
64662
+ const conversationId = conversations[next++];
64663
+ if (conversationId) await replayConversation(options, conversationId, cooldowns);
64664
+ }
64665
+ };
64666
+ await Promise.all(
64667
+ Array.from({ length: Math.min(REPLAY_CONCURRENCY, conversations.length) }, worker)
64668
+ );
64669
+ return cooldowns.earliest;
64670
+ }
64671
+ async function replayConversation(options, conversationId, cooldowns) {
64672
+ let conversation;
64673
+ try {
64674
+ migrateConversationStorage(options.configPath, conversationId);
64675
+ conversation = readConversationSyncState(options.configPath, conversationId);
64676
+ } catch (error61) {
64677
+ options.onProblem(`transcript-state-failed conversation=${conversationId}: ${String(error61)}`);
64678
+ return;
64679
+ }
64680
+ const runIds = Object.keys(conversation.runs).sort(
64681
+ (a, b) => (conversation.runs[b]?.startOffset ?? 0) - (conversation.runs[a]?.startOffset ?? 0)
64682
+ );
64683
+ for (const runId of runIds) {
64214
64684
  if (options.signal.aborted || !options.canReplay()) return;
64215
- let conversation;
64685
+ if (options.isRunActive(runId) || isSessionTranscriptOpen(runId)) continue;
64686
+ if (options.sandboxId && conversation.runs[runId]?.sandbox?.sandboxId !== options.sandboxId)
64687
+ continue;
64688
+ let state;
64216
64689
  try {
64217
- migrateConversationStorage(options.configPath, conversationId);
64218
- conversation = readConversationSyncState(options.configPath, conversationId);
64690
+ const recoveredEnd = await retryTranscriptWrites(
64691
+ transcriptPath(options.configPath, conversationId),
64692
+ runId
64693
+ );
64694
+ if (recoveredEnd !== void 0) {
64695
+ const current = readConversationSyncState(options.configPath, conversationId).runs[runId];
64696
+ if (!current) throw new Error("Transcript range disappeared during write recovery");
64697
+ writeRunTranscriptState(options.configPath, conversationId, runId, {
64698
+ ...current,
64699
+ endOffset: recoveredEnd,
64700
+ interrupted: true,
64701
+ finalRequested: false
64702
+ });
64703
+ const path2 = transcriptPath(options.configPath, conversationId);
64704
+ const buffer = getTranscriptBuffer(path2);
64705
+ if (buffer) closeTranscriptBuffer(path2, buffer);
64706
+ }
64707
+ state = recoverInterruptedRange(options, conversationId, runId);
64219
64708
  } catch (error61) {
64220
- options.onProblem(`transcript-state-failed conversation=${conversationId}: ${String(error61)}`);
64709
+ options.onProblem(`transcript-recovery-failed run=${runId}: ${String(error61)}`);
64221
64710
  continue;
64222
64711
  }
64223
- for (const runId of Object.keys(conversation.runs)) {
64224
- if (options.signal.aborted || !options.canReplay()) return;
64225
- if (options.isRunActive(runId) || isSessionTranscriptOpen(runId)) continue;
64226
- if (options.sandboxId && conversation.runs[runId]?.sandbox?.sandboxId !== options.sandboxId)
64227
- continue;
64228
- let state;
64229
- try {
64230
- const recoveredEnd = await retryTranscriptWrites(
64231
- transcriptPath(options.configPath, conversationId),
64232
- runId
64233
- );
64234
- if (recoveredEnd !== void 0) {
64235
- const current = readConversationSyncState(options.configPath, conversationId).runs[runId];
64236
- if (!current) throw new Error("Transcript range disappeared during write recovery");
64237
- writeRunTranscriptState(options.configPath, conversationId, runId, {
64238
- ...current,
64239
- endOffset: recoveredEnd,
64240
- interrupted: true,
64241
- finalRequested: false
64242
- });
64243
- const path2 = transcriptPath(options.configPath, conversationId);
64244
- const buffer = getTranscriptBuffer(path2);
64245
- if (buffer) closeTranscriptBuffer(path2, buffer);
64246
- }
64247
- state = recoverInterruptedRange(options, conversationId, runId);
64248
- } catch (error61) {
64249
- options.onProblem(`transcript-recovery-failed run=${runId}: ${String(error61)}`);
64250
- continue;
64251
- }
64252
- if (state.endOffset === void 0) continue;
64253
- const size = state.endOffset - state.startOffset;
64254
- if (state.syncedOffset === size && state.finalizedOffset === size) continue;
64255
- const target = {
64256
- configPath: options.configPath,
64257
- apiUrl: options.apiUrl,
64258
- conversationId,
64259
- runId,
64260
- getRuntimeToken: options.getRuntimeToken,
64261
- signal: options.signal,
64262
- sandbox: state.sandbox
64263
- };
64264
- const cooldownUntil = Math.max(state.pushRetryAfter ?? 0, state.liveRetryAfter ?? 0);
64265
- const push2 = startTranscriptPush(target, options.onProblem);
64266
- const live = startTranscriptLive(target, options.onProblem);
64267
- try {
64268
- await Promise.all([push2.flush(), live.flush()]);
64269
- if (cooldownUntil > Date.now() && !isDelivered(readConversationSyncState(options.configPath, conversationId).runs[runId])) {
64270
- await waitUntil(cooldownUntil, options.signal);
64271
- if (!options.signal.aborted) await Promise.all([push2.flush(), live.flush()]);
64272
- }
64273
- } finally {
64274
- await Promise.all([push2.stop(), live.stop()]);
64275
- }
64712
+ if (state.endOffset === void 0 || isTranscriptDelivered(state)) continue;
64713
+ const cooldownUntil = Math.max(state.pushRetryAfter ?? 0, state.liveRetryAfter ?? 0);
64714
+ if (cooldownUntil > Date.now()) {
64715
+ cooldowns.earliest = Math.min(cooldowns.earliest ?? cooldownUntil, cooldownUntil);
64716
+ continue;
64717
+ }
64718
+ const target = {
64719
+ configPath: options.configPath,
64720
+ apiUrl: options.apiUrl,
64721
+ conversationId,
64722
+ runId,
64723
+ getRuntimeToken: options.getRuntimeToken,
64724
+ signal: options.signal,
64725
+ sandbox: state.sandbox
64726
+ };
64727
+ const push2 = startTranscriptPush(target, options.onProblem);
64728
+ const live = startTranscriptLive(target, options.onProblem);
64729
+ try {
64730
+ await Promise.all([push2.flush(), live.flush()]);
64731
+ } finally {
64732
+ await Promise.all([push2.stop(), live.stop()]);
64276
64733
  }
64277
64734
  }
64278
64735
  }
64279
64736
 
64280
64737
  // src/events/run-events.ts
64738
+ var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
64739
+ "query_complete",
64740
+ "complete",
64741
+ "session_complete",
64742
+ "session_error",
64743
+ "agent_run_finished"
64744
+ ]);
64745
+ var TERMINAL_RETRY_WINDOW_MS = 10 * 6e4;
64746
+ var RETRY_BACKOFF_CAP_MS = 3e4;
64747
+ function isRetryableStatus(status2) {
64748
+ return status2 === 408 || status2 === 429 || status2 >= 500;
64749
+ }
64750
+ function pause(ms, signal) {
64751
+ return new Promise((resolve16) => {
64752
+ const done = () => {
64753
+ clearTimeout(timer);
64754
+ signal.removeEventListener("abort", done);
64755
+ resolve16();
64756
+ };
64757
+ const timer = setTimeout(done, ms);
64758
+ signal.addEventListener("abort", done, { once: true });
64759
+ });
64760
+ }
64281
64761
  function runEventsUrl(wsUrl) {
64282
64762
  const base = wsUrl.replace(/^ws:/, "http:").replace(/^wss:/, "https:").replace(/\/+$/, "");
64283
64763
  return `${base}/runtime/run-events`;
@@ -64339,24 +64819,41 @@ function createHttpRunEventSink(input2, url3, binding) {
64339
64819
  }
64340
64820
  }
64341
64821
  async function deliver(conversationId, runId, event) {
64342
- if (shutdown.signal.aborted) return;
64343
- const token = input2.getRuntimeToken();
64344
- if (!token) return;
64345
- try {
64346
- const response = await postRunEvent({
64347
- url: url3,
64348
- token,
64349
- conversationId,
64350
- runId,
64351
- event,
64352
- signal: shutdown.signal,
64353
- binding
64354
- });
64355
- if (!response.ok) input2.onFailure?.(`run-event rejected ${response.status} run=${runId}`);
64356
- } catch (error61) {
64357
- input2.onFailure?.(
64358
- `run-event send failed run=${runId}: ${error61 instanceof Error ? error61.message : String(error61)}`
64359
- );
64822
+ const terminal = TERMINAL_EVENT_TYPES.has(event.type);
64823
+ const giveUpAt = Date.now() + TERMINAL_RETRY_WINDOW_MS;
64824
+ for (let attempt = 0; ; attempt++) {
64825
+ if (shutdown.signal.aborted) return;
64826
+ const token = input2.getRuntimeToken();
64827
+ if (!token) return;
64828
+ let failure2;
64829
+ let retryable;
64830
+ const deadline = requestDeadline(1e4, shutdown.signal);
64831
+ try {
64832
+ const response = await postRunEvent({
64833
+ url: url3,
64834
+ token,
64835
+ conversationId,
64836
+ runId,
64837
+ event,
64838
+ signal: deadline.signal,
64839
+ binding
64840
+ });
64841
+ if (response.ok) return;
64842
+ failure2 = `run-event rejected ${response.status} run=${runId}`;
64843
+ retryable = isRetryableStatus(response.status);
64844
+ } catch (error61) {
64845
+ failure2 = `run-event send failed run=${runId}: ${error61 instanceof Error ? error61.message : String(error61)}`;
64846
+ retryable = true;
64847
+ } finally {
64848
+ deadline.dispose();
64849
+ }
64850
+ const backoff = Math.min(1e3 * 2 ** attempt, RETRY_BACKOFF_CAP_MS);
64851
+ if (!terminal || !retryable || Date.now() + backoff > giveUpAt) {
64852
+ input2.onFailure?.(failure2);
64853
+ return;
64854
+ }
64855
+ input2.onFailure?.(`${failure2}; retrying in ${backoff}ms`);
64856
+ await pause(backoff, shutdown.signal);
64360
64857
  }
64361
64858
  }
64362
64859
  return {
@@ -64424,7 +64921,7 @@ function createTranscriptWatchRegistry() {
64424
64921
  }
64425
64922
 
64426
64923
  // src/native/ingest.ts
64427
- var import_node_fs20 = require("fs");
64924
+ var import_node_fs21 = require("fs");
64428
64925
  var import_promises10 = require("fs/promises");
64429
64926
 
64430
64927
  // src/native/alan-runs.ts
@@ -64664,7 +65161,7 @@ async function pushNativeTurn(delivery, record4) {
64664
65161
 
64665
65162
  // src/native/session-store.ts
64666
65163
  var import_node_crypto9 = require("crypto");
64667
- var import_node_fs19 = require("fs");
65164
+ var import_node_fs20 = require("fs");
64668
65165
  var import_node_path19 = require("path");
64669
65166
  var MAX_NATIVE_SESSION_ENTRIES = 500;
64670
65167
  var nativeRunFinishEvents = ["agent_run_finished", "query_complete"];
@@ -64701,7 +65198,7 @@ function isSessionEntry(value2) {
64701
65198
  function loadSessions(path2) {
64702
65199
  let parsed;
64703
65200
  try {
64704
- parsed = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
65201
+ parsed = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
64705
65202
  } catch {
64706
65203
  return [];
64707
65204
  }
@@ -64709,14 +65206,14 @@ function loadSessions(path2) {
64709
65206
  return parsed.sessions.filter(isSessionEntry);
64710
65207
  }
64711
65208
  function writeAtomically(path2, contents) {
64712
- (0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(path2), { recursive: true, mode: 448 });
65209
+ (0, import_node_fs20.mkdirSync)((0, import_node_path19.dirname)(path2), { recursive: true, mode: 448 });
64713
65210
  const pending = `${path2}.pending-${process.pid}-${(0, import_node_crypto9.randomBytes)(6).toString("hex")}`;
64714
65211
  try {
64715
- (0, import_node_fs19.writeFileSync)(pending, contents, { mode: 384 });
64716
- (0, import_node_fs19.chmodSync)(pending, 384);
64717
- (0, import_node_fs19.renameSync)(pending, path2);
65212
+ (0, import_node_fs20.writeFileSync)(pending, contents, { mode: 384 });
65213
+ (0, import_node_fs20.chmodSync)(pending, 384);
65214
+ (0, import_node_fs20.renameSync)(pending, path2);
64718
65215
  } finally {
64719
- (0, import_node_fs19.rmSync)(pending, { force: true });
65216
+ (0, import_node_fs20.rmSync)(pending, { force: true });
64720
65217
  }
64721
65218
  }
64722
65219
  function evictionOrder(a, b) {
@@ -64965,7 +65462,7 @@ function sleep2(ms, signal) {
64965
65462
  });
64966
65463
  }
64967
65464
  function holdFor(entry) {
64968
- const stats = (0, import_node_fs20.statSync)(entry.transcriptPath, { bigint: true, throwIfNoEntry: false });
65465
+ const stats = (0, import_node_fs21.statSync)(entry.transcriptPath, { bigint: true, throwIfNoEntry: false });
64969
65466
  if (!stats) return [];
64970
65467
  return [
64971
65468
  {
@@ -65181,7 +65678,7 @@ function createNativeIngestionSink(deps) {
65181
65678
  const recordAlanRun = (hold, runId) => {
65182
65679
  const entry = store.get(hold.provider, hold.externalSessionId);
65183
65680
  if (!entry || entry.dead) return;
65184
- const stats = (0, import_node_fs20.statSync)(entry.transcriptPath, { bigint: true, throwIfNoEntry: false });
65681
+ const stats = (0, import_node_fs21.statSync)(entry.transcriptPath, { bigint: true, throwIfNoEntry: false });
65185
65682
  if (!stats) return;
65186
65683
  const identity = { device: String(stats.dev), inode: String(stats.ino) };
65187
65684
  const size = Number(stats.size);
@@ -65463,7 +65960,7 @@ function createNativeIngestionSink(deps) {
65463
65960
  schedule(worker, timing.debounceMs);
65464
65961
  };
65465
65962
  const hasUnreadBytes = (entry, now) => {
65466
- const stats = (0, import_node_fs20.statSync)(entry.transcriptPath, { bigint: true, throwIfNoEntry: false });
65963
+ const stats = (0, import_node_fs21.statSync)(entry.transcriptPath, { bigint: true, throwIfNoEntry: false });
65467
65964
  if (!stats || now - Number(stats.mtimeMs) > timing.sweepRecencyMs) return false;
65468
65965
  const replaced = String(stats.dev) !== entry.device || String(stats.ino) !== entry.inode;
65469
65966
  return replaced || Number(stats.size) > entry.cursor.offset;
@@ -67826,16 +68323,16 @@ function applyLeaseCheckResult(input2) {
67826
68323
  }
67827
68324
 
67828
68325
  // src/daemon/admission.ts
67829
- var import_node_fs22 = require("fs");
68326
+ var import_node_fs23 = require("fs");
67830
68327
  var import_node_path26 = require("path");
67831
68328
 
67832
68329
  // src/workspace-relocation.ts
67833
68330
  var import_node_child_process6 = require("child_process");
67834
- var import_node_fs21 = require("fs");
68331
+ var import_node_fs22 = require("fs");
67835
68332
  var import_node_path24 = require("path");
67836
68333
  var MAX_RELOCATION_CANDIDATES = 200;
67837
68334
  var DEFAULT_PROBE2 = {
67838
- childDirectories: (parentPath, limit) => (0, import_node_fs21.readdirSync)(parentPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).slice(0, limit).map((entry) => (0, import_node_path24.join)(parentPath, entry.name)),
68335
+ childDirectories: (parentPath, limit) => (0, import_node_fs22.readdirSync)(parentPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).slice(0, limit).map((entry) => (0, import_node_path24.join)(parentPath, entry.name)),
67839
68336
  gitRemoteUrl: (candidatePath) => {
67840
68337
  const result = (0, import_node_child_process6.spawnSync)("git", ["-C", candidatePath, "config", "--get", "remote.origin.url"], {
67841
68338
  encoding: "utf8",
@@ -68140,7 +68637,7 @@ function resolveWorkspace(ctx, payload) {
68140
68637
  const workspacePathPinned = payload.workspacePathPinned === true;
68141
68638
  if (isAlanDefaultWorkspace(payload.projectPath) && !workspacePathPinned) {
68142
68639
  try {
68143
- (0, import_node_fs22.mkdirSync)(cwd, { recursive: true });
68640
+ (0, import_node_fs23.mkdirSync)(cwd, { recursive: true });
68144
68641
  } catch {
68145
68642
  }
68146
68643
  }
@@ -68616,7 +69113,7 @@ async function launchRun(ctx, payload, plan) {
68616
69113
  }
68617
69114
 
68618
69115
  // src/daemon/journal.ts
68619
- var import_node_fs23 = require("fs");
69116
+ var import_node_fs24 = require("fs");
68620
69117
  var import_node_path27 = require("path");
68621
69118
  var JOURNAL_VERSION = 1;
68622
69119
  var INTERRUPTED_ENTRY_TTL_MS = 24 * 60 * 6e4;
@@ -68636,9 +69133,9 @@ function isEntry(value2) {
68636
69133
  var RuntimeRunJournal = class {
68637
69134
  constructor(path2, nowMs = Date.now()) {
68638
69135
  this.path = path2;
68639
- if (!(0, import_node_fs23.existsSync)(path2)) return;
69136
+ if (!(0, import_node_fs24.existsSync)(path2)) return;
68640
69137
  try {
68641
- const parsed = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8"));
69138
+ const parsed = JSON.parse((0, import_node_fs24.readFileSync)(path2, "utf8"));
68642
69139
  if (parsed.version !== JOURNAL_VERSION || !Array.isArray(parsed.entries)) {
68643
69140
  throw new Error("unsupported run journal format");
68644
69141
  }
@@ -68761,18 +69258,18 @@ var RuntimeRunJournal = class {
68761
69258
  }
68762
69259
  persist() {
68763
69260
  const pendingPath = `${this.path}.pending`;
68764
- (0, import_node_fs23.mkdirSync)((0, import_node_path27.dirname)(this.path), { recursive: true });
69261
+ (0, import_node_fs24.mkdirSync)((0, import_node_path27.dirname)(this.path), { recursive: true });
68765
69262
  try {
68766
- (0, import_node_fs23.writeFileSync)(
69263
+ (0, import_node_fs24.writeFileSync)(
68767
69264
  pendingPath,
68768
69265
  `${JSON.stringify({ version: JOURNAL_VERSION, entries: [...this.entries.values()] })}
68769
69266
  `,
68770
69267
  { mode: 384 }
68771
69268
  );
68772
- (0, import_node_fs23.chmodSync)(pendingPath, 384);
68773
- (0, import_node_fs23.renameSync)(pendingPath, this.path);
69269
+ (0, import_node_fs24.chmodSync)(pendingPath, 384);
69270
+ (0, import_node_fs24.renameSync)(pendingPath, this.path);
68774
69271
  } catch (error61) {
68775
- (0, import_node_fs23.rmSync)(pendingPath, { force: true });
69272
+ (0, import_node_fs24.rmSync)(pendingPath, { force: true });
68776
69273
  throw error61;
68777
69274
  }
68778
69275
  }
@@ -68931,7 +69428,7 @@ function bindLocalApi(server, port) {
68931
69428
  }
68932
69429
 
68933
69430
  // src/daemon/log.ts
68934
- var import_node_fs24 = require("fs");
69431
+ var import_node_fs25 = require("fs");
68935
69432
  var import_node_path28 = require("path");
68936
69433
  var RECENT_LIMIT = 200;
68937
69434
  var MAX_BYTES = 5 * 1024 * 1024;
@@ -68944,20 +69441,20 @@ function openDaemonLog(configPath) {
68944
69441
  let written = 0;
68945
69442
  let sinceSizeCheck = 0;
68946
69443
  const open6 = () => {
68947
- (0, import_node_fs24.mkdirSync)(directory, { recursive: true, mode: 448 });
68948
- handle = (0, import_node_fs24.openSync)(path2, "a", 384);
69444
+ (0, import_node_fs25.mkdirSync)(directory, { recursive: true, mode: 448 });
69445
+ handle = (0, import_node_fs25.openSync)(path2, "a", 384);
68949
69446
  try {
68950
- written = (0, import_node_fs24.statSync)(path2).size;
69447
+ written = (0, import_node_fs25.statSync)(path2).size;
68951
69448
  } catch {
68952
69449
  written = 0;
68953
69450
  }
68954
69451
  };
68955
69452
  const rotate = () => {
68956
69453
  if (handle === null) return;
68957
- (0, import_node_fs24.closeSync)(handle);
69454
+ (0, import_node_fs25.closeSync)(handle);
68958
69455
  handle = null;
68959
69456
  try {
68960
- (0, import_node_fs24.renameSync)(path2, `${path2}.1`);
69457
+ (0, import_node_fs25.renameSync)(path2, `${path2}.1`);
68961
69458
  } catch {
68962
69459
  }
68963
69460
  open6();
@@ -68982,7 +69479,7 @@ function openDaemonLog(configPath) {
68982
69479
  sinceSizeCheck = 0;
68983
69480
  rotate();
68984
69481
  }
68985
- if (handle !== null) written += (0, import_node_fs24.writeSync)(handle, line);
69482
+ if (handle !== null) written += (0, import_node_fs25.writeSync)(handle, line);
68986
69483
  } catch {
68987
69484
  }
68988
69485
  };
@@ -68992,7 +69489,7 @@ function openDaemonLog(configPath) {
68992
69489
  close: () => {
68993
69490
  if (handle === null) return;
68994
69491
  try {
68995
- (0, import_node_fs24.closeSync)(handle);
69492
+ (0, import_node_fs25.closeSync)(handle);
68996
69493
  } catch {
68997
69494
  }
68998
69495
  handle = null;
@@ -69251,16 +69748,16 @@ function handleCapabilities(res) {
69251
69748
  }
69252
69749
 
69253
69750
  // src/daemon/routes/local-transcript.ts
69254
- var import_node_fs25 = require("fs");
69751
+ var import_node_fs26 = require("fs");
69255
69752
  var LocalTranscriptUnavailable = class extends Error {
69256
69753
  };
69257
69754
  function completeEnd(path2, start, end) {
69258
- const file2 = (0, import_node_fs25.openSync)(path2, "r");
69755
+ const file2 = (0, import_node_fs26.openSync)(path2, "r");
69259
69756
  const buffer = Buffer.alloc(64 * 1024);
69260
69757
  try {
69261
69758
  while (end > start) {
69262
69759
  const from = Math.max(start, end - buffer.length);
69263
- const count2 = (0, import_node_fs25.readSync)(file2, buffer, 0, end - from, from);
69760
+ const count2 = (0, import_node_fs26.readSync)(file2, buffer, 0, end - from, from);
69264
69761
  if (count2 !== end - from)
69265
69762
  throw new LocalTranscriptUnavailable("Journal changed during snapshot");
69266
69763
  const newline = buffer.subarray(0, count2).lastIndexOf(10);
@@ -69269,12 +69766,12 @@ function completeEnd(path2, start, end) {
69269
69766
  }
69270
69767
  return start;
69271
69768
  } finally {
69272
- (0, import_node_fs25.closeSync)(file2);
69769
+ (0, import_node_fs26.closeSync)(file2);
69273
69770
  }
69274
69771
  }
69275
69772
  function localTranscriptSnapshot(configPath, conversationId) {
69276
69773
  const path2 = transcriptPath(configPath, conversationId);
69277
- const stat4 = (0, import_node_fs25.statSync)(path2);
69774
+ const stat4 = (0, import_node_fs26.statSync)(path2);
69278
69775
  const state = readConversationSyncState(configPath, conversationId);
69279
69776
  const messages = [];
69280
69777
  const active2 = getTranscriptBuffer(path2);
@@ -69352,9 +69849,9 @@ function localRange(snapshot, messageId, offset2, maxBytes) {
69352
69849
  throw new LocalTranscriptUnavailable("Unknown local message range");
69353
69850
  const length = message.role === "user" ? message.end - message.start : Math.min(maxBytes, message.bytes - offset2);
69354
69851
  const bytes = Buffer.alloc(length);
69355
- const file2 = (0, import_node_fs25.openSync)(snapshot.path, "r");
69852
+ const file2 = (0, import_node_fs26.openSync)(snapshot.path, "r");
69356
69853
  try {
69357
- const stat4 = (0, import_node_fs25.fstatSync)(file2);
69854
+ const stat4 = (0, import_node_fs26.fstatSync)(file2);
69358
69855
  if (`${stat4.dev}.${stat4.ino}.${stat4.birthtimeMs}` !== snapshot.generation)
69359
69856
  throw new LocalTranscriptUnavailable("Local journal generation changed");
69360
69857
  const start = message.start + offset2;
@@ -69362,7 +69859,7 @@ function localRange(snapshot, messageId, offset2, maxBytes) {
69362
69859
  const active2 = candidate && start + length <= candidate.produced ? candidate : void 0;
69363
69860
  const memoryStart = active2 ? Math.max(start, active2.retainedFrom) : start + length;
69364
69861
  const diskLength = Math.min(length, memoryStart - start);
69365
- if (diskLength > 0 && (0, import_node_fs25.readSync)(file2, bytes, 0, diskLength, start) !== diskLength)
69862
+ if (diskLength > 0 && (0, import_node_fs26.readSync)(file2, bytes, 0, diskLength, start) !== diskLength)
69366
69863
  throw new LocalTranscriptUnavailable("Local journal changed during read");
69367
69864
  if (diskLength < length) {
69368
69865
  const tail = active2?.read(start + diskLength, length - diskLength);
@@ -69370,7 +69867,7 @@ function localRange(snapshot, messageId, offset2, maxBytes) {
69370
69867
  tail.copy(bytes, diskLength);
69371
69868
  }
69372
69869
  } finally {
69373
- (0, import_node_fs25.closeSync)(file2);
69870
+ (0, import_node_fs26.closeSync)(file2);
69374
69871
  }
69375
69872
  if (message.role === "user") {
69376
69873
  const parsed = JSON.parse(bytes.toString());
@@ -69937,7 +70434,7 @@ var BindingSwitchGitError = class extends Error {
69937
70434
  // src/git/repo.ts
69938
70435
  var import_node_child_process8 = require("child_process");
69939
70436
  var import_node_crypto15 = require("crypto");
69940
- var import_node_fs26 = require("fs");
70437
+ var import_node_fs27 = require("fs");
69941
70438
  var import_node_os15 = require("os");
69942
70439
  var import_node_path29 = require("path");
69943
70440
  var import_node_url = require("url");
@@ -69970,12 +70467,12 @@ function networkGitTimeoutMs() {
69970
70467
  async function waitForCheckpointTestBarrier() {
69971
70468
  const barrierDir = process.env.NODE_ENV === "test" ? process.env.ALAN_TEST_GIT_CHECKPOINT_BARRIER_DIR : void 0;
69972
70469
  if (!barrierDir) return;
69973
- (0, import_node_fs26.mkdirSync)(barrierDir, { recursive: true });
70470
+ (0, import_node_fs27.mkdirSync)(barrierDir, { recursive: true });
69974
70471
  const readyPath = (0, import_node_path29.join)(barrierDir, "ready");
69975
70472
  const releasePath = (0, import_node_path29.join)(barrierDir, "release");
69976
- (0, import_node_fs26.writeFileSync)(readyPath, "ready\n");
70473
+ (0, import_node_fs27.writeFileSync)(readyPath, "ready\n");
69977
70474
  const deadline = Date.now() + 5e3;
69978
- while (!(0, import_node_fs26.existsSync)(releasePath)) {
70475
+ while (!(0, import_node_fs27.existsSync)(releasePath)) {
69979
70476
  if (Date.now() >= deadline) {
69980
70477
  throw new BindingSwitchGitError(
69981
70478
  "checkpoint_prepare_timeout",
@@ -69988,12 +70485,12 @@ async function waitForCheckpointTestBarrier() {
69988
70485
  async function waitForCheckpointFinalizeTestBarrier() {
69989
70486
  const barrierDir = process.env.NODE_ENV === "test" ? process.env.ALAN_TEST_GIT_CHECKPOINT_FINALIZE_BARRIER_DIR : void 0;
69990
70487
  if (!barrierDir) return;
69991
- (0, import_node_fs26.mkdirSync)(barrierDir, { recursive: true });
70488
+ (0, import_node_fs27.mkdirSync)(barrierDir, { recursive: true });
69992
70489
  const readyPath = (0, import_node_path29.join)(barrierDir, "ready");
69993
70490
  const releasePath = (0, import_node_path29.join)(barrierDir, "release");
69994
- (0, import_node_fs26.writeFileSync)(readyPath, "ready\n");
70491
+ (0, import_node_fs27.writeFileSync)(readyPath, "ready\n");
69995
70492
  const deadline = Date.now() + 5e3;
69996
- while (!(0, import_node_fs26.existsSync)(releasePath)) {
70493
+ while (!(0, import_node_fs27.existsSync)(releasePath)) {
69997
70494
  if (Date.now() >= deadline) {
69998
70495
  throw new BindingSwitchGitError(
69999
70496
  "checkpoint_finalize_timeout",
@@ -70106,7 +70603,7 @@ function sourceChangedDuringCheckpoint(message) {
70106
70603
  }
70107
70604
  function acquireCheckpointHeadLock(locks) {
70108
70605
  try {
70109
- (0, import_node_fs26.writeFileSync)(locks.headLockPath, "", { flag: "wx" });
70606
+ (0, import_node_fs27.writeFileSync)(locks.headLockPath, "", { flag: "wx" });
70110
70607
  locks.headLockHeld = true;
70111
70608
  } catch {
70112
70609
  throw sourceChangedDuringCheckpoint(
@@ -70116,7 +70613,7 @@ function acquireCheckpointHeadLock(locks) {
70116
70613
  }
70117
70614
  function releaseCheckpointHeadLock(locks) {
70118
70615
  if (!locks.headLockHeld) return;
70119
- (0, import_node_fs26.rmSync)(locks.headLockPath, { force: true });
70616
+ (0, import_node_fs27.rmSync)(locks.headLockPath, { force: true });
70120
70617
  locks.headLockHeld = false;
70121
70618
  }
70122
70619
  function acquireCheckpointRepositoryLocks(repoRoot) {
@@ -70138,13 +70635,13 @@ function acquireCheckpointRepositoryLocks(repoRoot) {
70138
70635
  indexPath
70139
70636
  };
70140
70637
  try {
70141
- (0, import_node_fs26.writeFileSync)(locks.indexLockPath, "", { flag: "wx" });
70638
+ (0, import_node_fs27.writeFileSync)(locks.indexLockPath, "", { flag: "wx" });
70142
70639
  locks.indexLockHeld = true;
70143
70640
  acquireCheckpointHeadLock(locks);
70144
70641
  return locks;
70145
70642
  } catch {
70146
70643
  if (locks.indexLockHeld) {
70147
- (0, import_node_fs26.rmSync)(locks.indexLockPath, { force: true });
70644
+ (0, import_node_fs27.rmSync)(locks.indexLockPath, { force: true });
70148
70645
  locks.indexLockHeld = false;
70149
70646
  }
70150
70647
  throw sourceChangedDuringCheckpoint(
@@ -70155,7 +70652,7 @@ function acquireCheckpointRepositoryLocks(repoRoot) {
70155
70652
  function releaseCheckpointRepositoryLocks(locks) {
70156
70653
  releaseCheckpointHeadLock(locks);
70157
70654
  if (locks.indexLockHeld) {
70158
- (0, import_node_fs26.rmSync)(locks.indexLockPath, { force: true });
70655
+ (0, import_node_fs27.rmSync)(locks.indexLockPath, { force: true });
70159
70656
  locks.indexLockHeld = false;
70160
70657
  }
70161
70658
  }
@@ -70173,7 +70670,7 @@ function assertCheckpointHead(repoRoot, expectedRef, expectedHeadSha) {
70173
70670
  }
70174
70671
  }
70175
70672
  function createCheckpointIndexBaseline(repoRoot, checkpointSha) {
70176
- const tempDir = (0, import_node_fs26.mkdtempSync)((0, import_node_path29.join)((0, import_node_os15.tmpdir)(), "alan-checkpoint-index-"));
70673
+ const tempDir = (0, import_node_fs27.mkdtempSync)((0, import_node_path29.join)((0, import_node_os15.tmpdir)(), "alan-checkpoint-index-"));
70177
70674
  const indexPath = (0, import_node_path29.join)(tempDir, "index");
70178
70675
  try {
70179
70676
  runGitWithOptions(
@@ -70184,14 +70681,14 @@ function createCheckpointIndexBaseline(repoRoot, checkpointSha) {
70184
70681
  );
70185
70682
  return { indexPath, tempDir };
70186
70683
  } catch (error61) {
70187
- (0, import_node_fs26.rmSync)(tempDir, { recursive: true, force: true });
70684
+ (0, import_node_fs27.rmSync)(tempDir, { recursive: true, force: true });
70188
70685
  throw error61;
70189
70686
  }
70190
70687
  }
70191
70688
  function installCheckpointIndexBaseline(locks, baselineIndexPath) {
70192
70689
  try {
70193
- (0, import_node_fs26.copyFileSync)(baselineIndexPath, locks.indexLockPath);
70194
- (0, import_node_fs26.renameSync)(locks.indexLockPath, locks.indexPath);
70690
+ (0, import_node_fs27.copyFileSync)(baselineIndexPath, locks.indexLockPath);
70691
+ (0, import_node_fs27.renameSync)(locks.indexLockPath, locks.indexPath);
70195
70692
  locks.indexLockHeld = false;
70196
70693
  } catch (error61) {
70197
70694
  throw new BindingSwitchGitError(
@@ -70354,7 +70851,7 @@ function inspectedRemoteUrl(repoRoot, params) {
70354
70851
  function canonicalPathWithMissingTail(path2) {
70355
70852
  const missingSegments = [];
70356
70853
  let cursor = (0, import_node_path29.resolve)(path2);
70357
- while (!(0, import_node_fs26.existsSync)(cursor)) {
70854
+ while (!(0, import_node_fs27.existsSync)(cursor)) {
70358
70855
  const parent = (0, import_node_path29.dirname)(cursor);
70359
70856
  if (parent === cursor) {
70360
70857
  throw new BindingSwitchGitError(
@@ -70365,10 +70862,10 @@ function canonicalPathWithMissingTail(path2) {
70365
70862
  missingSegments.unshift((0, import_node_path29.basename)(cursor));
70366
70863
  cursor = parent;
70367
70864
  }
70368
- return (0, import_node_path29.join)(import_node_fs26.realpathSync.native(cursor), ...missingSegments);
70865
+ return (0, import_node_path29.join)(import_node_fs27.realpathSync.native(cursor), ...missingSegments);
70369
70866
  }
70370
70867
  function resolveRepositoryRoot(requestedPath) {
70371
- if (!(0, import_node_fs26.existsSync)(requestedPath)) {
70868
+ if (!(0, import_node_fs27.existsSync)(requestedPath)) {
70372
70869
  throw new BindingSwitchGitError(
70373
70870
  "repository_not_found",
70374
70871
  `The selected repository path does not exist: ${requestedPath}`
@@ -70381,7 +70878,7 @@ function resolveRepositoryRoot(requestedPath) {
70381
70878
  "The selected folder is not inside a Git repository"
70382
70879
  );
70383
70880
  }
70384
- return import_node_fs26.realpathSync.native((0, import_node_path29.resolve)(root.stdout.trim()));
70881
+ return import_node_fs27.realpathSync.native((0, import_node_path29.resolve)(root.stdout.trim()));
70385
70882
  }
70386
70883
  function listWorktrees(repoRoot) {
70387
70884
  return runGit(repoRoot, ["worktree", "list", "--porcelain"]).split("\n\n").flatMap((entry) => {
@@ -70437,7 +70934,7 @@ function updateLengthPrefixed(hash2, value2) {
70437
70934
  hash2.update("\0");
70438
70935
  }
70439
70936
  function proposedTreeSha(repoRoot, headSha) {
70440
- const tempDir = (0, import_node_fs26.mkdtempSync)((0, import_node_path29.join)((0, import_node_os15.tmpdir)(), "alan-switch-index-"));
70937
+ const tempDir = (0, import_node_fs27.mkdtempSync)((0, import_node_path29.join)((0, import_node_os15.tmpdir)(), "alan-switch-index-"));
70441
70938
  const indexPath = (0, import_node_path29.join)(tempDir, "index");
70442
70939
  const options = { env: { GIT_INDEX_FILE: indexPath } };
70443
70940
  try {
@@ -70451,7 +70948,7 @@ function proposedTreeSha(repoRoot, headSha) {
70451
70948
  assertCommitSha(treeSha, "proposedTreeSha");
70452
70949
  return treeSha;
70453
70950
  } finally {
70454
- (0, import_node_fs26.rmSync)(tempDir, { recursive: true, force: true });
70951
+ (0, import_node_fs27.rmSync)(tempDir, { recursive: true, force: true });
70455
70952
  }
70456
70953
  }
70457
70954
  function statusFingerprint(repoRoot, porcelain, proposedTree) {
@@ -70686,7 +71183,7 @@ async function checkout(params) {
70686
71183
  }
70687
71184
 
70688
71185
  // src/git/checkpoint.ts
70689
- var import_node_fs27 = require("fs");
71186
+ var import_node_fs28 = require("fs");
70690
71187
  async function checkpoint(params) {
70691
71188
  const { switchId, repoKey } = commonRequest(params);
70692
71189
  const repoRoot = resolveAuthorizedRepository(params, true);
@@ -70711,7 +71208,7 @@ async function checkpoint(params) {
70711
71208
  return { repoRoot, sha: status2.headSha, replayed: true };
70712
71209
  } finally {
70713
71210
  if (locks2) releaseCheckpointRepositoryLocks(locks2);
70714
- (0, import_node_fs27.rmSync)(baseline2.tempDir, { recursive: true, force: true });
71211
+ (0, import_node_fs28.rmSync)(baseline2.tempDir, { recursive: true, force: true });
70715
71212
  }
70716
71213
  }
70717
71214
  assertSourcePreconditions(status2, expectedHeadSha, expectedStatusFingerprint, expectedBranch);
@@ -70819,7 +71316,7 @@ Alan-Repository-Id: ${repoKey}`;
70819
71316
  runGitResult(repoRoot, ["update-ref", expectedRef, expectedHeadSha, sha]);
70820
71317
  }
70821
71318
  if (locks) releaseCheckpointRepositoryLocks(locks);
70822
- (0, import_node_fs27.rmSync)(baseline.tempDir, { recursive: true, force: true });
71319
+ (0, import_node_fs28.rmSync)(baseline.tempDir, { recursive: true, force: true });
70823
71320
  }
70824
71321
  }
70825
71322
 
@@ -71025,7 +71522,7 @@ async function stopBackgroundTaskForRun(deps, payload) {
71025
71522
  }
71026
71523
 
71027
71524
  // src/daemon/sweep.ts
71028
- var import_node_fs28 = require("fs");
71525
+ var import_node_fs29 = require("fs");
71029
71526
  var import_node_path30 = require("path");
71030
71527
  function retiredPaths(configPath) {
71031
71528
  const directory = (0, import_node_path30.dirname)(configPath);
@@ -71044,9 +71541,9 @@ function retiredPaths(configPath) {
71044
71541
  function sweepRetiredArtifacts(configPath) {
71045
71542
  const removed = [];
71046
71543
  for (const path2 of retiredPaths(configPath)) {
71047
- if (!(0, import_node_fs28.existsSync)(path2)) continue;
71544
+ if (!(0, import_node_fs29.existsSync)(path2)) continue;
71048
71545
  try {
71049
- (0, import_node_fs28.rmSync)(path2, { recursive: true, force: true });
71546
+ (0, import_node_fs29.rmSync)(path2, { recursive: true, force: true });
71050
71547
  removed.push(path2);
71051
71548
  } catch {
71052
71549
  }
@@ -71056,6 +71553,8 @@ function sweepRetiredArtifacts(configPath) {
71056
71553
 
71057
71554
  // src/daemon/start.ts
71058
71555
  var RUNTIME_TOKEN_RENEWAL_WINDOW_MS2 = 24 * 60 * 60 * 1e3;
71556
+ var TRANSCRIPT_BACKLOG_TTL_MS = 1e4;
71557
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
71059
71558
  async function startDaemon(args) {
71060
71559
  bindConfigPath(args);
71061
71560
  const stored = readConfig();
@@ -71431,6 +71930,33 @@ async function startDaemon(args) {
71431
71930
  });
71432
71931
  const persistWorkspaceRelocations = relocationTracker.persist;
71433
71932
  const flushWorkspaceRelocations = relocationTracker.flush;
71933
+ let transcriptBacklog = {
71934
+ readAtMs: 0,
71935
+ runs: []
71936
+ };
71937
+ const transcriptSyncPending = () => {
71938
+ const nowMs = Date.now();
71939
+ if (nowMs - transcriptBacklog.readAtMs < TRANSCRIPT_BACKLOG_TTL_MS)
71940
+ return transcriptBacklog.runs;
71941
+ try {
71942
+ const runs = readTranscriptSyncBacklog(
71943
+ configPath,
71944
+ (runId) => activeAgents.has(runId) || pendingRunStarts.has(runId)
71945
+ ).filter(
71946
+ (entry) => UUID_PATTERN.test(entry.runId) && UUID_PATTERN.test(entry.conversationId)
71947
+ ).slice(0, MAX_REPORTED_TRANSCRIPT_SYNC_RUNS).map((entry) => ({
71948
+ runId: entry.runId,
71949
+ conversationId: entry.conversationId,
71950
+ ...entry.problem?.status !== void 0 ? { problemStatus: entry.problem.status } : {},
71951
+ ...entry.problem ? { problemPermanent: entry.problem.permanent } : {}
71952
+ }));
71953
+ transcriptBacklog = { readAtMs: nowMs, runs };
71954
+ } catch (error61) {
71955
+ pushLog(`transcript-backlog-scan-failed ${String(error61)}`);
71956
+ transcriptBacklog = { ...transcriptBacklog, readAtMs: nowMs };
71957
+ }
71958
+ return transcriptBacklog.runs;
71959
+ };
71434
71960
  const runnerStatusInput = () => ({
71435
71961
  connected: socket.connected,
71436
71962
  activeAgents,
@@ -71439,6 +71965,7 @@ async function startDaemon(args) {
71439
71965
  daemonEpoch,
71440
71966
  heartbeatSeq,
71441
71967
  interruptedRunIds: runJournal.recoveredRunIds(),
71968
+ transcriptSyncPending: transcriptSyncPending(),
71442
71969
  endpointProfile,
71443
71970
  configPath,
71444
71971
  runtimeId,
@@ -71460,6 +71987,19 @@ async function startDaemon(args) {
71460
71987
  let transcriptReplayEnabled = false;
71461
71988
  let transcriptReplay = null;
71462
71989
  let transcriptReplayRequested = false;
71990
+ let transcriptCooldownTimer;
71991
+ const scheduleCooldownReplay = (retryAt) => {
71992
+ if (retryAt === void 0 || shutdownAbort.signal.aborted) return;
71993
+ if (transcriptCooldownTimer) clearTimeout(transcriptCooldownTimer);
71994
+ transcriptCooldownTimer = setTimeout(
71995
+ () => {
71996
+ transcriptCooldownTimer = void 0;
71997
+ replayTranscripts();
71998
+ },
71999
+ Math.max(0, retryAt - Date.now())
72000
+ );
72001
+ transcriptCooldownTimer.unref?.();
72002
+ };
71463
72003
  const replayTranscripts = () => {
71464
72004
  if (!transcriptReplayEnabled || shutdownAbort.signal.aborted || terminalAuthFailureCode) return;
71465
72005
  if (transcriptReplay) {
@@ -71474,7 +72014,7 @@ async function startDaemon(args) {
71474
72014
  canReplay: () => !terminalAuthFailureCode,
71475
72015
  signal: shutdownAbort.signal,
71476
72016
  onProblem: pushLog
71477
- }).catch((error61) => pushLog(`transcript-replay-failed ${String(error61)}`)).finally(() => {
72017
+ }).then(scheduleCooldownReplay).catch((error61) => pushLog(`transcript-replay-failed ${String(error61)}`)).finally(() => {
71478
72018
  transcriptReplay = null;
71479
72019
  if (transcriptReplayRequested) {
71480
72020
  transcriptReplayRequested = false;
@@ -71519,6 +72059,11 @@ async function startDaemon(args) {
71519
72059
  socket.on("connect", () => {
71520
72060
  disconnectedSinceMs = null;
71521
72061
  clearConnectErrors();
72062
+ try {
72063
+ if (releasePermanentTranscriptCooldowns(configPath) > 0) replayTranscripts();
72064
+ } catch (error61) {
72065
+ pushLog(`transcript-cooldown-release-failed ${String(error61)}`);
72066
+ }
71522
72067
  deniedRedialAttempt = 0;
71523
72068
  if (deniedRedialTimer) {
71524
72069
  clearTimeout(deniedRedialTimer);
@@ -71845,6 +72390,7 @@ async function startDaemon(args) {
71845
72390
  );
71846
72391
  const abortStartup = async (error61, closeServer = false) => {
71847
72392
  clearInterval(heartbeat);
72393
+ if (transcriptCooldownTimer) clearTimeout(transcriptCooldownTimer);
71848
72394
  clearInterval(versionRefresh);
71849
72395
  leaseMonitor.stop();
71850
72396
  suspensionDetector.stop();
@@ -71883,6 +72429,7 @@ async function startDaemon(args) {
71883
72429
  let shutdownComplete = false;
71884
72430
  try {
71885
72431
  clearInterval(heartbeat);
72432
+ if (transcriptCooldownTimer) clearTimeout(transcriptCooldownTimer);
71886
72433
  clearInterval(versionRefresh);
71887
72434
  leaseMonitor.stop();
71888
72435
  clearInterval(skillSyncInterval);
@@ -71945,7 +72492,7 @@ async function startDaemon(args) {
71945
72492
  // src/service-manager.ts
71946
72493
  var import_node_child_process9 = require("child_process");
71947
72494
  var import_node_crypto17 = require("crypto");
71948
- var import_node_fs29 = require("fs");
72495
+ var import_node_fs30 = require("fs");
71949
72496
  var import_node_os17 = require("os");
71950
72497
  var import_node_path32 = require("path");
71951
72498
  var SERVICE_LABEL = "ai.tryalan.agent";
@@ -72383,19 +72930,19 @@ function currentServicePlan(args, runtime, options = {}) {
72383
72930
  });
72384
72931
  }
72385
72932
  function writeManifestAtomic(path2, contents) {
72386
- (0, import_node_fs29.mkdirSync)((0, import_node_path32.dirname)(path2), { recursive: true, mode: 448 });
72933
+ (0, import_node_fs30.mkdirSync)((0, import_node_path32.dirname)(path2), { recursive: true, mode: 448 });
72387
72934
  const pendingPath = `${path2}.pending-${process.pid}`;
72388
72935
  try {
72389
- (0, import_node_fs29.writeFileSync)(pendingPath, contents, { mode: 384 });
72390
- (0, import_node_fs29.chmodSync)(pendingPath, 384);
72391
- (0, import_node_fs29.renameSync)(pendingPath, path2);
72936
+ (0, import_node_fs30.writeFileSync)(pendingPath, contents, { mode: 384 });
72937
+ (0, import_node_fs30.chmodSync)(pendingPath, 384);
72938
+ (0, import_node_fs30.renameSync)(pendingPath, path2);
72392
72939
  } finally {
72393
- (0, import_node_fs29.rmSync)(pendingPath, { force: true });
72940
+ (0, import_node_fs30.rmSync)(pendingPath, { force: true });
72394
72941
  }
72395
72942
  }
72396
72943
  function defaultReadManifest(path2) {
72397
- if (!(0, import_node_fs29.existsSync)(path2)) return null;
72398
- return (0, import_node_fs29.readFileSync)(path2, "utf8");
72944
+ if (!(0, import_node_fs30.existsSync)(path2)) return null;
72945
+ return (0, import_node_fs30.readFileSync)(path2, "utf8");
72399
72946
  }
72400
72947
  function runServiceCommand(command, options = {}) {
72401
72948
  return new Promise((resolveCommand2, reject2) => {
@@ -72594,9 +73141,9 @@ function createHostContext(options = {}) {
72594
73141
  };
72595
73142
  const readManifest = options.readManifest ?? defaultReadManifest;
72596
73143
  const writeManifest = options.writeManifest ?? writeManifestAtomic;
72597
- const removeManifest = options.removeManifest ?? ((path2) => (0, import_node_fs29.rmSync)(path2, { force: true }));
73144
+ const removeManifest = options.removeManifest ?? ((path2) => (0, import_node_fs30.rmSync)(path2, { force: true }));
72598
73145
  const ensureLogDir = options.ensureLogDir ?? (() => {
72599
- if (plan.logDirectory) (0, import_node_fs29.mkdirSync)(plan.logDirectory, { recursive: true, mode: 448 });
73146
+ if (plan.logDirectory) (0, import_node_fs30.mkdirSync)(plan.logDirectory, { recursive: true, mode: 448 });
72600
73147
  });
72601
73148
  return {
72602
73149
  args,
@@ -72918,7 +73465,7 @@ ${result.output}
72918
73465
 
72919
73466
  // src/mcp-config-adapters.ts
72920
73467
  var import_node_crypto18 = require("crypto");
72921
- var import_node_fs30 = require("fs");
73468
+ var import_node_fs31 = require("fs");
72922
73469
  var import_node_os18 = require("os");
72923
73470
  var import_node_path33 = require("path");
72924
73471
  var log2 = {
@@ -72945,22 +73492,22 @@ function parseJsonSafe(content) {
72945
73492
  throw new MalformedToolConfigError("Existing tool config is malformed");
72946
73493
  }
72947
73494
  function readFileOr(path2) {
72948
- return (0, import_node_fs30.existsSync)(path2) ? (0, import_node_fs30.readFileSync)(path2, "utf-8") : null;
73495
+ return (0, import_node_fs31.existsSync)(path2) ? (0, import_node_fs31.readFileSync)(path2, "utf-8") : null;
72949
73496
  }
72950
73497
  function acquireConfigLock(path2) {
72951
- (0, import_node_fs30.mkdirSync)((0, import_node_path33.dirname)(path2), { recursive: true });
73498
+ (0, import_node_fs31.mkdirSync)((0, import_node_path33.dirname)(path2), { recursive: true });
72952
73499
  const lockPath = `${path2}.alan-lock`;
72953
73500
  const startedAt = Date.now();
72954
73501
  while (true) {
72955
73502
  try {
72956
- (0, import_node_fs30.mkdirSync)(lockPath);
72957
- return () => (0, import_node_fs30.rmSync)(lockPath, { recursive: true, force: true });
73503
+ (0, import_node_fs31.mkdirSync)(lockPath);
73504
+ return () => (0, import_node_fs31.rmSync)(lockPath, { recursive: true, force: true });
72958
73505
  } catch (error61) {
72959
73506
  const code = error61 && typeof error61 === "object" && "code" in error61 ? error61.code : void 0;
72960
73507
  if (code !== "EEXIST") throw error61;
72961
73508
  try {
72962
- if (Date.now() - (0, import_node_fs30.statSync)(lockPath).mtimeMs > 3e4) {
72963
- (0, import_node_fs30.rmSync)(lockPath, { recursive: true, force: true });
73509
+ if (Date.now() - (0, import_node_fs31.statSync)(lockPath).mtimeMs > 3e4) {
73510
+ (0, import_node_fs31.rmSync)(lockPath, { recursive: true, force: true });
72964
73511
  continue;
72965
73512
  }
72966
73513
  } catch {
@@ -72974,19 +73521,19 @@ function acquireConfigLock(path2) {
72974
73521
  }
72975
73522
  }
72976
73523
  function writeFileSafe(path2, content) {
72977
- (0, import_node_fs30.mkdirSync)((0, import_node_path33.dirname)(path2), { recursive: true });
73524
+ (0, import_node_fs31.mkdirSync)((0, import_node_path33.dirname)(path2), { recursive: true });
72978
73525
  const pendingPath = `${path2}.alan-pending-${process.pid}-${(0, import_node_crypto18.randomBytes)(6).toString("hex")}`;
72979
73526
  try {
72980
- (0, import_node_fs30.writeFileSync)(pendingPath, content, { encoding: "utf8", mode: 384, flag: "wx" });
72981
- (0, import_node_fs30.renameSync)(pendingPath, path2);
73527
+ (0, import_node_fs31.writeFileSync)(pendingPath, content, { encoding: "utf8", mode: 384, flag: "wx" });
73528
+ (0, import_node_fs31.renameSync)(pendingPath, path2);
72982
73529
  } finally {
72983
- (0, import_node_fs30.rmSync)(pendingPath, { force: true });
73530
+ (0, import_node_fs31.rmSync)(pendingPath, { force: true });
72984
73531
  }
72985
73532
  }
72986
73533
  function saveMalformedBackup(path2, content) {
72987
73534
  const hash2 = (0, import_node_crypto18.createHash)("sha256").update(content).digest("hex").slice(0, 16);
72988
73535
  const backupPath = `${path2}.alan-backup-${hash2}`;
72989
- if (!(0, import_node_fs30.existsSync)(backupPath)) (0, import_node_fs30.copyFileSync)(path2, backupPath);
73536
+ if (!(0, import_node_fs31.existsSync)(backupPath)) (0, import_node_fs31.copyFileSync)(path2, backupPath);
72990
73537
  return backupPath;
72991
73538
  }
72992
73539
  var claudeAdapter = {
@@ -73359,6 +73906,95 @@ var commandCodeAdapter = {
73359
73906
  return JSON.stringify(config4, null, 2);
73360
73907
  }
73361
73908
  };
73909
+ var copilotAdapter = {
73910
+ cli: "GitHub Copilot CLI",
73911
+ get configPath() {
73912
+ return (0, import_node_path33.join)(homeDirectory(), ".copilot", "mcp-config.json");
73913
+ },
73914
+ get guardDir() {
73915
+ return (0, import_node_path33.join)(homeDirectory(), ".copilot");
73916
+ },
73917
+ merge(existing, servers) {
73918
+ const config4 = parseJsonSafe(existing);
73919
+ const mcpServers = config4.mcpServers ?? {};
73920
+ for (const s of servers) {
73921
+ if (s.kind === "http") {
73922
+ mcpServers[s.name] = {
73923
+ type: "http",
73924
+ url: s.url,
73925
+ tools: ["*"],
73926
+ headers: s.headers
73927
+ };
73928
+ } else {
73929
+ mcpServers[s.name] = {
73930
+ type: "stdio",
73931
+ command: s.command,
73932
+ tools: ["*"],
73933
+ ...s.args?.length ? { args: s.args } : {},
73934
+ ...s.env && Object.keys(s.env).length > 0 ? { env: s.env } : {}
73935
+ };
73936
+ }
73937
+ }
73938
+ config4.mcpServers = mcpServers;
73939
+ return JSON.stringify(config4, null, 2);
73940
+ },
73941
+ remove(existing, serverNames) {
73942
+ const config4 = parseJsonSafe(existing);
73943
+ const mcpServers = config4.mcpServers ?? {};
73944
+ for (const name of serverNames) delete mcpServers[name];
73945
+ config4.mcpServers = mcpServers;
73946
+ return JSON.stringify(config4, null, 2);
73947
+ }
73948
+ };
73949
+ var grokAdapter = {
73950
+ cli: "Grok CLI",
73951
+ get configPath() {
73952
+ return (0, import_node_path33.join)(homeDirectory(), ".grok", "config.toml");
73953
+ },
73954
+ get guardDir() {
73955
+ return (0, import_node_path33.join)(homeDirectory(), ".grok");
73956
+ },
73957
+ merge(existing, servers) {
73958
+ let config4 = {};
73959
+ if (existing) {
73960
+ try {
73961
+ config4 = parse4(existing);
73962
+ } catch {
73963
+ throw new MalformedToolConfigError("Existing tool config is malformed");
73964
+ }
73965
+ }
73966
+ const mcpServers = config4.mcp_servers ?? {};
73967
+ for (const s of servers) {
73968
+ if (s.kind === "http") {
73969
+ mcpServers[s.name] = {
73970
+ url: s.url,
73971
+ ...Object.keys(s.headers).length > 0 ? { headers: s.headers } : {}
73972
+ };
73973
+ } else {
73974
+ mcpServers[s.name] = {
73975
+ command: s.command,
73976
+ ...s.args?.length ? { args: s.args } : {},
73977
+ ...s.env && Object.keys(s.env).length > 0 ? { env: s.env } : {}
73978
+ };
73979
+ }
73980
+ }
73981
+ config4.mcp_servers = mcpServers;
73982
+ return stringify(config4);
73983
+ },
73984
+ remove(existing, serverNames) {
73985
+ if (!existing) return "";
73986
+ let config4 = {};
73987
+ try {
73988
+ config4 = parse4(existing);
73989
+ } catch {
73990
+ return existing;
73991
+ }
73992
+ const mcpServers = config4.mcp_servers ?? {};
73993
+ for (const name of serverNames) delete mcpServers[name];
73994
+ config4.mcp_servers = mcpServers;
73995
+ return stringify(config4);
73996
+ }
73997
+ };
73362
73998
  var ALL_ADAPTERS = [
73363
73999
  claudeAdapter,
73364
74000
  codexAdapter,
@@ -73369,10 +74005,12 @@ var ALL_ADAPTERS = [
73369
74005
  commandCodeAdapter,
73370
74006
  opencodeAdapter,
73371
74007
  factoryAdapter,
73372
- ampAdapter
74008
+ ampAdapter,
74009
+ copilotAdapter,
74010
+ grokAdapter
73373
74011
  ];
73374
74012
  function serverEntry(adapter, content, name) {
73375
- if (adapter === codexAdapter) {
74013
+ if (adapter === codexAdapter || adapter === grokAdapter) {
73376
74014
  const parsed2 = parse4(content);
73377
74015
  const servers2 = parsed2.mcp_servers;
73378
74016
  return servers2 && typeof servers2 === "object" ? servers2[name] : void 0;
@@ -73551,7 +74189,7 @@ function syncMcpToAllClis(servers, oldManagedServers = []) {
73551
74189
  const written = [];
73552
74190
  const failures = [];
73553
74191
  for (const adapter of ALL_ADAPTERS) {
73554
- if (!(0, import_node_fs30.existsSync)(adapter.guardDir)) {
74192
+ if (!(0, import_node_fs31.existsSync)(adapter.guardDir)) {
73555
74193
  log2.debug("MCP Adapters", `Skipping ${adapter.cli} \u2014 guard dir not found`, {
73556
74194
  guardDir: adapter.guardDir
73557
74195
  });
@@ -73631,7 +74269,7 @@ function removeMcpFromAllClis(managedServers) {
73631
74269
  if (managedServers.length === 0) return;
73632
74270
  const ownershipKnown = isManagedServerList(managedServers);
73633
74271
  for (const adapter of ALL_ADAPTERS) {
73634
- if (!(0, import_node_fs30.existsSync)(adapter.guardDir)) continue;
74272
+ if (!(0, import_node_fs31.existsSync)(adapter.guardDir)) continue;
73635
74273
  const releaseLock = acquireConfigLock(adapter.configPath);
73636
74274
  try {
73637
74275
  const existing = readFileOr(adapter.configPath);
@@ -73670,7 +74308,7 @@ function inspectAlanMcpPresence(expectedServers = [], candidateNames = DEFAULT_A
73670
74308
  ]);
73671
74309
  const expectedByName = new Map(expectedServers.map((server) => [server.name, server]));
73672
74310
  for (const adapter of ALL_ADAPTERS) {
73673
- if (!(0, import_node_fs30.existsSync)(adapter.guardDir)) continue;
74311
+ if (!(0, import_node_fs31.existsSync)(adapter.guardDir)) continue;
73674
74312
  inspectedClis.push(adapter.cli);
73675
74313
  const existing = readFileOr(adapter.configPath);
73676
74314
  if (!existing) continue;
@@ -73781,11 +74419,11 @@ var McpSyncService = class {
73781
74419
  };
73782
74420
 
73783
74421
  // src/cli/ingestion-hooks.ts
73784
- var import_node_fs32 = require("fs");
74422
+ var import_node_fs33 = require("fs");
73785
74423
  var import_node_path35 = require("path");
73786
74424
 
73787
74425
  // src/native/hook-config.ts
73788
- var import_node_fs31 = require("fs");
74426
+ var import_node_fs32 = require("fs");
73789
74427
  var import_node_os19 = require("os");
73790
74428
  var import_node_path34 = require("path");
73791
74429
  var INGESTION_HOOK_MARKER = "# alan-session-ingest";
@@ -73854,9 +74492,9 @@ function hookHandler(provider, event, options) {
73854
74492
  return { type: "command", command: posixCommand(argv, environment), timeout: 5 };
73855
74493
  }
73856
74494
  function readSettings2(path2, backupWhenMalformed) {
73857
- if (!(0, import_node_fs31.existsSync)(path2)) return {};
74495
+ if (!(0, import_node_fs32.existsSync)(path2)) return {};
73858
74496
  try {
73859
- const value2 = JSON.parse((0, import_node_fs31.readFileSync)(path2, "utf8"));
74497
+ const value2 = JSON.parse((0, import_node_fs32.readFileSync)(path2, "utf8"));
73860
74498
  if (!isRecord7(value2)) throw new Error("settings root must be an object");
73861
74499
  if (value2.hooks !== void 0 && !isRecord7(value2.hooks)) {
73862
74500
  throw new Error("settings `hooks` must be an object");
@@ -73866,7 +74504,7 @@ function readSettings2(path2, backupWhenMalformed) {
73866
74504
  const reason = error61 instanceof Error ? error61.message : String(error61);
73867
74505
  if (!backupWhenMalformed) throw new Error(`${path2} is malformed: ${reason}`);
73868
74506
  const backup = `${path2}.malformed-${Date.now()}.bak`;
73869
- (0, import_node_fs31.copyFileSync)(path2, backup);
74507
+ (0, import_node_fs32.copyFileSync)(path2, backup);
73870
74508
  throw new Error(`${path2} is malformed and was copied to ${backup}: ${reason}`);
73871
74509
  }
73872
74510
  }
@@ -73874,14 +74512,14 @@ function hooksOf(settings2) {
73874
74512
  return isRecord7(settings2.hooks) ? settings2.hooks : {};
73875
74513
  }
73876
74514
  function writeSettings2(path2, settings2) {
73877
- (0, import_node_fs31.mkdirSync)((0, import_node_path34.dirname)(path2), { recursive: true, mode: 448 });
74515
+ (0, import_node_fs32.mkdirSync)((0, import_node_path34.dirname)(path2), { recursive: true, mode: 448 });
73878
74516
  const temporary = `${path2}.${process.pid}.tmp`;
73879
74517
  try {
73880
- (0, import_node_fs31.writeFileSync)(temporary, `${JSON.stringify(settings2, null, 2)}
74518
+ (0, import_node_fs32.writeFileSync)(temporary, `${JSON.stringify(settings2, null, 2)}
73881
74519
  `, { mode: 384 });
73882
- (0, import_node_fs31.renameSync)(temporary, path2);
74520
+ (0, import_node_fs32.renameSync)(temporary, path2);
73883
74521
  } finally {
73884
- (0, import_node_fs31.rmSync)(temporary, { force: true });
74522
+ (0, import_node_fs32.rmSync)(temporary, { force: true });
73885
74523
  }
73886
74524
  }
73887
74525
  function isIngestionHandler(handler) {
@@ -73926,7 +74564,7 @@ function installIngestionHooks(provider, options) {
73926
74564
  }
73927
74565
  function removeIngestionHooks(provider, options = {}) {
73928
74566
  const path2 = providerHookSettingsPath(provider, options);
73929
- if (!(0, import_node_fs31.existsSync)(path2)) return { provider, path: path2, changed: false };
74567
+ if (!(0, import_node_fs32.existsSync)(path2)) return { provider, path: path2, changed: false };
73930
74568
  const settings2 = readSettings2(path2, true);
73931
74569
  const hooks = hooksOf(settings2);
73932
74570
  const nextHooks = {};
@@ -73968,7 +74606,7 @@ function systemNodeProbe(env) {
73968
74606
  pathEntries: (env.PATH ?? "").split(import_node_path35.delimiter),
73969
74607
  realpath: (path2) => {
73970
74608
  try {
73971
- return (0, import_node_fs32.realpathSync)(path2);
74609
+ return (0, import_node_fs33.realpathSync)(path2);
73972
74610
  } catch {
73973
74611
  return null;
73974
74612
  }
@@ -74357,7 +74995,7 @@ var agentProbeAckSchema = external_exports.object({
74357
74995
  });
74358
74996
 
74359
74997
  // src/resource-diagnostics.ts
74360
- var import_node_fs33 = require("fs");
74998
+ var import_node_fs34 = require("fs");
74361
74999
  var import_node_os20 = require("os");
74362
75000
  var import_node_path36 = require("path");
74363
75001
  var import_node_perf_hooks = require("perf_hooks");
@@ -74365,7 +75003,7 @@ var MAX_RESOURCE_FILE_BYTES = 16384;
74365
75003
  var RESOURCE_READ_TIMEOUT_MS = 500;
74366
75004
  async function readResourceFile(path2) {
74367
75005
  try {
74368
- const stream = (0, import_node_fs33.createReadStream)(path2, {
75006
+ const stream = (0, import_node_fs34.createReadStream)(path2, {
74369
75007
  encoding: "utf8",
74370
75008
  start: 0,
74371
75009
  end: MAX_RESOURCE_FILE_BYTES,
@@ -75503,7 +76141,7 @@ var ExecutionLeaseClient = class {
75503
76141
  }
75504
76142
  const takeoverPending = response.status === 409 && code === "EXECUTION_LEASE_TAKEOVER_PENDING";
75505
76143
  const mixedVersionUnavailable = (response.status === 401 || response.status === 403 || response.status === 404) && !code?.startsWith("EXECUTION_LEASE_");
75506
- const retryable = takeoverPending || mixedVersionUnavailable || isRetryableStatus(response.status);
76144
+ const retryable = takeoverPending || mixedVersionUnavailable || isRetryableStatus2(response.status);
75507
76145
  if (!retryable || attempt === this.retryDelaysMs.length) {
75508
76146
  if (response.status === 404 && !code && this.isCurrentRun(activity, activityGeneration)) {
75509
76147
  this.protectionMode = "legacy";
@@ -75559,7 +76197,7 @@ async function readProtectionBody(response) {
75559
76197
  return {};
75560
76198
  }
75561
76199
  }
75562
- function isRetryableStatus(status2) {
76200
+ function isRetryableStatus2(status2) {
75563
76201
  return status2 === 408 || status2 === 429 || status2 >= 500;
75564
76202
  }
75565
76203
  async function readErrorBody(response) {