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
package/dist/cli.js CHANGED
@@ -8815,24 +8815,25 @@ function reapSession(sid, code, reason) {
8815
8815
  let jsonlAgeMs = null;
8816
8816
  try { jsonlAgeMs = Date.now() - fs.statSync(session.jsonlPath).mtimeMs; } catch {}
8817
8817
  const cleanExit = isTurnCompleteExit(session.jsonlPath);
8818
- // An INTENTIONAL daemon-initiated reclamation is not a failure, and the JSONL
8819
- // tail cannot tell us it happened. The idle scanner stamps idleReclaimAt
8820
- // before it signals; the death then lands here through whichever path notices
8821
- // it (usually orphan-poll ESRCH \u2192 code -1). Verified production case: the last
8822
- // line was a control_response (the reply to a Walnut-issued control request \u2014
8823
- // a routine shape), so isTurnCompleteExit said false, exitCode stayed -1, and
8824
- // the projection painted a red "Error" over a healthy, fully resumable session
8825
- // we reclaimed on purpose. Checked FIRST and unconditionally: the flag carries
8826
- // information the tail does not contain, which is why isTurnCompleteExit is
8827
- // left alone. Keep in sync with daemon-core.ts reapSession.
8828
- const intentionalReclaim = session.idleReclaimAt != null;
8829
- if (intentionalReclaim && code !== 0) {
8830
- logMsg('info', 'reapSession: intentional idle reclamation, normalizing exit code', {
8818
+ // A DELIBERATE stop is not a failure, and the JSONL tail cannot tell us it
8819
+ // happened. Both writers (our idle scanner, and any stop RPC via cmdStop)
8820
+ // stamp intentionalStopAt before signalling; the death then lands here through
8821
+ // whichever path notices it (usually orphan-poll ESRCH \u2192 code -1). Verified
8822
+ // production case: the last line was a control_response (the reply to a
8823
+ // Walnut-issued control request \u2014 a routine shape), so isTurnCompleteExit said
8824
+ // false, exitCode stayed -1, and the projection painted a red "Error" over a
8825
+ // healthy, fully resumable session that was stopped on purpose. Checked FIRST
8826
+ // and unconditionally: the flag carries information the tail does not contain,
8827
+ // which is why isTurnCompleteExit is left alone.
8828
+ // Keep in sync with daemon-core.ts reapSession.
8829
+ const intentionalStop = session.intentionalStopAt != null;
8830
+ if (intentionalStop && code !== 0) {
8831
+ logMsg('info', 'reapSession: intentional stop, normalizing exit code', {
8831
8832
  sid, pid: session.pid, originalCode: code, originalReason: reason,
8832
- jsonlAgeMs, cleanExit, idleReclaimAt: session.idleReclaimAt,
8833
+ jsonlAgeMs, cleanExit, intentionalStopAt: session.intentionalStopAt,
8833
8834
  });
8834
8835
  code = 0;
8835
- reason = reason + '+intentional-idle-reclaim';
8836
+ reason = reason + '+intentional-stop';
8836
8837
  } else if (code !== 0 && cleanExit) {
8837
8838
  logMsg('info', 'reapSession: turn-complete detected, normalizing exit code', {
8838
8839
  sid, pid: session.pid, originalCode: code, originalReason: reason, jsonlAgeMs,
@@ -12486,6 +12487,15 @@ function cmdStop(ws, id, cmd) {
12486
12487
  const pid = session.pid;
12487
12488
  logMsg('info', 'cmdStop: stopping session (process group kill)', { sid, pid });
12488
12489
 
12490
+ // Nobody sends stop by accident, so record the intent BEFORE the first signal:
12491
+ // the death lands asynchronously (orphan poll \u2192 ESRCH \u2192 code -1) and by then
12492
+ // nothing remembers this was asked for. This path is how the SERVER's idle
12493
+ // reaper kills (mgr.kill() \u2192 stop RPC), not the daemon's own scan, so it needs
12494
+ // its own stamp: a LOCAL session auto-stopped after 2h idle surfaced as
12495
+ // "Session ended unexpectedly and no cause was recorded" (2026-09-03).
12496
+ // Keep in sync with daemon-standalone.ts cmdStop.
12497
+ session.intentionalStopAt = Date.now();
12498
+
12489
12499
  // An explicit stop is a human decision \u2014 never undone by a pending auto-retry
12490
12500
  // respawning the CLI minutes later. Cancel the timer AND clear the streak so
12491
12501
  // an in-flight result line can't re-arm one either.
@@ -12522,7 +12532,7 @@ function cmdStop(ws, id, cmd) {
12522
12532
 
12523
12533
  // \u2500\u2500 Status \u2500\u2500
12524
12534
  function cmdStatus(ws, id, cmd) {
12525
- const { sid } = cmd;
12535
+ const { sid, includeArgs } = cmd;
12526
12536
  if (!sid) return sendError(ws, id, 'status: missing sid');
12527
12537
 
12528
12538
  const session = sessions.get(sid);
@@ -12553,6 +12563,11 @@ function cmdStatus(ws, id, cmd) {
12553
12563
  exitCode: session.exitCode,
12554
12564
  exitReason: session.exitReason,
12555
12565
  pendingCtrl: session.pendingCtrl,
12566
+ // Opt-in: the argv the live process was launched with (a fork copies it to
12567
+ // reproduce the parent's prompt-cache prefix). Off by default because the
12568
+ // health monitor polls status for every session and the argv carries the
12569
+ // multi-KB --append-system-prompt.
12570
+ ...(includeArgs && session.args && session.args.length > 0 ? { args: session.args } : {}),
12556
12571
  });
12557
12572
  }
12558
12573
 
@@ -14605,7 +14620,7 @@ function scanIdleSessions() {
14605
14620
  // end" for any session whose last line isn't a type:result \u2014 and the
14606
14621
  // projection then shows a red Error for our own routine reclamation.
14607
14622
  // Keep in sync with daemon-standalone.ts scanIdleSessions.
14608
- session.idleReclaimAt = now;
14623
+ session.intentionalStopAt = now;
14609
14624
  killSessionProcessGroup(pid, sid);
14610
14625
  }
14611
14626
  }
@@ -15289,19 +15304,19 @@ function createDaemonCore(deps) {
15289
15304
  jsonlAgeMs = clock() - fs121.statSync(session2.jsonlPath).mtimeMs;
15290
15305
  } catch {
15291
15306
  }
15292
- const intentionalReclaim = session2.idleReclaimAt != null;
15293
- if (intentionalReclaim && code !== 0) {
15294
- logger2("info", "reapSession: intentional idle reclamation, normalizing exit code", {
15307
+ const intentionalStop = session2.intentionalStopAt != null;
15308
+ if (intentionalStop && code !== 0) {
15309
+ logger2("info", "reapSession: intentional stop, normalizing exit code", {
15295
15310
  sid,
15296
15311
  pid: session2.pid,
15297
15312
  originalCode: code,
15298
15313
  originalReason: reason,
15299
15314
  jsonlAgeMs,
15300
15315
  cleanExit,
15301
- idleReclaimAt: session2.idleReclaimAt
15316
+ intentionalStopAt: session2.intentionalStopAt
15302
15317
  });
15303
15318
  code = 0;
15304
- reason = reason + "+intentional-idle-reclaim";
15319
+ reason = reason + "+intentional-stop";
15305
15320
  } else if (cleanExit && code !== 0) {
15306
15321
  logger2("info", "reapSession: turn-complete detected, normalizing exit code", {
15307
15322
  sid,
@@ -16092,6 +16107,7 @@ var init_daemon_core = __esm({
16092
16107
  var claude_cli_detect_exports = {};
16093
16108
  __export(claude_cli_detect_exports, {
16094
16109
  _CLAUDE_CREDENTIALS_HOME: () => _CLAUDE_CREDENTIALS_HOME,
16110
+ describeClaudeCliAuth: () => describeClaudeCliAuth,
16095
16111
  detectClaudeCli: () => detectClaudeCli,
16096
16112
  hasClaudeSubscriptionAuth: () => hasClaudeSubscriptionAuth,
16097
16113
  isClaudeCliInstalled: () => isClaudeCliInstalled,
@@ -16152,9 +16168,32 @@ function detectClaudeCli() {
16152
16168
  return {
16153
16169
  installed: installed2,
16154
16170
  subscriptionAuth,
16155
- subscriptionReady: installed2 && subscriptionAuth
16171
+ subscriptionReady: installed2 && subscriptionAuth,
16172
+ ready: installed2,
16173
+ auth: installed2 ? describeClaudeCliAuth() : { mode: "unknown", label: "not installed" }
16156
16174
  };
16157
16175
  }
16176
+ function describeClaudeCliAuth(env3 = process.env, settingsEnv = readClaudeSettingsEnv()) {
16177
+ const merged = { ...env3, ...settingsEnv };
16178
+ const truthy = (v3) => !!v3 && v3 !== "0" && v3.toLowerCase() !== "false";
16179
+ if (truthy(merged.CLAUDE_CODE_USE_BEDROCK)) {
16180
+ const region = merged.AWS_REGION || merged.AWS_DEFAULT_REGION;
16181
+ return { mode: "bedrock", label: region ? `Bedrock (${region})` : "Bedrock" };
16182
+ }
16183
+ if (truthy(merged.CLAUDE_CODE_USE_VERTEX)) return { mode: "vertex", label: "Vertex AI" };
16184
+ if (merged.ANTHROPIC_API_KEY || merged.ANTHROPIC_AUTH_TOKEN) return { mode: "api-key", label: "an Anthropic API key" };
16185
+ if (hasClaudeSubscriptionAuth()) return { mode: "subscription", label: "your Claude subscription" };
16186
+ return { mode: "unknown", label: "its own login" };
16187
+ }
16188
+ function readClaudeSettingsEnv() {
16189
+ try {
16190
+ const raw = fs16.readFileSync(path17.join(os4.homedir(), ".claude", "settings.json"), "utf8");
16191
+ const env3 = JSON.parse(raw).env ?? {};
16192
+ return Object.fromEntries(Object.entries(env3).filter(([, v3]) => typeof v3 === "string"));
16193
+ } catch {
16194
+ return {};
16195
+ }
16196
+ }
16158
16197
  var KEYCHAIN_OAUTH_SERVICE, _CLAUDE_CREDENTIALS_HOME;
16159
16198
  var init_claude_cli_detect = __esm({
16160
16199
  "src/core/claude-cli-detect.ts"() {
@@ -23524,7 +23563,7 @@ __export(credential_resolver_exports, {
23524
23563
  DEFAULT_REGION: () => DEFAULT_REGION,
23525
23564
  hasResolvableCredential: () => hasResolvableCredential,
23526
23565
  readClaudeCredentialExport: () => readClaudeCredentialExport,
23527
- readClaudeSettingsEnv: () => readClaudeSettingsEnv,
23566
+ readClaudeSettingsEnv: () => readClaudeSettingsEnv2,
23528
23567
  resolveCredentialFrom: () => resolveCredentialFrom,
23529
23568
  resolveCredentials: () => resolveCredentials,
23530
23569
  traceCredentialResolution: () => traceCredentialResolution
@@ -23679,7 +23718,7 @@ function traceCredentialResolution(inputs) {
23679
23718
  region: { value: winner.region ?? DEFAULT_REGION, source: regionSource }
23680
23719
  };
23681
23720
  }
23682
- function readClaudeSettingsEnv(settingsFile = CLAUDE_SETTINGS_FILE) {
23721
+ function readClaudeSettingsEnv2(settingsFile = CLAUDE_SETTINGS_FILE) {
23683
23722
  try {
23684
23723
  const raw = fs30.readFileSync(settingsFile, "utf-8");
23685
23724
  const parsed = JSON.parse(raw);
@@ -23719,7 +23758,7 @@ function probeAwsFiles() {
23719
23758
  function resolveCredentials(config) {
23720
23759
  const result = resolveCredentialFrom({
23721
23760
  config,
23722
- claudeEnv: readClaudeSettingsEnv(),
23761
+ claudeEnv: readClaudeSettingsEnv2(),
23723
23762
  processEnv: process.env,
23724
23763
  awsFiles: probeAwsFiles(),
23725
23764
  claudeCredentialExport: readClaudeCredentialExport()
@@ -66501,10 +66540,9 @@ function buildArgs(opts) {
66501
66540
  // Input stays default (text): we write the whole prompt to stdin and close.
66502
66541
  // (stream-json INPUT would force us into a JSON envelope for no gain here.)
66503
66542
  "--tools",
66504
- "",
66543
+ ""
66505
66544
  // disable ALL built-in tools (deny-all)
66506
- "--settings",
66507
- FORCE_SUBSCRIPTION_SETTINGS
66545
+ // No --settings override: the user's settings.json (and so their auth) applies as-is.
66508
66546
  ];
66509
66547
  let systemText = systemToText(opts.system).trim();
66510
66548
  if (opts.tools && opts.tools.length > 0) {
@@ -66533,7 +66571,7 @@ function systemToText(system) {
66533
66571
  }
66534
66572
  function buildSpawnEnv(base = process.env) {
66535
66573
  const env3 = { ...base };
66536
- for (const key2 of AUTH_ENV_TO_STRIP) {
66574
+ for (const key2 of ENV_TO_STRIP) {
66537
66575
  delete env3[key2];
66538
66576
  }
66539
66577
  env3.CLAUDE_CODE_ENTRYPOINT = WALNUT_UTILITY_ENTRYPOINT;
@@ -66595,7 +66633,7 @@ function normalizeCliModel(model) {
66595
66633
  }
66596
66634
  return void 0;
66597
66635
  }
66598
- var AUTH_ENV_TO_STRIP, FORCE_SUBSCRIPTION_SETTINGS, CLI_TURN_TIMEOUT_MS, SESSION_REUSE_TTL_MS, MAX_TRACKED_SESSIONS, ClaudeCliAdapter;
66636
+ var ENV_TO_STRIP, MAX_CONCURRENT_CLI, CLI_TURN_TIMEOUT_MS, SESSION_REUSE_TTL_MS, MAX_TRACKED_SESSIONS, ClaudeCliAdapter;
66599
66637
  var init_adapter_claude_cli = __esm({
66600
66638
  "src/agent/providers/adapter-claude-cli.ts"() {
66601
66639
  "use strict";
@@ -66605,24 +66643,11 @@ var init_adapter_claude_cli = __esm({
66605
66643
  init_logging();
66606
66644
  init_claude_cli_detect();
66607
66645
  init_claude_cli_protocol();
66608
- AUTH_ENV_TO_STRIP = [
66609
- "ANTHROPIC_API_KEY",
66610
- "ANTHROPIC_AUTH_TOKEN",
66611
- "ANTHROPIC_BASE_URL",
66612
- "AWS_BEARER_TOKEN_BEDROCK",
66613
- "AWS_ACCESS_KEY_ID",
66614
- "AWS_SECRET_ACCESS_KEY",
66615
- "AWS_SESSION_TOKEN",
66616
- "AWS_PROFILE",
66617
- "CLAUDE_CODE_USE_BEDROCK",
66618
- "CLAUDE_CODE_USE_VERTEX",
66619
- // Nested-session marker — inline-subagent strips this too so the child doesn't
66620
- // think it's running inside another Claude session.
66621
- "CLAUDECODE"
66622
- ];
66623
- FORCE_SUBSCRIPTION_SETTINGS = JSON.stringify({
66624
- env: { CLAUDE_CODE_USE_BEDROCK: "", CLAUDE_CODE_USE_VERTEX: "" }
66625
- });
66646
+ ENV_TO_STRIP = ["CLAUDECODE"];
66647
+ MAX_CONCURRENT_CLI = (() => {
66648
+ const n6 = Number(process.env.WALNUT_CLAUDE_CLI_CONCURRENCY);
66649
+ return Number.isFinite(n6) && n6 > 0 ? Math.floor(n6) : 3;
66650
+ })();
66626
66651
  CLI_TURN_TIMEOUT_MS = 12e4;
66627
66652
  SESSION_REUSE_TTL_MS = 30 * 60 * 1e3;
66628
66653
  MAX_TRACKED_SESSIONS = 50;
@@ -66630,6 +66655,25 @@ var init_adapter_claude_cli = __esm({
66630
66655
  protocol = "claude-cli";
66631
66656
  /** conversationKey → CLI session for --resume chaining. In-process only. */
66632
66657
  sessions = /* @__PURE__ */ new Map();
66658
+ /** Process gate: MAX_CONCURRENT_CLI turns at once, the rest queue in order. */
66659
+ inFlight = 0;
66660
+ waiters = [];
66661
+ async withSlot(fn) {
66662
+ if (this.inFlight >= MAX_CONCURRENT_CLI) {
66663
+ await new Promise((resolve) => this.waiters.push(resolve));
66664
+ }
66665
+ this.inFlight++;
66666
+ try {
66667
+ return await fn();
66668
+ } finally {
66669
+ this.inFlight--;
66670
+ this.waiters.shift()?.();
66671
+ }
66672
+ }
66673
+ /** Test hook: how many turns are running / waiting right now. */
66674
+ _gateStateForTesting() {
66675
+ return { inFlight: this.inFlight, waiting: this.waiters.length };
66676
+ }
66633
66677
  resetClient() {
66634
66678
  this.sessions.clear();
66635
66679
  }
@@ -66707,7 +66751,7 @@ User: ${overridePrompt}` : "");
66707
66751
  }
66708
66752
  if (!prompt.trim()) prompt = "(continue)";
66709
66753
  const args = [...buildArgs(opts), session2.flag, session2.id];
66710
- return this.execCli(command6, args, env3, prompt, opts.signal, onTextDelta).then((result) => {
66754
+ return this.withSlot(() => this.execCli(command6, args, env3, prompt, opts.signal, onTextDelta)).then((result) => {
66711
66755
  this.sessions.set(key2, {
66712
66756
  sessionId: session2.id,
66713
66757
  seenMessages: opts.messages.length,
@@ -66726,7 +66770,7 @@ User: ${overridePrompt}` : "");
66726
66770
  User: ${overridePrompt}` : "");
66727
66771
  const freshId = randomUUID3();
66728
66772
  const freshArgs = [...buildArgs(opts), "--session-id", freshId];
66729
- return this.execCli(command6, freshArgs, env3, freshPrompt || "(continue)", opts.signal, onTextDelta).then((result) => {
66773
+ return this.withSlot(() => this.execCli(command6, freshArgs, env3, freshPrompt || "(continue)", opts.signal, onTextDelta)).then((result) => {
66730
66774
  this.sessions.set(key2, {
66731
66775
  sessionId: freshId,
66732
66776
  seenMessages: opts.messages.length,
@@ -66810,7 +66854,7 @@ User: ${overridePrompt}` : "");
66810
66854
  }
66811
66855
  const resultText = finalResult?.result || accumulatedText;
66812
66856
  if (resultText && resultText.length < 300 && /^\s*failed to authenticate|^\s*api error: 4\d\d/i.test(resultText)) {
66813
- reject(new Error(`claude-cli subscription auth failed \u2014 run \`claude login\` to re-authenticate. CLI said: ${resultText.slice(0, 200)}`));
66857
+ 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)}`));
66814
66858
  return;
66815
66859
  }
66816
66860
  if (code !== 0 && !accumulatedText && !finalResult) {
@@ -67010,6 +67054,50 @@ var init_registry2 = __esm({
67010
67054
  }
67011
67055
  });
67012
67056
 
67057
+ // src/agent/providers/default-provider.ts
67058
+ var default_provider_exports = {};
67059
+ __export(default_provider_exports, {
67060
+ CLAUDE_CLI_PROVIDER: () => CLAUDE_CLI_PROVIDER,
67061
+ LEGACY_DEFAULT_PROVIDER: () => LEGACY_DEFAULT_PROVIDER,
67062
+ _resetDefaultProviderCacheForTesting: () => _resetDefaultProviderCacheForTesting,
67063
+ hasExplicitBedrockConfig: () => hasExplicitBedrockConfig,
67064
+ mainProviderIsImplicit: () => mainProviderIsImplicit,
67065
+ resolveMainProviderName: () => resolveMainProviderName
67066
+ });
67067
+ function claudeCliInstalledCached() {
67068
+ const now = Date.now();
67069
+ if (!cliProbe || now - cliProbe.at > CLI_PROBE_TTL_MS) {
67070
+ cliProbe = { at: now, installed: isClaudeCliInstalled() };
67071
+ }
67072
+ return cliProbe.installed;
67073
+ }
67074
+ function _resetDefaultProviderCacheForTesting() {
67075
+ cliProbe = void 0;
67076
+ }
67077
+ function hasExplicitBedrockConfig(config) {
67078
+ const b8 = config.providers?.bedrock;
67079
+ if (b8 && (b8.bearer_token || b8.aws_access_key_id || b8.aws_profile || b8.aws_credential_export)) return true;
67080
+ return !!config.provider?.bedrock_bearer_token;
67081
+ }
67082
+ function resolveMainProviderName(config, claudeInstalled = claudeCliInstalledCached()) {
67083
+ if (config.agent?.main_provider) return config.agent.main_provider;
67084
+ if (claudeInstalled) return CLAUDE_CLI_PROVIDER;
67085
+ return LEGACY_DEFAULT_PROVIDER;
67086
+ }
67087
+ function mainProviderIsImplicit(config) {
67088
+ return !config.agent?.main_provider;
67089
+ }
67090
+ var LEGACY_DEFAULT_PROVIDER, CLAUDE_CLI_PROVIDER, CLI_PROBE_TTL_MS, cliProbe;
67091
+ var init_default_provider = __esm({
67092
+ "src/agent/providers/default-provider.ts"() {
67093
+ "use strict";
67094
+ init_claude_cli_detect();
67095
+ LEGACY_DEFAULT_PROVIDER = "bedrock";
67096
+ CLAUDE_CLI_PROVIDER = "claude_cli";
67097
+ CLI_PROBE_TTL_MS = 6e4;
67098
+ }
67099
+ });
67100
+
67013
67101
  // src/agent/model.ts
67014
67102
  var model_exports = {};
67015
67103
  __export(model_exports, {
@@ -67044,7 +67132,7 @@ function getContextThreshold(model, percent) {
67044
67132
  }
67045
67133
  async function resolveForCall(config) {
67046
67134
  const fullConfig = await getConfig();
67047
- const providerName = config?.provider ?? fullConfig.agent?.main_provider ?? "bedrock";
67135
+ const providerName = config?.provider ?? resolveMainProviderName(fullConfig);
67048
67136
  const hasExplicitProviders = fullConfig.providers && Object.keys(fullConfig.providers).length > 0;
67049
67137
  let providers = hasExplicitProviders ? buildProviderMap(fullConfig.providers, fullConfig) : synthesizeFromLegacy(fullConfig);
67050
67138
  const resolved = resolveProvider(providerName, providers);
@@ -67114,6 +67202,7 @@ var init_model = __esm({
67114
67202
  "use strict";
67115
67203
  init_config_manager();
67116
67204
  init_registry2();
67205
+ init_default_provider();
67117
67206
  init_defaults();
67118
67207
  init_model_catalog();
67119
67208
  }
@@ -68554,7 +68643,7 @@ __export(cheap_model_exports, {
68554
68643
  });
68555
68644
  function fastModelFor(config) {
68556
68645
  if (config.agent?.fast_model) return config.agent.fast_model;
68557
- const providerName = config.agent?.main_provider ?? "bedrock";
68646
+ const providerName = resolveMainProviderName(config);
68558
68647
  return MODEL_CATALOG[providerName]?.find(
68559
68648
  (model) => model.id.toLowerCase().includes("haiku")
68560
68649
  )?.id;
@@ -68566,6 +68655,7 @@ var init_cheap_model = __esm({
68566
68655
  "src/core/cheap-model.ts"() {
68567
68656
  "use strict";
68568
68657
  init_model_catalog();
68658
+ init_default_provider();
68569
68659
  }
68570
68660
  });
68571
68661
 
@@ -70385,7 +70475,20 @@ async function applySnapshot(sessionId, snapshot2, source) {
70385
70475
  if (projected !== "error") {
70386
70476
  updates.errorMessage = void 0;
70387
70477
  updates.errorKind = void 0;
70388
- clearSuppressedErrorReason(sessionId);
70478
+ const stashedStop = projected === "stopped" ? takeSuppressedErrorReason(sessionId) : null;
70479
+ if (stashedStop?.message) {
70480
+ updates.errorMessage = stashedStop.message;
70481
+ if (stashedStop.kind) updates.errorKind = stashedStop.kind;
70482
+ log.session.info("snapshot projection adopted suppressed stop reason", {
70483
+ sessionId,
70484
+ suppressedReason: stashedStop.reason,
70485
+ kind: stashedStop.kind ?? null,
70486
+ projected,
70487
+ source
70488
+ });
70489
+ } else {
70490
+ clearSuppressedErrorReason(sessionId);
70491
+ }
70389
70492
  } else {
70390
70493
  let droppedDisproven = false;
70391
70494
  if (assertsHostUnreachable(record)) {
@@ -74459,6 +74562,26 @@ var init_transcript_chain = __esm({
74459
74562
  }
74460
74563
  });
74461
74564
 
74565
+ // src/core/sessions/side-thread-warmup.ts
74566
+ function isCacheWarmupText(text) {
74567
+ return text.startsWith(CACHE_WARMUP_TAG);
74568
+ }
74569
+ function markWarmupTurnPending(sessionId) {
74570
+ pendingWarmupTurns.add(sessionId);
74571
+ }
74572
+ function consumeWarmupTurn(sessionId) {
74573
+ return pendingWarmupTurns.delete(sessionId);
74574
+ }
74575
+ var CACHE_WARMUP_TAG, CACHE_WARMUP_MESSAGE, pendingWarmupTurns;
74576
+ var init_side_thread_warmup = __esm({
74577
+ "src/core/sessions/side-thread-warmup.ts"() {
74578
+ "use strict";
74579
+ CACHE_WARMUP_TAG = "<walnut-cache-warmup>";
74580
+ 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>`;
74581
+ pendingWarmupTurns = /* @__PURE__ */ new Set();
74582
+ }
74583
+ });
74584
+
74462
74585
  // src/core/history-disk-cache.ts
74463
74586
  var history_disk_cache_exports = {};
74464
74587
  __export(history_disk_cache_exports, {
@@ -76619,6 +76742,7 @@ __export(session_lifecycle_exports, {
76619
76742
  retrySession: () => retrySession,
76620
76743
  terminateSession: () => terminateSession
76621
76744
  });
76745
+ import { randomUUID as randomUUID4 } from "crypto";
76622
76746
  async function readProviderSessionHistory(sessionId, record, nativeHost, skipSubagents = true, opts) {
76623
76747
  if (record && engineCaps(record.engine).historySource === "acp-journal") {
76624
76748
  const { readAcpSessionHistoryState: readAcpSessionHistoryState2 } = await Promise.resolve().then(() => (init_acp_session_history(), acp_session_history_exports));
@@ -76729,6 +76853,33 @@ async function changeSessionMode(existingRecord, sessionId, mode, updates = {})
76729
76853
  });
76730
76854
  return updated;
76731
76855
  }
76856
+ function normalizePinQuote(value) {
76857
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
76858
+ throw new SessionControlError("pinned_messages[].quote must be an object", 400);
76859
+ }
76860
+ const quote = value;
76861
+ const exact = quote.exact;
76862
+ if (typeof exact !== "string" || !exact.trim() || exact.length > MAX_PIN_QUOTE_CHARS) {
76863
+ throw new SessionControlError(
76864
+ `pinned_messages[].quote.exact must be a non-empty string (max ${MAX_PIN_QUOTE_CHARS} chars)`,
76865
+ 400
76866
+ );
76867
+ }
76868
+ for (const side of ["prefix", "suffix"]) {
76869
+ const context = quote[side];
76870
+ if (context !== void 0 && context !== null && (typeof context !== "string" || context.length > MAX_PIN_QUOTE_CONTEXT_CHARS)) {
76871
+ throw new SessionControlError(
76872
+ `pinned_messages[].quote.${side} must be a string (max ${MAX_PIN_QUOTE_CONTEXT_CHARS} chars)`,
76873
+ 400
76874
+ );
76875
+ }
76876
+ }
76877
+ return {
76878
+ exact,
76879
+ ...typeof quote.prefix === "string" && quote.prefix ? { prefix: quote.prefix } : {},
76880
+ ...typeof quote.suffix === "string" && quote.suffix ? { suffix: quote.suffix } : {}
76881
+ };
76882
+ }
76732
76883
  function normalizePinnedMessages(value) {
76733
76884
  if (!Array.isArray(value)) {
76734
76885
  throw new SessionControlError("pinned_messages must be an array", 400);
@@ -76737,7 +76888,9 @@ function normalizePinnedMessages(value) {
76737
76888
  throw new SessionControlError(`pinned_messages holds at most ${MAX_PINNED_MESSAGES} entries`, 400);
76738
76889
  }
76739
76890
  const out = [];
76740
- const seen = /* @__PURE__ */ new Set();
76891
+ const seenWholeMessage = /* @__PURE__ */ new Set();
76892
+ const seenQuoteId = /* @__PURE__ */ new Set();
76893
+ const seenQuoteShape = /* @__PURE__ */ new Set();
76741
76894
  for (const raw of value) {
76742
76895
  if (!raw || typeof raw !== "object") {
76743
76896
  throw new SessionControlError("each pinned_messages entry must be an object", 400);
@@ -76747,13 +76900,40 @@ function normalizePinnedMessages(value) {
76747
76900
  if (typeof msgId !== "string" || !msgId.trim() || msgId.length > MAX_PIN_ID_CHARS) {
76748
76901
  throw new SessionControlError("pinned_messages[].msgId must be a non-empty string", 400);
76749
76902
  }
76750
- if (seen.has(msgId)) continue;
76751
- seen.add(msgId);
76903
+ const rawId = entry.id;
76904
+ if (rawId !== void 0 && rawId !== null && (typeof rawId !== "string" || !rawId.trim() || rawId.length > MAX_PIN_ENTRY_ID_CHARS)) {
76905
+ throw new SessionControlError(
76906
+ `pinned_messages[].id must be a non-empty string (max ${MAX_PIN_ENTRY_ID_CHARS} chars)`,
76907
+ 400
76908
+ );
76909
+ }
76910
+ const id = typeof rawId === "string" ? rawId : void 0;
76911
+ const quote = entry.quote === void 0 || entry.quote === null ? void 0 : normalizePinQuote(entry.quote);
76912
+ let pinId = id;
76913
+ if (quote) {
76914
+ if (id && seenQuoteId.has(id)) continue;
76915
+ const shape = [msgId, quote.exact, quote.prefix ?? "", quote.suffix ?? ""].join("\0");
76916
+ if (seenQuoteShape.has(shape)) continue;
76917
+ pinId = id ?? randomUUID4();
76918
+ seenQuoteId.add(pinId);
76919
+ seenQuoteShape.add(shape);
76920
+ } else {
76921
+ if (seenWholeMessage.has(msgId)) continue;
76922
+ seenWholeMessage.add(msgId);
76923
+ }
76752
76924
  const role = entry.role === "user" || entry.role === "assistant" || entry.role === "system" ? entry.role : "assistant";
76753
76925
  const label = typeof entry.label === "string" ? entry.label.trim().slice(0, MAX_PIN_LABEL_CHARS) : "";
76754
76926
  const timestamp = typeof entry.timestamp === "string" ? entry.timestamp : void 0;
76755
76927
  const pinnedAt = typeof entry.pinnedAt === "string" ? entry.pinnedAt : (/* @__PURE__ */ new Date()).toISOString();
76756
- out.push({ msgId, label, role, ...timestamp ? { timestamp } : {}, pinnedAt });
76928
+ out.push({
76929
+ msgId,
76930
+ label,
76931
+ role,
76932
+ ...timestamp ? { timestamp } : {},
76933
+ pinnedAt,
76934
+ ...pinId ? { id: pinId } : {},
76935
+ ...quote ? { quote } : {}
76936
+ });
76757
76937
  }
76758
76938
  return out;
76759
76939
  }
@@ -77412,7 +77592,7 @@ async function readSessionRichHistory(sessionId, tail) {
77412
77592
  ...historyWindowed ? { windowed: true } : {}
77413
77593
  };
77414
77594
  }
77415
- 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;
77595
+ 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;
77416
77596
  var init_session_lifecycle = __esm({
77417
77597
  "src/core/sessions/session-lifecycle.ts"() {
77418
77598
  "use strict";
@@ -77426,6 +77606,9 @@ var init_session_lifecycle = __esm({
77426
77606
  MAX_PINNED_MESSAGES = 200;
77427
77607
  MAX_PIN_LABEL_CHARS = 300;
77428
77608
  MAX_PIN_ID_CHARS = 200;
77609
+ MAX_PIN_ENTRY_ID_CHARS = 64;
77610
+ MAX_PIN_QUOTE_CHARS = 2e3;
77611
+ MAX_PIN_QUOTE_CONTEXT_CHARS = 64;
77429
77612
  RECHECK_RPC_TIMEOUT_MS = 5e3;
77430
77613
  RECHECK_TIMED_OUT = /* @__PURE__ */ Symbol("recheck-timeout");
77431
77614
  GIT_BASES = /* @__PURE__ */ new Set(["uncommitted", "previous", "remote"]);
@@ -77728,7 +77911,7 @@ __export(compact_inject_exports, {
77728
77911
  extractSessionMeta: () => extractSessionMeta,
77729
77912
  injectCompactBoundary: () => injectCompactBoundary
77730
77913
  });
77731
- import { randomUUID as randomUUID4 } from "crypto";
77914
+ import { randomUUID as randomUUID5 } from "crypto";
77732
77915
  import fsp26 from "fs/promises";
77733
77916
  async function extractSessionMeta(jsonlPath) {
77734
77917
  const content = await fsp26.readFile(jsonlPath, "utf-8");
@@ -77780,8 +77963,8 @@ async function injectCompactBoundary(jsonlPath, summary, meta) {
77780
77963
  log.session.error("Could not find last message UUID in JSONL", { jsonlPath });
77781
77964
  return null;
77782
77965
  }
77783
- const boundaryUuid = randomUUID4();
77784
- const summaryUuid = randomUUID4();
77966
+ const boundaryUuid = randomUUID5();
77967
+ const summaryUuid = randomUUID5();
77785
77968
  const now = /* @__PURE__ */ new Date();
77786
77969
  const boundaryTs = now.toISOString();
77787
77970
  const summaryTs = new Date(now.getTime() + 1e3).toISOString();
@@ -88565,6 +88748,82 @@ var init_task = __esm({
88565
88748
  }
88566
88749
  });
88567
88750
 
88751
+ // src/core/sessions/spawn-prefix.ts
88752
+ var spawn_prefix_exports = {};
88753
+ __export(spawn_prefix_exports, {
88754
+ MAX_SPAWN_PROMPT_BYTES: () => MAX_SPAWN_PROMPT_BYTES,
88755
+ parseSpawnPrefixFromArgs: () => parseSpawnPrefixFromArgs,
88756
+ readParentSpawnPrefix: () => readParentSpawnPrefix,
88757
+ spawnPrefixFromRecord: () => spawnPrefixFromRecord
88758
+ });
88759
+ function flagValue(args, flag) {
88760
+ const i8 = args.indexOf(flag);
88761
+ if (i8 < 0 || i8 + 1 >= args.length) return void 0;
88762
+ const v3 = args[i8 + 1];
88763
+ return v3 !== void 0 && !v3.startsWith("--") ? v3 : void 0;
88764
+ }
88765
+ function parseSpawnPrefixFromArgs(args) {
88766
+ const prompt = flagValue(args, "--append-system-prompt");
88767
+ return {
88768
+ appendSystemPrompt: prompt !== void 0 && Buffer.byteLength(prompt, "utf8") <= MAX_SPAWN_PROMPT_BYTES ? prompt : null,
88769
+ ...flagValue(args, "--model") ? { model: flagValue(args, "--model") } : {},
88770
+ ...EFFORTS.includes(flagValue(args, "--effort") ?? "") ? { effort: flagValue(args, "--effort") } : {},
88771
+ ...flagValue(args, "--permission-mode") ? { permissionMode: flagValue(args, "--permission-mode") } : {},
88772
+ source: "live-process"
88773
+ };
88774
+ }
88775
+ function spawnPrefixFromRecord(record) {
88776
+ const stored = record.appliedAppendSystemPrompt;
88777
+ if (stored === void 0 || stored === null) return { appendSystemPrompt: null, source: "unknown" };
88778
+ const usable = stored.length > 0 && Buffer.byteLength(stored, "utf8") <= MAX_SPAWN_PROMPT_BYTES;
88779
+ return { appendSystemPrompt: usable ? stored : null, source: "record" };
88780
+ }
88781
+ async function defaultLiveArgsReader(hostKey2, sessionId) {
88782
+ const { probeDaemonSessionArgs: probeDaemonSessionArgs2 } = await Promise.resolve().then(() => (init_daemon_connection(), daemon_connection_exports));
88783
+ return probeDaemonSessionArgs2(hostKey2, sessionId);
88784
+ }
88785
+ async function readParentSpawnPrefix(parent, deps = {}) {
88786
+ const readLiveArgs = deps.readLiveArgs ?? defaultLiveArgsReader;
88787
+ const hostKey2 = parent.host || "__local__";
88788
+ let live = null;
88789
+ try {
88790
+ live = await readLiveArgs(hostKey2, parent.claudeSessionId);
88791
+ } catch (err2) {
88792
+ log.session.debug("spawn prefix: live argv read threw", {
88793
+ sessionId: parent.claudeSessionId,
88794
+ error: err2 instanceof Error ? err2.message : String(err2)
88795
+ });
88796
+ }
88797
+ if (live && live.length > 0) {
88798
+ const prefix = parseSpawnPrefixFromArgs(live);
88799
+ const remembered = parent.appliedAppendSystemPrompt ?? void 0;
88800
+ const actual = prefix.appendSystemPrompt ?? "";
88801
+ if (remembered !== actual) {
88802
+ const persist4 = deps.persist ?? (async (sid, prompt) => {
88803
+ const { updateSessionRecord: updateSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
88804
+ return updateSessionRecord2(sid, { appliedAppendSystemPrompt: prompt });
88805
+ });
88806
+ void persist4(parent.claudeSessionId, actual).catch((err2) => {
88807
+ log.session.warn("spawn prefix: backfill of applied prompt failed", {
88808
+ sessionId: parent.claudeSessionId,
88809
+ error: err2 instanceof Error ? err2.message : String(err2)
88810
+ });
88811
+ });
88812
+ }
88813
+ return prefix;
88814
+ }
88815
+ return spawnPrefixFromRecord(parent);
88816
+ }
88817
+ var EFFORTS, MAX_SPAWN_PROMPT_BYTES;
88818
+ var init_spawn_prefix = __esm({
88819
+ "src/core/sessions/spawn-prefix.ts"() {
88820
+ "use strict";
88821
+ init_logging();
88822
+ EFFORTS = ["low", "medium", "high", "xhigh", "max"];
88823
+ MAX_SPAWN_PROMPT_BYTES = 65536;
88824
+ }
88825
+ });
88826
+
88568
88827
  // src/core/sessions/side-thread-fork.ts
88569
88828
  var side_thread_fork_exports = {};
88570
88829
  __export(side_thread_fork_exports, {
@@ -88621,6 +88880,10 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
88621
88880
  const sessionId = crypto16.randomUUID();
88622
88881
  const lane = sideThreadLaneKey(parentSid, threadId);
88623
88882
  const mode = parent.mode !== "default" ? parent.mode : void 0;
88883
+ const { readParentSpawnPrefix: readParentSpawnPrefix2 } = await Promise.resolve().then(() => (init_spawn_prefix(), spawn_prefix_exports));
88884
+ const prefix = await readParentSpawnPrefix2(parent);
88885
+ const cliModel = prefix.model ?? parent.cliModel;
88886
+ const effort = prefix.effort ?? parent.effort;
88624
88887
  await createSessionRecord(sessionId, "", parent.project ?? "", parent.cwd, {
88625
88888
  title,
88626
88889
  ...mode ? { mode } : {},
@@ -88628,8 +88891,10 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
88628
88891
  // The thread inherits the parent's launch bundle verbatim, re-applied from
88629
88892
  // the record on every cold resume.
88630
88893
  ...parent.profile ? { profile: parent.profile } : {},
88631
- ...parent.effort ? { effort: parent.effort } : {},
88632
- ...parent.cliModel ? { cliModel: parent.cliModel } : {},
88894
+ ...effort ? { effort } : {},
88895
+ ...cliModel ? { cliModel } : {},
88896
+ // '' = the parent runs without an append prompt, and so does the thread.
88897
+ appliedAppendSystemPrompt: prefix.appendSystemPrompt ?? "",
88633
88898
  lane,
88634
88899
  forkedFromSessionId: resumeFrom,
88635
88900
  initialProcessStatus: "idle",
@@ -88645,11 +88910,12 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
88645
88910
  ...mode ? { mode } : {},
88646
88911
  ...parent.host ? { host: parent.host } : {},
88647
88912
  ...parent.profile ? { profile: parent.profile } : {},
88648
- ...parent.effort ? { effort: parent.effort } : {},
88913
+ ...effort ? { effort } : {},
88649
88914
  // Must be `cliModel` (the parent's verbatim --model arg, [1m] marker
88650
88915
  // included): the CLI does NOT inherit a model across --fork-session, and
88651
88916
  // the reported `model` has lost the marker.
88652
- ...parent.cliModel ? { model: parent.cliModel } : {},
88917
+ ...cliModel ? { model: cliModel } : {},
88918
+ appendSystemPromptExact: prefix.appendSystemPrompt,
88653
88919
  lane,
88654
88920
  forkedFromSessionId: resumeFrom
88655
88921
  }, ["session-runner"], { source: "side-thread-fork" });
@@ -88658,7 +88924,9 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
88658
88924
  threadId,
88659
88925
  sessionId,
88660
88926
  resumeFrom,
88661
- initOnly: !opts?.message
88927
+ initOnly: !opts?.message,
88928
+ prefixSource: prefix.source,
88929
+ prefixPromptLength: prefix.appendSystemPrompt?.length ?? 0
88662
88930
  });
88663
88931
  return { sessionId, resumeFromSessionId: resumeFrom };
88664
88932
  }
@@ -100914,14 +101182,16 @@ function isLive(r8) {
100914
101182
  function isRevivable(r8) {
100915
101183
  return !!r8.outputFile || r8.consumedOffset !== void 0;
100916
101184
  }
100917
- var STANDBY_TTL_MS, MAX_LIVE_THREADS, IDLE_SWEEP_INTERVAL_MS, THREAD_IDLE_MS, SideThreadManager, sideThreadManager;
101185
+ var STANDBY_TTL_MS, WARMED_STANDBY_TTL_MS, MAX_LIVE_THREADS, IDLE_SWEEP_INTERVAL_MS, THREAD_IDLE_MS, SideThreadManager, sideThreadManager;
100918
101186
  var init_side_thread_manager = __esm({
100919
101187
  "src/core/sessions/side-thread-manager.ts"() {
100920
101188
  "use strict";
100921
101189
  init_logging();
100922
101190
  init_side_thread_fork();
100923
101191
  init_session_controls();
101192
+ init_side_thread_warmup();
100924
101193
  STANDBY_TTL_MS = 12e4;
101194
+ WARMED_STANDBY_TTL_MS = 15 * 6e4;
100925
101195
  MAX_LIVE_THREADS = 3;
100926
101196
  IDLE_SWEEP_INTERVAL_MS = 5 * 6e4;
100927
101197
  THREAD_IDLE_MS = 30 * 6e4;
@@ -100932,6 +101202,8 @@ var init_side_thread_manager = __esm({
100932
101202
  * In-memory only: a server restart loses it, but the boot sweep archives
100933
101203
  * every standby anyway, so a record can never outlive its map entry. */
100934
101204
  standbyParentOffsets = /* @__PURE__ */ new Map();
101205
+ /** parentSid → standby sid that already ran its cache warm-up turn. */
101206
+ warmedStandbys = /* @__PURE__ */ new Map();
100935
101207
  /** One in-flight standby/create op per parent — two concurrent asks must not
100936
101208
  * both consume the same standby (they would collide on its lane rename). */
100937
101209
  inFlight = /* @__PURE__ */ new Map();
@@ -100963,6 +101235,7 @@ var init_side_thread_manager = __esm({
100963
101235
  for (const timer3 of this.standbyTimers.values()) clearTimeout(timer3);
100964
101236
  this.standbyTimers.clear();
100965
101237
  this.standbyParentOffsets.clear();
101238
+ this.warmedStandbys.clear();
100966
101239
  this.inFlight.clear();
100967
101240
  }
100968
101241
  // ── Standby ────────────────────────────────────────────────────────────────
@@ -100978,10 +101251,12 @@ var init_side_thread_manager = __esm({
100978
101251
  const existing = await this.findStandby(parentSid);
100979
101252
  if (existing) {
100980
101253
  if (await this.isStandbyUsable(parentSid, existing)) {
100981
- this.armStandbyTtl(parentSid, existing.claudeSessionId);
101254
+ const warmed = this.warmedStandbys.get(parentSid) === existing.claudeSessionId;
101255
+ this.armStandbyTtl(parentSid, existing.claudeSessionId, warmed ? WARMED_STANDBY_TTL_MS : STANDBY_TTL_MS);
100982
101256
  return existing.claudeSessionId;
100983
101257
  }
100984
101258
  void this.retireSession(existing.claudeSessionId, "side_thread_standby_stale");
101259
+ this.warmedStandbys.delete(parentSid);
100985
101260
  }
100986
101261
  const parentOffset = parent.consumedOffset;
100987
101262
  const { sessionId } = await forkSideThreadSession(parentSid, STANDBY_THREAD_ID);
@@ -101007,7 +101282,7 @@ var init_side_thread_manager = __esm({
101007
101282
  if (!parent) return false;
101008
101283
  return parent.consumedOffset === this.standbyParentOffsets.get(parentSid);
101009
101284
  }
101010
- armStandbyTtl(parentSid, sessionId) {
101285
+ armStandbyTtl(parentSid, sessionId, ttlMs = STANDBY_TTL_MS) {
101011
101286
  const prev = this.standbyTimers.get(parentSid);
101012
101287
  if (prev) clearTimeout(prev);
101013
101288
  const timer3 = setTimeout(() => {
@@ -101017,13 +101292,41 @@ var init_side_thread_manager = __esm({
101017
101292
  const record = await getSessionByClaudeId2(sessionId).catch(() => null);
101018
101293
  if (!record || parseSideLaneKey(record.lane)?.threadId !== STANDBY_THREAD_ID) return;
101019
101294
  this.standbyParentOffsets.delete(parentSid);
101295
+ this.warmedStandbys.delete(parentSid);
101020
101296
  await this.retireSession(sessionId, "side_thread_standby_ttl");
101021
101297
  }).catch(() => {
101022
101298
  });
101023
- }, STANDBY_TTL_MS);
101299
+ }, ttlMs);
101024
101300
  timer3.unref?.();
101025
101301
  this.standbyTimers.set(parentSid, timer3);
101026
101302
  }
101303
+ /**
101304
+ * Run the standby's cache warm-up turn (see side-thread-warmup.ts). Called
101305
+ * when the user starts TYPING a new question: the fork's first API call pays
101306
+ * the full prefix write no matter what, so paying it while they type turns
101307
+ * the actual question into a cached follow-up. Idempotent per standby; a
101308
+ * missing or stale standby is a no-op (the ask path will fork fresh anyway).
101309
+ */
101310
+ async warmStandby(parentSid) {
101311
+ return this.serialize(parentSid, async () => {
101312
+ const standby = await this.findStandby(parentSid);
101313
+ if (!standby) return { warmed: false, reason: "no_standby" };
101314
+ if (!await this.isStandbyUsable(parentSid, standby)) return { warmed: false, reason: "stale" };
101315
+ if (this.warmedStandbys.get(parentSid) === standby.claudeSessionId) {
101316
+ return { warmed: true, reason: "already_warm" };
101317
+ }
101318
+ const { sendMessageToSession: sendMessageToSession2 } = await Promise.resolve().then(() => (init_session_message_queue(), session_message_queue_exports));
101319
+ markWarmupTurnPending(standby.claudeSessionId);
101320
+ await sendMessageToSession2(standby.claudeSessionId, CACHE_WARMUP_MESSAGE, { source: "side-thread-warmup" });
101321
+ this.warmedStandbys.set(parentSid, standby.claudeSessionId);
101322
+ this.armStandbyTtl(parentSid, standby.claudeSessionId, WARMED_STANDBY_TTL_MS);
101323
+ log.session.info("side thread: standby cache warm-up sent", {
101324
+ parentSid,
101325
+ standbySid: standby.claudeSessionId
101326
+ });
101327
+ return { warmed: true };
101328
+ });
101329
+ }
101027
101330
  // ── Threads ────────────────────────────────────────────────────────────────
101028
101331
  /**
101029
101332
  * Open a side thread on `parentSid` and ask `question` in it.
@@ -101034,6 +101337,10 @@ var init_side_thread_manager = __esm({
101034
101337
  * question as its spawn's first turn instead — a send issued right after
101035
101338
  * SESSION_START can lose that race and cold-`--resume` an id the CLI has never
101036
101339
  * seen (see personal-ai-lane.ts LaneSession.created).
101340
+ *
101341
+ * `imageContext` (attachment paths for the CLI to Read) rides the MESSAGE only.
101342
+ * The stored entry keeps the plain question, so the chip label, the injected
101343
+ * transcript and a promoted task never carry the machine preamble.
101037
101344
  */
101038
101345
  async createThread(parentSid, input) {
101039
101346
  const question = input.question?.trim();
@@ -101041,8 +101348,11 @@ var init_side_thread_manager = __esm({
101041
101348
  return this.serialize(parentSid, async () => {
101042
101349
  const threadId = mintSideThreadId();
101043
101350
  const title = input.title?.trim() || void 0;
101351
+ const message = input.imageContext ? `${input.imageContext}
101352
+
101353
+ ${question}` : question;
101044
101354
  const consumed = await this.consumeStandby(parentSid, threadId, title);
101045
- const threadSessionId = consumed ?? (await forkSideThreadSession(parentSid, threadId, { message: question, ...title ? { title } : {} })).sessionId;
101355
+ const threadSessionId = consumed ?? (await forkSideThreadSession(parentSid, threadId, { message, ...title ? { title } : {} })).sessionId;
101046
101356
  let entry;
101047
101357
  try {
101048
101358
  const { addSideThread: addSideThread2 } = await Promise.resolve().then(() => (init_side_questions(), side_questions_exports));
@@ -101054,7 +101364,7 @@ var init_side_thread_manager = __esm({
101054
101364
  });
101055
101365
  if (consumed) {
101056
101366
  const { sendMessageToSession: sendMessageToSession2 } = await Promise.resolve().then(() => (init_session_message_queue(), session_message_queue_exports));
101057
- await sendMessageToSession2(threadSessionId, question, { source: "side-thread" });
101367
+ await sendMessageToSession2(threadSessionId, message, { source: "side-thread" });
101058
101368
  }
101059
101369
  } catch (err2) {
101060
101370
  void this.retireSession(threadSessionId, "side_thread_create_failed");
@@ -101089,6 +101399,7 @@ var init_side_thread_manager = __esm({
101089
101399
  if (!await this.isStandbyUsable(parentSid, standby)) {
101090
101400
  void this.retireSession(standby.claudeSessionId, "side_thread_standby_stale");
101091
101401
  this.standbyParentOffsets.delete(parentSid);
101402
+ this.warmedStandbys.delete(parentSid);
101092
101403
  return null;
101093
101404
  }
101094
101405
  const timer3 = this.standbyTimers.get(parentSid);
@@ -101097,6 +101408,7 @@ var init_side_thread_manager = __esm({
101097
101408
  this.standbyTimers.delete(parentSid);
101098
101409
  }
101099
101410
  this.standbyParentOffsets.delete(parentSid);
101411
+ this.warmedStandbys.delete(parentSid);
101100
101412
  const lane = sideThreadLaneKey(parentSid, threadId);
101101
101413
  const { updateSessionRecord: updateSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
101102
101414
  await updateSessionRecord2(standby.claudeSessionId, {
@@ -101283,16 +101595,30 @@ async function promoteSideThread(parentSid, threadId, opts) {
101283
101595
  const threadRecord = await getSessionByClaudeId2(sessionId);
101284
101596
  if (!threadRecord) throw new SessionControlError("Side thread session not found", 404);
101285
101597
  const parentRecord = await getSessionByClaudeId2(parentSid);
101286
- const parentTaskId = parentRecord?.taskId?.trim() || void 0;
101287
- const title = opts?.title?.trim() || entry.title?.trim() || entry.question;
101598
+ const siblingOfTaskId = parentRecord?.taskId?.trim() || void 0;
101599
+ const threadLabel = opts?.title?.trim() || entry.title?.trim() || void 0;
101288
101600
  const { addTask: addTask2, linkSession: linkSession2, linkSessionSlot: linkSessionSlot2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
101289
- const { task } = await addTask2({
101290
- title,
101291
- description: entry.question,
101292
- ...parentTaskId ? { parent_task_id: parentTaskId } : {},
101293
- // A person clicked "promote" — same board default as any hand-made task.
101294
- pinned: true
101295
- });
101601
+ let groupId;
101602
+ let task;
101603
+ if (siblingOfTaskId) {
101604
+ const created = await createForkSiblingTask(siblingOfTaskId, {
101605
+ // A known label short-circuits the background refine; without one the
101606
+ // thread's first question is what gets summarized (same as a fork's message).
101607
+ titlePrefix: threadLabel,
101608
+ prompt: entry.question,
101609
+ description: entry.question,
101610
+ source: "side-thread-promote"
101611
+ });
101612
+ task = created.task;
101613
+ groupId = created.groupId;
101614
+ } else {
101615
+ ({ task } = await addTask2({
101616
+ title: threadLabel || entry.question,
101617
+ description: entry.question,
101618
+ // A person clicked "promote" — same board default as any hand-made task.
101619
+ pinned: true
101620
+ }));
101621
+ }
101296
101622
  const stamped = await markThreadPromoted2(parentSid, threadId, task.id);
101297
101623
  if (!stamped) {
101298
101624
  log.session.warn("side thread promote: entry vanished before stamp \u2014 promote proceeds, 409 guard is unarmed", {
@@ -101306,7 +101632,7 @@ async function promoteSideThread(parentSid, threadId, opts) {
101306
101632
  project: task.project ?? "",
101307
101633
  // Clearing the lane un-hides the session (see the file header).
101308
101634
  lane: void 0,
101309
- title
101635
+ title: task.title
101310
101636
  });
101311
101637
  try {
101312
101638
  const { sessionRunner: sessionRunner2 } = await Promise.resolve().then(() => (init_claude_code_session(), claude_code_session_exports));
@@ -101325,10 +101651,18 @@ async function promoteSideThread(parentSid, threadId, opts) {
101325
101651
  error: err2 instanceof Error ? err2.message : String(err2)
101326
101652
  });
101327
101653
  }
101328
- log.session.info("side thread: promoted to task", { parentSid, threadId, sessionId, taskId: task.id });
101654
+ log.session.info("side thread: promoted to task", {
101655
+ parentSid,
101656
+ threadId,
101657
+ sessionId,
101658
+ taskId: task.id,
101659
+ siblingOfTaskId,
101660
+ groupId
101661
+ });
101329
101662
  return {
101330
101663
  taskId: task.id,
101331
- ...task.parent_task_id ? { parentTaskId: task.parent_task_id } : {},
101664
+ ...siblingOfTaskId ? { siblingOfTaskId } : {},
101665
+ ...groupId ? { groupId } : {},
101332
101666
  sessionId
101333
101667
  };
101334
101668
  }
@@ -101343,7 +101677,7 @@ var init_side_thread_promote = __esm({
101343
101677
 
101344
101678
  // src/web/routes/sessions.ts
101345
101679
  import { Router as Router10 } from "express";
101346
- import { randomUUID as randomUUID5 } from "crypto";
101680
+ import { randomUUID as randomUUID6 } from "crypto";
101347
101681
  import os19 from "os";
101348
101682
  import path91 from "path";
101349
101683
  function condenseStaleReason(msg) {
@@ -101689,7 +102023,7 @@ var init_sessions2 = __esm({
101689
102023
  }
101690
102024
  const isNativeEngine = !isAcpEngine(engine);
101691
102025
  const clientSessionId = typeof req.body.sessionId === "string" && UUID_RE2.test(req.body.sessionId) ? req.body.sessionId : void 0;
101692
- const preassignedSessionId = isNativeEngine ? clientSessionId ?? randomUUID5() : void 0;
102026
+ const preassignedSessionId = isNativeEngine ? clientSessionId ?? randomUUID6() : void 0;
101693
102027
  const updatedTask = await quickStartSession({
101694
102028
  message: sessionMessage,
101695
102029
  messagePrefix,
@@ -102664,18 +102998,38 @@ var init_sessions2 = __esm({
102664
102998
  });
102665
102999
  }
102666
103000
  });
103001
+ sessionsRouter.post("/:sessionId/side-threads/standby/warm", async (req, res) => {
103002
+ if (refuseSideThreadsOnReplica(res)) return;
103003
+ const sessionId = String(req.params.sessionId);
103004
+ try {
103005
+ const { sideThreadManager: sideThreadManager2 } = await Promise.resolve().then(() => (init_side_thread_manager(), side_thread_manager_exports));
103006
+ res.json(await sideThreadManager2.warmStandby(sessionId));
103007
+ } catch (err2) {
103008
+ log.web.warn("side thread standby warm-up failed", {
103009
+ sessionId,
103010
+ error: err2 instanceof Error ? err2.message : String(err2)
103011
+ });
103012
+ res.json({ warmed: false, reason: "error" });
103013
+ }
103014
+ });
102667
103015
  sessionsRouter.post("/:sessionId/side-threads", async (req, res, next) => {
102668
103016
  if (refuseSideThreadsOnReplica(res)) return;
102669
103017
  try {
102670
- const { question, title } = req.body ?? {};
103018
+ const { question, title, images } = req.body ?? {};
102671
103019
  if (!question || typeof question !== "string" || !question.trim()) {
102672
103020
  res.status(400).json({ error: "question (non-empty string) is required" });
102673
103021
  return;
102674
103022
  }
103023
+ let imageContext = "";
103024
+ if (images && images.length > 0) {
103025
+ const processed = await processAndSaveImages(images);
103026
+ if (processed) imageContext = buildSessionImageContext(processed.savedImages);
103027
+ }
102675
103028
  const { sideThreadManager: sideThreadManager2 } = await Promise.resolve().then(() => (init_side_thread_manager(), side_thread_manager_exports));
102676
103029
  const thread = await sideThreadManager2.createThread(String(req.params.sessionId), {
102677
103030
  question,
102678
- ...typeof title === "string" && title.trim() ? { title } : {}
103031
+ ...typeof title === "string" && title.trim() ? { title } : {},
103032
+ ...imageContext ? { imageContext } : {}
102679
103033
  });
102680
103034
  res.json({ thread });
102681
103035
  } catch (err2) {
@@ -102694,7 +103048,8 @@ var init_sessions2 = __esm({
102694
103048
  );
102695
103049
  res.json({
102696
103050
  taskId: result.taskId,
102697
- ...result.parentTaskId ? { parentTaskId: result.parentTaskId } : {},
103051
+ ...result.siblingOfTaskId ? { siblingOfTaskId: result.siblingOfTaskId } : {},
103052
+ ...result.groupId ? { groupId: result.groupId } : {},
102698
103053
  sessionId: result.sessionId
102699
103054
  });
102700
103055
  } catch (err2) {
@@ -103389,7 +103744,7 @@ var micro_claude_exports = {};
103389
103744
  __export(micro_claude_exports, {
103390
103745
  runMicroClaude: () => runMicroClaude
103391
103746
  });
103392
- import { randomUUID as randomUUID6 } from "crypto";
103747
+ import { randomUUID as randomUUID7 } from "crypto";
103393
103748
  async function runMicroClaude(opts) {
103394
103749
  if (opts.warm) {
103395
103750
  const run10 = await runWarmMicroClaude({
@@ -103398,7 +103753,7 @@ async function runMicroClaude(opts) {
103398
103753
  tools: opts.tools ?? [],
103399
103754
  prompt: opts.prompt,
103400
103755
  timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
103401
- toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID6()}`,
103756
+ toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID7()}`,
103402
103757
  ...opts.onBlock ? { onBlock: opts.onBlock } : {}
103403
103758
  });
103404
103759
  return { response: run10.response, costUsd: run10.costUsd, durationMs: run10.durationMs };
@@ -103408,7 +103763,7 @@ async function runMicroClaude(opts) {
103408
103763
  systemPrompt: opts.system,
103409
103764
  model: opts.model ?? DEFAULT_MODEL2,
103410
103765
  timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
103411
- toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID6()}`,
103766
+ toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID7()}`,
103412
103767
  slim: true,
103413
103768
  thinking: opts.thinking ?? false,
103414
103769
  ...opts.tools && opts.tools.length > 0 ? { tools: opts.tools } : {},
@@ -104233,7 +104588,7 @@ __export(micro_agent_exports, {
104233
104588
  async function resolveTierModel(tier) {
104234
104589
  const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
104235
104590
  const config = await getConfig2();
104236
- const provider = config.agent?.main_provider ?? "bedrock";
104591
+ const provider = resolveMainProviderName(config);
104237
104592
  const { MODEL_CATALOG: MODEL_CATALOG3 } = await Promise.resolve().then(() => (init_model_catalog(), model_catalog_exports));
104238
104593
  const entry = MODEL_CATALOG3[provider]?.find((m6) => {
104239
104594
  const id = m6.id.toLowerCase();
@@ -104302,6 +104657,7 @@ var DEFAULT_TIER, DEFAULT_MAX_TOKENS2, DEFAULT_MAX_ROUNDS, DEFAULT_TIMEOUT_MS3;
104302
104657
  var init_micro_agent = __esm({
104303
104658
  "src/agent/micro-agent.ts"() {
104304
104659
  "use strict";
104660
+ init_default_provider();
104305
104661
  DEFAULT_TIER = "sonnet";
104306
104662
  DEFAULT_MAX_TOKENS2 = 2e3;
104307
104663
  DEFAULT_MAX_ROUNDS = 3;
@@ -104317,7 +104673,7 @@ __export(task_search_agent_exports, {
104317
104673
  prewarmAgentSearchChild: () => prewarmAgentSearchChild,
104318
104674
  runTaskSearchAgent: () => runTaskSearchAgent
104319
104675
  });
104320
- import { randomUUID as randomUUID7 } from "crypto";
104676
+ import { randomUUID as randomUUID8 } from "crypto";
104321
104677
  function useCliEngine() {
104322
104678
  return process.env.WALNUT_AGENT_SEARCH_ENGINE !== "inprocess";
104323
104679
  }
@@ -104397,7 +104753,7 @@ async function claudeCliEngine(userPrompt, options) {
104397
104753
  model: options.model,
104398
104754
  timeoutMs: options.timeoutMs,
104399
104755
  tools: ["Bash"],
104400
- toolUseId: `task-search-${randomUUID7()}`,
104756
+ toolUseId: `task-search-${randomUUID8()}`,
104401
104757
  warm: true,
104402
104758
  onBlock
104403
104759
  });
@@ -105660,10 +106016,11 @@ async function buildProvidersPayload() {
105660
106016
  providers[name] = {
105661
106017
  api: prov.api,
105662
106018
  base_url: prov.base_url,
105663
- status: caps.subscriptionReady ? "ready" : "no_key",
106019
+ status: caps.installed ? "ready" : "no_key",
105664
106020
  auto_detected: !explicitNames.has(name),
105665
106021
  models: getModelsForProvider(name, prov.models),
105666
- credential_source: caps.subscriptionReady ? "subscription" : caps.installed ? "cli_no_subscription" : "cli_not_installed"
106022
+ credential_source: caps.installed ? `cli_${caps.auth.mode}` : "cli_not_installed",
106023
+ credential_detail: caps.installed ? caps.auth.label : void 0
105667
106024
  };
105668
106025
  continue;
105669
106026
  }
@@ -105722,10 +106079,11 @@ async function buildProvidersPayload() {
105722
106079
  const caps = detectClaudeCli2();
105723
106080
  providers[name] = {
105724
106081
  api: template.api,
105725
- status: caps.subscriptionReady ? "ready" : "no_key",
106082
+ status: caps.installed ? "ready" : "no_key",
105726
106083
  auto_detected: false,
105727
106084
  models: getModelsForProvider(name),
105728
- credential_source: caps.subscriptionReady ? "subscription" : caps.installed ? "cli_no_subscription" : "cli_not_installed"
106085
+ credential_source: caps.installed ? `cli_${caps.auth.mode}` : "cli_not_installed",
106086
+ credential_detail: caps.installed ? caps.auth.label : void 0
105729
106087
  };
105730
106088
  continue;
105731
106089
  }
@@ -105881,7 +106239,7 @@ var init_config11 = __esm({
105881
106239
  const {
105882
106240
  traceCredentialResolution: traceCredentialResolution2,
105883
106241
  resolveCredentials: resolveCredentials2,
105884
- readClaudeSettingsEnv: readClaudeSettingsEnv2,
106242
+ readClaudeSettingsEnv: readClaudeSettingsEnv3,
105885
106243
  readClaudeCredentialExport: readClaudeCredentialExport2
105886
106244
  } = await Promise.resolve().then(() => (init_credential_resolver(), credential_resolver_exports));
105887
106245
  const fs121 = await import("fs");
@@ -105897,7 +106255,7 @@ var init_config11 = __esm({
105897
106255
  };
105898
106256
  const trace = traceCredentialResolution2({
105899
106257
  config,
105900
- claudeEnv: readClaudeSettingsEnv2(),
106258
+ claudeEnv: readClaudeSettingsEnv3(),
105901
106259
  processEnv: process.env,
105902
106260
  awsFiles: {
105903
106261
  credentials: exists3(path193.join(home, ".aws", "credentials")),
@@ -133332,7 +133690,7 @@ __export(session_requests_exports, {
133332
133690
  settleNotified: () => settleNotified,
133333
133691
  settleReplied: () => settleReplied
133334
133692
  });
133335
- import { createHash as createHash18, randomUUID as randomUUID8 } from "crypto";
133693
+ import { createHash as createHash18, randomUUID as randomUUID9 } from "crypto";
133336
133694
  import path137 from "path";
133337
133695
  function oneLine(text, max = 160) {
133338
133696
  const flat = text.replace(/\s+/g, " ").trim();
@@ -133356,7 +133714,7 @@ function clampReplyTimeoutSecs(secs) {
133356
133714
  }
133357
133715
  async function createSessionRequest(input) {
133358
133716
  const request3 = {
133359
- id: `rq-${randomUUID8().replace(/-/g, "").slice(0, 12)}`,
133717
+ id: `rq-${randomUUID9().replace(/-/g, "").slice(0, 12)}`,
133360
133718
  fromSessionId: input.fromSessionId,
133361
133719
  ...input.toSessionId ? { toSessionId: input.toSessionId } : {},
133362
133720
  ...input.toTaskId ? { toTaskId: input.toTaskId } : {},
@@ -133789,7 +134147,7 @@ __export(task_start_exports, {
133789
134147
  SessionExistsError: () => SessionExistsError,
133790
134148
  startSessionForTask: () => startSessionForTask
133791
134149
  });
133792
- import { randomUUID as randomUUID9 } from "crypto";
134150
+ import { randomUUID as randomUUID10 } from "crypto";
133793
134151
  async function startSessionForTask(params) {
133794
134152
  const { taskIdPrefix, source } = params;
133795
134153
  let task;
@@ -133840,7 +134198,7 @@ async function startSessionForTask(params) {
133840
134198
  }
133841
134199
  let message = params.message?.trim() || `Working on task: ${task.title}`;
133842
134200
  const engine = normalizeEngine(params.engine);
133843
- const preassignedSessionId = engineCaps(engine).idProvisioning === "provider-issued" ? void 0 : randomUUID9();
134201
+ const preassignedSessionId = engineCaps(engine).idProvisioning === "provider-issued" ? void 0 : randomUUID10();
133844
134202
  let requestId;
133845
134203
  if (params.expectReply) {
133846
134204
  const { resolveCaller: resolveCaller2 } = await Promise.resolve().then(() => (init_session_send_core(), session_send_core_exports));
@@ -149338,7 +149696,7 @@ async function generateLedgerDesc(task, timeoutMs = 15e3) {
149338
149696
  try {
149339
149697
  const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
149340
149698
  const config = await getConfig2();
149341
- const providerName = config.agent?.main_provider ?? "bedrock";
149699
+ const providerName = resolveMainProviderName(config);
149342
149700
  const model = cheapModelFor(providerName);
149343
149701
  const detail = (task.description ?? "").trim().slice(0, 800);
149344
149702
  const project = (task.project ?? "").trim();
@@ -149397,6 +149755,7 @@ var init_task_ledger_desc = __esm({
149397
149755
  "use strict";
149398
149756
  init_model();
149399
149757
  init_model_catalog();
149758
+ init_default_provider();
149400
149759
  init_logging();
149401
149760
  init_task_manager();
149402
149761
  MAX_DESC_LEN = 120;
@@ -149420,6 +149779,10 @@ function diffStreamedVsPersisted(streamedTextMsgIds, persistedMsgIds) {
149420
149779
  }
149421
149780
  function armStreamConvergenceCheck(sessionId, streamedTextMsgIds) {
149422
149781
  if (streamedTextMsgIds.length === 0) return;
149782
+ if (consumeWarmupTurn(sessionId)) {
149783
+ log.obs.debug("stream-convergence: skipped hidden warm-up turn", { sessionId });
149784
+ return;
149785
+ }
149423
149786
  const timer3 = setTimeout(() => {
149424
149787
  void runCheck(sessionId, streamedTextMsgIds).catch((err2) => {
149425
149788
  log.obs.warn("stream-convergence check failed", {
@@ -149485,6 +149848,7 @@ var init_stream_convergence = __esm({
149485
149848
  "src/core/observability/stream-convergence.ts"() {
149486
149849
  "use strict";
149487
149850
  init_logging();
149851
+ init_side_thread_warmup();
149488
149852
  CHECK_DELAY_MS = 15e3;
149489
149853
  INCIDENT_DEDUPE_MS = 30 * 6e4;
149490
149854
  lastIncidentAt = /* @__PURE__ */ new Map();
@@ -149735,12 +150099,17 @@ function getSystemHealth() {
149735
150099
  }
149736
150100
  async function refreshSystemHealth() {
149737
150101
  systemHealth.claudeCliAvailable = checkClaudeCliAvailable();
149738
- const cred = await resolveCredentialHealth();
150102
+ applyCredentialHealth(await resolveCredentialHealth());
150103
+ broadcastEvent("system:health", systemHealth);
150104
+ return systemHealth;
150105
+ }
150106
+ function applyCredentialHealth(cred) {
149739
150107
  systemHealth.hasReadyProvider = cred.hasReadyProvider;
149740
150108
  systemHealth.credentialSource = cred.source;
149741
150109
  systemHealth.credentialDetail = cred.detail;
149742
- broadcastEvent("system:health", systemHealth);
149743
- return systemHealth;
150110
+ systemHealth.mainProvider = cred.mainProvider;
150111
+ systemHealth.mainProviderImplicit = cred.mainProviderImplicit;
150112
+ systemHealth.claudeCliAuth = cred.claudeCliAuth;
149744
150113
  }
149745
150114
  function checkClaudeCliAvailable() {
149746
150115
  return isClaudeCliInstalled();
@@ -149750,27 +150119,36 @@ async function resolveCredentialHealth() {
149750
150119
  const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
149751
150120
  const { resolveCredentials: resolveCredentials2 } = await Promise.resolve().then(() => (init_credential_resolver(), credential_resolver_exports));
149752
150121
  const { buildProviderMap: buildProviderMap2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports));
150122
+ const { resolveMainProviderName: resolveMainProviderName2, mainProviderIsImplicit: mainProviderIsImplicit2 } = await Promise.resolve().then(() => (init_default_provider(), default_provider_exports));
150123
+ const { detectClaudeCli: detectClaudeCli2 } = await Promise.resolve().then(() => (init_claude_cli_detect(), claude_cli_detect_exports));
149753
150124
  const config = await getConfig2();
150125
+ const cli = detectClaudeCli2();
150126
+ const mainProvider = resolveMainProviderName2(config, cli.installed);
150127
+ const base = {
150128
+ mainProvider,
150129
+ mainProviderImplicit: mainProviderIsImplicit2(config),
150130
+ claudeCliAuth: cli.installed ? cli.auth.label : void 0
150131
+ };
150132
+ if (mainProvider === "claude_cli") {
150133
+ return cli.installed ? { ...base, hasReadyProvider: true, source: "config", detail: `claude-cli (${cli.auth.label})` } : { ...base, hasReadyProvider: false, source: "none" };
150134
+ }
149754
150135
  const cred = resolveCredentials2(config);
149755
150136
  if (cred.source !== "none") {
149756
- return { hasReadyProvider: true, source: cred.source, detail: cred.detail };
150137
+ return { ...base, hasReadyProvider: true, source: cred.source, detail: cred.detail };
149757
150138
  }
149758
150139
  const providers = buildProviderMap2(config.providers, config);
149759
150140
  for (const [, prov] of Object.entries(providers)) {
149760
150141
  if (prov.api === "bedrock" || prov.api === "ollama") continue;
149761
150142
  if (prov.api === "claude-cli") {
149762
- const { detectClaudeCli: detectClaudeCli2 } = await Promise.resolve().then(() => (init_claude_cli_detect(), claude_cli_detect_exports));
149763
- if (detectClaudeCli2().subscriptionReady) {
149764
- return { hasReadyProvider: true, source: "config", detail: "claude-cli (subscription)" };
149765
- }
150143
+ if (cli.installed) return { ...base, hasReadyProvider: true, source: "config", detail: `claude-cli (${cli.auth.label})` };
149766
150144
  continue;
149767
150145
  }
149768
150146
  const implemented = prov.api === "anthropic-messages" || prov.api === "openai-chat" || prov.api === "google-generative-ai";
149769
150147
  if (implemented && (prov.api_key || prov.bearer_token)) {
149770
- return { hasReadyProvider: true, source: "config", detail: prov.api };
150148
+ return { ...base, hasReadyProvider: true, source: "config", detail: prov.api };
149771
150149
  }
149772
150150
  }
149773
- return { hasReadyProvider: false, source: "none" };
150151
+ return { ...base, hasReadyProvider: false, source: "none" };
149774
150152
  } catch {
149775
150153
  return { hasReadyProvider: false, source: "none" };
149776
150154
  }
@@ -149809,11 +150187,11 @@ async function startServer(options = {}) {
149809
150187
  }
149810
150188
  {
149811
150189
  const cred = await resolveCredentialHealth();
149812
- systemHealth.hasReadyProvider = cred.hasReadyProvider;
149813
- systemHealth.credentialSource = cred.source;
149814
- systemHealth.credentialDetail = cred.detail;
150190
+ applyCredentialHealth(cred);
149815
150191
  if (!systemHealth.hasReadyProvider) {
149816
150192
  log.web.warn("No AI provider configured \u2014 configure one in Settings");
150193
+ } else if (cred.mainProvider === "claude_cli") {
150194
+ log.web.info("AI provider: Claude Code", { auth: cred.claudeCliAuth, implicit: cred.mainProviderImplicit });
149817
150195
  } else if (cred.source && cred.source !== "config") {
149818
150196
  log.web.info("Auto-detected Bedrock credential", { source: cred.source, detail: cred.detail });
149819
150197
  }
@@ -151093,10 +151471,9 @@ async function startServer(options = {}) {
151093
151471
  const prev = systemHealth.hasReadyProvider;
151094
151472
  const prevSource = systemHealth.credentialSource;
151095
151473
  const cred = await resolveCredentialHealth();
151096
- systemHealth.hasReadyProvider = cred.hasReadyProvider;
151097
- systemHealth.credentialSource = cred.source;
151098
- systemHealth.credentialDetail = cred.detail;
151099
- if (cred.hasReadyProvider !== prev || cred.source !== prevSource) {
151474
+ const prevProvider = systemHealth.mainProvider;
151475
+ applyCredentialHealth(cred);
151476
+ if (cred.hasReadyProvider !== prev || cred.source !== prevSource || cred.mainProvider !== prevProvider) {
151100
151477
  broadcastEvent("system:health", systemHealth);
151101
151478
  }
151102
151479
  } catch {
@@ -158523,11 +158900,12 @@ __export(session_controls_exports, {
158523
158900
  applySessionModelChange: () => applySessionModelChange,
158524
158901
  computeModelOptions: () => computeModelOptions,
158525
158902
  controlErrorKind: () => controlErrorKind,
158903
+ createForkSiblingTask: () => createForkSiblingTask,
158526
158904
  forkSessionToTask: () => forkSessionToTask,
158527
158905
  handleSessionControlRelay: () => handleSessionControlRelay,
158528
158906
  modelReadBackMatches: () => modelReadBackMatches
158529
158907
  });
158530
- import { randomUUID as randomUUID10 } from "crypto";
158908
+ import { randomUUID as randomUUID11 } from "crypto";
158531
158909
  function controlErrorKind(status2) {
158532
158910
  if (status2 === 404) return "not_found";
158533
158911
  if (status2 === 405) return "method_not_allowed";
@@ -158700,6 +159078,124 @@ async function computeModelOptions(sessionId) {
158700
159078
  currentEffort: liveEffort ?? record.effectiveEffort ?? record.effort ?? null
158701
159079
  };
158702
159080
  }
159081
+ async function createForkSiblingTask(sourceTaskId, opts) {
159082
+ const {
159083
+ getTask: getTask2,
159084
+ addTask: addTask2,
159085
+ updateTask: updateTask2,
159086
+ groupTasks: groupTasks2,
159087
+ addToGroup: addToGroup2,
159088
+ renameGroup: renameGroup2
159089
+ } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
159090
+ let sourceTask;
159091
+ try {
159092
+ sourceTask = await getTask2(sourceTaskId);
159093
+ } catch {
159094
+ throw new SessionControlError(`Source task "${sourceTaskId}" not found`, 404);
159095
+ }
159096
+ const sourceBaseTitle = sourceTask.title.replace(/^Fork of\s+/i, "").replace(/\s+-\s+fork of\s+.*$/i, "").trim() || sourceTask.title;
159097
+ const explicitTitle = opts.childTitle || (opts.titlePrefix ? `${opts.titlePrefix} - fork of ${sourceBaseTitle}` : void 0);
159098
+ const autoTitle = !explicitTitle;
159099
+ const newTitle = explicitTitle || `Fork of ${sourceBaseTitle}`;
159100
+ let inheritedTier;
159101
+ if (sourceTask.pinned && sourceTask.focus_tier) {
159102
+ const { getCustomTiers: getCustomTiers2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
159103
+ const known = ["focus", "backlog", "wait"].includes(sourceTask.focus_tier) || (await getCustomTiers2()).some((t3) => t3.id === sourceTask.focus_tier);
159104
+ if (known) inheritedTier = sourceTask.focus_tier;
159105
+ else {
159106
+ log.session.warn("fork: source tier is not registered, filing the fork in Satellite", {
159107
+ sourceTaskId: sourceTask.id,
159108
+ tier: sourceTask.focus_tier
159109
+ });
159110
+ }
159111
+ }
159112
+ const { task: newFork } = await addTask2({
159113
+ title: newTitle,
159114
+ project: sourceTask.project || "",
159115
+ source: sourceTask.source,
159116
+ ...opts.description ? { description: opts.description } : {},
159117
+ // A fork is a person splitting off live work, so it joins the board like
159118
+ // any other hand-made task (Satellite = pinned, no stored tier).
159119
+ pinned: true,
159120
+ ...inheritedTier ? { focus_tier: inheritedTier } : {},
159121
+ // Forking an Ask Walnut conversation yields another Ask Walnut
159122
+ // conversation: without the flag the fork loses the amber marker AND the
159123
+ // persona drift repair (both key on task.walnut_agent, never on project).
159124
+ ...sourceTask.walnut_agent ? { walnut_agent: true } : {}
159125
+ });
159126
+ let forkGroupId;
159127
+ try {
159128
+ if (sourceTask.group_id) {
159129
+ const r8 = await addToGroup2(sourceTask.group_id, [newFork.id]);
159130
+ forkGroupId = r8.group_id;
159131
+ } else {
159132
+ const r8 = await groupTasks2([sourceTask.id, newFork.id], sourceTask.title);
159133
+ forkGroupId = r8.group_id;
159134
+ }
159135
+ } catch (err2) {
159136
+ log.session.warn("fork: failed to group source + fork", {
159137
+ sourceTaskId: sourceTask.id,
159138
+ forkTaskId: newFork.id,
159139
+ error: err2 instanceof Error ? err2.message : String(err2)
159140
+ });
159141
+ }
159142
+ let task;
159143
+ try {
159144
+ task = await getTask2(newFork.id);
159145
+ } catch {
159146
+ task = newFork;
159147
+ }
159148
+ bus.emit(EventNames.TASK_CREATED, { task }, ["web-ui", "main-agent"], { source: opts.source });
159149
+ if (autoTitle && opts.prompt?.trim()) {
159150
+ const forkId = newFork.id;
159151
+ const sourceTitle = sourceBaseTitle;
159152
+ const placeholderTitle = newTitle;
159153
+ const prompt = opts.prompt;
159154
+ const titleSource = `${opts.source}-title`;
159155
+ void (async () => {
159156
+ try {
159157
+ const { summarizeForkPrompt: summarizeForkPrompt2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
159158
+ const label = await summarizeForkPrompt2(prompt);
159159
+ if (!label) return;
159160
+ const current5 = await getTask2(forkId);
159161
+ if (current5.title !== placeholderTitle) {
159162
+ log.session.info("fork title refine skipped \u2014 title changed since fork", { taskId: forkId });
159163
+ return;
159164
+ }
159165
+ const refinedTitle = `${label} - fork of ${sourceTitle}`;
159166
+ const { task: updated } = await updateTask2(forkId, { title: refinedTitle }, { source: titleSource });
159167
+ bus.emit(EventNames.TASK_UPDATED, { task: updated }, ["web-ui", "main-agent"], { source: titleSource });
159168
+ log.session.info("fork title refined", { taskId: forkId, title: refinedTitle });
159169
+ } catch (err2) {
159170
+ log.session.warn("fork title refine failed", {
159171
+ taskId: forkId,
159172
+ error: err2 instanceof Error ? err2.message : String(err2)
159173
+ });
159174
+ }
159175
+ })();
159176
+ }
159177
+ if (forkGroupId && !sourceTask.group_id) {
159178
+ const gid = forkGroupId;
159179
+ const seedTitles = [sourceTask.title, newTitle];
159180
+ const groupSource = opts.source;
159181
+ void (async () => {
159182
+ try {
159183
+ const { summarizeGroupLabel: summarizeGroupLabel2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
159184
+ const label = await summarizeGroupLabel2(seedTitles);
159185
+ if (!label) return;
159186
+ await renameGroup2(gid, label);
159187
+ bus.emit(EventNames.TASK_GROUPS_CHANGED, { group_id: gid, label }, ["web-ui", "main-agent"], { source: groupSource });
159188
+ log.session.info("fork group label refined", { groupId: gid, label });
159189
+ } catch (err2) {
159190
+ log.session.warn("fork group label refine failed", {
159191
+ groupId: gid,
159192
+ error: err2 instanceof Error ? err2.message : String(err2)
159193
+ });
159194
+ }
159195
+ })();
159196
+ }
159197
+ return { task, ...forkGroupId ? { groupId: forkGroupId } : {} };
159198
+ }
158703
159199
  async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
158704
159200
  const { task_id, create_child_task, child_title, message, title, model, imageContext } = input;
158705
159201
  if (!task_id && !create_child_task) {
@@ -158709,14 +159205,7 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
158709
159205
  throw new SessionControlError("task_id and create_child_task are mutually exclusive", 400);
158710
159206
  }
158711
159207
  const { getSessionByClaudeId: getSessionByClaudeId2, getSessionsForTask: getSessionsForTask2, createSessionRecord: createSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
158712
- const {
158713
- getTask: getTask2,
158714
- addTask: addTask2,
158715
- updateTask: updateTask2,
158716
- groupTasks: groupTasks2,
158717
- addToGroup: addToGroup2,
158718
- renameGroup: renameGroup2
158719
- } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
159208
+ const { getTask: getTask2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
158720
159209
  const sourceRecord = await getSessionByClaudeId2(sourceSessionId);
158721
159210
  if (!sourceRecord) throw new SessionControlError("Source session not found", 404);
158722
159211
  if (!engineCaps(sourceRecord.engine).fork) {
@@ -158733,108 +159222,13 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
158733
159222
  if (!sourceRecord.taskId) {
158734
159223
  throw new SessionControlError("Source session has no task \u2014 cannot create fork task", 400);
158735
159224
  }
158736
- let sourceTask;
158737
- try {
158738
- sourceTask = await getTask2(sourceRecord.taskId);
158739
- } catch {
158740
- throw new SessionControlError(`Source task "${sourceRecord.taskId}" not found`, 404);
158741
- }
158742
- const sourceBaseTitle = sourceTask.title.replace(/^Fork of\s+/i, "").replace(/\s+-\s+fork of\s+.*$/i, "").trim() || sourceTask.title;
158743
- const autoTitle = !child_title;
158744
- const newTitle = child_title || `Fork of ${sourceBaseTitle}`;
158745
- let inheritedTier;
158746
- if (sourceTask.pinned && sourceTask.focus_tier) {
158747
- const { getCustomTiers: getCustomTiers2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
158748
- const known = ["focus", "backlog", "wait"].includes(sourceTask.focus_tier) || (await getCustomTiers2()).some((t3) => t3.id === sourceTask.focus_tier);
158749
- if (known) inheritedTier = sourceTask.focus_tier;
158750
- else {
158751
- log.session.warn("fork: source tier is not registered, filing the fork in Satellite", {
158752
- sourceTaskId: sourceTask.id,
158753
- tier: sourceTask.focus_tier
158754
- });
158755
- }
158756
- }
158757
- const { task: newFork } = await addTask2({
158758
- title: newTitle,
158759
- project: sourceTask.project || "",
158760
- source: sourceTask.source,
158761
- // A fork is a person splitting off live work, so it joins the board like
158762
- // any other hand-made task (Satellite = pinned, no stored tier).
158763
- pinned: true,
158764
- ...inheritedTier ? { focus_tier: inheritedTier } : {},
158765
- // Forking an Ask Walnut conversation yields another Ask Walnut
158766
- // conversation: without the flag the fork loses the amber marker AND the
158767
- // persona drift repair (both key on task.walnut_agent, never on project).
158768
- ...sourceTask.walnut_agent ? { walnut_agent: true } : {}
158769
- });
158770
- let forkGroupId;
158771
- try {
158772
- if (sourceTask.group_id) {
158773
- const r8 = await addToGroup2(sourceTask.group_id, [newFork.id]);
158774
- forkGroupId = r8.group_id;
158775
- } else {
158776
- const r8 = await groupTasks2([sourceTask.id, newFork.id], sourceTask.title);
158777
- forkGroupId = r8.group_id;
158778
- }
158779
- } catch (err2) {
158780
- log.session.warn("fork: failed to group source + fork", {
158781
- sourceTaskId: sourceTask.id,
158782
- forkTaskId: newFork.id,
158783
- error: err2 instanceof Error ? err2.message : String(err2)
158784
- });
158785
- }
158786
- try {
158787
- task = await getTask2(newFork.id);
158788
- } catch {
158789
- task = newFork;
158790
- }
158791
- bus.emit(EventNames.TASK_CREATED, { task }, ["web-ui", "main-agent"], { source: "fork" });
159225
+ const created = await createForkSiblingTask(sourceRecord.taskId, {
159226
+ childTitle: child_title,
159227
+ prompt: message,
159228
+ source: "fork"
159229
+ });
159230
+ task = created.task;
158792
159231
  childTaskCreated = true;
158793
- if (autoTitle && message?.trim()) {
158794
- const forkId = newFork.id;
158795
- const sourceTitle = sourceBaseTitle;
158796
- const placeholderTitle = newTitle;
158797
- void (async () => {
158798
- try {
158799
- const { summarizeForkPrompt: summarizeForkPrompt2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
158800
- const label = await summarizeForkPrompt2(message);
158801
- if (!label) return;
158802
- const current5 = await getTask2(forkId);
158803
- if (current5.title !== placeholderTitle) {
158804
- log.session.info("fork title refine skipped \u2014 title changed since fork", { taskId: forkId });
158805
- return;
158806
- }
158807
- const refinedTitle = `${label} - fork of ${sourceTitle}`;
158808
- const { task: updated } = await updateTask2(forkId, { title: refinedTitle }, { source: "fork-title" });
158809
- bus.emit(EventNames.TASK_UPDATED, { task: updated }, ["web-ui", "main-agent"], { source: "fork-title" });
158810
- log.session.info("fork title refined", { taskId: forkId, title: refinedTitle });
158811
- } catch (err2) {
158812
- log.session.warn("fork title refine failed", {
158813
- taskId: forkId,
158814
- error: err2 instanceof Error ? err2.message : String(err2)
158815
- });
158816
- }
158817
- })();
158818
- }
158819
- if (forkGroupId && !sourceTask.group_id) {
158820
- const gid = forkGroupId;
158821
- const seedTitles = [sourceTask.title, newTitle];
158822
- void (async () => {
158823
- try {
158824
- const { summarizeGroupLabel: summarizeGroupLabel2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
158825
- const label = await summarizeGroupLabel2(seedTitles);
158826
- if (!label) return;
158827
- await renameGroup2(gid, label);
158828
- bus.emit(EventNames.TASK_GROUPS_CHANGED, { group_id: gid, label }, ["web-ui", "main-agent"], { source: "fork" });
158829
- log.session.info("fork group label refined", { groupId: gid, label });
158830
- } catch (err2) {
158831
- log.session.warn("fork group label refine failed", {
158832
- groupId: gid,
158833
- error: err2 instanceof Error ? err2.message : String(err2)
158834
- });
158835
- }
158836
- })();
158837
- }
158838
159232
  } else {
158839
159233
  try {
158840
159234
  task = await getTask2(task_id);
@@ -158856,7 +159250,7 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
158856
159250
  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";
158857
159251
  const forkMessage = `${FORK_FOCUS_PREFIX}${imageContext ?? ""}New request:
158858
159252
  ${userRequest}`;
158859
- const forkSessionId = randomUUID10();
159253
+ const forkSessionId = randomUUID11();
158860
159254
  const forkTitle = title ?? `Fork of ${sourceRecord.title ?? sourceSessionId.slice(0, 16)}`;
158861
159255
  try {
158862
159256
  await createSessionRecord2(forkSessionId, task.id, task.project ?? "", sourceRecord.cwd, {
@@ -159579,7 +159973,7 @@ __export(session_rewind_exports, {
159579
159973
  probeRewindViaDaemon: () => probeRewindViaDaemon,
159580
159974
  rewindSessionToMessage: () => rewindSessionToMessage
159581
159975
  });
159582
- import { randomUUID as randomUUID11 } from "crypto";
159976
+ import { randomUUID as randomUUID12 } from "crypto";
159583
159977
  function cutAncestorHistoryAtRewindPoint(ancestorMessages, rewoundAtMessageUuid) {
159584
159978
  if (!rewoundAtMessageUuid) {
159585
159979
  return { messages: ancestorMessages, found: false, dropped: 0 };
@@ -159780,7 +160174,7 @@ async function rewindSessionToMessage(sessionId, input, source = "web-api") {
159780
160174
  error: err2 instanceof Error ? err2.message : String(err2)
159781
160175
  });
159782
160176
  }
159783
- const rewoundId = randomUUID11();
160177
+ const rewoundId = randomUUID12();
159784
160178
  const baseTitle = (record.title ?? sessionId.slice(0, 16)).replace(/\s*\(rewound\)$/i, "");
159785
160179
  const title = `${baseTitle} (rewound)`.slice(0, 200);
159786
160180
  const { createSessionRecord: createSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
@@ -160500,6 +160894,7 @@ function parseSessionMessages(content, opts) {
160500
160894
  }
160501
160895
  }
160502
160896
  let lastPlanContent = null;
160897
+ let dropWarmupReply = false;
160503
160898
  const resultParentIds = [];
160504
160899
  const result = [];
160505
160900
  for (const [mapMsgId, msg] of messageMap) {
@@ -160577,10 +160972,18 @@ ${imgPath}` : imgPath;
160577
160972
  let text = textParts.join("\n").trim();
160578
160973
  if (!text && tools2.length === 0 && !thinking) continue;
160579
160974
  if (msg.role === "user" && text.startsWith("<")) {
160975
+ if (isCacheWarmupText(text)) {
160976
+ dropWarmupReply = true;
160977
+ continue;
160978
+ }
160580
160979
  const transformed = transformInjectedUserText(text);
160581
160980
  if (transformed === null) continue;
160582
160981
  if (transformed !== void 0) text = transformed;
160583
160982
  }
160983
+ if (msg.role === "assistant" && dropWarmupReply) {
160984
+ dropWarmupReply = false;
160985
+ if (tools2.length === 0) continue;
160986
+ }
160584
160987
  if (msg.role === "user") text = stripOutputModeWrappers(text);
160585
160988
  if (msg.role === "assistant" && !text && tools2.length === 0) continue;
160586
160989
  result.push({
@@ -161531,6 +161934,7 @@ var init_session_history = __esm({
161531
161934
  init_session_io();
161532
161935
  init_constants();
161533
161936
  init_remote_image_mirror();
161937
+ init_side_thread_warmup();
161534
161938
  init_session_file_reader();
161535
161939
  LOCAL_HOME = os36.homedir();
161536
161940
  TAIL_SEGMENT_ROLL_BYTES = 4 * 1024 * 1024;
@@ -161678,7 +162082,7 @@ var init_context_window = __esm({
161678
162082
  });
161679
162083
 
161680
162084
  // src/providers/turn-ledger.ts
161681
- import { randomUUID as randomUUID12 } from "crypto";
162085
+ import { randomUUID as randomUUID13 } from "crypto";
161682
162086
  function openTurn(sessionId) {
161683
162087
  const existing = openTurns.get(sessionId);
161684
162088
  if (existing && !existing.settled) {
@@ -161688,7 +162092,7 @@ function openTurn(sessionId) {
161688
162092
  });
161689
162093
  return { turnId: existing.turnId, promise: existing.promise };
161690
162094
  }
161691
- const turnId = randomUUID12();
162095
+ const turnId = randomUUID13();
161692
162096
  let resolve;
161693
162097
  let reject;
161694
162098
  const promise = new Promise((res, rej) => {
@@ -168436,11 +168840,39 @@ var init_claude_code_session = __esm({
168436
168840
  session2.pendingDescription = message.slice(0, 500);
168437
168841
  let appendSystemPrompt;
168438
168842
  const isFork = !!data.forkedFromSessionId;
168439
- if (data.appendSystemPrompt && !isFork) {
168440
- appendSystemPrompt = data.appendSystemPrompt;
168441
- log.session.info("using caller-provided system prompt", { taskId: taskId2, promptLength: data.appendSystemPrompt.length });
168442
- }
168443
- {
168843
+ const { parseSideLaneKey: parseSideLaneKey2 } = await Promise.resolve().then(() => (init_side_thread_fork(), side_thread_fork_exports));
168844
+ const sideIds = parseSideLaneKey2(data.lane);
168845
+ if (sideIds) {
168846
+ let exact = data.appendSystemPromptExact;
168847
+ let source = "live-or-record";
168848
+ if (exact === void 0) {
168849
+ try {
168850
+ const { getSessionByClaudeId: getSessionByClaudeId2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
168851
+ const { spawnPrefixFromRecord: spawnPrefixFromRecord2 } = await Promise.resolve().then(() => (init_spawn_prefix(), spawn_prefix_exports));
168852
+ const parentRecord = await getSessionByClaudeId2(sideIds.parentSid);
168853
+ const prefix = parentRecord ? spawnPrefixFromRecord2(parentRecord) : null;
168854
+ exact = prefix?.appendSystemPrompt ?? null;
168855
+ source = prefix?.source ?? "no-parent-record";
168856
+ } catch (err2) {
168857
+ exact = null;
168858
+ source = "lookup-failed";
168859
+ log.session.warn("side thread: parent prompt lookup failed \u2014 spawning without an append prompt", {
168860
+ parentSid: sideIds.parentSid,
168861
+ error: err2 instanceof Error ? err2.message : String(err2)
168862
+ });
168863
+ }
168864
+ }
168865
+ appendSystemPrompt = exact ?? void 0;
168866
+ log.session.info("side thread: prefix prompt resolved", {
168867
+ parentSid: sideIds.parentSid,
168868
+ source,
168869
+ promptLength: appendSystemPrompt?.length ?? 0
168870
+ });
168871
+ } else {
168872
+ if (data.appendSystemPrompt && !isFork) {
168873
+ appendSystemPrompt = data.appendSystemPrompt;
168874
+ log.session.info("using caller-provided system prompt", { taskId: taskId2, promptLength: data.appendSystemPrompt.length });
168875
+ }
168444
168876
  try {
168445
168877
  const { buildSessionContext: buildSessionContext2 } = await Promise.resolve().then(() => (init_session_context(), session_context_exports));
168446
168878
  const ctx = await buildSessionContext2(taskId2 ?? "", cwd, data.host);
@@ -168458,32 +168890,6 @@ ${ctx.systemPrompt}` : ctx.systemPrompt;
168458
168890
  log.session.warn("failed to build session context", { taskId: taskId2, error: err2 instanceof Error ? err2.message : String(err2) });
168459
168891
  }
168460
168892
  }
168461
- {
168462
- const { parseSideLaneKey: parseSideLaneKey2 } = await Promise.resolve().then(() => (init_side_thread_fork(), side_thread_fork_exports));
168463
- const sideIds = parseSideLaneKey2(data.lane);
168464
- if (sideIds) {
168465
- try {
168466
- const { getSessionByClaudeId: getSessionByClaudeId2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
168467
- const parentRecord = await getSessionByClaudeId2(sideIds.parentSid);
168468
- const inherited = parentRecord?.appliedAppendSystemPrompt;
168469
- if (inherited && Buffer.byteLength(inherited, "utf8") <= 65536) {
168470
- appendSystemPrompt = inherited;
168471
- log.session.info("side thread: inherited parent system prompt verbatim", {
168472
- parentSid: sideIds.parentSid,
168473
- promptLength: appendSystemPrompt.length
168474
- });
168475
- } else {
168476
- log.session.info("side thread: parent has no stored system prompt \u2014 fresh build (cache prefix will diverge)", {
168477
- parentSid: sideIds.parentSid
168478
- });
168479
- }
168480
- } catch (err2) {
168481
- log.session.warn("side thread: parent prompt lookup failed \u2014 fresh build", {
168482
- error: err2 instanceof Error ? err2.message : String(err2)
168483
- });
168484
- }
168485
- }
168486
- }
168487
168893
  if (data.preassignedSessionId && appendSystemPrompt && Buffer.byteLength(appendSystemPrompt, "utf8") <= 65536) {
168488
168894
  const promptToStore = appendSystemPrompt;
168489
168895
  const sidToStore = data.preassignedSessionId;
@@ -177482,7 +177888,7 @@ __export(mobile_launch_exports, {
177482
177888
  performMobileLaunch: () => performMobileLaunch,
177483
177889
  validateMobileLaunchBody: () => validateMobileLaunchBody
177484
177890
  });
177485
- import { randomUUID as randomUUID13 } from "crypto";
177891
+ import { randomUUID as randomUUID14 } from "crypto";
177486
177892
  function launchErrorCode(status2) {
177487
177893
  if (status2 === 404) return "not_found";
177488
177894
  if (status2 === 409) return "conflict";
@@ -177604,7 +178010,7 @@ async function performMobileLaunch(input, source) {
177604
178010
  );
177605
178011
  }
177606
178012
  }
177607
- const preassignedSessionId = engineCaps(input.engine).idProvisioning === "provider-issued" ? void 0 : randomUUID13();
178013
+ const preassignedSessionId = engineCaps(input.engine).idProvisioning === "provider-issued" ? void 0 : randomUUID14();
177608
178014
  const task = await quickStartSession({
177609
178015
  message: input.message,
177610
178016
  cwd: input.cwd,
@@ -178031,6 +178437,7 @@ __export(daemon_connection_exports, {
178031
178437
  isDaemonConnected: () => isDaemonConnected,
178032
178438
  isPooledConnection: () => isPooledConnection,
178033
178439
  probeDaemonSession: () => probeDaemonSession,
178440
+ probeDaemonSessionArgs: () => probeDaemonSessionArgs,
178034
178441
  pushDaemonHooksToAllHosts: () => pushDaemonHooksToAllHosts,
178035
178442
  setOnDaemonHostConnected: () => setOnDaemonHostConnected,
178036
178443
  setOnDaemonStatusChange: () => setOnDaemonStatusChange,
@@ -178207,6 +178614,28 @@ async function probeDaemonSession(hostKey2, sessionId) {
178207
178614
  return null;
178208
178615
  }
178209
178616
  }
178617
+ async function probeDaemonSessionArgs(hostKey2, sessionId, deadlineMs = 2e3) {
178618
+ const conn = getConnectedDaemonConnection(hostKey2);
178619
+ if (!conn) return null;
178620
+ try {
178621
+ const result = await Promise.race([
178622
+ conn.send("status", { sid: sessionId, includeArgs: true }),
178623
+ new Promise((_, reject) => setTimeout(
178624
+ () => reject(new Error(`status probe exceeded ${deadlineMs}ms`)),
178625
+ deadlineMs
178626
+ ).unref?.())
178627
+ ]);
178628
+ if (!result.ok || !result.alive || !Array.isArray(result.args)) return null;
178629
+ return result.args.filter((a8) => typeof a8 === "string");
178630
+ } catch (err2) {
178631
+ log.session.debug("probeDaemonSessionArgs: status probe failed", {
178632
+ hostKey: hostKey2,
178633
+ sessionId,
178634
+ error: err2 instanceof Error ? err2.message : String(err2)
178635
+ });
178636
+ return null;
178637
+ }
178638
+ }
178210
178639
  var execFileAsync23, MOBILE_ENQUEUE_LEDGER_MAX, recentMobileEnqueues, DaemonConnection, onPoolStatusChange, onHostConnected, hostConnectedListeners, connectionPool, connectingPromises, failureCache, FAILURE_CACHE_TTL_MS;
178211
178640
  var init_daemon_connection = __esm({
178212
178641
  "src/providers/daemon-connection.ts"() {
@@ -182774,6 +183203,7 @@ async function createSessionRecord(claudeSessionId, taskId2, project, cwd, extra
182774
183203
  ...extra?.effort ? { effort: extra.effort } : {},
182775
183204
  ...extra?.profile ? { profile: extra.profile } : {},
182776
183205
  ...extra?.lane ? { lane: extra.lane } : {},
183206
+ ...extra?.appliedAppendSystemPrompt !== void 0 ? { appliedAppendSystemPrompt: extra.appliedAppendSystemPrompt } : {},
182777
183207
  ...extra?.engine ? { engine: extra.engine } : {},
182778
183208
  ...extra?.acpRuntimeId ? { acpRuntimeId: extra.acpRuntimeId } : {},
182779
183209
  ...extra?.acpJournalPath ? { acpJournalPath: extra.acpJournalPath } : {},
@@ -182928,7 +183358,7 @@ function applyUpdateToSession(session2, updates, logLabel) {
182928
183358
  };
182929
183359
  if (count2 === 1) log.session.info("legacy status write suppressed", payload);
182930
183360
  else log.session.debug("legacy status write suppressed", payload);
182931
- if (updates.process_status === "error") {
183361
+ if (updates.process_status === "error" || updates.process_status === "stopped") {
182932
183362
  noteSuppressedErrorReason(session2.claudeSessionId, {
182933
183363
  reason: typeof reason === "string" ? reason : "unknown",
182934
183364
  message: typeof updates.errorMessage === "string" ? updates.errorMessage : void 0,