open-walnut 0.4.1 → 0.4.2

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 (31) hide show
  1. package/README.md +2 -5
  2. package/dist/cli-fast.js +69 -46
  3. package/dist/cli.js +704 -274
  4. package/dist/daemon-binaries/acp-worker.js.version +1 -1
  5. package/dist/web/server.js +704 -274
  6. package/dist/web/static/assets/{OfficePreview-C8OMbtiq.js → OfficePreview-CEHO0xkd.js} +2 -2
  7. package/dist/web/static/assets/{docx-preview-hDgtrOpv.js → docx-preview-CuZwjAMS.js} +1 -1
  8. package/dist/web/static/assets/{index-CWIydpPr.js → index-0MyC-yGG.js} +1 -1
  9. package/dist/web/static/assets/{index-BAb6UVet.js → index-BBjg7m-_.js} +1 -1
  10. package/dist/web/static/assets/{index-D1uUk0AQ.js → index-BFF_G72i.js} +289 -289
  11. package/dist/web/static/assets/{index-CPHXL-wy.js → index-BRLxNhyy.js} +1 -1
  12. package/dist/web/static/assets/{index-VcSnwuHG.js → index-Bqg4_WEG.js} +1 -1
  13. package/dist/web/static/assets/{index-bfZj1PAc.js → index-C2YGUYGS.js} +1 -1
  14. package/dist/web/static/assets/{index-ph8Hxff-.js → index-D1dpOEzW.js} +1 -1
  15. package/dist/web/static/assets/{index-nRBC-NJN.js → index-D9qPPm7h.js} +1 -1
  16. package/dist/web/static/assets/{index-D81YKipC.js → index-DAOPV34I.js} +1 -1
  17. package/dist/web/static/assets/{index-CBHRNnCk.js → index-DLjK8dGE.js} +1 -1
  18. package/dist/web/static/assets/{index-KY36bfpA.js → index-DOXm2Aye.js} +1 -1
  19. package/dist/web/static/assets/{index-EowLf_Xf.js → index-DR-LSqAg.js} +1 -1
  20. package/dist/web/static/assets/{index-gv-TJwHT.js → index-DXtp_ZWh.js} +1 -1
  21. package/dist/web/static/assets/index-DeYCGmOR.css +32 -0
  22. package/dist/web/static/assets/{index-B2-N8jTH.js → index-DrWzb338.js} +1 -1
  23. package/dist/web/static/assets/{index-Caj09uf7.js → index-DtRwkh8J.js} +1 -1
  24. package/dist/web/static/assets/{index-C5-vT4kT.js → index-ccs5s80D.js} +1 -1
  25. package/dist/web/static/assets/{index-DtRt21Zi.js → index-mWEwJcqw.js} +1 -1
  26. package/dist/web/static/assets/{index-B35fidy6.js → index-unnPilnK.js} +1 -1
  27. package/dist/web/static/assets/{jszip.min-DyrlNEg4.js → jszip.min-DYFwy4PE.js} +1 -1
  28. package/dist/web/static/assets/{pptx-preview.es-DjND6y6O.js → pptx-preview.es-eu2nX9JC.js} +1 -1
  29. package/dist/web/static/index.html +2 -2
  30. package/package.json +1 -1
  31. package/dist/web/static/assets/index-Btn4AoIy.css +0 -32
@@ -10199,24 +10199,25 @@ function reapSession(sid, code, reason) {
10199
10199
  let jsonlAgeMs = null;
10200
10200
  try { jsonlAgeMs = Date.now() - fs.statSync(session.jsonlPath).mtimeMs; } catch {}
10201
10201
  const cleanExit = isTurnCompleteExit(session.jsonlPath);
10202
- // An INTENTIONAL daemon-initiated reclamation is not a failure, and the JSONL
10203
- // tail cannot tell us it happened. The idle scanner stamps idleReclaimAt
10204
- // before it signals; the death then lands here through whichever path notices
10205
- // it (usually orphan-poll ESRCH \u2192 code -1). Verified production case: the last
10206
- // line was a control_response (the reply to a Walnut-issued control request \u2014
10207
- // a routine shape), so isTurnCompleteExit said false, exitCode stayed -1, and
10208
- // the projection painted a red "Error" over a healthy, fully resumable session
10209
- // we reclaimed on purpose. Checked FIRST and unconditionally: the flag carries
10210
- // information the tail does not contain, which is why isTurnCompleteExit is
10211
- // left alone. Keep in sync with daemon-core.ts reapSession.
10212
- const intentionalReclaim = session.idleReclaimAt != null;
10213
- if (intentionalReclaim && code !== 0) {
10214
- logMsg('info', 'reapSession: intentional idle reclamation, normalizing exit code', {
10202
+ // A DELIBERATE stop is not a failure, and the JSONL tail cannot tell us it
10203
+ // happened. Both writers (our idle scanner, and any stop RPC via cmdStop)
10204
+ // stamp intentionalStopAt before signalling; the death then lands here through
10205
+ // whichever path notices it (usually orphan-poll ESRCH \u2192 code -1). Verified
10206
+ // production case: the last line was a control_response (the reply to a
10207
+ // Walnut-issued control request \u2014 a routine shape), so isTurnCompleteExit said
10208
+ // false, exitCode stayed -1, and the projection painted a red "Error" over a
10209
+ // healthy, fully resumable session that was stopped on purpose. Checked FIRST
10210
+ // and unconditionally: the flag carries information the tail does not contain,
10211
+ // which is why isTurnCompleteExit is left alone.
10212
+ // Keep in sync with daemon-core.ts reapSession.
10213
+ const intentionalStop = session.intentionalStopAt != null;
10214
+ if (intentionalStop && code !== 0) {
10215
+ logMsg('info', 'reapSession: intentional stop, normalizing exit code', {
10215
10216
  sid, pid: session.pid, originalCode: code, originalReason: reason,
10216
- jsonlAgeMs, cleanExit, idleReclaimAt: session.idleReclaimAt,
10217
+ jsonlAgeMs, cleanExit, intentionalStopAt: session.intentionalStopAt,
10217
10218
  });
10218
10219
  code = 0;
10219
- reason = reason + '+intentional-idle-reclaim';
10220
+ reason = reason + '+intentional-stop';
10220
10221
  } else if (code !== 0 && cleanExit) {
10221
10222
  logMsg('info', 'reapSession: turn-complete detected, normalizing exit code', {
10222
10223
  sid, pid: session.pid, originalCode: code, originalReason: reason, jsonlAgeMs,
@@ -13870,6 +13871,15 @@ function cmdStop(ws, id, cmd) {
13870
13871
  const pid = session.pid;
13871
13872
  logMsg('info', 'cmdStop: stopping session (process group kill)', { sid, pid });
13872
13873
 
13874
+ // Nobody sends stop by accident, so record the intent BEFORE the first signal:
13875
+ // the death lands asynchronously (orphan poll \u2192 ESRCH \u2192 code -1) and by then
13876
+ // nothing remembers this was asked for. This path is how the SERVER's idle
13877
+ // reaper kills (mgr.kill() \u2192 stop RPC), not the daemon's own scan, so it needs
13878
+ // its own stamp: a LOCAL session auto-stopped after 2h idle surfaced as
13879
+ // "Session ended unexpectedly and no cause was recorded" (2026-09-03).
13880
+ // Keep in sync with daemon-standalone.ts cmdStop.
13881
+ session.intentionalStopAt = Date.now();
13882
+
13873
13883
  // An explicit stop is a human decision \u2014 never undone by a pending auto-retry
13874
13884
  // respawning the CLI minutes later. Cancel the timer AND clear the streak so
13875
13885
  // an in-flight result line can't re-arm one either.
@@ -13906,7 +13916,7 @@ function cmdStop(ws, id, cmd) {
13906
13916
 
13907
13917
  // \u2500\u2500 Status \u2500\u2500
13908
13918
  function cmdStatus(ws, id, cmd) {
13909
- const { sid } = cmd;
13919
+ const { sid, includeArgs } = cmd;
13910
13920
  if (!sid) return sendError(ws, id, 'status: missing sid');
13911
13921
 
13912
13922
  const session = sessions.get(sid);
@@ -13937,6 +13947,11 @@ function cmdStatus(ws, id, cmd) {
13937
13947
  exitCode: session.exitCode,
13938
13948
  exitReason: session.exitReason,
13939
13949
  pendingCtrl: session.pendingCtrl,
13950
+ // Opt-in: the argv the live process was launched with (a fork copies it to
13951
+ // reproduce the parent's prompt-cache prefix). Off by default because the
13952
+ // health monitor polls status for every session and the argv carries the
13953
+ // multi-KB --append-system-prompt.
13954
+ ...(includeArgs && session.args && session.args.length > 0 ? { args: session.args } : {}),
13940
13955
  });
13941
13956
  }
13942
13957
 
@@ -15989,7 +16004,7 @@ function scanIdleSessions() {
15989
16004
  // end" for any session whose last line isn't a type:result \u2014 and the
15990
16005
  // projection then shows a red Error for our own routine reclamation.
15991
16006
  // Keep in sync with daemon-standalone.ts scanIdleSessions.
15992
- session.idleReclaimAt = now;
16007
+ session.intentionalStopAt = now;
15993
16008
  killSessionProcessGroup(pid, sid);
15994
16009
  }
15995
16010
  }
@@ -16673,19 +16688,19 @@ function createDaemonCore(deps) {
16673
16688
  jsonlAgeMs = clock() - fs114.statSync(session2.jsonlPath).mtimeMs;
16674
16689
  } catch {
16675
16690
  }
16676
- const intentionalReclaim = session2.idleReclaimAt != null;
16677
- if (intentionalReclaim && code !== 0) {
16678
- logger2("info", "reapSession: intentional idle reclamation, normalizing exit code", {
16691
+ const intentionalStop = session2.intentionalStopAt != null;
16692
+ if (intentionalStop && code !== 0) {
16693
+ logger2("info", "reapSession: intentional stop, normalizing exit code", {
16679
16694
  sid,
16680
16695
  pid: session2.pid,
16681
16696
  originalCode: code,
16682
16697
  originalReason: reason,
16683
16698
  jsonlAgeMs,
16684
16699
  cleanExit,
16685
- idleReclaimAt: session2.idleReclaimAt
16700
+ intentionalStopAt: session2.intentionalStopAt
16686
16701
  });
16687
16702
  code = 0;
16688
- reason = reason + "+intentional-idle-reclaim";
16703
+ reason = reason + "+intentional-stop";
16689
16704
  } else if (cleanExit && code !== 0) {
16690
16705
  logger2("info", "reapSession: turn-complete detected, normalizing exit code", {
16691
16706
  sid,
@@ -17476,6 +17491,7 @@ var init_daemon_core = __esm({
17476
17491
  var claude_cli_detect_exports = {};
17477
17492
  __export(claude_cli_detect_exports, {
17478
17493
  _CLAUDE_CREDENTIALS_HOME: () => _CLAUDE_CREDENTIALS_HOME,
17494
+ describeClaudeCliAuth: () => describeClaudeCliAuth,
17479
17495
  detectClaudeCli: () => detectClaudeCli,
17480
17496
  hasClaudeSubscriptionAuth: () => hasClaudeSubscriptionAuth,
17481
17497
  isClaudeCliInstalled: () => isClaudeCliInstalled,
@@ -17536,9 +17552,32 @@ function detectClaudeCli() {
17536
17552
  return {
17537
17553
  installed: installed2,
17538
17554
  subscriptionAuth,
17539
- subscriptionReady: installed2 && subscriptionAuth
17555
+ subscriptionReady: installed2 && subscriptionAuth,
17556
+ ready: installed2,
17557
+ auth: installed2 ? describeClaudeCliAuth() : { mode: "unknown", label: "not installed" }
17540
17558
  };
17541
17559
  }
17560
+ function describeClaudeCliAuth(env3 = process.env, settingsEnv = readClaudeSettingsEnv()) {
17561
+ const merged = { ...env3, ...settingsEnv };
17562
+ const truthy = (v3) => !!v3 && v3 !== "0" && v3.toLowerCase() !== "false";
17563
+ if (truthy(merged.CLAUDE_CODE_USE_BEDROCK)) {
17564
+ const region = merged.AWS_REGION || merged.AWS_DEFAULT_REGION;
17565
+ return { mode: "bedrock", label: region ? `Bedrock (${region})` : "Bedrock" };
17566
+ }
17567
+ if (truthy(merged.CLAUDE_CODE_USE_VERTEX)) return { mode: "vertex", label: "Vertex AI" };
17568
+ if (merged.ANTHROPIC_API_KEY || merged.ANTHROPIC_AUTH_TOKEN) return { mode: "api-key", label: "an Anthropic API key" };
17569
+ if (hasClaudeSubscriptionAuth()) return { mode: "subscription", label: "your Claude subscription" };
17570
+ return { mode: "unknown", label: "its own login" };
17571
+ }
17572
+ function readClaudeSettingsEnv() {
17573
+ try {
17574
+ const raw = fs17.readFileSync(path18.join(os4.homedir(), ".claude", "settings.json"), "utf8");
17575
+ const env3 = JSON.parse(raw).env ?? {};
17576
+ return Object.fromEntries(Object.entries(env3).filter(([, v3]) => typeof v3 === "string"));
17577
+ } catch {
17578
+ return {};
17579
+ }
17580
+ }
17542
17581
  var KEYCHAIN_OAUTH_SERVICE, _CLAUDE_CREDENTIALS_HOME;
17543
17582
  var init_claude_cli_detect = __esm({
17544
17583
  "src/core/claude-cli-detect.ts"() {
@@ -24728,7 +24767,7 @@ __export(credential_resolver_exports, {
24728
24767
  DEFAULT_REGION: () => DEFAULT_REGION,
24729
24768
  hasResolvableCredential: () => hasResolvableCredential,
24730
24769
  readClaudeCredentialExport: () => readClaudeCredentialExport,
24731
- readClaudeSettingsEnv: () => readClaudeSettingsEnv,
24770
+ readClaudeSettingsEnv: () => readClaudeSettingsEnv2,
24732
24771
  resolveCredentialFrom: () => resolveCredentialFrom,
24733
24772
  resolveCredentials: () => resolveCredentials,
24734
24773
  traceCredentialResolution: () => traceCredentialResolution
@@ -24883,7 +24922,7 @@ function traceCredentialResolution(inputs) {
24883
24922
  region: { value: winner.region ?? DEFAULT_REGION, source: regionSource }
24884
24923
  };
24885
24924
  }
24886
- function readClaudeSettingsEnv(settingsFile = CLAUDE_SETTINGS_FILE) {
24925
+ function readClaudeSettingsEnv2(settingsFile = CLAUDE_SETTINGS_FILE) {
24887
24926
  try {
24888
24927
  const raw = fs31.readFileSync(settingsFile, "utf-8");
24889
24928
  const parsed = JSON.parse(raw);
@@ -24923,7 +24962,7 @@ function probeAwsFiles() {
24923
24962
  function resolveCredentials(config) {
24924
24963
  const result = resolveCredentialFrom({
24925
24964
  config,
24926
- claudeEnv: readClaudeSettingsEnv(),
24965
+ claudeEnv: readClaudeSettingsEnv2(),
24927
24966
  processEnv: process.env,
24928
24967
  awsFiles: probeAwsFiles(),
24929
24968
  claudeCredentialExport: readClaudeCredentialExport()
@@ -67705,10 +67744,9 @@ function buildArgs(opts) {
67705
67744
  // Input stays default (text): we write the whole prompt to stdin and close.
67706
67745
  // (stream-json INPUT would force us into a JSON envelope for no gain here.)
67707
67746
  "--tools",
67708
- "",
67747
+ ""
67709
67748
  // disable ALL built-in tools (deny-all)
67710
- "--settings",
67711
- FORCE_SUBSCRIPTION_SETTINGS
67749
+ // No --settings override: the user's settings.json (and so their auth) applies as-is.
67712
67750
  ];
67713
67751
  let systemText = systemToText(opts.system).trim();
67714
67752
  if (opts.tools && opts.tools.length > 0) {
@@ -67737,7 +67775,7 @@ function systemToText(system) {
67737
67775
  }
67738
67776
  function buildSpawnEnv(base = process.env) {
67739
67777
  const env3 = { ...base };
67740
- for (const key2 of AUTH_ENV_TO_STRIP) {
67778
+ for (const key2 of ENV_TO_STRIP) {
67741
67779
  delete env3[key2];
67742
67780
  }
67743
67781
  env3.CLAUDE_CODE_ENTRYPOINT = WALNUT_UTILITY_ENTRYPOINT;
@@ -67799,7 +67837,7 @@ function normalizeCliModel(model) {
67799
67837
  }
67800
67838
  return void 0;
67801
67839
  }
67802
- var AUTH_ENV_TO_STRIP, FORCE_SUBSCRIPTION_SETTINGS, CLI_TURN_TIMEOUT_MS, SESSION_REUSE_TTL_MS, MAX_TRACKED_SESSIONS, ClaudeCliAdapter;
67840
+ var ENV_TO_STRIP, MAX_CONCURRENT_CLI, CLI_TURN_TIMEOUT_MS, SESSION_REUSE_TTL_MS, MAX_TRACKED_SESSIONS, ClaudeCliAdapter;
67803
67841
  var init_adapter_claude_cli = __esm({
67804
67842
  "src/agent/providers/adapter-claude-cli.ts"() {
67805
67843
  "use strict";
@@ -67809,24 +67847,11 @@ var init_adapter_claude_cli = __esm({
67809
67847
  init_logging();
67810
67848
  init_claude_cli_detect();
67811
67849
  init_claude_cli_protocol();
67812
- AUTH_ENV_TO_STRIP = [
67813
- "ANTHROPIC_API_KEY",
67814
- "ANTHROPIC_AUTH_TOKEN",
67815
- "ANTHROPIC_BASE_URL",
67816
- "AWS_BEARER_TOKEN_BEDROCK",
67817
- "AWS_ACCESS_KEY_ID",
67818
- "AWS_SECRET_ACCESS_KEY",
67819
- "AWS_SESSION_TOKEN",
67820
- "AWS_PROFILE",
67821
- "CLAUDE_CODE_USE_BEDROCK",
67822
- "CLAUDE_CODE_USE_VERTEX",
67823
- // Nested-session marker — inline-subagent strips this too so the child doesn't
67824
- // think it's running inside another Claude session.
67825
- "CLAUDECODE"
67826
- ];
67827
- FORCE_SUBSCRIPTION_SETTINGS = JSON.stringify({
67828
- env: { CLAUDE_CODE_USE_BEDROCK: "", CLAUDE_CODE_USE_VERTEX: "" }
67829
- });
67850
+ ENV_TO_STRIP = ["CLAUDECODE"];
67851
+ MAX_CONCURRENT_CLI = (() => {
67852
+ const n6 = Number(process.env.WALNUT_CLAUDE_CLI_CONCURRENCY);
67853
+ return Number.isFinite(n6) && n6 > 0 ? Math.floor(n6) : 3;
67854
+ })();
67830
67855
  CLI_TURN_TIMEOUT_MS = 12e4;
67831
67856
  SESSION_REUSE_TTL_MS = 30 * 60 * 1e3;
67832
67857
  MAX_TRACKED_SESSIONS = 50;
@@ -67834,6 +67859,25 @@ var init_adapter_claude_cli = __esm({
67834
67859
  protocol = "claude-cli";
67835
67860
  /** conversationKey → CLI session for --resume chaining. In-process only. */
67836
67861
  sessions = /* @__PURE__ */ new Map();
67862
+ /** Process gate: MAX_CONCURRENT_CLI turns at once, the rest queue in order. */
67863
+ inFlight = 0;
67864
+ waiters = [];
67865
+ async withSlot(fn) {
67866
+ if (this.inFlight >= MAX_CONCURRENT_CLI) {
67867
+ await new Promise((resolve) => this.waiters.push(resolve));
67868
+ }
67869
+ this.inFlight++;
67870
+ try {
67871
+ return await fn();
67872
+ } finally {
67873
+ this.inFlight--;
67874
+ this.waiters.shift()?.();
67875
+ }
67876
+ }
67877
+ /** Test hook: how many turns are running / waiting right now. */
67878
+ _gateStateForTesting() {
67879
+ return { inFlight: this.inFlight, waiting: this.waiters.length };
67880
+ }
67837
67881
  resetClient() {
67838
67882
  this.sessions.clear();
67839
67883
  }
@@ -67911,7 +67955,7 @@ User: ${overridePrompt}` : "");
67911
67955
  }
67912
67956
  if (!prompt.trim()) prompt = "(continue)";
67913
67957
  const args = [...buildArgs(opts), session2.flag, session2.id];
67914
- return this.execCli(command6, args, env3, prompt, opts.signal, onTextDelta).then((result) => {
67958
+ return this.withSlot(() => this.execCli(command6, args, env3, prompt, opts.signal, onTextDelta)).then((result) => {
67915
67959
  this.sessions.set(key2, {
67916
67960
  sessionId: session2.id,
67917
67961
  seenMessages: opts.messages.length,
@@ -67930,7 +67974,7 @@ User: ${overridePrompt}` : "");
67930
67974
  User: ${overridePrompt}` : "");
67931
67975
  const freshId = randomUUID3();
67932
67976
  const freshArgs = [...buildArgs(opts), "--session-id", freshId];
67933
- return this.execCli(command6, freshArgs, env3, freshPrompt || "(continue)", opts.signal, onTextDelta).then((result) => {
67977
+ return this.withSlot(() => this.execCli(command6, freshArgs, env3, freshPrompt || "(continue)", opts.signal, onTextDelta)).then((result) => {
67934
67978
  this.sessions.set(key2, {
67935
67979
  sessionId: freshId,
67936
67980
  seenMessages: opts.messages.length,
@@ -68014,7 +68058,7 @@ User: ${overridePrompt}` : "");
68014
68058
  }
68015
68059
  const resultText = finalResult?.result || accumulatedText;
68016
68060
  if (resultText && resultText.length < 300 && /^\s*failed to authenticate|^\s*api error: 4\d\d/i.test(resultText)) {
68017
- reject(new Error(`claude-cli subscription auth failed \u2014 run \`claude login\` to re-authenticate. CLI said: ${resultText.slice(0, 200)}`));
68061
+ reject(new Error(`claude-cli could not authenticate \u2014 run \`claude\` once in a terminal to sign in (or check its Bedrock settings). CLI said: ${resultText.slice(0, 200)}`));
68018
68062
  return;
68019
68063
  }
68020
68064
  if (code !== 0 && !accumulatedText && !finalResult) {
@@ -68214,6 +68258,50 @@ var init_registry = __esm({
68214
68258
  }
68215
68259
  });
68216
68260
 
68261
+ // src/agent/providers/default-provider.ts
68262
+ var default_provider_exports = {};
68263
+ __export(default_provider_exports, {
68264
+ CLAUDE_CLI_PROVIDER: () => CLAUDE_CLI_PROVIDER,
68265
+ LEGACY_DEFAULT_PROVIDER: () => LEGACY_DEFAULT_PROVIDER,
68266
+ _resetDefaultProviderCacheForTesting: () => _resetDefaultProviderCacheForTesting,
68267
+ hasExplicitBedrockConfig: () => hasExplicitBedrockConfig,
68268
+ mainProviderIsImplicit: () => mainProviderIsImplicit,
68269
+ resolveMainProviderName: () => resolveMainProviderName
68270
+ });
68271
+ function claudeCliInstalledCached() {
68272
+ const now = Date.now();
68273
+ if (!cliProbe || now - cliProbe.at > CLI_PROBE_TTL_MS) {
68274
+ cliProbe = { at: now, installed: isClaudeCliInstalled() };
68275
+ }
68276
+ return cliProbe.installed;
68277
+ }
68278
+ function _resetDefaultProviderCacheForTesting() {
68279
+ cliProbe = void 0;
68280
+ }
68281
+ function hasExplicitBedrockConfig(config) {
68282
+ const b8 = config.providers?.bedrock;
68283
+ if (b8 && (b8.bearer_token || b8.aws_access_key_id || b8.aws_profile || b8.aws_credential_export)) return true;
68284
+ return !!config.provider?.bedrock_bearer_token;
68285
+ }
68286
+ function resolveMainProviderName(config, claudeInstalled = claudeCliInstalledCached()) {
68287
+ if (config.agent?.main_provider) return config.agent.main_provider;
68288
+ if (claudeInstalled) return CLAUDE_CLI_PROVIDER;
68289
+ return LEGACY_DEFAULT_PROVIDER;
68290
+ }
68291
+ function mainProviderIsImplicit(config) {
68292
+ return !config.agent?.main_provider;
68293
+ }
68294
+ var LEGACY_DEFAULT_PROVIDER, CLAUDE_CLI_PROVIDER, CLI_PROBE_TTL_MS, cliProbe;
68295
+ var init_default_provider = __esm({
68296
+ "src/agent/providers/default-provider.ts"() {
68297
+ "use strict";
68298
+ init_claude_cli_detect();
68299
+ LEGACY_DEFAULT_PROVIDER = "bedrock";
68300
+ CLAUDE_CLI_PROVIDER = "claude_cli";
68301
+ CLI_PROBE_TTL_MS = 6e4;
68302
+ }
68303
+ });
68304
+
68217
68305
  // src/agent/model.ts
68218
68306
  var model_exports = {};
68219
68307
  __export(model_exports, {
@@ -68248,7 +68336,7 @@ function getContextThreshold(model, percent) {
68248
68336
  }
68249
68337
  async function resolveForCall(config) {
68250
68338
  const fullConfig = await getConfig();
68251
- const providerName = config?.provider ?? fullConfig.agent?.main_provider ?? "bedrock";
68339
+ const providerName = config?.provider ?? resolveMainProviderName(fullConfig);
68252
68340
  const hasExplicitProviders = fullConfig.providers && Object.keys(fullConfig.providers).length > 0;
68253
68341
  let providers = hasExplicitProviders ? buildProviderMap(fullConfig.providers, fullConfig) : synthesizeFromLegacy(fullConfig);
68254
68342
  const resolved = resolveProvider(providerName, providers);
@@ -68318,6 +68406,7 @@ var init_model = __esm({
68318
68406
  "use strict";
68319
68407
  init_config_manager();
68320
68408
  init_registry();
68409
+ init_default_provider();
68321
68410
  init_defaults();
68322
68411
  init_model_catalog();
68323
68412
  }
@@ -69758,7 +69847,7 @@ __export(cheap_model_exports, {
69758
69847
  });
69759
69848
  function fastModelFor(config) {
69760
69849
  if (config.agent?.fast_model) return config.agent.fast_model;
69761
- const providerName = config.agent?.main_provider ?? "bedrock";
69850
+ const providerName = resolveMainProviderName(config);
69762
69851
  return MODEL_CATALOG[providerName]?.find(
69763
69852
  (model) => model.id.toLowerCase().includes("haiku")
69764
69853
  )?.id;
@@ -69770,6 +69859,7 @@ var init_cheap_model = __esm({
69770
69859
  "src/core/cheap-model.ts"() {
69771
69860
  "use strict";
69772
69861
  init_model_catalog();
69862
+ init_default_provider();
69773
69863
  }
69774
69864
  });
69775
69865
 
@@ -71589,7 +71679,20 @@ async function applySnapshot(sessionId, snapshot2, source) {
71589
71679
  if (projected !== "error") {
71590
71680
  updates.errorMessage = void 0;
71591
71681
  updates.errorKind = void 0;
71592
- clearSuppressedErrorReason(sessionId);
71682
+ const stashedStop = projected === "stopped" ? takeSuppressedErrorReason(sessionId) : null;
71683
+ if (stashedStop?.message) {
71684
+ updates.errorMessage = stashedStop.message;
71685
+ if (stashedStop.kind) updates.errorKind = stashedStop.kind;
71686
+ log.session.info("snapshot projection adopted suppressed stop reason", {
71687
+ sessionId,
71688
+ suppressedReason: stashedStop.reason,
71689
+ kind: stashedStop.kind ?? null,
71690
+ projected,
71691
+ source
71692
+ });
71693
+ } else {
71694
+ clearSuppressedErrorReason(sessionId);
71695
+ }
71593
71696
  } else {
71594
71697
  let droppedDisproven = false;
71595
71698
  if (assertsHostUnreachable(record)) {
@@ -75663,6 +75766,26 @@ var init_transcript_chain = __esm({
75663
75766
  }
75664
75767
  });
75665
75768
 
75769
+ // src/core/sessions/side-thread-warmup.ts
75770
+ function isCacheWarmupText(text) {
75771
+ return text.startsWith(CACHE_WARMUP_TAG);
75772
+ }
75773
+ function markWarmupTurnPending(sessionId) {
75774
+ pendingWarmupTurns.add(sessionId);
75775
+ }
75776
+ function consumeWarmupTurn(sessionId) {
75777
+ return pendingWarmupTurns.delete(sessionId);
75778
+ }
75779
+ var CACHE_WARMUP_TAG, CACHE_WARMUP_MESSAGE, pendingWarmupTurns;
75780
+ var init_side_thread_warmup = __esm({
75781
+ "src/core/sessions/side-thread-warmup.ts"() {
75782
+ "use strict";
75783
+ CACHE_WARMUP_TAG = "<walnut-cache-warmup>";
75784
+ CACHE_WARMUP_MESSAGE = `${CACHE_WARMUP_TAG}This is a cache warm-up from the tool hosting this side thread, not a user message. Reply with exactly one word: Ready. Use no tools and add nothing else.</walnut-cache-warmup>`;
75785
+ pendingWarmupTurns = /* @__PURE__ */ new Set();
75786
+ }
75787
+ });
75788
+
75666
75789
  // src/core/history-disk-cache.ts
75667
75790
  var history_disk_cache_exports = {};
75668
75791
  __export(history_disk_cache_exports, {
@@ -77823,6 +77946,7 @@ __export(session_lifecycle_exports, {
77823
77946
  retrySession: () => retrySession,
77824
77947
  terminateSession: () => terminateSession
77825
77948
  });
77949
+ import { randomUUID as randomUUID4 } from "crypto";
77826
77950
  async function readProviderSessionHistory(sessionId, record, nativeHost, skipSubagents = true, opts) {
77827
77951
  if (record && engineCaps(record.engine).historySource === "acp-journal") {
77828
77952
  const { readAcpSessionHistoryState: readAcpSessionHistoryState2 } = await Promise.resolve().then(() => (init_acp_session_history(), acp_session_history_exports));
@@ -77933,6 +78057,33 @@ async function changeSessionMode(existingRecord, sessionId, mode, updates = {})
77933
78057
  });
77934
78058
  return updated;
77935
78059
  }
78060
+ function normalizePinQuote(value) {
78061
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
78062
+ throw new SessionControlError("pinned_messages[].quote must be an object", 400);
78063
+ }
78064
+ const quote = value;
78065
+ const exact = quote.exact;
78066
+ if (typeof exact !== "string" || !exact.trim() || exact.length > MAX_PIN_QUOTE_CHARS) {
78067
+ throw new SessionControlError(
78068
+ `pinned_messages[].quote.exact must be a non-empty string (max ${MAX_PIN_QUOTE_CHARS} chars)`,
78069
+ 400
78070
+ );
78071
+ }
78072
+ for (const side of ["prefix", "suffix"]) {
78073
+ const context = quote[side];
78074
+ if (context !== void 0 && context !== null && (typeof context !== "string" || context.length > MAX_PIN_QUOTE_CONTEXT_CHARS)) {
78075
+ throw new SessionControlError(
78076
+ `pinned_messages[].quote.${side} must be a string (max ${MAX_PIN_QUOTE_CONTEXT_CHARS} chars)`,
78077
+ 400
78078
+ );
78079
+ }
78080
+ }
78081
+ return {
78082
+ exact,
78083
+ ...typeof quote.prefix === "string" && quote.prefix ? { prefix: quote.prefix } : {},
78084
+ ...typeof quote.suffix === "string" && quote.suffix ? { suffix: quote.suffix } : {}
78085
+ };
78086
+ }
77936
78087
  function normalizePinnedMessages(value) {
77937
78088
  if (!Array.isArray(value)) {
77938
78089
  throw new SessionControlError("pinned_messages must be an array", 400);
@@ -77941,7 +78092,9 @@ function normalizePinnedMessages(value) {
77941
78092
  throw new SessionControlError(`pinned_messages holds at most ${MAX_PINNED_MESSAGES} entries`, 400);
77942
78093
  }
77943
78094
  const out = [];
77944
- const seen = /* @__PURE__ */ new Set();
78095
+ const seenWholeMessage = /* @__PURE__ */ new Set();
78096
+ const seenQuoteId = /* @__PURE__ */ new Set();
78097
+ const seenQuoteShape = /* @__PURE__ */ new Set();
77945
78098
  for (const raw of value) {
77946
78099
  if (!raw || typeof raw !== "object") {
77947
78100
  throw new SessionControlError("each pinned_messages entry must be an object", 400);
@@ -77951,13 +78104,40 @@ function normalizePinnedMessages(value) {
77951
78104
  if (typeof msgId !== "string" || !msgId.trim() || msgId.length > MAX_PIN_ID_CHARS) {
77952
78105
  throw new SessionControlError("pinned_messages[].msgId must be a non-empty string", 400);
77953
78106
  }
77954
- if (seen.has(msgId)) continue;
77955
- seen.add(msgId);
78107
+ const rawId = entry.id;
78108
+ if (rawId !== void 0 && rawId !== null && (typeof rawId !== "string" || !rawId.trim() || rawId.length > MAX_PIN_ENTRY_ID_CHARS)) {
78109
+ throw new SessionControlError(
78110
+ `pinned_messages[].id must be a non-empty string (max ${MAX_PIN_ENTRY_ID_CHARS} chars)`,
78111
+ 400
78112
+ );
78113
+ }
78114
+ const id = typeof rawId === "string" ? rawId : void 0;
78115
+ const quote = entry.quote === void 0 || entry.quote === null ? void 0 : normalizePinQuote(entry.quote);
78116
+ let pinId = id;
78117
+ if (quote) {
78118
+ if (id && seenQuoteId.has(id)) continue;
78119
+ const shape = [msgId, quote.exact, quote.prefix ?? "", quote.suffix ?? ""].join("\0");
78120
+ if (seenQuoteShape.has(shape)) continue;
78121
+ pinId = id ?? randomUUID4();
78122
+ seenQuoteId.add(pinId);
78123
+ seenQuoteShape.add(shape);
78124
+ } else {
78125
+ if (seenWholeMessage.has(msgId)) continue;
78126
+ seenWholeMessage.add(msgId);
78127
+ }
77956
78128
  const role = entry.role === "user" || entry.role === "assistant" || entry.role === "system" ? entry.role : "assistant";
77957
78129
  const label = typeof entry.label === "string" ? entry.label.trim().slice(0, MAX_PIN_LABEL_CHARS) : "";
77958
78130
  const timestamp = typeof entry.timestamp === "string" ? entry.timestamp : void 0;
77959
78131
  const pinnedAt = typeof entry.pinnedAt === "string" ? entry.pinnedAt : (/* @__PURE__ */ new Date()).toISOString();
77960
- out.push({ msgId, label, role, ...timestamp ? { timestamp } : {}, pinnedAt });
78132
+ out.push({
78133
+ msgId,
78134
+ label,
78135
+ role,
78136
+ ...timestamp ? { timestamp } : {},
78137
+ pinnedAt,
78138
+ ...pinId ? { id: pinId } : {},
78139
+ ...quote ? { quote } : {}
78140
+ });
77961
78141
  }
77962
78142
  return out;
77963
78143
  }
@@ -78616,7 +78796,7 @@ async function readSessionRichHistory(sessionId, tail) {
78616
78796
  ...historyWindowed ? { windowed: true } : {}
78617
78797
  };
78618
78798
  }
78619
- var CLAUDE_SESSION_MODES, MAX_PINNED_MESSAGES, MAX_PIN_LABEL_CHARS, MAX_PIN_ID_CHARS, RECHECK_RPC_TIMEOUT_MS, RECHECK_TIMED_OUT, GIT_BASES, MAX_FORK_DEPTH, HISTORY_STARTUP_GRACE_MS;
78799
+ var CLAUDE_SESSION_MODES, MAX_PINNED_MESSAGES, MAX_PIN_LABEL_CHARS, MAX_PIN_ID_CHARS, MAX_PIN_ENTRY_ID_CHARS, MAX_PIN_QUOTE_CHARS, MAX_PIN_QUOTE_CONTEXT_CHARS, RECHECK_RPC_TIMEOUT_MS, RECHECK_TIMED_OUT, GIT_BASES, MAX_FORK_DEPTH, HISTORY_STARTUP_GRACE_MS;
78620
78800
  var init_session_lifecycle = __esm({
78621
78801
  "src/core/sessions/session-lifecycle.ts"() {
78622
78802
  "use strict";
@@ -78630,6 +78810,9 @@ var init_session_lifecycle = __esm({
78630
78810
  MAX_PINNED_MESSAGES = 200;
78631
78811
  MAX_PIN_LABEL_CHARS = 300;
78632
78812
  MAX_PIN_ID_CHARS = 200;
78813
+ MAX_PIN_ENTRY_ID_CHARS = 64;
78814
+ MAX_PIN_QUOTE_CHARS = 2e3;
78815
+ MAX_PIN_QUOTE_CONTEXT_CHARS = 64;
78633
78816
  RECHECK_RPC_TIMEOUT_MS = 5e3;
78634
78817
  RECHECK_TIMED_OUT = /* @__PURE__ */ Symbol("recheck-timeout");
78635
78818
  GIT_BASES = /* @__PURE__ */ new Set(["uncommitted", "previous", "remote"]);
@@ -78932,7 +79115,7 @@ __export(compact_inject_exports, {
78932
79115
  extractSessionMeta: () => extractSessionMeta,
78933
79116
  injectCompactBoundary: () => injectCompactBoundary
78934
79117
  });
78935
- import { randomUUID as randomUUID4 } from "crypto";
79118
+ import { randomUUID as randomUUID5 } from "crypto";
78936
79119
  import fsp26 from "fs/promises";
78937
79120
  async function extractSessionMeta(jsonlPath) {
78938
79121
  const content = await fsp26.readFile(jsonlPath, "utf-8");
@@ -78984,8 +79167,8 @@ async function injectCompactBoundary(jsonlPath, summary, meta) {
78984
79167
  log.session.error("Could not find last message UUID in JSONL", { jsonlPath });
78985
79168
  return null;
78986
79169
  }
78987
- const boundaryUuid = randomUUID4();
78988
- const summaryUuid = randomUUID4();
79170
+ const boundaryUuid = randomUUID5();
79171
+ const summaryUuid = randomUUID5();
78989
79172
  const now = /* @__PURE__ */ new Date();
78990
79173
  const boundaryTs = now.toISOString();
78991
79174
  const summaryTs = new Date(now.getTime() + 1e3).toISOString();
@@ -89769,6 +89952,82 @@ var init_task = __esm({
89769
89952
  }
89770
89953
  });
89771
89954
 
89955
+ // src/core/sessions/spawn-prefix.ts
89956
+ var spawn_prefix_exports = {};
89957
+ __export(spawn_prefix_exports, {
89958
+ MAX_SPAWN_PROMPT_BYTES: () => MAX_SPAWN_PROMPT_BYTES,
89959
+ parseSpawnPrefixFromArgs: () => parseSpawnPrefixFromArgs,
89960
+ readParentSpawnPrefix: () => readParentSpawnPrefix,
89961
+ spawnPrefixFromRecord: () => spawnPrefixFromRecord
89962
+ });
89963
+ function flagValue(args, flag) {
89964
+ const i8 = args.indexOf(flag);
89965
+ if (i8 < 0 || i8 + 1 >= args.length) return void 0;
89966
+ const v3 = args[i8 + 1];
89967
+ return v3 !== void 0 && !v3.startsWith("--") ? v3 : void 0;
89968
+ }
89969
+ function parseSpawnPrefixFromArgs(args) {
89970
+ const prompt = flagValue(args, "--append-system-prompt");
89971
+ return {
89972
+ appendSystemPrompt: prompt !== void 0 && Buffer.byteLength(prompt, "utf8") <= MAX_SPAWN_PROMPT_BYTES ? prompt : null,
89973
+ ...flagValue(args, "--model") ? { model: flagValue(args, "--model") } : {},
89974
+ ...EFFORTS.includes(flagValue(args, "--effort") ?? "") ? { effort: flagValue(args, "--effort") } : {},
89975
+ ...flagValue(args, "--permission-mode") ? { permissionMode: flagValue(args, "--permission-mode") } : {},
89976
+ source: "live-process"
89977
+ };
89978
+ }
89979
+ function spawnPrefixFromRecord(record) {
89980
+ const stored = record.appliedAppendSystemPrompt;
89981
+ if (stored === void 0 || stored === null) return { appendSystemPrompt: null, source: "unknown" };
89982
+ const usable = stored.length > 0 && Buffer.byteLength(stored, "utf8") <= MAX_SPAWN_PROMPT_BYTES;
89983
+ return { appendSystemPrompt: usable ? stored : null, source: "record" };
89984
+ }
89985
+ async function defaultLiveArgsReader(hostKey2, sessionId) {
89986
+ const { probeDaemonSessionArgs: probeDaemonSessionArgs2 } = await Promise.resolve().then(() => (init_daemon_connection(), daemon_connection_exports));
89987
+ return probeDaemonSessionArgs2(hostKey2, sessionId);
89988
+ }
89989
+ async function readParentSpawnPrefix(parent, deps = {}) {
89990
+ const readLiveArgs = deps.readLiveArgs ?? defaultLiveArgsReader;
89991
+ const hostKey2 = parent.host || "__local__";
89992
+ let live = null;
89993
+ try {
89994
+ live = await readLiveArgs(hostKey2, parent.claudeSessionId);
89995
+ } catch (err2) {
89996
+ log.session.debug("spawn prefix: live argv read threw", {
89997
+ sessionId: parent.claudeSessionId,
89998
+ error: err2 instanceof Error ? err2.message : String(err2)
89999
+ });
90000
+ }
90001
+ if (live && live.length > 0) {
90002
+ const prefix = parseSpawnPrefixFromArgs(live);
90003
+ const remembered = parent.appliedAppendSystemPrompt ?? void 0;
90004
+ const actual = prefix.appendSystemPrompt ?? "";
90005
+ if (remembered !== actual) {
90006
+ const persist4 = deps.persist ?? (async (sid, prompt) => {
90007
+ const { updateSessionRecord: updateSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
90008
+ return updateSessionRecord2(sid, { appliedAppendSystemPrompt: prompt });
90009
+ });
90010
+ void persist4(parent.claudeSessionId, actual).catch((err2) => {
90011
+ log.session.warn("spawn prefix: backfill of applied prompt failed", {
90012
+ sessionId: parent.claudeSessionId,
90013
+ error: err2 instanceof Error ? err2.message : String(err2)
90014
+ });
90015
+ });
90016
+ }
90017
+ return prefix;
90018
+ }
90019
+ return spawnPrefixFromRecord(parent);
90020
+ }
90021
+ var EFFORTS, MAX_SPAWN_PROMPT_BYTES;
90022
+ var init_spawn_prefix = __esm({
90023
+ "src/core/sessions/spawn-prefix.ts"() {
90024
+ "use strict";
90025
+ init_logging();
90026
+ EFFORTS = ["low", "medium", "high", "xhigh", "max"];
90027
+ MAX_SPAWN_PROMPT_BYTES = 65536;
90028
+ }
90029
+ });
90030
+
89772
90031
  // src/core/sessions/side-thread-fork.ts
89773
90032
  var side_thread_fork_exports = {};
89774
90033
  __export(side_thread_fork_exports, {
@@ -89825,6 +90084,10 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
89825
90084
  const sessionId = crypto18.randomUUID();
89826
90085
  const lane = sideThreadLaneKey(parentSid, threadId);
89827
90086
  const mode = parent.mode !== "default" ? parent.mode : void 0;
90087
+ const { readParentSpawnPrefix: readParentSpawnPrefix2 } = await Promise.resolve().then(() => (init_spawn_prefix(), spawn_prefix_exports));
90088
+ const prefix = await readParentSpawnPrefix2(parent);
90089
+ const cliModel = prefix.model ?? parent.cliModel;
90090
+ const effort = prefix.effort ?? parent.effort;
89828
90091
  await createSessionRecord(sessionId, "", parent.project ?? "", parent.cwd, {
89829
90092
  title,
89830
90093
  ...mode ? { mode } : {},
@@ -89832,8 +90095,10 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
89832
90095
  // The thread inherits the parent's launch bundle verbatim, re-applied from
89833
90096
  // the record on every cold resume.
89834
90097
  ...parent.profile ? { profile: parent.profile } : {},
89835
- ...parent.effort ? { effort: parent.effort } : {},
89836
- ...parent.cliModel ? { cliModel: parent.cliModel } : {},
90098
+ ...effort ? { effort } : {},
90099
+ ...cliModel ? { cliModel } : {},
90100
+ // '' = the parent runs without an append prompt, and so does the thread.
90101
+ appliedAppendSystemPrompt: prefix.appendSystemPrompt ?? "",
89837
90102
  lane,
89838
90103
  forkedFromSessionId: resumeFrom,
89839
90104
  initialProcessStatus: "idle",
@@ -89849,11 +90114,12 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
89849
90114
  ...mode ? { mode } : {},
89850
90115
  ...parent.host ? { host: parent.host } : {},
89851
90116
  ...parent.profile ? { profile: parent.profile } : {},
89852
- ...parent.effort ? { effort: parent.effort } : {},
90117
+ ...effort ? { effort } : {},
89853
90118
  // Must be `cliModel` (the parent's verbatim --model arg, [1m] marker
89854
90119
  // included): the CLI does NOT inherit a model across --fork-session, and
89855
90120
  // the reported `model` has lost the marker.
89856
- ...parent.cliModel ? { model: parent.cliModel } : {},
90121
+ ...cliModel ? { model: cliModel } : {},
90122
+ appendSystemPromptExact: prefix.appendSystemPrompt,
89857
90123
  lane,
89858
90124
  forkedFromSessionId: resumeFrom
89859
90125
  }, ["session-runner"], { source: "side-thread-fork" });
@@ -89862,7 +90128,9 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
89862
90128
  threadId,
89863
90129
  sessionId,
89864
90130
  resumeFrom,
89865
- initOnly: !opts?.message
90131
+ initOnly: !opts?.message,
90132
+ prefixSource: prefix.source,
90133
+ prefixPromptLength: prefix.appendSystemPrompt?.length ?? 0
89866
90134
  });
89867
90135
  return { sessionId, resumeFromSessionId: resumeFrom };
89868
90136
  }
@@ -115734,11 +116002,12 @@ __export(session_controls_exports, {
115734
116002
  applySessionModelChange: () => applySessionModelChange,
115735
116003
  computeModelOptions: () => computeModelOptions,
115736
116004
  controlErrorKind: () => controlErrorKind,
116005
+ createForkSiblingTask: () => createForkSiblingTask,
115737
116006
  forkSessionToTask: () => forkSessionToTask,
115738
116007
  handleSessionControlRelay: () => handleSessionControlRelay,
115739
116008
  modelReadBackMatches: () => modelReadBackMatches
115740
116009
  });
115741
- import { randomUUID as randomUUID5 } from "crypto";
116010
+ import { randomUUID as randomUUID6 } from "crypto";
115742
116011
  function controlErrorKind(status2) {
115743
116012
  if (status2 === 404) return "not_found";
115744
116013
  if (status2 === 405) return "method_not_allowed";
@@ -115911,6 +116180,124 @@ async function computeModelOptions(sessionId) {
115911
116180
  currentEffort: liveEffort ?? record.effectiveEffort ?? record.effort ?? null
115912
116181
  };
115913
116182
  }
116183
+ async function createForkSiblingTask(sourceTaskId, opts) {
116184
+ const {
116185
+ getTask: getTask2,
116186
+ addTask: addTask2,
116187
+ updateTask: updateTask2,
116188
+ groupTasks: groupTasks2,
116189
+ addToGroup: addToGroup2,
116190
+ renameGroup: renameGroup2
116191
+ } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
116192
+ let sourceTask;
116193
+ try {
116194
+ sourceTask = await getTask2(sourceTaskId);
116195
+ } catch {
116196
+ throw new SessionControlError(`Source task "${sourceTaskId}" not found`, 404);
116197
+ }
116198
+ const sourceBaseTitle = sourceTask.title.replace(/^Fork of\s+/i, "").replace(/\s+-\s+fork of\s+.*$/i, "").trim() || sourceTask.title;
116199
+ const explicitTitle = opts.childTitle || (opts.titlePrefix ? `${opts.titlePrefix} - fork of ${sourceBaseTitle}` : void 0);
116200
+ const autoTitle = !explicitTitle;
116201
+ const newTitle = explicitTitle || `Fork of ${sourceBaseTitle}`;
116202
+ let inheritedTier;
116203
+ if (sourceTask.pinned && sourceTask.focus_tier) {
116204
+ const { getCustomTiers: getCustomTiers2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
116205
+ const known = ["focus", "backlog", "wait"].includes(sourceTask.focus_tier) || (await getCustomTiers2()).some((t3) => t3.id === sourceTask.focus_tier);
116206
+ if (known) inheritedTier = sourceTask.focus_tier;
116207
+ else {
116208
+ log.session.warn("fork: source tier is not registered, filing the fork in Satellite", {
116209
+ sourceTaskId: sourceTask.id,
116210
+ tier: sourceTask.focus_tier
116211
+ });
116212
+ }
116213
+ }
116214
+ const { task: newFork } = await addTask2({
116215
+ title: newTitle,
116216
+ project: sourceTask.project || "",
116217
+ source: sourceTask.source,
116218
+ ...opts.description ? { description: opts.description } : {},
116219
+ // A fork is a person splitting off live work, so it joins the board like
116220
+ // any other hand-made task (Satellite = pinned, no stored tier).
116221
+ pinned: true,
116222
+ ...inheritedTier ? { focus_tier: inheritedTier } : {},
116223
+ // Forking an Ask Walnut conversation yields another Ask Walnut
116224
+ // conversation: without the flag the fork loses the amber marker AND the
116225
+ // persona drift repair (both key on task.walnut_agent, never on project).
116226
+ ...sourceTask.walnut_agent ? { walnut_agent: true } : {}
116227
+ });
116228
+ let forkGroupId;
116229
+ try {
116230
+ if (sourceTask.group_id) {
116231
+ const r8 = await addToGroup2(sourceTask.group_id, [newFork.id]);
116232
+ forkGroupId = r8.group_id;
116233
+ } else {
116234
+ const r8 = await groupTasks2([sourceTask.id, newFork.id], sourceTask.title);
116235
+ forkGroupId = r8.group_id;
116236
+ }
116237
+ } catch (err2) {
116238
+ log.session.warn("fork: failed to group source + fork", {
116239
+ sourceTaskId: sourceTask.id,
116240
+ forkTaskId: newFork.id,
116241
+ error: err2 instanceof Error ? err2.message : String(err2)
116242
+ });
116243
+ }
116244
+ let task;
116245
+ try {
116246
+ task = await getTask2(newFork.id);
116247
+ } catch {
116248
+ task = newFork;
116249
+ }
116250
+ bus.emit(EventNames.TASK_CREATED, { task }, ["web-ui", "main-agent"], { source: opts.source });
116251
+ if (autoTitle && opts.prompt?.trim()) {
116252
+ const forkId = newFork.id;
116253
+ const sourceTitle = sourceBaseTitle;
116254
+ const placeholderTitle = newTitle;
116255
+ const prompt = opts.prompt;
116256
+ const titleSource = `${opts.source}-title`;
116257
+ void (async () => {
116258
+ try {
116259
+ const { summarizeForkPrompt: summarizeForkPrompt2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
116260
+ const label = await summarizeForkPrompt2(prompt);
116261
+ if (!label) return;
116262
+ const current5 = await getTask2(forkId);
116263
+ if (current5.title !== placeholderTitle) {
116264
+ log.session.info("fork title refine skipped \u2014 title changed since fork", { taskId: forkId });
116265
+ return;
116266
+ }
116267
+ const refinedTitle = `${label} - fork of ${sourceTitle}`;
116268
+ const { task: updated } = await updateTask2(forkId, { title: refinedTitle }, { source: titleSource });
116269
+ bus.emit(EventNames.TASK_UPDATED, { task: updated }, ["web-ui", "main-agent"], { source: titleSource });
116270
+ log.session.info("fork title refined", { taskId: forkId, title: refinedTitle });
116271
+ } catch (err2) {
116272
+ log.session.warn("fork title refine failed", {
116273
+ taskId: forkId,
116274
+ error: err2 instanceof Error ? err2.message : String(err2)
116275
+ });
116276
+ }
116277
+ })();
116278
+ }
116279
+ if (forkGroupId && !sourceTask.group_id) {
116280
+ const gid = forkGroupId;
116281
+ const seedTitles = [sourceTask.title, newTitle];
116282
+ const groupSource = opts.source;
116283
+ void (async () => {
116284
+ try {
116285
+ const { summarizeGroupLabel: summarizeGroupLabel2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
116286
+ const label = await summarizeGroupLabel2(seedTitles);
116287
+ if (!label) return;
116288
+ await renameGroup2(gid, label);
116289
+ bus.emit(EventNames.TASK_GROUPS_CHANGED, { group_id: gid, label }, ["web-ui", "main-agent"], { source: groupSource });
116290
+ log.session.info("fork group label refined", { groupId: gid, label });
116291
+ } catch (err2) {
116292
+ log.session.warn("fork group label refine failed", {
116293
+ groupId: gid,
116294
+ error: err2 instanceof Error ? err2.message : String(err2)
116295
+ });
116296
+ }
116297
+ })();
116298
+ }
116299
+ return { task, ...forkGroupId ? { groupId: forkGroupId } : {} };
116300
+ }
115914
116301
  async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
115915
116302
  const { task_id, create_child_task, child_title, message, title, model, imageContext } = input;
115916
116303
  if (!task_id && !create_child_task) {
@@ -115920,14 +116307,7 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
115920
116307
  throw new SessionControlError("task_id and create_child_task are mutually exclusive", 400);
115921
116308
  }
115922
116309
  const { getSessionByClaudeId: getSessionByClaudeId2, getSessionsForTask: getSessionsForTask2, createSessionRecord: createSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
115923
- const {
115924
- getTask: getTask2,
115925
- addTask: addTask2,
115926
- updateTask: updateTask2,
115927
- groupTasks: groupTasks2,
115928
- addToGroup: addToGroup2,
115929
- renameGroup: renameGroup2
115930
- } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
116310
+ const { getTask: getTask2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
115931
116311
  const sourceRecord = await getSessionByClaudeId2(sourceSessionId);
115932
116312
  if (!sourceRecord) throw new SessionControlError("Source session not found", 404);
115933
116313
  if (!engineCaps(sourceRecord.engine).fork) {
@@ -115944,108 +116324,13 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
115944
116324
  if (!sourceRecord.taskId) {
115945
116325
  throw new SessionControlError("Source session has no task \u2014 cannot create fork task", 400);
115946
116326
  }
115947
- let sourceTask;
115948
- try {
115949
- sourceTask = await getTask2(sourceRecord.taskId);
115950
- } catch {
115951
- throw new SessionControlError(`Source task "${sourceRecord.taskId}" not found`, 404);
115952
- }
115953
- const sourceBaseTitle = sourceTask.title.replace(/^Fork of\s+/i, "").replace(/\s+-\s+fork of\s+.*$/i, "").trim() || sourceTask.title;
115954
- const autoTitle = !child_title;
115955
- const newTitle = child_title || `Fork of ${sourceBaseTitle}`;
115956
- let inheritedTier;
115957
- if (sourceTask.pinned && sourceTask.focus_tier) {
115958
- const { getCustomTiers: getCustomTiers2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
115959
- const known = ["focus", "backlog", "wait"].includes(sourceTask.focus_tier) || (await getCustomTiers2()).some((t3) => t3.id === sourceTask.focus_tier);
115960
- if (known) inheritedTier = sourceTask.focus_tier;
115961
- else {
115962
- log.session.warn("fork: source tier is not registered, filing the fork in Satellite", {
115963
- sourceTaskId: sourceTask.id,
115964
- tier: sourceTask.focus_tier
115965
- });
115966
- }
115967
- }
115968
- const { task: newFork } = await addTask2({
115969
- title: newTitle,
115970
- project: sourceTask.project || "",
115971
- source: sourceTask.source,
115972
- // A fork is a person splitting off live work, so it joins the board like
115973
- // any other hand-made task (Satellite = pinned, no stored tier).
115974
- pinned: true,
115975
- ...inheritedTier ? { focus_tier: inheritedTier } : {},
115976
- // Forking an Ask Walnut conversation yields another Ask Walnut
115977
- // conversation: without the flag the fork loses the amber marker AND the
115978
- // persona drift repair (both key on task.walnut_agent, never on project).
115979
- ...sourceTask.walnut_agent ? { walnut_agent: true } : {}
115980
- });
115981
- let forkGroupId;
115982
- try {
115983
- if (sourceTask.group_id) {
115984
- const r8 = await addToGroup2(sourceTask.group_id, [newFork.id]);
115985
- forkGroupId = r8.group_id;
115986
- } else {
115987
- const r8 = await groupTasks2([sourceTask.id, newFork.id], sourceTask.title);
115988
- forkGroupId = r8.group_id;
115989
- }
115990
- } catch (err2) {
115991
- log.session.warn("fork: failed to group source + fork", {
115992
- sourceTaskId: sourceTask.id,
115993
- forkTaskId: newFork.id,
115994
- error: err2 instanceof Error ? err2.message : String(err2)
115995
- });
115996
- }
115997
- try {
115998
- task = await getTask2(newFork.id);
115999
- } catch {
116000
- task = newFork;
116001
- }
116002
- bus.emit(EventNames.TASK_CREATED, { task }, ["web-ui", "main-agent"], { source: "fork" });
116327
+ const created = await createForkSiblingTask(sourceRecord.taskId, {
116328
+ childTitle: child_title,
116329
+ prompt: message,
116330
+ source: "fork"
116331
+ });
116332
+ task = created.task;
116003
116333
  childTaskCreated = true;
116004
- if (autoTitle && message?.trim()) {
116005
- const forkId = newFork.id;
116006
- const sourceTitle = sourceBaseTitle;
116007
- const placeholderTitle = newTitle;
116008
- void (async () => {
116009
- try {
116010
- const { summarizeForkPrompt: summarizeForkPrompt2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
116011
- const label = await summarizeForkPrompt2(message);
116012
- if (!label) return;
116013
- const current5 = await getTask2(forkId);
116014
- if (current5.title !== placeholderTitle) {
116015
- log.session.info("fork title refine skipped \u2014 title changed since fork", { taskId: forkId });
116016
- return;
116017
- }
116018
- const refinedTitle = `${label} - fork of ${sourceTitle}`;
116019
- const { task: updated } = await updateTask2(forkId, { title: refinedTitle }, { source: "fork-title" });
116020
- bus.emit(EventNames.TASK_UPDATED, { task: updated }, ["web-ui", "main-agent"], { source: "fork-title" });
116021
- log.session.info("fork title refined", { taskId: forkId, title: refinedTitle });
116022
- } catch (err2) {
116023
- log.session.warn("fork title refine failed", {
116024
- taskId: forkId,
116025
- error: err2 instanceof Error ? err2.message : String(err2)
116026
- });
116027
- }
116028
- })();
116029
- }
116030
- if (forkGroupId && !sourceTask.group_id) {
116031
- const gid = forkGroupId;
116032
- const seedTitles = [sourceTask.title, newTitle];
116033
- void (async () => {
116034
- try {
116035
- const { summarizeGroupLabel: summarizeGroupLabel2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
116036
- const label = await summarizeGroupLabel2(seedTitles);
116037
- if (!label) return;
116038
- await renameGroup2(gid, label);
116039
- bus.emit(EventNames.TASK_GROUPS_CHANGED, { group_id: gid, label }, ["web-ui", "main-agent"], { source: "fork" });
116040
- log.session.info("fork group label refined", { groupId: gid, label });
116041
- } catch (err2) {
116042
- log.session.warn("fork group label refine failed", {
116043
- groupId: gid,
116044
- error: err2 instanceof Error ? err2.message : String(err2)
116045
- });
116046
- }
116047
- })();
116048
- }
116049
116334
  } else {
116050
116335
  try {
116051
116336
  task = await getTask2(task_id);
@@ -116067,7 +116352,7 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
116067
116352
  const FORK_FOCUS_PREFIX = "This is a forked session. Focus on the NEW request below \u2014 treat it as your primary task. Do not resume or continue the parent session's previous work unless the user explicitly asks you to.\n\n";
116068
116353
  const forkMessage = `${FORK_FOCUS_PREFIX}${imageContext ?? ""}New request:
116069
116354
  ${userRequest}`;
116070
- const forkSessionId = randomUUID5();
116355
+ const forkSessionId = randomUUID6();
116071
116356
  const forkTitle = title ?? `Fork of ${sourceRecord.title ?? sourceSessionId.slice(0, 16)}`;
116072
116357
  try {
116073
116358
  await createSessionRecord2(forkSessionId, task.id, task.project ?? "", sourceRecord.cwd, {
@@ -116790,7 +117075,7 @@ __export(session_rewind_exports, {
116790
117075
  probeRewindViaDaemon: () => probeRewindViaDaemon,
116791
117076
  rewindSessionToMessage: () => rewindSessionToMessage
116792
117077
  });
116793
- import { randomUUID as randomUUID6 } from "crypto";
117078
+ import { randomUUID as randomUUID7 } from "crypto";
116794
117079
  function cutAncestorHistoryAtRewindPoint(ancestorMessages, rewoundAtMessageUuid) {
116795
117080
  if (!rewoundAtMessageUuid) {
116796
117081
  return { messages: ancestorMessages, found: false, dropped: 0 };
@@ -116991,7 +117276,7 @@ async function rewindSessionToMessage(sessionId, input, source = "web-api") {
116991
117276
  error: err2 instanceof Error ? err2.message : String(err2)
116992
117277
  });
116993
117278
  }
116994
- const rewoundId = randomUUID6();
117279
+ const rewoundId = randomUUID7();
116995
117280
  const baseTitle = (record.title ?? sessionId.slice(0, 16)).replace(/\s*\(rewound\)$/i, "");
116996
117281
  const title = `${baseTitle} (rewound)`.slice(0, 200);
116997
117282
  const { createSessionRecord: createSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
@@ -117711,6 +117996,7 @@ function parseSessionMessages(content, opts) {
117711
117996
  }
117712
117997
  }
117713
117998
  let lastPlanContent = null;
117999
+ let dropWarmupReply = false;
117714
118000
  const resultParentIds = [];
117715
118001
  const result = [];
117716
118002
  for (const [mapMsgId, msg] of messageMap) {
@@ -117788,10 +118074,18 @@ ${imgPath}` : imgPath;
117788
118074
  let text = textParts.join("\n").trim();
117789
118075
  if (!text && tools2.length === 0 && !thinking) continue;
117790
118076
  if (msg.role === "user" && text.startsWith("<")) {
118077
+ if (isCacheWarmupText(text)) {
118078
+ dropWarmupReply = true;
118079
+ continue;
118080
+ }
117791
118081
  const transformed = transformInjectedUserText(text);
117792
118082
  if (transformed === null) continue;
117793
118083
  if (transformed !== void 0) text = transformed;
117794
118084
  }
118085
+ if (msg.role === "assistant" && dropWarmupReply) {
118086
+ dropWarmupReply = false;
118087
+ if (tools2.length === 0) continue;
118088
+ }
117795
118089
  if (msg.role === "user") text = stripOutputModeWrappers(text);
117796
118090
  if (msg.role === "assistant" && !text && tools2.length === 0) continue;
117797
118091
  result.push({
@@ -118742,6 +119036,7 @@ var init_session_history = __esm({
118742
119036
  init_session_io();
118743
119037
  init_constants();
118744
119038
  init_remote_image_mirror();
119039
+ init_side_thread_warmup();
118745
119040
  init_session_file_reader();
118746
119041
  LOCAL_HOME = os19.homedir();
118747
119042
  TAIL_SEGMENT_ROLL_BYTES = 4 * 1024 * 1024;
@@ -119034,7 +119329,7 @@ var init_context_window = __esm({
119034
119329
  });
119035
119330
 
119036
119331
  // src/providers/turn-ledger.ts
119037
- import { randomUUID as randomUUID7 } from "crypto";
119332
+ import { randomUUID as randomUUID8 } from "crypto";
119038
119333
  function openTurn(sessionId) {
119039
119334
  const existing = openTurns.get(sessionId);
119040
119335
  if (existing && !existing.settled) {
@@ -119044,7 +119339,7 @@ function openTurn(sessionId) {
119044
119339
  });
119045
119340
  return { turnId: existing.turnId, promise: existing.promise };
119046
119341
  }
119047
- const turnId = randomUUID7();
119342
+ const turnId = randomUUID8();
119048
119343
  let resolve;
119049
119344
  let reject;
119050
119345
  const promise = new Promise((res, rej) => {
@@ -127038,11 +127333,39 @@ var init_claude_code_session = __esm({
127038
127333
  session2.pendingDescription = message.slice(0, 500);
127039
127334
  let appendSystemPrompt;
127040
127335
  const isFork = !!data.forkedFromSessionId;
127041
- if (data.appendSystemPrompt && !isFork) {
127042
- appendSystemPrompt = data.appendSystemPrompt;
127043
- log.session.info("using caller-provided system prompt", { taskId: taskId2, promptLength: data.appendSystemPrompt.length });
127044
- }
127045
- {
127336
+ const { parseSideLaneKey: parseSideLaneKey2 } = await Promise.resolve().then(() => (init_side_thread_fork(), side_thread_fork_exports));
127337
+ const sideIds = parseSideLaneKey2(data.lane);
127338
+ if (sideIds) {
127339
+ let exact = data.appendSystemPromptExact;
127340
+ let source = "live-or-record";
127341
+ if (exact === void 0) {
127342
+ try {
127343
+ const { getSessionByClaudeId: getSessionByClaudeId2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
127344
+ const { spawnPrefixFromRecord: spawnPrefixFromRecord2 } = await Promise.resolve().then(() => (init_spawn_prefix(), spawn_prefix_exports));
127345
+ const parentRecord = await getSessionByClaudeId2(sideIds.parentSid);
127346
+ const prefix = parentRecord ? spawnPrefixFromRecord2(parentRecord) : null;
127347
+ exact = prefix?.appendSystemPrompt ?? null;
127348
+ source = prefix?.source ?? "no-parent-record";
127349
+ } catch (err2) {
127350
+ exact = null;
127351
+ source = "lookup-failed";
127352
+ log.session.warn("side thread: parent prompt lookup failed \u2014 spawning without an append prompt", {
127353
+ parentSid: sideIds.parentSid,
127354
+ error: err2 instanceof Error ? err2.message : String(err2)
127355
+ });
127356
+ }
127357
+ }
127358
+ appendSystemPrompt = exact ?? void 0;
127359
+ log.session.info("side thread: prefix prompt resolved", {
127360
+ parentSid: sideIds.parentSid,
127361
+ source,
127362
+ promptLength: appendSystemPrompt?.length ?? 0
127363
+ });
127364
+ } else {
127365
+ if (data.appendSystemPrompt && !isFork) {
127366
+ appendSystemPrompt = data.appendSystemPrompt;
127367
+ log.session.info("using caller-provided system prompt", { taskId: taskId2, promptLength: data.appendSystemPrompt.length });
127368
+ }
127046
127369
  try {
127047
127370
  const { buildSessionContext: buildSessionContext2 } = await Promise.resolve().then(() => (init_session_context(), session_context_exports));
127048
127371
  const ctx = await buildSessionContext2(taskId2 ?? "", cwd, data.host);
@@ -127060,32 +127383,6 @@ ${ctx.systemPrompt}` : ctx.systemPrompt;
127060
127383
  log.session.warn("failed to build session context", { taskId: taskId2, error: err2 instanceof Error ? err2.message : String(err2) });
127061
127384
  }
127062
127385
  }
127063
- {
127064
- const { parseSideLaneKey: parseSideLaneKey2 } = await Promise.resolve().then(() => (init_side_thread_fork(), side_thread_fork_exports));
127065
- const sideIds = parseSideLaneKey2(data.lane);
127066
- if (sideIds) {
127067
- try {
127068
- const { getSessionByClaudeId: getSessionByClaudeId2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
127069
- const parentRecord = await getSessionByClaudeId2(sideIds.parentSid);
127070
- const inherited = parentRecord?.appliedAppendSystemPrompt;
127071
- if (inherited && Buffer.byteLength(inherited, "utf8") <= 65536) {
127072
- appendSystemPrompt = inherited;
127073
- log.session.info("side thread: inherited parent system prompt verbatim", {
127074
- parentSid: sideIds.parentSid,
127075
- promptLength: appendSystemPrompt.length
127076
- });
127077
- } else {
127078
- log.session.info("side thread: parent has no stored system prompt \u2014 fresh build (cache prefix will diverge)", {
127079
- parentSid: sideIds.parentSid
127080
- });
127081
- }
127082
- } catch (err2) {
127083
- log.session.warn("side thread: parent prompt lookup failed \u2014 fresh build", {
127084
- error: err2 instanceof Error ? err2.message : String(err2)
127085
- });
127086
- }
127087
- }
127088
- }
127089
127386
  if (data.preassignedSessionId && appendSystemPrompt && Buffer.byteLength(appendSystemPrompt, "utf8") <= 65536) {
127090
127387
  const promptToStore = appendSystemPrompt;
127091
127388
  const sidToStore = data.preassignedSessionId;
@@ -128285,7 +128582,7 @@ __export(session_requests_exports, {
128285
128582
  settleNotified: () => settleNotified,
128286
128583
  settleReplied: () => settleReplied
128287
128584
  });
128288
- import { createHash as createHash14, randomUUID as randomUUID8 } from "crypto";
128585
+ import { createHash as createHash14, randomUUID as randomUUID9 } from "crypto";
128289
128586
  import path119 from "path";
128290
128587
  function oneLine2(text, max = 160) {
128291
128588
  const flat = text.replace(/\s+/g, " ").trim();
@@ -128309,7 +128606,7 @@ function clampReplyTimeoutSecs(secs) {
128309
128606
  }
128310
128607
  async function createSessionRequest(input) {
128311
128608
  const request2 = {
128312
- id: `rq-${randomUUID8().replace(/-/g, "").slice(0, 12)}`,
128609
+ id: `rq-${randomUUID9().replace(/-/g, "").slice(0, 12)}`,
128313
128610
  fromSessionId: input.fromSessionId,
128314
128611
  ...input.toSessionId ? { toSessionId: input.toSessionId } : {},
128315
128612
  ...input.toTaskId ? { toTaskId: input.toTaskId } : {},
@@ -137268,7 +137565,7 @@ __export(mobile_launch_exports, {
137268
137565
  performMobileLaunch: () => performMobileLaunch,
137269
137566
  validateMobileLaunchBody: () => validateMobileLaunchBody
137270
137567
  });
137271
- import { randomUUID as randomUUID9 } from "crypto";
137568
+ import { randomUUID as randomUUID10 } from "crypto";
137272
137569
  function launchErrorCode(status2) {
137273
137570
  if (status2 === 404) return "not_found";
137274
137571
  if (status2 === 409) return "conflict";
@@ -137390,7 +137687,7 @@ async function performMobileLaunch(input, source) {
137390
137687
  );
137391
137688
  }
137392
137689
  }
137393
- const preassignedSessionId = engineCaps(input.engine).idProvisioning === "provider-issued" ? void 0 : randomUUID9();
137690
+ const preassignedSessionId = engineCaps(input.engine).idProvisioning === "provider-issued" ? void 0 : randomUUID10();
137394
137691
  const task = await quickStartSession({
137395
137692
  message: input.message,
137396
137693
  cwd: input.cwd,
@@ -138189,6 +138486,7 @@ __export(daemon_connection_exports, {
138189
138486
  isDaemonConnected: () => isDaemonConnected,
138190
138487
  isPooledConnection: () => isPooledConnection,
138191
138488
  probeDaemonSession: () => probeDaemonSession,
138489
+ probeDaemonSessionArgs: () => probeDaemonSessionArgs,
138192
138490
  pushDaemonHooksToAllHosts: () => pushDaemonHooksToAllHosts,
138193
138491
  setOnDaemonHostConnected: () => setOnDaemonHostConnected,
138194
138492
  setOnDaemonStatusChange: () => setOnDaemonStatusChange,
@@ -138365,6 +138663,28 @@ async function probeDaemonSession(hostKey2, sessionId) {
138365
138663
  return null;
138366
138664
  }
138367
138665
  }
138666
+ async function probeDaemonSessionArgs(hostKey2, sessionId, deadlineMs = 2e3) {
138667
+ const conn = getConnectedDaemonConnection(hostKey2);
138668
+ if (!conn) return null;
138669
+ try {
138670
+ const result = await Promise.race([
138671
+ conn.send("status", { sid: sessionId, includeArgs: true }),
138672
+ new Promise((_, reject) => setTimeout(
138673
+ () => reject(new Error(`status probe exceeded ${deadlineMs}ms`)),
138674
+ deadlineMs
138675
+ ).unref?.())
138676
+ ]);
138677
+ if (!result.ok || !result.alive || !Array.isArray(result.args)) return null;
138678
+ return result.args.filter((a8) => typeof a8 === "string");
138679
+ } catch (err2) {
138680
+ log.session.debug("probeDaemonSessionArgs: status probe failed", {
138681
+ hostKey: hostKey2,
138682
+ sessionId,
138683
+ error: err2 instanceof Error ? err2.message : String(err2)
138684
+ });
138685
+ return null;
138686
+ }
138687
+ }
138368
138688
  var execFileAsync11, MOBILE_ENQUEUE_LEDGER_MAX, recentMobileEnqueues, DaemonConnection, onPoolStatusChange, onHostConnected, hostConnectedListeners, connectionPool, connectingPromises, failureCache, FAILURE_CACHE_TTL_MS;
138369
138689
  var init_daemon_connection = __esm({
138370
138690
  "src/providers/daemon-connection.ts"() {
@@ -143710,6 +144030,7 @@ async function createSessionRecord(claudeSessionId, taskId2, project, cwd, extra
143710
144030
  ...extra?.effort ? { effort: extra.effort } : {},
143711
144031
  ...extra?.profile ? { profile: extra.profile } : {},
143712
144032
  ...extra?.lane ? { lane: extra.lane } : {},
144033
+ ...extra?.appliedAppendSystemPrompt !== void 0 ? { appliedAppendSystemPrompt: extra.appliedAppendSystemPrompt } : {},
143713
144034
  ...extra?.engine ? { engine: extra.engine } : {},
143714
144035
  ...extra?.acpRuntimeId ? { acpRuntimeId: extra.acpRuntimeId } : {},
143715
144036
  ...extra?.acpJournalPath ? { acpJournalPath: extra.acpJournalPath } : {},
@@ -143864,7 +144185,7 @@ function applyUpdateToSession(session2, updates, logLabel) {
143864
144185
  };
143865
144186
  if (count2 === 1) log.session.info("legacy status write suppressed", payload);
143866
144187
  else log.session.debug("legacy status write suppressed", payload);
143867
- if (updates.process_status === "error") {
144188
+ if (updates.process_status === "error" || updates.process_status === "stopped") {
143868
144189
  noteSuppressedErrorReason(session2.claudeSessionId, {
143869
144190
  reason: typeof reason === "string" ? reason : "unknown",
143870
144191
  message: typeof updates.errorMessage === "string" ? updates.errorMessage : void 0,
@@ -151813,14 +152134,16 @@ function isLive(r8) {
151813
152134
  function isRevivable(r8) {
151814
152135
  return !!r8.outputFile || r8.consumedOffset !== void 0;
151815
152136
  }
151816
- var STANDBY_TTL_MS, MAX_LIVE_THREADS, IDLE_SWEEP_INTERVAL_MS, THREAD_IDLE_MS, SideThreadManager, sideThreadManager;
152137
+ var STANDBY_TTL_MS, WARMED_STANDBY_TTL_MS, MAX_LIVE_THREADS, IDLE_SWEEP_INTERVAL_MS, THREAD_IDLE_MS, SideThreadManager, sideThreadManager;
151817
152138
  var init_side_thread_manager = __esm({
151818
152139
  "src/core/sessions/side-thread-manager.ts"() {
151819
152140
  "use strict";
151820
152141
  init_logging();
151821
152142
  init_side_thread_fork();
151822
152143
  init_session_controls();
152144
+ init_side_thread_warmup();
151823
152145
  STANDBY_TTL_MS = 12e4;
152146
+ WARMED_STANDBY_TTL_MS = 15 * 6e4;
151824
152147
  MAX_LIVE_THREADS = 3;
151825
152148
  IDLE_SWEEP_INTERVAL_MS = 5 * 6e4;
151826
152149
  THREAD_IDLE_MS = 30 * 6e4;
@@ -151831,6 +152154,8 @@ var init_side_thread_manager = __esm({
151831
152154
  * In-memory only: a server restart loses it, but the boot sweep archives
151832
152155
  * every standby anyway, so a record can never outlive its map entry. */
151833
152156
  standbyParentOffsets = /* @__PURE__ */ new Map();
152157
+ /** parentSid → standby sid that already ran its cache warm-up turn. */
152158
+ warmedStandbys = /* @__PURE__ */ new Map();
151834
152159
  /** One in-flight standby/create op per parent — two concurrent asks must not
151835
152160
  * both consume the same standby (they would collide on its lane rename). */
151836
152161
  inFlight = /* @__PURE__ */ new Map();
@@ -151862,6 +152187,7 @@ var init_side_thread_manager = __esm({
151862
152187
  for (const timer3 of this.standbyTimers.values()) clearTimeout(timer3);
151863
152188
  this.standbyTimers.clear();
151864
152189
  this.standbyParentOffsets.clear();
152190
+ this.warmedStandbys.clear();
151865
152191
  this.inFlight.clear();
151866
152192
  }
151867
152193
  // ── Standby ────────────────────────────────────────────────────────────────
@@ -151877,10 +152203,12 @@ var init_side_thread_manager = __esm({
151877
152203
  const existing = await this.findStandby(parentSid);
151878
152204
  if (existing) {
151879
152205
  if (await this.isStandbyUsable(parentSid, existing)) {
151880
- this.armStandbyTtl(parentSid, existing.claudeSessionId);
152206
+ const warmed = this.warmedStandbys.get(parentSid) === existing.claudeSessionId;
152207
+ this.armStandbyTtl(parentSid, existing.claudeSessionId, warmed ? WARMED_STANDBY_TTL_MS : STANDBY_TTL_MS);
151881
152208
  return existing.claudeSessionId;
151882
152209
  }
151883
152210
  void this.retireSession(existing.claudeSessionId, "side_thread_standby_stale");
152211
+ this.warmedStandbys.delete(parentSid);
151884
152212
  }
151885
152213
  const parentOffset = parent.consumedOffset;
151886
152214
  const { sessionId } = await forkSideThreadSession(parentSid, STANDBY_THREAD_ID);
@@ -151906,7 +152234,7 @@ var init_side_thread_manager = __esm({
151906
152234
  if (!parent) return false;
151907
152235
  return parent.consumedOffset === this.standbyParentOffsets.get(parentSid);
151908
152236
  }
151909
- armStandbyTtl(parentSid, sessionId) {
152237
+ armStandbyTtl(parentSid, sessionId, ttlMs = STANDBY_TTL_MS) {
151910
152238
  const prev = this.standbyTimers.get(parentSid);
151911
152239
  if (prev) clearTimeout(prev);
151912
152240
  const timer3 = setTimeout(() => {
@@ -151916,13 +152244,41 @@ var init_side_thread_manager = __esm({
151916
152244
  const record = await getSessionByClaudeId2(sessionId).catch(() => null);
151917
152245
  if (!record || parseSideLaneKey(record.lane)?.threadId !== STANDBY_THREAD_ID) return;
151918
152246
  this.standbyParentOffsets.delete(parentSid);
152247
+ this.warmedStandbys.delete(parentSid);
151919
152248
  await this.retireSession(sessionId, "side_thread_standby_ttl");
151920
152249
  }).catch(() => {
151921
152250
  });
151922
- }, STANDBY_TTL_MS);
152251
+ }, ttlMs);
151923
152252
  timer3.unref?.();
151924
152253
  this.standbyTimers.set(parentSid, timer3);
151925
152254
  }
152255
+ /**
152256
+ * Run the standby's cache warm-up turn (see side-thread-warmup.ts). Called
152257
+ * when the user starts TYPING a new question: the fork's first API call pays
152258
+ * the full prefix write no matter what, so paying it while they type turns
152259
+ * the actual question into a cached follow-up. Idempotent per standby; a
152260
+ * missing or stale standby is a no-op (the ask path will fork fresh anyway).
152261
+ */
152262
+ async warmStandby(parentSid) {
152263
+ return this.serialize(parentSid, async () => {
152264
+ const standby = await this.findStandby(parentSid);
152265
+ if (!standby) return { warmed: false, reason: "no_standby" };
152266
+ if (!await this.isStandbyUsable(parentSid, standby)) return { warmed: false, reason: "stale" };
152267
+ if (this.warmedStandbys.get(parentSid) === standby.claudeSessionId) {
152268
+ return { warmed: true, reason: "already_warm" };
152269
+ }
152270
+ const { sendMessageToSession: sendMessageToSession2 } = await Promise.resolve().then(() => (init_session_message_queue(), session_message_queue_exports));
152271
+ markWarmupTurnPending(standby.claudeSessionId);
152272
+ await sendMessageToSession2(standby.claudeSessionId, CACHE_WARMUP_MESSAGE, { source: "side-thread-warmup" });
152273
+ this.warmedStandbys.set(parentSid, standby.claudeSessionId);
152274
+ this.armStandbyTtl(parentSid, standby.claudeSessionId, WARMED_STANDBY_TTL_MS);
152275
+ log.session.info("side thread: standby cache warm-up sent", {
152276
+ parentSid,
152277
+ standbySid: standby.claudeSessionId
152278
+ });
152279
+ return { warmed: true };
152280
+ });
152281
+ }
151926
152282
  // ── Threads ────────────────────────────────────────────────────────────────
151927
152283
  /**
151928
152284
  * Open a side thread on `parentSid` and ask `question` in it.
@@ -151933,6 +152289,10 @@ var init_side_thread_manager = __esm({
151933
152289
  * question as its spawn's first turn instead — a send issued right after
151934
152290
  * SESSION_START can lose that race and cold-`--resume` an id the CLI has never
151935
152291
  * seen (see personal-ai-lane.ts LaneSession.created).
152292
+ *
152293
+ * `imageContext` (attachment paths for the CLI to Read) rides the MESSAGE only.
152294
+ * The stored entry keeps the plain question, so the chip label, the injected
152295
+ * transcript and a promoted task never carry the machine preamble.
151936
152296
  */
151937
152297
  async createThread(parentSid, input) {
151938
152298
  const question = input.question?.trim();
@@ -151940,8 +152300,11 @@ var init_side_thread_manager = __esm({
151940
152300
  return this.serialize(parentSid, async () => {
151941
152301
  const threadId = mintSideThreadId();
151942
152302
  const title = input.title?.trim() || void 0;
152303
+ const message = input.imageContext ? `${input.imageContext}
152304
+
152305
+ ${question}` : question;
151943
152306
  const consumed = await this.consumeStandby(parentSid, threadId, title);
151944
- const threadSessionId = consumed ?? (await forkSideThreadSession(parentSid, threadId, { message: question, ...title ? { title } : {} })).sessionId;
152307
+ const threadSessionId = consumed ?? (await forkSideThreadSession(parentSid, threadId, { message, ...title ? { title } : {} })).sessionId;
151945
152308
  let entry;
151946
152309
  try {
151947
152310
  const { addSideThread: addSideThread2 } = await Promise.resolve().then(() => (init_side_questions(), side_questions_exports));
@@ -151953,7 +152316,7 @@ var init_side_thread_manager = __esm({
151953
152316
  });
151954
152317
  if (consumed) {
151955
152318
  const { sendMessageToSession: sendMessageToSession2 } = await Promise.resolve().then(() => (init_session_message_queue(), session_message_queue_exports));
151956
- await sendMessageToSession2(threadSessionId, question, { source: "side-thread" });
152319
+ await sendMessageToSession2(threadSessionId, message, { source: "side-thread" });
151957
152320
  }
151958
152321
  } catch (err2) {
151959
152322
  void this.retireSession(threadSessionId, "side_thread_create_failed");
@@ -151988,6 +152351,7 @@ var init_side_thread_manager = __esm({
151988
152351
  if (!await this.isStandbyUsable(parentSid, standby)) {
151989
152352
  void this.retireSession(standby.claudeSessionId, "side_thread_standby_stale");
151990
152353
  this.standbyParentOffsets.delete(parentSid);
152354
+ this.warmedStandbys.delete(parentSid);
151991
152355
  return null;
151992
152356
  }
151993
152357
  const timer3 = this.standbyTimers.get(parentSid);
@@ -151996,6 +152360,7 @@ var init_side_thread_manager = __esm({
151996
152360
  this.standbyTimers.delete(parentSid);
151997
152361
  }
151998
152362
  this.standbyParentOffsets.delete(parentSid);
152363
+ this.warmedStandbys.delete(parentSid);
151999
152364
  const lane = sideThreadLaneKey(parentSid, threadId);
152000
152365
  const { updateSessionRecord: updateSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
152001
152366
  await updateSessionRecord2(standby.claudeSessionId, {
@@ -152182,16 +152547,30 @@ async function promoteSideThread(parentSid, threadId, opts) {
152182
152547
  const threadRecord = await getSessionByClaudeId2(sessionId);
152183
152548
  if (!threadRecord) throw new SessionControlError("Side thread session not found", 404);
152184
152549
  const parentRecord = await getSessionByClaudeId2(parentSid);
152185
- const parentTaskId = parentRecord?.taskId?.trim() || void 0;
152186
- const title = opts?.title?.trim() || entry.title?.trim() || entry.question;
152550
+ const siblingOfTaskId = parentRecord?.taskId?.trim() || void 0;
152551
+ const threadLabel = opts?.title?.trim() || entry.title?.trim() || void 0;
152187
152552
  const { addTask: addTask2, linkSession: linkSession2, linkSessionSlot: linkSessionSlot2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
152188
- const { task } = await addTask2({
152189
- title,
152190
- description: entry.question,
152191
- ...parentTaskId ? { parent_task_id: parentTaskId } : {},
152192
- // A person clicked "promote" — same board default as any hand-made task.
152193
- pinned: true
152194
- });
152553
+ let groupId;
152554
+ let task;
152555
+ if (siblingOfTaskId) {
152556
+ const created = await createForkSiblingTask(siblingOfTaskId, {
152557
+ // A known label short-circuits the background refine; without one the
152558
+ // thread's first question is what gets summarized (same as a fork's message).
152559
+ titlePrefix: threadLabel,
152560
+ prompt: entry.question,
152561
+ description: entry.question,
152562
+ source: "side-thread-promote"
152563
+ });
152564
+ task = created.task;
152565
+ groupId = created.groupId;
152566
+ } else {
152567
+ ({ task } = await addTask2({
152568
+ title: threadLabel || entry.question,
152569
+ description: entry.question,
152570
+ // A person clicked "promote" — same board default as any hand-made task.
152571
+ pinned: true
152572
+ }));
152573
+ }
152195
152574
  const stamped = await markThreadPromoted2(parentSid, threadId, task.id);
152196
152575
  if (!stamped) {
152197
152576
  log.session.warn("side thread promote: entry vanished before stamp \u2014 promote proceeds, 409 guard is unarmed", {
@@ -152205,7 +152584,7 @@ async function promoteSideThread(parentSid, threadId, opts) {
152205
152584
  project: task.project ?? "",
152206
152585
  // Clearing the lane un-hides the session (see the file header).
152207
152586
  lane: void 0,
152208
- title
152587
+ title: task.title
152209
152588
  });
152210
152589
  try {
152211
152590
  const { sessionRunner: sessionRunner2 } = await Promise.resolve().then(() => (init_claude_code_session(), claude_code_session_exports));
@@ -152224,10 +152603,18 @@ async function promoteSideThread(parentSid, threadId, opts) {
152224
152603
  error: err2 instanceof Error ? err2.message : String(err2)
152225
152604
  });
152226
152605
  }
152227
- log.session.info("side thread: promoted to task", { parentSid, threadId, sessionId, taskId: task.id });
152606
+ log.session.info("side thread: promoted to task", {
152607
+ parentSid,
152608
+ threadId,
152609
+ sessionId,
152610
+ taskId: task.id,
152611
+ siblingOfTaskId,
152612
+ groupId
152613
+ });
152228
152614
  return {
152229
152615
  taskId: task.id,
152230
- ...task.parent_task_id ? { parentTaskId: task.parent_task_id } : {},
152616
+ ...siblingOfTaskId ? { siblingOfTaskId } : {},
152617
+ ...groupId ? { groupId } : {},
152231
152618
  sessionId
152232
152619
  };
152233
152620
  }
@@ -152242,7 +152629,7 @@ var init_side_thread_promote = __esm({
152242
152629
 
152243
152630
  // src/web/routes/sessions.ts
152244
152631
  import { Router as Router16 } from "express";
152245
- import { randomUUID as randomUUID10 } from "crypto";
152632
+ import { randomUUID as randomUUID11 } from "crypto";
152246
152633
  import os27 from "os";
152247
152634
  import path142 from "path";
152248
152635
  function condenseStaleReason(msg) {
@@ -152588,7 +152975,7 @@ var init_sessions = __esm({
152588
152975
  }
152589
152976
  const isNativeEngine = !isAcpEngine(engine);
152590
152977
  const clientSessionId = typeof req.body.sessionId === "string" && UUID_RE3.test(req.body.sessionId) ? req.body.sessionId : void 0;
152591
- const preassignedSessionId = isNativeEngine ? clientSessionId ?? randomUUID10() : void 0;
152978
+ const preassignedSessionId = isNativeEngine ? clientSessionId ?? randomUUID11() : void 0;
152592
152979
  const updatedTask = await quickStartSession({
152593
152980
  message: sessionMessage,
152594
152981
  messagePrefix,
@@ -153563,18 +153950,38 @@ var init_sessions = __esm({
153563
153950
  });
153564
153951
  }
153565
153952
  });
153953
+ sessionsRouter.post("/:sessionId/side-threads/standby/warm", async (req, res) => {
153954
+ if (refuseSideThreadsOnReplica(res)) return;
153955
+ const sessionId = String(req.params.sessionId);
153956
+ try {
153957
+ const { sideThreadManager: sideThreadManager2 } = await Promise.resolve().then(() => (init_side_thread_manager(), side_thread_manager_exports));
153958
+ res.json(await sideThreadManager2.warmStandby(sessionId));
153959
+ } catch (err2) {
153960
+ log.web.warn("side thread standby warm-up failed", {
153961
+ sessionId,
153962
+ error: err2 instanceof Error ? err2.message : String(err2)
153963
+ });
153964
+ res.json({ warmed: false, reason: "error" });
153965
+ }
153966
+ });
153566
153967
  sessionsRouter.post("/:sessionId/side-threads", async (req, res, next) => {
153567
153968
  if (refuseSideThreadsOnReplica(res)) return;
153568
153969
  try {
153569
- const { question, title } = req.body ?? {};
153970
+ const { question, title, images } = req.body ?? {};
153570
153971
  if (!question || typeof question !== "string" || !question.trim()) {
153571
153972
  res.status(400).json({ error: "question (non-empty string) is required" });
153572
153973
  return;
153573
153974
  }
153975
+ let imageContext = "";
153976
+ if (images && images.length > 0) {
153977
+ const processed = await processAndSaveImages(images);
153978
+ if (processed) imageContext = buildSessionImageContext(processed.savedImages);
153979
+ }
153574
153980
  const { sideThreadManager: sideThreadManager2 } = await Promise.resolve().then(() => (init_side_thread_manager(), side_thread_manager_exports));
153575
153981
  const thread = await sideThreadManager2.createThread(String(req.params.sessionId), {
153576
153982
  question,
153577
- ...typeof title === "string" && title.trim() ? { title } : {}
153983
+ ...typeof title === "string" && title.trim() ? { title } : {},
153984
+ ...imageContext ? { imageContext } : {}
153578
153985
  });
153579
153986
  res.json({ thread });
153580
153987
  } catch (err2) {
@@ -153593,7 +154000,8 @@ var init_sessions = __esm({
153593
154000
  );
153594
154001
  res.json({
153595
154002
  taskId: result.taskId,
153596
- ...result.parentTaskId ? { parentTaskId: result.parentTaskId } : {},
154003
+ ...result.siblingOfTaskId ? { siblingOfTaskId: result.siblingOfTaskId } : {},
154004
+ ...result.groupId ? { groupId: result.groupId } : {},
153597
154005
  sessionId: result.sessionId
153598
154006
  });
153599
154007
  } catch (err2) {
@@ -154288,7 +154696,7 @@ var micro_claude_exports = {};
154288
154696
  __export(micro_claude_exports, {
154289
154697
  runMicroClaude: () => runMicroClaude
154290
154698
  });
154291
- import { randomUUID as randomUUID11 } from "crypto";
154699
+ import { randomUUID as randomUUID12 } from "crypto";
154292
154700
  async function runMicroClaude(opts) {
154293
154701
  if (opts.warm) {
154294
154702
  const run10 = await runWarmMicroClaude({
@@ -154297,7 +154705,7 @@ async function runMicroClaude(opts) {
154297
154705
  tools: opts.tools ?? [],
154298
154706
  prompt: opts.prompt,
154299
154707
  timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
154300
- toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID11()}`,
154708
+ toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID12()}`,
154301
154709
  ...opts.onBlock ? { onBlock: opts.onBlock } : {}
154302
154710
  });
154303
154711
  return { response: run10.response, costUsd: run10.costUsd, durationMs: run10.durationMs };
@@ -154307,7 +154715,7 @@ async function runMicroClaude(opts) {
154307
154715
  systemPrompt: opts.system,
154308
154716
  model: opts.model ?? DEFAULT_MODEL2,
154309
154717
  timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
154310
- toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID11()}`,
154718
+ toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID12()}`,
154311
154719
  slim: true,
154312
154720
  thinking: opts.thinking ?? false,
154313
154721
  ...opts.tools && opts.tools.length > 0 ? { tools: opts.tools } : {},
@@ -154337,7 +154745,7 @@ __export(micro_agent_exports, {
154337
154745
  async function resolveTierModel(tier) {
154338
154746
  const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
154339
154747
  const config = await getConfig2();
154340
- const provider = config.agent?.main_provider ?? "bedrock";
154748
+ const provider = resolveMainProviderName(config);
154341
154749
  const { MODEL_CATALOG: MODEL_CATALOG3 } = await Promise.resolve().then(() => (init_model_catalog(), model_catalog_exports));
154342
154750
  const entry = MODEL_CATALOG3[provider]?.find((m6) => {
154343
154751
  const id = m6.id.toLowerCase();
@@ -154406,6 +154814,7 @@ var DEFAULT_TIER, DEFAULT_MAX_TOKENS2, DEFAULT_MAX_ROUNDS, DEFAULT_TIMEOUT_MS3;
154406
154814
  var init_micro_agent = __esm({
154407
154815
  "src/agent/micro-agent.ts"() {
154408
154816
  "use strict";
154817
+ init_default_provider();
154409
154818
  DEFAULT_TIER = "sonnet";
154410
154819
  DEFAULT_MAX_TOKENS2 = 2e3;
154411
154820
  DEFAULT_MAX_ROUNDS = 3;
@@ -154421,7 +154830,7 @@ __export(task_search_agent_exports, {
154421
154830
  prewarmAgentSearchChild: () => prewarmAgentSearchChild,
154422
154831
  runTaskSearchAgent: () => runTaskSearchAgent
154423
154832
  });
154424
- import { randomUUID as randomUUID12 } from "crypto";
154833
+ import { randomUUID as randomUUID13 } from "crypto";
154425
154834
  function useCliEngine() {
154426
154835
  return process.env.WALNUT_AGENT_SEARCH_ENGINE !== "inprocess";
154427
154836
  }
@@ -154501,7 +154910,7 @@ async function claudeCliEngine(userPrompt, options) {
154501
154910
  model: options.model,
154502
154911
  timeoutMs: options.timeoutMs,
154503
154912
  tools: ["Bash"],
154504
- toolUseId: `task-search-${randomUUID12()}`,
154913
+ toolUseId: `task-search-${randomUUID13()}`,
154505
154914
  warm: true,
154506
154915
  onBlock
154507
154916
  });
@@ -155579,10 +155988,11 @@ async function buildProvidersPayload() {
155579
155988
  providers[name] = {
155580
155989
  api: prov.api,
155581
155990
  base_url: prov.base_url,
155582
- status: caps.subscriptionReady ? "ready" : "no_key",
155991
+ status: caps.installed ? "ready" : "no_key",
155583
155992
  auto_detected: !explicitNames.has(name),
155584
155993
  models: getModelsForProvider(name, prov.models),
155585
- credential_source: caps.subscriptionReady ? "subscription" : caps.installed ? "cli_no_subscription" : "cli_not_installed"
155994
+ credential_source: caps.installed ? `cli_${caps.auth.mode}` : "cli_not_installed",
155995
+ credential_detail: caps.installed ? caps.auth.label : void 0
155586
155996
  };
155587
155997
  continue;
155588
155998
  }
@@ -155641,10 +156051,11 @@ async function buildProvidersPayload() {
155641
156051
  const caps = detectClaudeCli2();
155642
156052
  providers[name] = {
155643
156053
  api: template.api,
155644
- status: caps.subscriptionReady ? "ready" : "no_key",
156054
+ status: caps.installed ? "ready" : "no_key",
155645
156055
  auto_detected: false,
155646
156056
  models: getModelsForProvider(name),
155647
- credential_source: caps.subscriptionReady ? "subscription" : caps.installed ? "cli_no_subscription" : "cli_not_installed"
156057
+ credential_source: caps.installed ? `cli_${caps.auth.mode}` : "cli_not_installed",
156058
+ credential_detail: caps.installed ? caps.auth.label : void 0
155648
156059
  };
155649
156060
  continue;
155650
156061
  }
@@ -155800,7 +156211,7 @@ var init_config11 = __esm({
155800
156211
  const {
155801
156212
  traceCredentialResolution: traceCredentialResolution2,
155802
156213
  resolveCredentials: resolveCredentials2,
155803
- readClaudeSettingsEnv: readClaudeSettingsEnv2,
156214
+ readClaudeSettingsEnv: readClaudeSettingsEnv3,
155804
156215
  readClaudeCredentialExport: readClaudeCredentialExport2
155805
156216
  } = await Promise.resolve().then(() => (init_credential_resolver(), credential_resolver_exports));
155806
156217
  const fs114 = await import("fs");
@@ -155816,7 +156227,7 @@ var init_config11 = __esm({
155816
156227
  };
155817
156228
  const trace = traceCredentialResolution2({
155818
156229
  config,
155819
- claudeEnv: readClaudeSettingsEnv2(),
156230
+ claudeEnv: readClaudeSettingsEnv3(),
155820
156231
  processEnv: process.env,
155821
156232
  awsFiles: {
155822
156233
  credentials: exists3(path185.join(home, ".aws", "credentials")),
@@ -173146,7 +173557,7 @@ __export(task_start_exports, {
173146
173557
  SessionExistsError: () => SessionExistsError,
173147
173558
  startSessionForTask: () => startSessionForTask
173148
173559
  });
173149
- import { randomUUID as randomUUID13 } from "crypto";
173560
+ import { randomUUID as randomUUID14 } from "crypto";
173150
173561
  async function startSessionForTask(params) {
173151
173562
  const { taskIdPrefix, source } = params;
173152
173563
  let task;
@@ -173197,7 +173608,7 @@ async function startSessionForTask(params) {
173197
173608
  }
173198
173609
  let message = params.message?.trim() || `Working on task: ${task.title}`;
173199
173610
  const engine = normalizeEngine(params.engine);
173200
- const preassignedSessionId = engineCaps(engine).idProvisioning === "provider-issued" ? void 0 : randomUUID13();
173611
+ const preassignedSessionId = engineCaps(engine).idProvisioning === "provider-issued" ? void 0 : randomUUID14();
173201
173612
  let requestId;
173202
173613
  if (params.expectReply) {
173203
173614
  const { resolveCaller: resolveCaller2 } = await Promise.resolve().then(() => (init_session_send_core(), session_send_core_exports));
@@ -184525,7 +184936,7 @@ async function generateLedgerDesc(task, timeoutMs = 15e3) {
184525
184936
  try {
184526
184937
  const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
184527
184938
  const config = await getConfig2();
184528
- const providerName = config.agent?.main_provider ?? "bedrock";
184939
+ const providerName = resolveMainProviderName(config);
184529
184940
  const model = cheapModelFor(providerName);
184530
184941
  const detail = (task.description ?? "").trim().slice(0, 800);
184531
184942
  const project = (task.project ?? "").trim();
@@ -184584,6 +184995,7 @@ var init_task_ledger_desc = __esm({
184584
184995
  "use strict";
184585
184996
  init_model();
184586
184997
  init_model_catalog();
184998
+ init_default_provider();
184587
184999
  init_logging();
184588
185000
  init_task_manager();
184589
185001
  MAX_DESC_LEN = 120;
@@ -184607,6 +185019,10 @@ function diffStreamedVsPersisted(streamedTextMsgIds, persistedMsgIds) {
184607
185019
  }
184608
185020
  function armStreamConvergenceCheck(sessionId, streamedTextMsgIds) {
184609
185021
  if (streamedTextMsgIds.length === 0) return;
185022
+ if (consumeWarmupTurn(sessionId)) {
185023
+ log.obs.debug("stream-convergence: skipped hidden warm-up turn", { sessionId });
185024
+ return;
185025
+ }
184610
185026
  const timer3 = setTimeout(() => {
184611
185027
  void runCheck(sessionId, streamedTextMsgIds).catch((err2) => {
184612
185028
  log.obs.warn("stream-convergence check failed", {
@@ -184672,6 +185088,7 @@ var init_stream_convergence = __esm({
184672
185088
  "src/core/observability/stream-convergence.ts"() {
184673
185089
  "use strict";
184674
185090
  init_logging();
185091
+ init_side_thread_warmup();
184675
185092
  CHECK_DELAY_MS = 15e3;
184676
185093
  INCIDENT_DEDUPE_MS = 30 * 6e4;
184677
185094
  lastIncidentAt = /* @__PURE__ */ new Map();
@@ -184922,12 +185339,17 @@ function getSystemHealth() {
184922
185339
  }
184923
185340
  async function refreshSystemHealth() {
184924
185341
  systemHealth.claudeCliAvailable = checkClaudeCliAvailable();
184925
- const cred = await resolveCredentialHealth();
185342
+ applyCredentialHealth(await resolveCredentialHealth());
185343
+ broadcastEvent("system:health", systemHealth);
185344
+ return systemHealth;
185345
+ }
185346
+ function applyCredentialHealth(cred) {
184926
185347
  systemHealth.hasReadyProvider = cred.hasReadyProvider;
184927
185348
  systemHealth.credentialSource = cred.source;
184928
185349
  systemHealth.credentialDetail = cred.detail;
184929
- broadcastEvent("system:health", systemHealth);
184930
- return systemHealth;
185350
+ systemHealth.mainProvider = cred.mainProvider;
185351
+ systemHealth.mainProviderImplicit = cred.mainProviderImplicit;
185352
+ systemHealth.claudeCliAuth = cred.claudeCliAuth;
184931
185353
  }
184932
185354
  function checkClaudeCliAvailable() {
184933
185355
  return isClaudeCliInstalled();
@@ -184937,27 +185359,36 @@ async function resolveCredentialHealth() {
184937
185359
  const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
184938
185360
  const { resolveCredentials: resolveCredentials2 } = await Promise.resolve().then(() => (init_credential_resolver(), credential_resolver_exports));
184939
185361
  const { buildProviderMap: buildProviderMap2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
185362
+ const { resolveMainProviderName: resolveMainProviderName2, mainProviderIsImplicit: mainProviderIsImplicit2 } = await Promise.resolve().then(() => (init_default_provider(), default_provider_exports));
185363
+ const { detectClaudeCli: detectClaudeCli2 } = await Promise.resolve().then(() => (init_claude_cli_detect(), claude_cli_detect_exports));
184940
185364
  const config = await getConfig2();
185365
+ const cli = detectClaudeCli2();
185366
+ const mainProvider = resolveMainProviderName2(config, cli.installed);
185367
+ const base = {
185368
+ mainProvider,
185369
+ mainProviderImplicit: mainProviderIsImplicit2(config),
185370
+ claudeCliAuth: cli.installed ? cli.auth.label : void 0
185371
+ };
185372
+ if (mainProvider === "claude_cli") {
185373
+ return cli.installed ? { ...base, hasReadyProvider: true, source: "config", detail: `claude-cli (${cli.auth.label})` } : { ...base, hasReadyProvider: false, source: "none" };
185374
+ }
184941
185375
  const cred = resolveCredentials2(config);
184942
185376
  if (cred.source !== "none") {
184943
- return { hasReadyProvider: true, source: cred.source, detail: cred.detail };
185377
+ return { ...base, hasReadyProvider: true, source: cred.source, detail: cred.detail };
184944
185378
  }
184945
185379
  const providers = buildProviderMap2(config.providers, config);
184946
185380
  for (const [, prov] of Object.entries(providers)) {
184947
185381
  if (prov.api === "bedrock" || prov.api === "ollama") continue;
184948
185382
  if (prov.api === "claude-cli") {
184949
- const { detectClaudeCli: detectClaudeCli2 } = await Promise.resolve().then(() => (init_claude_cli_detect(), claude_cli_detect_exports));
184950
- if (detectClaudeCli2().subscriptionReady) {
184951
- return { hasReadyProvider: true, source: "config", detail: "claude-cli (subscription)" };
184952
- }
185383
+ if (cli.installed) return { ...base, hasReadyProvider: true, source: "config", detail: `claude-cli (${cli.auth.label})` };
184953
185384
  continue;
184954
185385
  }
184955
185386
  const implemented = prov.api === "anthropic-messages" || prov.api === "openai-chat" || prov.api === "google-generative-ai";
184956
185387
  if (implemented && (prov.api_key || prov.bearer_token)) {
184957
- return { hasReadyProvider: true, source: "config", detail: prov.api };
185388
+ return { ...base, hasReadyProvider: true, source: "config", detail: prov.api };
184958
185389
  }
184959
185390
  }
184960
- return { hasReadyProvider: false, source: "none" };
185391
+ return { ...base, hasReadyProvider: false, source: "none" };
184961
185392
  } catch {
184962
185393
  return { hasReadyProvider: false, source: "none" };
184963
185394
  }
@@ -184996,11 +185427,11 @@ async function startServer(options = {}) {
184996
185427
  }
184997
185428
  {
184998
185429
  const cred = await resolveCredentialHealth();
184999
- systemHealth.hasReadyProvider = cred.hasReadyProvider;
185000
- systemHealth.credentialSource = cred.source;
185001
- systemHealth.credentialDetail = cred.detail;
185430
+ applyCredentialHealth(cred);
185002
185431
  if (!systemHealth.hasReadyProvider) {
185003
185432
  log.web.warn("No AI provider configured \u2014 configure one in Settings");
185433
+ } else if (cred.mainProvider === "claude_cli") {
185434
+ log.web.info("AI provider: Claude Code", { auth: cred.claudeCliAuth, implicit: cred.mainProviderImplicit });
185004
185435
  } else if (cred.source && cred.source !== "config") {
185005
185436
  log.web.info("Auto-detected Bedrock credential", { source: cred.source, detail: cred.detail });
185006
185437
  }
@@ -186280,10 +186711,9 @@ async function startServer(options = {}) {
186280
186711
  const prev = systemHealth.hasReadyProvider;
186281
186712
  const prevSource = systemHealth.credentialSource;
186282
186713
  const cred = await resolveCredentialHealth();
186283
- systemHealth.hasReadyProvider = cred.hasReadyProvider;
186284
- systemHealth.credentialSource = cred.source;
186285
- systemHealth.credentialDetail = cred.detail;
186286
- if (cred.hasReadyProvider !== prev || cred.source !== prevSource) {
186714
+ const prevProvider = systemHealth.mainProvider;
186715
+ applyCredentialHealth(cred);
186716
+ if (cred.hasReadyProvider !== prev || cred.source !== prevSource || cred.mainProvider !== prevProvider) {
186287
186717
  broadcastEvent("system:health", systemHealth);
186288
186718
  }
186289
186719
  } catch {