open-walnut 0.4.1 → 0.4.3

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 (34) hide show
  1. package/README.md +2 -5
  2. package/dist/cli-fast.js +69 -46
  3. package/dist/cli.js +882 -450
  4. package/dist/daemon-binaries/acp-worker.js.version +1 -1
  5. package/dist/integrations/calendar/index.js +42 -36
  6. package/dist/integrations/jira/index.js +100 -78
  7. package/dist/integrations/ms-todo/index.js +94 -72
  8. package/dist/web/server.js +930 -498
  9. package/dist/web/static/assets/{OfficePreview-C8OMbtiq.js → OfficePreview-DcPiG7nq.js} +2 -2
  10. package/dist/web/static/assets/{docx-preview-hDgtrOpv.js → docx-preview-BWSwr6o8.js} +1 -1
  11. package/dist/web/static/assets/index-1mQq6abn.css +32 -0
  12. package/dist/web/static/assets/{index-CPHXL-wy.js → index-A2G2h66W.js} +1 -1
  13. package/dist/web/static/assets/{index-B2-N8jTH.js → index-BWTJQD8q.js} +1 -1
  14. package/dist/web/static/assets/{index-CBHRNnCk.js → index-BcxyDA3F.js} +1 -1
  15. package/dist/web/static/assets/{index-EowLf_Xf.js → index-CEldo7Tq.js} +1 -1
  16. package/dist/web/static/assets/{index-KY36bfpA.js → index-CYY_N36O.js} +1 -1
  17. package/dist/web/static/assets/{index-ph8Hxff-.js → index-CakB6vm_.js} +1 -1
  18. package/dist/web/static/assets/{index-D81YKipC.js → index-Ch4LQw8-.js} +1 -1
  19. package/dist/web/static/assets/{index-BAb6UVet.js → index-CnmikCRj.js} +1 -1
  20. package/dist/web/static/assets/{index-Caj09uf7.js → index-CtytO7Ce.js} +1 -1
  21. package/dist/web/static/assets/{index-gv-TJwHT.js → index-CvUyfkEn.js} +1 -1
  22. package/dist/web/static/assets/{index-D1uUk0AQ.js → index-Cw6d8sbx.js} +289 -289
  23. package/dist/web/static/assets/{index-CWIydpPr.js → index-DK9AxMTA.js} +1 -1
  24. package/dist/web/static/assets/{index-DtRt21Zi.js → index-DPeOcZuH.js} +1 -1
  25. package/dist/web/static/assets/{index-nRBC-NJN.js → index-DUWox7Cm.js} +1 -1
  26. package/dist/web/static/assets/{index-bfZj1PAc.js → index-Zb9B6znf.js} +1 -1
  27. package/dist/web/static/assets/{index-C5-vT4kT.js → index-a6-uB8ph.js} +1 -1
  28. package/dist/web/static/assets/{index-VcSnwuHG.js → index-aAfs_cWd.js} +1 -1
  29. package/dist/web/static/assets/{index-B35fidy6.js → index-xpwLXfWl.js} +1 -1
  30. package/dist/web/static/assets/{jszip.min-DyrlNEg4.js → jszip.min-BXx8_j4Y.js} +1 -1
  31. package/dist/web/static/assets/{pptx-preview.es-DjND6y6O.js → pptx-preview.es-ByVfeUXp.js} +1 -1
  32. package/dist/web/static/index.html +2 -2
  33. package/package.json +1 -1
  34. package/dist/web/static/assets/index-Btn4AoIy.css +0 -32
@@ -2018,10 +2018,155 @@ var init_model_catalog = __esm({
2018
2018
  }
2019
2019
  });
2020
2020
 
2021
- // src/core/ssh-config-scanner.ts
2022
- import fs3 from "fs/promises";
2023
- import path3 from "path";
2021
+ // src/core/claude-cli-detect.ts
2022
+ var claude_cli_detect_exports = {};
2023
+ __export(claude_cli_detect_exports, {
2024
+ _CLAUDE_CREDENTIALS_HOME: () => _CLAUDE_CREDENTIALS_HOME,
2025
+ describeClaudeCliAuth: () => describeClaudeCliAuth,
2026
+ detectClaudeCli: () => detectClaudeCli,
2027
+ hasClaudeSubscriptionAuth: () => hasClaudeSubscriptionAuth,
2028
+ isClaudeCliInstalled: () => isClaudeCliInstalled,
2029
+ resolveClaudeCliExecutable: () => resolveClaudeCliExecutable
2030
+ });
2031
+ import { execFileSync } from "child_process";
2032
+ import fs3 from "fs";
2024
2033
  import os2 from "os";
2034
+ import path3 from "path";
2035
+ function resolveClaudeCliExecutable(env3 = process.env) {
2036
+ const home = env3.HOME || os2.homedir();
2037
+ const pathDirs = (env3.PATH ?? "").split(path3.delimiter).filter(Boolean);
2038
+ const fallbackDirs = [
2039
+ path3.join(home, ".toolbox", "bin"),
2040
+ path3.join(home, ".local", "bin"),
2041
+ path3.join(home, ".npm-global", "bin"),
2042
+ path3.join(home, ".bun", "bin"),
2043
+ "/usr/local/bin",
2044
+ "/opt/homebrew/bin",
2045
+ "/usr/bin",
2046
+ "/bin"
2047
+ ];
2048
+ for (const dir2 of [.../* @__PURE__ */ new Set([...pathDirs, ...fallbackDirs])]) {
2049
+ const candidate = path3.join(dir2, "claude");
2050
+ try {
2051
+ fs3.accessSync(candidate, fs3.constants.X_OK);
2052
+ return candidate;
2053
+ } catch {
2054
+ }
2055
+ }
2056
+ return null;
2057
+ }
2058
+ function isClaudeCliInstalled() {
2059
+ return resolveClaudeCliExecutable() !== null;
2060
+ }
2061
+ function hasClaudeSubscriptionAuth() {
2062
+ if (process.platform === "darwin") {
2063
+ try {
2064
+ execFileSync("security", ["find-generic-password", "-s", KEYCHAIN_OAUTH_SERVICE], {
2065
+ stdio: "ignore"
2066
+ });
2067
+ return true;
2068
+ } catch {
2069
+ }
2070
+ }
2071
+ return fileCredentialExists();
2072
+ }
2073
+ function fileCredentialExists() {
2074
+ try {
2075
+ return fs3.existsSync(CLAUDE_CREDENTIALS_FILE);
2076
+ } catch {
2077
+ return false;
2078
+ }
2079
+ }
2080
+ function detectClaudeCli() {
2081
+ const installed2 = isClaudeCliInstalled();
2082
+ const subscriptionAuth = installed2 && hasClaudeSubscriptionAuth();
2083
+ return {
2084
+ installed: installed2,
2085
+ subscriptionAuth,
2086
+ subscriptionReady: installed2 && subscriptionAuth,
2087
+ ready: installed2,
2088
+ auth: installed2 ? describeClaudeCliAuth() : { mode: "unknown", label: "not installed" }
2089
+ };
2090
+ }
2091
+ function describeClaudeCliAuth(env3 = process.env, settingsEnv = readClaudeSettingsEnv()) {
2092
+ const merged = { ...env3, ...settingsEnv };
2093
+ const truthy = (v3) => !!v3 && v3 !== "0" && v3.toLowerCase() !== "false";
2094
+ if (truthy(merged.CLAUDE_CODE_USE_BEDROCK)) {
2095
+ const region = merged.AWS_REGION || merged.AWS_DEFAULT_REGION;
2096
+ return { mode: "bedrock", label: region ? `Bedrock (${region})` : "Bedrock" };
2097
+ }
2098
+ if (truthy(merged.CLAUDE_CODE_USE_VERTEX)) return { mode: "vertex", label: "Vertex AI" };
2099
+ if (merged.ANTHROPIC_API_KEY || merged.ANTHROPIC_AUTH_TOKEN) return { mode: "api-key", label: "an Anthropic API key" };
2100
+ if (hasClaudeSubscriptionAuth()) return { mode: "subscription", label: "your Claude subscription" };
2101
+ return { mode: "unknown", label: "its own login" };
2102
+ }
2103
+ function readClaudeSettingsEnv() {
2104
+ try {
2105
+ const raw = fs3.readFileSync(path3.join(os2.homedir(), ".claude", "settings.json"), "utf8");
2106
+ const env3 = JSON.parse(raw).env ?? {};
2107
+ return Object.fromEntries(Object.entries(env3).filter(([, v3]) => typeof v3 === "string"));
2108
+ } catch {
2109
+ return {};
2110
+ }
2111
+ }
2112
+ var KEYCHAIN_OAUTH_SERVICE, _CLAUDE_CREDENTIALS_HOME;
2113
+ var init_claude_cli_detect = __esm({
2114
+ "src/core/claude-cli-detect.ts"() {
2115
+ "use strict";
2116
+ init_constants();
2117
+ KEYCHAIN_OAUTH_SERVICE = "Claude Code-credentials";
2118
+ _CLAUDE_CREDENTIALS_HOME = os2.homedir();
2119
+ }
2120
+ });
2121
+
2122
+ // src/agent/providers/default-provider.ts
2123
+ var default_provider_exports = {};
2124
+ __export(default_provider_exports, {
2125
+ CLAUDE_CLI_PROVIDER: () => CLAUDE_CLI_PROVIDER,
2126
+ LEGACY_DEFAULT_PROVIDER: () => LEGACY_DEFAULT_PROVIDER,
2127
+ _resetDefaultProviderCacheForTesting: () => _resetDefaultProviderCacheForTesting,
2128
+ hasExplicitBedrockConfig: () => hasExplicitBedrockConfig,
2129
+ mainProviderIsImplicit: () => mainProviderIsImplicit,
2130
+ resolveMainProviderName: () => resolveMainProviderName
2131
+ });
2132
+ function claudeCliInstalledCached() {
2133
+ const now = Date.now();
2134
+ if (!cliProbe || now - cliProbe.at > CLI_PROBE_TTL_MS) {
2135
+ cliProbe = { at: now, installed: isClaudeCliInstalled() };
2136
+ }
2137
+ return cliProbe.installed;
2138
+ }
2139
+ function _resetDefaultProviderCacheForTesting() {
2140
+ cliProbe = void 0;
2141
+ }
2142
+ function hasExplicitBedrockConfig(config) {
2143
+ const b8 = config.providers?.bedrock;
2144
+ if (b8 && (b8.bearer_token || b8.aws_access_key_id || b8.aws_profile || b8.aws_credential_export)) return true;
2145
+ return !!config.provider?.bedrock_bearer_token;
2146
+ }
2147
+ function resolveMainProviderName(config, claudeInstalled = claudeCliInstalledCached()) {
2148
+ if (config.agent?.main_provider) return config.agent.main_provider;
2149
+ if (claudeInstalled) return CLAUDE_CLI_PROVIDER;
2150
+ return LEGACY_DEFAULT_PROVIDER;
2151
+ }
2152
+ function mainProviderIsImplicit(config) {
2153
+ return !config.agent?.main_provider;
2154
+ }
2155
+ var LEGACY_DEFAULT_PROVIDER, CLAUDE_CLI_PROVIDER, CLI_PROBE_TTL_MS, cliProbe;
2156
+ var init_default_provider = __esm({
2157
+ "src/agent/providers/default-provider.ts"() {
2158
+ "use strict";
2159
+ init_claude_cli_detect();
2160
+ LEGACY_DEFAULT_PROVIDER = "bedrock";
2161
+ CLAUDE_CLI_PROVIDER = "claude_cli";
2162
+ CLI_PROBE_TTL_MS = 6e4;
2163
+ }
2164
+ });
2165
+
2166
+ // src/core/ssh-config-scanner.ts
2167
+ import fs4 from "fs/promises";
2168
+ import path4 from "path";
2169
+ import os3 from "os";
2025
2170
  function isGitServerHost(h8) {
2026
2171
  if (h8.user === "git") return true;
2027
2172
  const gitPrefix = /^(git|github|gitlab|bitbucket|codecommit|ssh\.github)\./i;
@@ -2037,10 +2182,10 @@ function shortLabelForFqdn(hostname) {
2037
2182
  return `${first.slice(0, 12)}\u2026${first.slice(-8)}`;
2038
2183
  }
2039
2184
  async function scanSshConfig(configPath) {
2040
- const sshConfigPath = configPath ?? path3.join(os2.homedir(), ".ssh", "config");
2185
+ const sshConfigPath = configPath ?? path4.join(os3.homedir(), ".ssh", "config");
2041
2186
  let mtimeMs2;
2042
2187
  try {
2043
- mtimeMs2 = (await fs3.stat(sshConfigPath)).mtimeMs;
2188
+ mtimeMs2 = (await fs4.stat(sshConfigPath)).mtimeMs;
2044
2189
  } catch {
2045
2190
  return /* @__PURE__ */ new Map();
2046
2191
  }
@@ -2053,7 +2198,7 @@ async function scanSshConfig(configPath) {
2053
2198
  async function parseSshConfigFile(sshConfigPath) {
2054
2199
  const discovered = /* @__PURE__ */ new Map();
2055
2200
  try {
2056
- const content = await fs3.readFile(sshConfigPath, "utf-8");
2201
+ const content = await fs4.readFile(sshConfigPath, "utf-8");
2057
2202
  const lines = content.split("\n");
2058
2203
  let currentHost = null;
2059
2204
  for (const rawLine of lines) {
@@ -2154,23 +2299,24 @@ __export(config_manager_exports, {
2154
2299
  updatePushTokens: () => updatePushTokens,
2155
2300
  writeFileAtomic: () => writeFileAtomic
2156
2301
  });
2157
- import fs4 from "fs/promises";
2302
+ import fs5 from "fs/promises";
2158
2303
  import yaml from "js-yaml";
2159
- function resolveAgentEngineProvider(config) {
2304
+ function resolveAgentEngineProvider(config, claudeInstalled) {
2160
2305
  if (CLOUD_MODE) return "walnut-agent";
2161
2306
  const raw = config.agent?.provider;
2162
2307
  if (typeof raw === "string" && VALID_AGENT_ENGINE_PROVIDERS.has(raw)) {
2163
2308
  return raw;
2164
2309
  }
2165
- if (raw !== void 0) {
2166
- log.session.warn("config-manager: agent.provider is not a known engine \u2014 using the default", {
2167
- value: typeof raw === "string" ? raw : `<${typeof raw}>`,
2168
- using: FALLBACK_AGENT_ENGINE_PROVIDER,
2169
- valid: [...VALID_AGENT_ENGINE_PROVIDERS].join(", ")
2170
- });
2171
- return FALLBACK_AGENT_ENGINE_PROVIDER;
2310
+ if (raw === void 0) {
2311
+ const main = claudeInstalled === void 0 ? resolveMainProviderName(config) : resolveMainProviderName(config, claudeInstalled);
2312
+ return main === CLAUDE_CLI_PROVIDER ? "claude-code" : "walnut-agent";
2172
2313
  }
2173
- return DEFAULT_AGENT_ENGINE_PROVIDER;
2314
+ log.session.warn("config-manager: agent.provider is not a known engine \u2014 using the fallback", {
2315
+ value: typeof raw === "string" ? raw : `<${typeof raw}>`,
2316
+ using: FALLBACK_AGENT_ENGINE_PROVIDER,
2317
+ valid: [...VALID_AGENT_ENGINE_PROVIDERS].join(", ")
2318
+ });
2319
+ return FALLBACK_AGENT_ENGINE_PROVIDER;
2174
2320
  }
2175
2321
  function migrateConfigToProjectOnly(config) {
2176
2322
  let changed = false;
@@ -2243,13 +2389,13 @@ function migrateConfigToProjectOnly(config) {
2243
2389
  }
2244
2390
  async function readRawConfigContent() {
2245
2391
  try {
2246
- const primary = await fs4.readFile(CONFIG_FILE, "utf-8");
2392
+ const primary = await fs5.readFile(CONFIG_FILE, "utf-8");
2247
2393
  if (primary.trim()) return primary;
2248
2394
  throw new Error("config.yaml is empty");
2249
2395
  } catch (primaryErr) {
2250
2396
  let backup;
2251
2397
  try {
2252
- backup = await fs4.readFile(CONFIG_BACKUP_FILE, "utf-8");
2398
+ backup = await fs5.readFile(CONFIG_BACKUP_FILE, "utf-8");
2253
2399
  } catch {
2254
2400
  return null;
2255
2401
  }
@@ -2259,7 +2405,7 @@ async function readRawConfigContent() {
2259
2405
  backup: CONFIG_BACKUP_FILE
2260
2406
  });
2261
2407
  try {
2262
- await fs4.writeFile(CONFIG_FILE, backup, "utf-8");
2408
+ await fs5.writeFile(CONFIG_FILE, backup, "utf-8");
2263
2409
  } catch {
2264
2410
  }
2265
2411
  return backup;
@@ -2268,10 +2414,10 @@ async function readRawConfigContent() {
2268
2414
  async function writeFileAtomic(filePath, content) {
2269
2415
  const tmp = `${filePath}.tmp-${process.pid}-${Date.now().toString(36)}`;
2270
2416
  try {
2271
- await fs4.writeFile(tmp, content, "utf-8");
2272
- await fs4.rename(tmp, filePath);
2417
+ await fs5.writeFile(tmp, content, "utf-8");
2418
+ await fs5.rename(tmp, filePath);
2273
2419
  } catch (err2) {
2274
- await fs4.rm(tmp, { force: true }).catch(() => {
2420
+ await fs5.rm(tmp, { force: true }).catch(() => {
2275
2421
  });
2276
2422
  throw err2;
2277
2423
  }
@@ -2469,7 +2615,7 @@ async function seedConfigDefaults() {
2469
2615
  const config = await getConfig();
2470
2616
  let needsWrite = false;
2471
2617
  try {
2472
- const raw = await fs4.readFile(CONFIG_FILE, "utf-8");
2618
+ const raw = await fs5.readFile(CONFIG_FILE, "utf-8");
2473
2619
  if (!raw.includes("available_models")) {
2474
2620
  needsWrite = true;
2475
2621
  }
@@ -2497,6 +2643,7 @@ var init_config_manager = __esm({
2497
2643
  init_constants();
2498
2644
  init_types();
2499
2645
  init_model_catalog();
2646
+ init_default_provider();
2500
2647
  init_ssh_config_scanner();
2501
2648
  DEFAULT_CONFIG = {
2502
2649
  version: 1,
@@ -2508,12 +2655,12 @@ var init_config_manager = __esm({
2508
2655
  // so this never re-routes an established setup.
2509
2656
  defaults: { priority: "none", platform: "local" },
2510
2657
  provider: { type: "claude-code" },
2511
- // Which ENGINE answers a Personal AI chat turn. 'walnut-agent' = today's in-process
2512
- // agent loop; 'claude-code' routes the turn into a lane-bound `claude` session.
2513
- // NOTE: getConfig() spreads the parsed file OVER these defaults at the TOP
2514
- // level, so a config.yaml with any `agent:` section replaces this whole object
2515
- // — resolveAgentEngineProvider() (below) is what actually applies the default.
2516
- agent: { provider: DEFAULT_AGENT_ENGINE_PROVIDER }
2658
+ // Which ENGINE answers a Personal AI chat turn is NOT defaulted here: it follows
2659
+ // the AI provider (Settings → AI Provider) — see resolveAgentEngineProvider().
2660
+ // getConfig() spreads the parsed file OVER these defaults at the TOP level, so
2661
+ // anything put in this object is dropped by any config.yaml with an `agent:`
2662
+ // section; the resolver is what actually applies the rule.
2663
+ agent: {}
2517
2664
  };
2518
2665
  CONFIG_BACKUP_FILE = `${CONFIG_FILE}.bak`;
2519
2666
  writeLock = Promise.resolve();
@@ -2521,14 +2668,14 @@ var init_config_manager = __esm({
2521
2668
  });
2522
2669
 
2523
2670
  // src/utils/file-lock.ts
2524
- import fs5 from "fs";
2671
+ import fs6 from "fs";
2525
2672
  import fsp2 from "fs/promises";
2526
- import path4 from "path";
2673
+ import path5 from "path";
2527
2674
  function lockDir(filePath) {
2528
2675
  return filePath + ".lock";
2529
2676
  }
2530
2677
  function pidFile(lock) {
2531
- return path4.join(lock, "pid");
2678
+ return path5.join(lock, "pid");
2532
2679
  }
2533
2680
  function isPidAlive(pid) {
2534
2681
  try {
@@ -2541,7 +2688,7 @@ function isPidAlive(pid) {
2541
2688
  async function withFileLock(filePath, fn) {
2542
2689
  const lock = lockDir(filePath);
2543
2690
  const deadline4 = Date.now() + TIMEOUT_MS;
2544
- await fsp2.mkdir(path4.dirname(lock), { recursive: true });
2691
+ await fsp2.mkdir(path5.dirname(lock), { recursive: true });
2545
2692
  while (true) {
2546
2693
  try {
2547
2694
  await fsp2.mkdir(lock);
@@ -2616,10 +2763,10 @@ __export(device_auth_exports, {
2616
2763
  verifyDeviceToken: () => verifyDeviceToken
2617
2764
  });
2618
2765
  import crypto2 from "crypto";
2619
- import fs6 from "fs/promises";
2620
- import path5 from "path";
2766
+ import fs7 from "fs/promises";
2767
+ import path6 from "path";
2621
2768
  function authFilePath() {
2622
- return path5.join(WALNUT_HOME, "auth.json");
2769
+ return path6.join(WALNUT_HOME, "auth.json");
2623
2770
  }
2624
2771
  function sha256Hex(input) {
2625
2772
  return crypto2.createHash("sha256").update(input, "utf-8").digest("hex");
@@ -2634,7 +2781,7 @@ async function loadAuth() {
2634
2781
  const file = authFilePath();
2635
2782
  let raw;
2636
2783
  try {
2637
- raw = await fs6.readFile(file, "utf-8");
2784
+ raw = await fs7.readFile(file, "utf-8");
2638
2785
  } catch {
2639
2786
  const recovered = await readAuthBackup();
2640
2787
  if (recovered) {
@@ -2667,7 +2814,7 @@ function authBackupPath() {
2667
2814
  }
2668
2815
  async function readAuthBackup() {
2669
2816
  try {
2670
- const raw = await fs6.readFile(authBackupPath(), "utf-8");
2817
+ const raw = await fs7.readFile(authBackupPath(), "utf-8");
2671
2818
  const parsed = JSON.parse(raw);
2672
2819
  if (!Array.isArray(parsed.devices)) return null;
2673
2820
  const devices = parsed.devices.filter((d8) => d8 && typeof d8.name === "string" && typeof d8.tokenHash === "string");
@@ -2678,14 +2825,14 @@ async function readAuthBackup() {
2678
2825
  }
2679
2826
  async function saveAuth(auth) {
2680
2827
  const file = authFilePath();
2681
- await fs6.mkdir(path5.dirname(file), { recursive: true });
2828
+ await fs7.mkdir(path6.dirname(file), { recursive: true });
2682
2829
  const tmp = `${file}.tmp-${process.pid}`;
2683
- await fs6.writeFile(tmp, JSON.stringify(auth, null, 2) + "\n", { mode: 384 });
2684
- await fs6.rename(tmp, file);
2830
+ await fs7.writeFile(tmp, JSON.stringify(auth, null, 2) + "\n", { mode: 384 });
2831
+ await fs7.rename(tmp, file);
2685
2832
  try {
2686
2833
  const btmp = `${authBackupPath()}.tmp-${process.pid}`;
2687
- await fs6.writeFile(btmp, JSON.stringify(auth, null, 2) + "\n", { mode: 384 });
2688
- await fs6.rename(btmp, authBackupPath());
2834
+ await fs7.writeFile(btmp, JSON.stringify(auth, null, 2) + "\n", { mode: 384 });
2835
+ await fs7.rename(btmp, authBackupPath());
2689
2836
  } catch (err2) {
2690
2837
  log.web.warn("failed to update auth.json.bak", { error: err2 instanceof Error ? err2.message : String(err2) });
2691
2838
  }
@@ -2813,7 +2960,7 @@ async function readProvisionedSetupToken() {
2813
2960
  if (file) {
2814
2961
  source = "file";
2815
2962
  try {
2816
- candidate = await fs6.readFile(file, "utf-8");
2963
+ candidate = await fs7.readFile(file, "utf-8");
2817
2964
  } catch (err2) {
2818
2965
  const code = err2.code;
2819
2966
  if (code !== "ENOENT") {
@@ -2904,7 +3051,7 @@ async function claimInstance(candidateToken, deviceName) {
2904
3051
  const tokenFile = process.env.WALNUT_SETUP_TOKEN_FILE;
2905
3052
  if (tokenFile) {
2906
3053
  try {
2907
- await fs6.unlink(tokenFile);
3054
+ await fs7.unlink(tokenFile);
2908
3055
  } catch {
2909
3056
  }
2910
3057
  }
@@ -4893,8 +5040,8 @@ var init_format = __esm({
4893
5040
 
4894
5041
  // src/utils/json-conflict-recovery.ts
4895
5042
  import fsp3 from "fs/promises";
4896
- import path6 from "path";
4897
- import os3 from "os";
5043
+ import path7 from "path";
5044
+ import os4 from "os";
4898
5045
  import crypto5 from "crypto";
4899
5046
  import { execFile } from "child_process";
4900
5047
  import { promisify } from "util";
@@ -4974,12 +5121,12 @@ function jsonCandidatesFromPorcelain(lines) {
4974
5121
  return [...out];
4975
5122
  }
4976
5123
  function resolveWalnutDataDir() {
4977
- return process.env.OPEN_WALNUT_HOME || path6.join(os3.homedir(), ".open-walnut");
5124
+ return process.env.OPEN_WALNUT_HOME || path7.join(os4.homedir(), ".open-walnut");
4978
5125
  }
4979
5126
  function isInsideWalnutDataDir(filePath) {
4980
- const dataDir2 = path6.resolve(resolveWalnutDataDir());
4981
- const abs = path6.resolve(filePath);
4982
- return abs === dataDir2 || abs.startsWith(dataDir2 + path6.sep);
5127
+ const dataDir2 = path7.resolve(resolveWalnutDataDir());
5128
+ const abs = path7.resolve(filePath);
5129
+ return abs === dataDir2 || abs.startsWith(dataDir2 + path7.sep);
4983
5130
  }
4984
5131
  async function gitRead(repoDir, args) {
4985
5132
  try {
@@ -5014,17 +5161,17 @@ async function findLastValidJsonVersion(repoDir, relPath, depth = HISTORY_DEPTH)
5014
5161
  return null;
5015
5162
  }
5016
5163
  async function findRepoRootUpward(startDir, boundaryDir) {
5017
- const boundary = path6.resolve(boundaryDir);
5018
- let dir2 = path6.resolve(startDir);
5019
- if (dir2 !== boundary && !dir2.startsWith(boundary + path6.sep)) return null;
5164
+ const boundary = path7.resolve(boundaryDir);
5165
+ let dir2 = path7.resolve(startDir);
5166
+ if (dir2 !== boundary && !dir2.startsWith(boundary + path7.sep)) return null;
5020
5167
  for (; ; ) {
5021
5168
  try {
5022
- await fsp3.stat(path6.join(dir2, ".git"));
5169
+ await fsp3.stat(path7.join(dir2, ".git"));
5023
5170
  return dir2;
5024
5171
  } catch {
5025
5172
  }
5026
5173
  if (dir2 === boundary) return null;
5027
- const parent = path6.dirname(dir2);
5174
+ const parent = path7.dirname(dir2);
5028
5175
  if (parent === dir2) return null;
5029
5176
  dir2 = parent;
5030
5177
  }
@@ -5055,8 +5202,8 @@ function sidecarPath(filePath, label) {
5055
5202
  return `${filePath}.${label}-${stamp}`;
5056
5203
  }
5057
5204
  async function writeFileAtomic2(filePath, content) {
5058
- const dir2 = path6.dirname(filePath);
5059
- const tmp = path6.join(dir2, `.open-walnut-${crypto5.randomBytes(8).toString("hex")}.tmp`);
5205
+ const dir2 = path7.dirname(filePath);
5206
+ const tmp = path7.join(dir2, `.open-walnut-${crypto5.randomBytes(8).toString("hex")}.tmp`);
5060
5207
  try {
5061
5208
  await fsp3.writeFile(tmp, content, "utf-8");
5062
5209
  await fsp3.rename(tmp, filePath);
@@ -5067,7 +5214,7 @@ async function writeFileAtomic2(filePath, content) {
5067
5214
  }
5068
5215
  }
5069
5216
  async function healJsonFileFromHistory(opts) {
5070
- const relPath = (opts.relPath ?? path6.relative(opts.repoDir, opts.filePath)).split(path6.sep).join("/");
5217
+ const relPath = (opts.relPath ?? path7.relative(opts.repoDir, opts.filePath)).split(path7.sep).join("/");
5071
5218
  if (relPath.startsWith("..")) return { action: "skipped", error: "file is outside repoDir" };
5072
5219
  const valid = await findLastValidJsonVersion(opts.repoDir, relPath, opts.depth);
5073
5220
  const parked = sidecarPath(opts.filePath, opts.label);
@@ -5094,7 +5241,7 @@ async function healJsonFileFromHistory(opts) {
5094
5241
  return { action: "restored", restoredFrom: valid.rev, movedTo, content: valid.content };
5095
5242
  }
5096
5243
  async function selfHealDataDirJson(filePath) {
5097
- const abs = path6.resolve(filePath);
5244
+ const abs = path7.resolve(filePath);
5098
5245
  if (!isInsideWalnutDataDir(abs)) return null;
5099
5246
  const failedAt = selfHealFailedAt.get(abs);
5100
5247
  if (failedAt !== void 0 && Date.now() - failedAt < SELF_HEAL_FAILURE_MEMO_MS) return null;
@@ -5102,7 +5249,7 @@ async function selfHealDataDirJson(filePath) {
5102
5249
  if (existing) return existing;
5103
5250
  const work = (async () => {
5104
5251
  const dataDir2 = resolveWalnutDataDir();
5105
- const repoRoot = await findRepoRootUpward(path6.dirname(abs), dataDir2);
5252
+ const repoRoot = await findRepoRootUpward(path7.dirname(abs), dataDir2);
5106
5253
  if (!repoRoot) return null;
5107
5254
  const result = await healJsonFileFromHistory({
5108
5255
  repoDir: repoRoot,
@@ -5145,25 +5292,25 @@ var init_json_conflict_recovery = __esm({
5145
5292
  });
5146
5293
 
5147
5294
  // src/utils/fs.ts
5148
- import fs7 from "fs/promises";
5149
- import path7 from "path";
5295
+ import fs8 from "fs/promises";
5296
+ import path8 from "path";
5150
5297
  import crypto6 from "crypto";
5151
5298
  async function writeJsonFile(filePath, data, options = {}) {
5152
- const dir2 = path7.dirname(filePath);
5153
- await fs7.mkdir(dir2, { recursive: true });
5154
- const tmpFile = path7.join(
5299
+ const dir2 = path8.dirname(filePath);
5300
+ await fs8.mkdir(dir2, { recursive: true });
5301
+ const tmpFile = path8.join(
5155
5302
  dir2,
5156
5303
  `.open-walnut-${crypto6.randomBytes(8).toString("hex")}.tmp`
5157
5304
  );
5158
5305
  try {
5159
- await fs7.writeFile(tmpFile, JSON.stringify(data, null, 2) + "\n", {
5306
+ await fs8.writeFile(tmpFile, JSON.stringify(data, null, 2) + "\n", {
5160
5307
  encoding: "utf-8",
5161
5308
  ...options.mode === void 0 ? {} : { mode: options.mode }
5162
5309
  });
5163
- await fs7.rename(tmpFile, filePath);
5164
- if (options.mode !== void 0) await fs7.chmod(filePath, options.mode);
5310
+ await fs8.rename(tmpFile, filePath);
5311
+ if (options.mode !== void 0) await fs8.chmod(filePath, options.mode);
5165
5312
  } catch (err2) {
5166
- await fs7.rm(tmpFile, { force: true }).catch(() => {
5313
+ await fs8.rm(tmpFile, { force: true }).catch(() => {
5167
5314
  });
5168
5315
  throw err2;
5169
5316
  }
@@ -5171,7 +5318,7 @@ async function writeJsonFile(filePath, data, options = {}) {
5171
5318
  async function readJsonFile(filePath, fallback) {
5172
5319
  let content;
5173
5320
  try {
5174
- content = await fs7.readFile(filePath, "utf-8");
5321
+ content = await fs8.readFile(filePath, "utf-8");
5175
5322
  } catch (err2) {
5176
5323
  if (err2 && typeof err2 === "object" && "code" in err2 && err2.code === "ENOENT") {
5177
5324
  return fallback;
@@ -5213,7 +5360,7 @@ async function updateJsonFile(filePath, fallback, mutate2, options = {}) {
5213
5360
  });
5214
5361
  }
5215
5362
  async function ensureDir(dirPath) {
5216
- await fs7.mkdir(dirPath, { recursive: true });
5363
+ await fs8.mkdir(dirPath, { recursive: true });
5217
5364
  }
5218
5365
  var init_fs = __esm({
5219
5366
  "src/utils/fs.ts"() {
@@ -5238,7 +5385,7 @@ __export(file_ops_exports, {
5238
5385
  });
5239
5386
  import crypto7 from "crypto";
5240
5387
  import fsp4 from "fs/promises";
5241
- import path8 from "path";
5388
+ import path9 from "path";
5242
5389
  function computeContentHash(content) {
5243
5390
  return crypto7.createHash("sha256").update(content).digest("hex").slice(0, 12);
5244
5391
  }
@@ -5314,7 +5461,7 @@ async function editFileContent(filePath, oldContent, newContent, opts) {
5314
5461
  });
5315
5462
  }
5316
5463
  async function writeFileChecked(filePath, content, opts) {
5317
- await fsp4.mkdir(path8.dirname(filePath), { recursive: true });
5464
+ await fsp4.mkdir(path9.dirname(filePath), { recursive: true });
5318
5465
  return withFileLock(filePath, async () => {
5319
5466
  if (opts?.expectedHash != null) {
5320
5467
  let raw;
@@ -5386,16 +5533,16 @@ __export(memory_file_exports, {
5386
5533
  getMemoryFile: () => getMemoryFile,
5387
5534
  updateMemoryFile: () => updateMemoryFile
5388
5535
  });
5389
- import fs8 from "fs";
5390
- import path9 from "path";
5536
+ import fs9 from "fs";
5537
+ import path10 from "path";
5391
5538
  function resolveMemoryPath(agentId) {
5392
5539
  if (!agentId || agentId === "general") return MEMORY_FILE;
5393
- return path9.join(agentMemoryDir(agentId), "MEMORY.md");
5540
+ return path10.join(agentMemoryDir(agentId), "MEMORY.md");
5394
5541
  }
5395
5542
  function getMemoryFile(agentId) {
5396
5543
  const filePath = resolveMemoryPath(agentId);
5397
5544
  try {
5398
- const content = fs8.readFileSync(filePath, "utf-8");
5545
+ const content = fs9.readFileSync(filePath, "utf-8");
5399
5546
  return { content, contentHash: computeContentHash(content) };
5400
5547
  } catch (err2) {
5401
5548
  log.memory.debug("memory-file: MEMORY.md not found or unreadable", {
@@ -5406,7 +5553,7 @@ function getMemoryFile(agentId) {
5406
5553
  }
5407
5554
  async function updateMemoryFile(content, expectedHash, agentId) {
5408
5555
  const filePath = resolveMemoryPath(agentId);
5409
- fs8.mkdirSync(path9.dirname(filePath), { recursive: true });
5556
+ fs9.mkdirSync(path10.dirname(filePath), { recursive: true });
5410
5557
  const result = await writeFileChecked(filePath, content, {
5411
5558
  expectedHash
5412
5559
  });
@@ -5421,9 +5568,9 @@ async function editMemoryFile(oldContent, newContent, expectedHash, replaceAll,
5421
5568
  }
5422
5569
  function ensureMemoryFile(agentId) {
5423
5570
  const filePath = resolveMemoryPath(agentId);
5424
- if (fs8.existsSync(filePath)) return;
5425
- fs8.mkdirSync(path9.dirname(filePath), { recursive: true });
5426
- fs8.writeFileSync(filePath, DEFAULT_TEMPLATE, "utf-8");
5571
+ if (fs9.existsSync(filePath)) return;
5572
+ fs9.mkdirSync(path10.dirname(filePath), { recursive: true });
5573
+ fs9.writeFileSync(filePath, DEFAULT_TEMPLATE, "utf-8");
5427
5574
  }
5428
5575
  var DEFAULT_TEMPLATE;
5429
5576
  var init_memory_file = __esm({
@@ -5443,15 +5590,15 @@ description: >
5443
5590
 
5444
5591
  // src/core/tmp-sweep.ts
5445
5592
  import fsp5 from "fs/promises";
5446
- import path10 from "path";
5593
+ import path11 from "path";
5447
5594
  function sweepDirs() {
5448
5595
  return [
5449
5596
  WALNUT_HOME,
5450
5597
  SYNC_DIR,
5451
- path10.join(WALNUT_HOME, "sessions"),
5452
- path10.join(WALNUT_HOME, "sessions", "transcripts"),
5598
+ path11.join(WALNUT_HOME, "sessions"),
5599
+ path11.join(WALNUT_HOME, "sessions", "transcripts"),
5453
5600
  TASKS_DIR,
5454
- path10.join(TASKS_DIR, "outbox"),
5601
+ path11.join(TASKS_DIR, "outbox"),
5455
5602
  CONVERSATIONS_DIR,
5456
5603
  MEMORY_DIR,
5457
5604
  DAILY_DIR,
@@ -5472,7 +5619,7 @@ async function sweepOrphanAtomicTmpFiles() {
5472
5619
  const entries = await fsp5.readdir(dir2, { withFileTypes: true });
5473
5620
  for (const entry of entries) {
5474
5621
  if (!entry.isFile() || !ORPHAN_RE.test(entry.name)) continue;
5475
- const full = path10.join(dir2, entry.name);
5622
+ const full = path11.join(dir2, entry.name);
5476
5623
  try {
5477
5624
  const st = await fsp5.stat(full);
5478
5625
  if (st.mtimeMs > cutoff) continue;
@@ -5509,10 +5656,10 @@ __export(init_exports, {
5509
5656
  initDirectories: () => initDirectories
5510
5657
  });
5511
5658
  import fsp6 from "fs/promises";
5512
- import fs9 from "fs";
5659
+ import fs10 from "fs";
5513
5660
  async function migrateLegacyMemoryFile() {
5514
5661
  try {
5515
- if (fs9.existsSync(LEGACY_MEMORY_FILE) && !fs9.existsSync(MEMORY_FILE)) {
5662
+ if (fs10.existsSync(LEGACY_MEMORY_FILE) && !fs10.existsSync(MEMORY_FILE)) {
5516
5663
  await fsp6.rename(LEGACY_MEMORY_FILE, MEMORY_FILE);
5517
5664
  log.memory.info("Migrated MEMORY.md into memory/ folder", { from: LEGACY_MEMORY_FILE, to: MEMORY_FILE });
5518
5665
  }
@@ -6057,15 +6204,15 @@ __export(task_db_exports, {
6057
6204
  transaction: () => transaction
6058
6205
  });
6059
6206
  import Database from "better-sqlite3";
6060
- import fs10 from "fs";
6061
- import path11 from "path";
6207
+ import fs11 from "fs";
6208
+ import path12 from "path";
6062
6209
  import yaml2 from "js-yaml";
6063
6210
  function getDb() {
6064
6211
  if (db) return db;
6065
6212
  if (initAttempted) throw initError;
6066
6213
  initAttempted = true;
6067
6214
  try {
6068
- fs10.mkdirSync(path11.dirname(TASK_DB_PATH), { recursive: true });
6215
+ fs11.mkdirSync(path12.dirname(TASK_DB_PATH), { recursive: true });
6069
6216
  const handle2 = new Database(TASK_DB_PATH);
6070
6217
  handle2.pragma("journal_mode = WAL");
6071
6218
  handle2.pragma("busy_timeout = 5000");
@@ -6295,8 +6442,8 @@ function migrateToProjectOnly(handle2) {
6295
6442
  }
6296
6443
  try {
6297
6444
  handle2.pragma("wal_checkpoint(TRUNCATE)");
6298
- if (!fs10.existsSync(TASK_DB_PRE_V5_BACKUP_PATH)) {
6299
- fs10.copyFileSync(TASK_DB_PATH, TASK_DB_PRE_V5_BACKUP_PATH);
6445
+ if (!fs11.existsSync(TASK_DB_PRE_V5_BACKUP_PATH)) {
6446
+ fs11.copyFileSync(TASK_DB_PATH, TASK_DB_PRE_V5_BACKUP_PATH);
6300
6447
  log.task.info("task-db v5: wrote pre-migration backup", { path: TASK_DB_PRE_V5_BACKUP_PATH });
6301
6448
  }
6302
6449
  } catch (err2) {
@@ -6806,7 +6953,7 @@ var init_task_db = __esm({
6806
6953
  init_constants();
6807
6954
  init_logging();
6808
6955
  init_phase2();
6809
- TASK_DB_PATH = path11.join(TASKS_DIR, "tasks.sqlite");
6956
+ TASK_DB_PATH = path12.join(TASKS_DIR, "tasks.sqlite");
6810
6957
  EXPLICIT_TASK_COLUMNS = [
6811
6958
  "id",
6812
6959
  "title",
@@ -6969,8 +7116,8 @@ var init_task_db = __esm({
6969
7116
  });
6970
7117
 
6971
7118
  // src/core/task-db-migration.ts
6972
- import fs11 from "fs";
6973
- import path12 from "path";
7119
+ import fs12 from "fs";
7120
+ import path13 from "path";
6974
7121
  import yaml3 from "js-yaml";
6975
7122
  async function runMigrationIfNeeded() {
6976
7123
  const db5 = getDb();
@@ -6981,7 +7128,7 @@ async function runMigrationIfNeeded() {
6981
7128
  if (existing.n > 0) {
6982
7129
  return { migrated: false, count: existing.n };
6983
7130
  }
6984
- if (!fs11.existsSync(TASKS_FILE)) {
7131
+ if (!fs12.existsSync(TASKS_FILE)) {
6985
7132
  return { migrated: false, count: 0 };
6986
7133
  }
6987
7134
  const store3 = await readJsonFile(TASKS_FILE, { version: 1, tasks: [] });
@@ -7138,9 +7285,9 @@ async function runMigrationIfNeeded() {
7138
7285
  }
7139
7286
  const after = db5.prepare("SELECT COUNT(*) AS n FROM tasks").get();
7140
7287
  const count2 = after.n;
7141
- const backupPath = path12.join(path12.dirname(TASKS_FILE), "tasks.json.migrated-from-json.backup");
7288
+ const backupPath = path13.join(path13.dirname(TASKS_FILE), "tasks.json.migrated-from-json.backup");
7142
7289
  try {
7143
- fs11.copyFileSync(TASKS_FILE, backupPath);
7290
+ fs12.copyFileSync(TASKS_FILE, backupPath);
7144
7291
  } catch (err2) {
7145
7292
  log.task.warn("task-db migration: backup copy failed (non-fatal)", {
7146
7293
  path: backupPath,
@@ -7164,9 +7311,9 @@ var init_task_db_migration = __esm({
7164
7311
  });
7165
7312
 
7166
7313
  // src/core/memory-dir-migration.ts
7167
- import fs12 from "fs";
7314
+ import fs13 from "fs";
7168
7315
  import fsp7 from "fs/promises";
7169
- import path13 from "path";
7316
+ import path14 from "path";
7170
7317
  function bodyOf(content) {
7171
7318
  const fm = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
7172
7319
  return fm ? content.slice(fm[0].length) : content;
@@ -7184,7 +7331,7 @@ ${legacyBody}
7184
7331
  }
7185
7332
  function mtimeMs(file) {
7186
7333
  try {
7187
- return fs12.statSync(file).mtimeMs;
7334
+ return fs13.statSync(file).mtimeMs;
7188
7335
  } catch {
7189
7336
  return 0;
7190
7337
  }
@@ -7193,24 +7340,24 @@ function findCandidates(root6) {
7193
7340
  const out = [];
7194
7341
  let level1;
7195
7342
  try {
7196
- level1 = fs12.readdirSync(root6, { withFileTypes: true });
7343
+ level1 = fs13.readdirSync(root6, { withFileTypes: true });
7197
7344
  } catch {
7198
7345
  return out;
7199
7346
  }
7200
7347
  for (const cat of level1) {
7201
7348
  if (!cat.isDirectory()) continue;
7202
- const catDir = path13.join(root6, cat.name);
7203
- if (fs12.existsSync(path13.join(catDir, MEMORY_FILE_NAME))) continue;
7349
+ const catDir = path14.join(root6, cat.name);
7350
+ if (fs13.existsSync(path14.join(catDir, MEMORY_FILE_NAME))) continue;
7204
7351
  let level2;
7205
7352
  try {
7206
- level2 = fs12.readdirSync(catDir, { withFileTypes: true });
7353
+ level2 = fs13.readdirSync(catDir, { withFileTypes: true });
7207
7354
  } catch {
7208
7355
  continue;
7209
7356
  }
7210
7357
  for (const proj of level2) {
7211
7358
  if (!proj.isDirectory()) continue;
7212
- const sourceDir = path13.join(catDir, proj.name);
7213
- if (!fs12.existsSync(path13.join(sourceDir, MEMORY_FILE_NAME))) continue;
7359
+ const sourceDir = path14.join(catDir, proj.name);
7360
+ if (!fs13.existsSync(path14.join(sourceDir, MEMORY_FILE_NAME))) continue;
7214
7361
  out.push({ category: cat.name, project: proj.name, sourceDir });
7215
7362
  }
7216
7363
  }
@@ -7224,11 +7371,11 @@ async function drainDir(from, to) {
7224
7371
  return;
7225
7372
  }
7226
7373
  for (const name of entries) {
7227
- const target = path13.join(to, name);
7228
- if (fs12.existsSync(target)) continue;
7229
- await fsp7.rename(path13.join(from, name), target).catch((err2) => {
7374
+ const target = path14.join(to, name);
7375
+ if (fs13.existsSync(target)) continue;
7376
+ await fsp7.rename(path14.join(from, name), target).catch((err2) => {
7230
7377
  log.memory.warn("memory-dir migration: entry move failed", {
7231
- from: path13.join(from, name),
7378
+ from: path14.join(from, name),
7232
7379
  to: target,
7233
7380
  error: String(err2)
7234
7381
  });
@@ -7240,23 +7387,23 @@ async function drainDir(from, to) {
7240
7387
  async function migrateProjectMemoryDirs() {
7241
7388
  const result = { ran: false, moved: 0, merged: 0 };
7242
7389
  try {
7243
- if (!fs12.existsSync(PROJECTS_MEMORY_DIR)) return result;
7244
- if (fs12.existsSync(MEMORY_DIR_MIGRATION_MARKER)) return result;
7390
+ if (!fs13.existsSync(PROJECTS_MEMORY_DIR)) return result;
7391
+ if (fs13.existsSync(MEMORY_DIR_MIGRATION_MARKER)) return result;
7245
7392
  result.ran = true;
7246
7393
  const candidates = findCandidates(PROJECTS_MEMORY_DIR);
7247
7394
  const touchedCategories = new Set(candidates.map((c8) => c8.category));
7248
7395
  for (const c8 of candidates) {
7249
- const targetDir = path13.join(PROJECTS_MEMORY_DIR, c8.project);
7396
+ const targetDir = path14.join(PROJECTS_MEMORY_DIR, c8.project);
7250
7397
  const label = `${c8.category}/${c8.project}`;
7251
7398
  try {
7252
- if (!fs12.existsSync(targetDir)) {
7399
+ if (!fs13.existsSync(targetDir)) {
7253
7400
  await fsp7.rename(c8.sourceDir, targetDir);
7254
7401
  result.moved += 1;
7255
7402
  continue;
7256
7403
  }
7257
- const sourceMem = path13.join(c8.sourceDir, MEMORY_FILE_NAME);
7258
- const targetMem = path13.join(targetDir, MEMORY_FILE_NAME);
7259
- if (!fs12.existsSync(targetMem)) {
7404
+ const sourceMem = path14.join(c8.sourceDir, MEMORY_FILE_NAME);
7405
+ const targetMem = path14.join(targetDir, MEMORY_FILE_NAME);
7406
+ if (!fs13.existsSync(targetMem)) {
7260
7407
  await fsp7.rename(sourceMem, targetMem);
7261
7408
  await drainDir(c8.sourceDir, targetDir);
7262
7409
  result.moved += 1;
@@ -7278,7 +7425,7 @@ async function migrateProjectMemoryDirs() {
7278
7425
  }
7279
7426
  }
7280
7427
  for (const cat of touchedCategories) {
7281
- await fsp7.rmdir(path13.join(PROJECTS_MEMORY_DIR, cat)).catch(() => {
7428
+ await fsp7.rmdir(path14.join(PROJECTS_MEMORY_DIR, cat)).catch(() => {
7282
7429
  });
7283
7430
  }
7284
7431
  await fsp7.writeFile(
@@ -7306,7 +7453,7 @@ var init_memory_dir_migration = __esm({
7306
7453
  "use strict";
7307
7454
  init_constants();
7308
7455
  init_logging();
7309
- MEMORY_DIR_MIGRATION_MARKER = path13.join(PROJECTS_MEMORY_DIR, ".migrated-project-only");
7456
+ MEMORY_DIR_MIGRATION_MARKER = path14.join(PROJECTS_MEMORY_DIR, ".migrated-project-only");
7310
7457
  MEMORY_FILE_NAME = "MEMORY.md";
7311
7458
  }
7312
7459
  });
@@ -7522,7 +7669,7 @@ __export(microsoft_todo_exports, {
7522
7669
  });
7523
7670
  import * as msal from "@azure/msal-node";
7524
7671
  import https from "https";
7525
- import path14 from "path";
7672
+ import path15 from "path";
7526
7673
  function getMsTodoConfig(config) {
7527
7674
  return config.plugins?.["ms-todo"];
7528
7675
  }
@@ -8741,8 +8888,8 @@ var init_microsoft_todo = __esm({
8741
8888
  init_format();
8742
8889
  init_task_manager();
8743
8890
  init_task_remote_links();
8744
- TOKENS_FILE = path14.join(SYNC_DIR, "ms-todo-tokens.json");
8745
- DELTA_FILE = path14.join(SYNC_DIR, "ms-todo-delta.json");
8891
+ TOKENS_FILE = path15.join(SYNC_DIR, "ms-todo-tokens.json");
8892
+ DELTA_FILE = path15.join(SYNC_DIR, "ms-todo-delta.json");
8746
8893
  GRAPH_BASE = "https://graph.microsoft.com/v1.0";
8747
8894
  SCOPES = ["Tasks.ReadWrite"];
8748
8895
  PRIORITY_TO_IMPORTANCE = {
@@ -8860,8 +9007,8 @@ var init_daemon_capabilities = __esm({
8860
9007
 
8861
9008
  // src/providers/daemon-version-check.ts
8862
9009
  import { createHash } from "crypto";
8863
- import fs13 from "fs";
8864
- import path15 from "path";
9010
+ import fs14 from "fs";
9011
+ import path16 from "path";
8865
9012
  import { spawnSync } from "child_process";
8866
9013
  import { fileURLToPath as fileURLToPath2 } from "url";
8867
9014
  function computeExpectedDaemonVersion() {
@@ -8870,11 +9017,11 @@ function computeExpectedDaemonVersion() {
8870
9017
  const hash = createHash("sha256");
8871
9018
  const NUL = Buffer.from([0]);
8872
9019
  for (const rel of DAEMON_SOURCE_FILES) {
8873
- const abs = path15.join(repoRoot, rel);
9020
+ const abs = path16.join(repoRoot, rel);
8874
9021
  try {
8875
9022
  hash.update(Buffer.from(rel));
8876
9023
  hash.update(NUL);
8877
- hash.update(fs13.readFileSync(abs));
9024
+ hash.update(fs14.readFileSync(abs));
8878
9025
  hash.update(NUL);
8879
9026
  } catch {
8880
9027
  return null;
@@ -8883,12 +9030,12 @@ function computeExpectedDaemonVersion() {
8883
9030
  return "walnut-daemon-" + hash.digest("hex").slice(0, 12);
8884
9031
  }
8885
9032
  function findRepoRoot() {
8886
- let dir2 = path15.dirname(fileURLToPath2(import.meta.url));
9033
+ let dir2 = path16.dirname(fileURLToPath2(import.meta.url));
8887
9034
  for (let i8 = 0; i8 < 10; i8++) {
8888
- if (fs13.existsSync(path15.join(dir2, "package.json")) && fs13.existsSync(path15.join(dir2, "src", "providers", "daemon-standalone.ts"))) {
9035
+ if (fs14.existsSync(path16.join(dir2, "package.json")) && fs14.existsSync(path16.join(dir2, "src", "providers", "daemon-standalone.ts"))) {
8889
9036
  return dir2;
8890
9037
  }
8891
- const parent = path15.dirname(dir2);
9038
+ const parent = path16.dirname(dir2);
8892
9039
  if (parent === dir2) break;
8893
9040
  dir2 = parent;
8894
9041
  }
@@ -9233,12 +9380,12 @@ var init_daemon_fold = __esm({
9233
9380
  });
9234
9381
 
9235
9382
  // src/providers/daemon-source.ts
9236
- import fs14 from "fs";
9237
- import path16 from "path";
9383
+ import fs15 from "fs";
9384
+ import path17 from "path";
9238
9385
  import { fileURLToPath as fileURLToPath3 } from "url";
9239
9386
  function resolveDaemonSourceVersion() {
9240
9387
  const here = fileURLToPath3(import.meta.url);
9241
- const bundled = here.split(path16.sep).includes("dist");
9388
+ const bundled = here.split(path17.sep).includes("dist");
9242
9389
  if (bundled) {
9243
9390
  const sidecar = readSidecarDaemonVersion(here);
9244
9391
  if (sidecar) return sidecar;
@@ -9246,32 +9393,32 @@ function resolveDaemonSourceVersion() {
9246
9393
  const computed = computeExpectedDaemonVersion();
9247
9394
  if (computed) return computed;
9248
9395
  if (process.env.DAEMON_VERSION) return process.env.DAEMON_VERSION;
9249
- let dir2 = path16.dirname(fileURLToPath3(import.meta.url));
9396
+ let dir2 = path17.dirname(fileURLToPath3(import.meta.url));
9250
9397
  for (let i8 = 0; i8 < 10; i8++) {
9251
9398
  try {
9252
- const pkg = JSON.parse(fs14.readFileSync(path16.join(dir2, "package.json"), "utf-8"));
9399
+ const pkg = JSON.parse(fs15.readFileSync(path17.join(dir2, "package.json"), "utf-8"));
9253
9400
  if (pkg.name === "open-walnut" && pkg.version) return `walnut-daemon-pkg-${pkg.version}`;
9254
9401
  } catch {
9255
9402
  }
9256
- const parent = path16.dirname(dir2);
9403
+ const parent = path17.dirname(dir2);
9257
9404
  if (parent === dir2) break;
9258
9405
  dir2 = parent;
9259
9406
  }
9260
9407
  return "dev-source";
9261
9408
  }
9262
9409
  function readSidecarDaemonVersion(fromPath) {
9263
- let dir2 = path16.dirname(fromPath);
9410
+ let dir2 = path17.dirname(fromPath);
9264
9411
  for (let i8 = 0; i8 < 5; i8++) {
9265
- const binDir = path16.join(dir2, "daemon-binaries");
9412
+ const binDir = path17.join(dir2, "daemon-binaries");
9266
9413
  try {
9267
- for (const name of fs14.readdirSync(binDir)) {
9414
+ for (const name of fs15.readdirSync(binDir)) {
9268
9415
  if (!name.endsWith(".version")) continue;
9269
- const v3 = fs14.readFileSync(path16.join(binDir, name), "utf-8").trim();
9416
+ const v3 = fs15.readFileSync(path17.join(binDir, name), "utf-8").trim();
9270
9417
  if (v3) return v3;
9271
9418
  }
9272
9419
  } catch {
9273
9420
  }
9274
- const parent = path16.dirname(dir2);
9421
+ const parent = path17.dirname(dir2);
9275
9422
  if (parent === dir2) break;
9276
9423
  dir2 = parent;
9277
9424
  }
@@ -10199,24 +10346,25 @@ function reapSession(sid, code, reason) {
10199
10346
  let jsonlAgeMs = null;
10200
10347
  try { jsonlAgeMs = Date.now() - fs.statSync(session.jsonlPath).mtimeMs; } catch {}
10201
10348
  const cleanExit = isTurnCompleteExit(session.jsonlPath);
10202
- // An INTENTIONAL daemon-initiated reclamation is not a failure, and the JSONL
10203
- // tail cannot tell us it happened. The idle scanner stamps idleReclaimAt
10204
- // before it signals; the death then lands here through whichever path notices
10205
- // it (usually orphan-poll ESRCH \u2192 code -1). Verified production case: the last
10206
- // line was a control_response (the reply to a Walnut-issued control request \u2014
10207
- // a routine shape), so isTurnCompleteExit said false, exitCode stayed -1, and
10208
- // the projection painted a red "Error" over a healthy, fully resumable session
10209
- // we reclaimed on purpose. Checked FIRST and unconditionally: the flag carries
10210
- // information the tail does not contain, which is why isTurnCompleteExit is
10211
- // left alone. Keep in sync with daemon-core.ts reapSession.
10212
- const intentionalReclaim = session.idleReclaimAt != null;
10213
- if (intentionalReclaim && code !== 0) {
10214
- logMsg('info', 'reapSession: intentional idle reclamation, normalizing exit code', {
10349
+ // A DELIBERATE stop is not a failure, and the JSONL tail cannot tell us it
10350
+ // happened. Both writers (our idle scanner, and any stop RPC via cmdStop)
10351
+ // stamp intentionalStopAt before signalling; the death then lands here through
10352
+ // whichever path notices it (usually orphan-poll ESRCH \u2192 code -1). Verified
10353
+ // production case: the last line was a control_response (the reply to a
10354
+ // Walnut-issued control request \u2014 a routine shape), so isTurnCompleteExit said
10355
+ // false, exitCode stayed -1, and the projection painted a red "Error" over a
10356
+ // healthy, fully resumable session that was stopped on purpose. Checked FIRST
10357
+ // and unconditionally: the flag carries information the tail does not contain,
10358
+ // which is why isTurnCompleteExit is left alone.
10359
+ // Keep in sync with daemon-core.ts reapSession.
10360
+ const intentionalStop = session.intentionalStopAt != null;
10361
+ if (intentionalStop && code !== 0) {
10362
+ logMsg('info', 'reapSession: intentional stop, normalizing exit code', {
10215
10363
  sid, pid: session.pid, originalCode: code, originalReason: reason,
10216
- jsonlAgeMs, cleanExit, idleReclaimAt: session.idleReclaimAt,
10364
+ jsonlAgeMs, cleanExit, intentionalStopAt: session.intentionalStopAt,
10217
10365
  });
10218
10366
  code = 0;
10219
- reason = reason + '+intentional-idle-reclaim';
10367
+ reason = reason + '+intentional-stop';
10220
10368
  } else if (code !== 0 && cleanExit) {
10221
10369
  logMsg('info', 'reapSession: turn-complete detected, normalizing exit code', {
10222
10370
  sid, pid: session.pid, originalCode: code, originalReason: reason, jsonlAgeMs,
@@ -13870,6 +14018,15 @@ function cmdStop(ws, id, cmd) {
13870
14018
  const pid = session.pid;
13871
14019
  logMsg('info', 'cmdStop: stopping session (process group kill)', { sid, pid });
13872
14020
 
14021
+ // Nobody sends stop by accident, so record the intent BEFORE the first signal:
14022
+ // the death lands asynchronously (orphan poll \u2192 ESRCH \u2192 code -1) and by then
14023
+ // nothing remembers this was asked for. This path is how the SERVER's idle
14024
+ // reaper kills (mgr.kill() \u2192 stop RPC), not the daemon's own scan, so it needs
14025
+ // its own stamp: a LOCAL session auto-stopped after 2h idle surfaced as
14026
+ // "Session ended unexpectedly and no cause was recorded" (2026-09-03).
14027
+ // Keep in sync with daemon-standalone.ts cmdStop.
14028
+ session.intentionalStopAt = Date.now();
14029
+
13873
14030
  // An explicit stop is a human decision \u2014 never undone by a pending auto-retry
13874
14031
  // respawning the CLI minutes later. Cancel the timer AND clear the streak so
13875
14032
  // an in-flight result line can't re-arm one either.
@@ -13906,7 +14063,7 @@ function cmdStop(ws, id, cmd) {
13906
14063
 
13907
14064
  // \u2500\u2500 Status \u2500\u2500
13908
14065
  function cmdStatus(ws, id, cmd) {
13909
- const { sid } = cmd;
14066
+ const { sid, includeArgs } = cmd;
13910
14067
  if (!sid) return sendError(ws, id, 'status: missing sid');
13911
14068
 
13912
14069
  const session = sessions.get(sid);
@@ -13937,6 +14094,11 @@ function cmdStatus(ws, id, cmd) {
13937
14094
  exitCode: session.exitCode,
13938
14095
  exitReason: session.exitReason,
13939
14096
  pendingCtrl: session.pendingCtrl,
14097
+ // Opt-in: the argv the live process was launched with (a fork copies it to
14098
+ // reproduce the parent's prompt-cache prefix). Off by default because the
14099
+ // health monitor polls status for every session and the argv carries the
14100
+ // multi-KB --append-system-prompt.
14101
+ ...(includeArgs && session.args && session.args.length > 0 ? { args: session.args } : {}),
13940
14102
  });
13941
14103
  }
13942
14104
 
@@ -15989,7 +16151,7 @@ function scanIdleSessions() {
15989
16151
  // end" for any session whose last line isn't a type:result \u2014 and the
15990
16152
  // projection then shows a red Error for our own routine reclamation.
15991
16153
  // Keep in sync with daemon-standalone.ts scanIdleSessions.
15992
- session.idleReclaimAt = now;
16154
+ session.intentionalStopAt = now;
15993
16155
  killSessionProcessGroup(pid, sid);
15994
16156
  }
15995
16157
  }
@@ -16370,7 +16532,7 @@ if (action === '--start') {
16370
16532
  });
16371
16533
 
16372
16534
  // src/core/jsonl-tailer.ts
16373
- import fs15 from "fs";
16535
+ import fs16 from "fs";
16374
16536
  var init_jsonl_tailer = __esm({
16375
16537
  "src/core/jsonl-tailer.ts"() {
16376
16538
  "use strict";
@@ -16379,10 +16541,10 @@ var init_jsonl_tailer = __esm({
16379
16541
  });
16380
16542
 
16381
16543
  // src/providers/session-io.ts
16382
- import fs16 from "fs";
16544
+ import fs17 from "fs";
16383
16545
  import fsp8 from "fs/promises";
16384
- import path17 from "path";
16385
- import { execFileSync } from "child_process";
16546
+ import path18 from "path";
16547
+ import { execFileSync as execFileSync2 } from "child_process";
16386
16548
  function shellQuote(s4) {
16387
16549
  return "'" + s4.replace(/'/g, "'\\''") + "'";
16388
16550
  }
@@ -16427,7 +16589,7 @@ function findRelativeImageNames(text, opts) {
16427
16589
  function findLocalImagePaths(text) {
16428
16590
  return findImagePaths(text).filter((p6) => {
16429
16591
  try {
16430
- return fs16.statSync(p6).isFile();
16592
+ return fs17.statSync(p6).isFile();
16431
16593
  } catch {
16432
16594
  return false;
16433
16595
  }
@@ -16673,19 +16835,19 @@ function createDaemonCore(deps) {
16673
16835
  jsonlAgeMs = clock() - fs114.statSync(session2.jsonlPath).mtimeMs;
16674
16836
  } catch {
16675
16837
  }
16676
- const intentionalReclaim = session2.idleReclaimAt != null;
16677
- if (intentionalReclaim && code !== 0) {
16678
- logger2("info", "reapSession: intentional idle reclamation, normalizing exit code", {
16838
+ const intentionalStop = session2.intentionalStopAt != null;
16839
+ if (intentionalStop && code !== 0) {
16840
+ logger2("info", "reapSession: intentional stop, normalizing exit code", {
16679
16841
  sid,
16680
16842
  pid: session2.pid,
16681
16843
  originalCode: code,
16682
16844
  originalReason: reason,
16683
16845
  jsonlAgeMs,
16684
16846
  cleanExit,
16685
- idleReclaimAt: session2.idleReclaimAt
16847
+ intentionalStopAt: session2.intentionalStopAt
16686
16848
  });
16687
16849
  code = 0;
16688
- reason = reason + "+intentional-idle-reclaim";
16850
+ reason = reason + "+intentional-stop";
16689
16851
  } else if (cleanExit && code !== 0) {
16690
16852
  logger2("info", "reapSession: turn-complete detected, normalizing exit code", {
16691
16853
  sid,
@@ -17472,83 +17634,6 @@ var init_daemon_core = __esm({
17472
17634
  }
17473
17635
  });
17474
17636
 
17475
- // src/core/claude-cli-detect.ts
17476
- var claude_cli_detect_exports = {};
17477
- __export(claude_cli_detect_exports, {
17478
- _CLAUDE_CREDENTIALS_HOME: () => _CLAUDE_CREDENTIALS_HOME,
17479
- detectClaudeCli: () => detectClaudeCli,
17480
- hasClaudeSubscriptionAuth: () => hasClaudeSubscriptionAuth,
17481
- isClaudeCliInstalled: () => isClaudeCliInstalled,
17482
- resolveClaudeCliExecutable: () => resolveClaudeCliExecutable
17483
- });
17484
- import { execFileSync as execFileSync2 } from "child_process";
17485
- import fs17 from "fs";
17486
- import os4 from "os";
17487
- import path18 from "path";
17488
- function resolveClaudeCliExecutable(env3 = process.env) {
17489
- const home = env3.HOME || os4.homedir();
17490
- const pathDirs = (env3.PATH ?? "").split(path18.delimiter).filter(Boolean);
17491
- const fallbackDirs = [
17492
- path18.join(home, ".toolbox", "bin"),
17493
- path18.join(home, ".local", "bin"),
17494
- path18.join(home, ".npm-global", "bin"),
17495
- path18.join(home, ".bun", "bin"),
17496
- "/usr/local/bin",
17497
- "/opt/homebrew/bin",
17498
- "/usr/bin",
17499
- "/bin"
17500
- ];
17501
- for (const dir2 of [.../* @__PURE__ */ new Set([...pathDirs, ...fallbackDirs])]) {
17502
- const candidate = path18.join(dir2, "claude");
17503
- try {
17504
- fs17.accessSync(candidate, fs17.constants.X_OK);
17505
- return candidate;
17506
- } catch {
17507
- }
17508
- }
17509
- return null;
17510
- }
17511
- function isClaudeCliInstalled() {
17512
- return resolveClaudeCliExecutable() !== null;
17513
- }
17514
- function hasClaudeSubscriptionAuth() {
17515
- if (process.platform === "darwin") {
17516
- try {
17517
- execFileSync2("security", ["find-generic-password", "-s", KEYCHAIN_OAUTH_SERVICE], {
17518
- stdio: "ignore"
17519
- });
17520
- return true;
17521
- } catch {
17522
- }
17523
- }
17524
- return fileCredentialExists();
17525
- }
17526
- function fileCredentialExists() {
17527
- try {
17528
- return fs17.existsSync(CLAUDE_CREDENTIALS_FILE);
17529
- } catch {
17530
- return false;
17531
- }
17532
- }
17533
- function detectClaudeCli() {
17534
- const installed2 = isClaudeCliInstalled();
17535
- const subscriptionAuth = installed2 && hasClaudeSubscriptionAuth();
17536
- return {
17537
- installed: installed2,
17538
- subscriptionAuth,
17539
- subscriptionReady: installed2 && subscriptionAuth
17540
- };
17541
- }
17542
- var KEYCHAIN_OAUTH_SERVICE, _CLAUDE_CREDENTIALS_HOME;
17543
- var init_claude_cli_detect = __esm({
17544
- "src/core/claude-cli-detect.ts"() {
17545
- "use strict";
17546
- init_constants();
17547
- KEYCHAIN_OAUTH_SERVICE = "Claude Code-credentials";
17548
- _CLAUDE_CREDENTIALS_HOME = os4.homedir();
17549
- }
17550
- });
17551
-
17552
17637
  // src/providers/daemon-ownership.ts
17553
17638
  import fs18 from "fs";
17554
17639
  import os5 from "os";
@@ -24728,7 +24813,7 @@ __export(credential_resolver_exports, {
24728
24813
  DEFAULT_REGION: () => DEFAULT_REGION,
24729
24814
  hasResolvableCredential: () => hasResolvableCredential,
24730
24815
  readClaudeCredentialExport: () => readClaudeCredentialExport,
24731
- readClaudeSettingsEnv: () => readClaudeSettingsEnv,
24816
+ readClaudeSettingsEnv: () => readClaudeSettingsEnv2,
24732
24817
  resolveCredentialFrom: () => resolveCredentialFrom,
24733
24818
  resolveCredentials: () => resolveCredentials,
24734
24819
  traceCredentialResolution: () => traceCredentialResolution
@@ -24883,7 +24968,7 @@ function traceCredentialResolution(inputs) {
24883
24968
  region: { value: winner.region ?? DEFAULT_REGION, source: regionSource }
24884
24969
  };
24885
24970
  }
24886
- function readClaudeSettingsEnv(settingsFile = CLAUDE_SETTINGS_FILE) {
24971
+ function readClaudeSettingsEnv2(settingsFile = CLAUDE_SETTINGS_FILE) {
24887
24972
  try {
24888
24973
  const raw = fs31.readFileSync(settingsFile, "utf-8");
24889
24974
  const parsed = JSON.parse(raw);
@@ -24923,7 +25008,7 @@ function probeAwsFiles() {
24923
25008
  function resolveCredentials(config) {
24924
25009
  const result = resolveCredentialFrom({
24925
25010
  config,
24926
- claudeEnv: readClaudeSettingsEnv(),
25011
+ claudeEnv: readClaudeSettingsEnv2(),
24927
25012
  processEnv: process.env,
24928
25013
  awsFiles: probeAwsFiles(),
24929
25014
  claudeCredentialExport: readClaudeCredentialExport()
@@ -67705,10 +67790,9 @@ function buildArgs(opts) {
67705
67790
  // Input stays default (text): we write the whole prompt to stdin and close.
67706
67791
  // (stream-json INPUT would force us into a JSON envelope for no gain here.)
67707
67792
  "--tools",
67708
- "",
67793
+ ""
67709
67794
  // disable ALL built-in tools (deny-all)
67710
- "--settings",
67711
- FORCE_SUBSCRIPTION_SETTINGS
67795
+ // No --settings override: the user's settings.json (and so their auth) applies as-is.
67712
67796
  ];
67713
67797
  let systemText = systemToText(opts.system).trim();
67714
67798
  if (opts.tools && opts.tools.length > 0) {
@@ -67737,7 +67821,7 @@ function systemToText(system) {
67737
67821
  }
67738
67822
  function buildSpawnEnv(base = process.env) {
67739
67823
  const env3 = { ...base };
67740
- for (const key2 of AUTH_ENV_TO_STRIP) {
67824
+ for (const key2 of ENV_TO_STRIP) {
67741
67825
  delete env3[key2];
67742
67826
  }
67743
67827
  env3.CLAUDE_CODE_ENTRYPOINT = WALNUT_UTILITY_ENTRYPOINT;
@@ -67799,7 +67883,7 @@ function normalizeCliModel(model) {
67799
67883
  }
67800
67884
  return void 0;
67801
67885
  }
67802
- var AUTH_ENV_TO_STRIP, FORCE_SUBSCRIPTION_SETTINGS, CLI_TURN_TIMEOUT_MS, SESSION_REUSE_TTL_MS, MAX_TRACKED_SESSIONS, ClaudeCliAdapter;
67886
+ var ENV_TO_STRIP, MAX_CONCURRENT_CLI, CLI_TURN_TIMEOUT_MS, SESSION_REUSE_TTL_MS, MAX_TRACKED_SESSIONS, ClaudeCliAdapter;
67803
67887
  var init_adapter_claude_cli = __esm({
67804
67888
  "src/agent/providers/adapter-claude-cli.ts"() {
67805
67889
  "use strict";
@@ -67809,24 +67893,11 @@ var init_adapter_claude_cli = __esm({
67809
67893
  init_logging();
67810
67894
  init_claude_cli_detect();
67811
67895
  init_claude_cli_protocol();
67812
- AUTH_ENV_TO_STRIP = [
67813
- "ANTHROPIC_API_KEY",
67814
- "ANTHROPIC_AUTH_TOKEN",
67815
- "ANTHROPIC_BASE_URL",
67816
- "AWS_BEARER_TOKEN_BEDROCK",
67817
- "AWS_ACCESS_KEY_ID",
67818
- "AWS_SECRET_ACCESS_KEY",
67819
- "AWS_SESSION_TOKEN",
67820
- "AWS_PROFILE",
67821
- "CLAUDE_CODE_USE_BEDROCK",
67822
- "CLAUDE_CODE_USE_VERTEX",
67823
- // Nested-session marker — inline-subagent strips this too so the child doesn't
67824
- // think it's running inside another Claude session.
67825
- "CLAUDECODE"
67826
- ];
67827
- FORCE_SUBSCRIPTION_SETTINGS = JSON.stringify({
67828
- env: { CLAUDE_CODE_USE_BEDROCK: "", CLAUDE_CODE_USE_VERTEX: "" }
67829
- });
67896
+ ENV_TO_STRIP = ["CLAUDECODE"];
67897
+ MAX_CONCURRENT_CLI = (() => {
67898
+ const n6 = Number(process.env.WALNUT_CLAUDE_CLI_CONCURRENCY);
67899
+ return Number.isFinite(n6) && n6 > 0 ? Math.floor(n6) : 3;
67900
+ })();
67830
67901
  CLI_TURN_TIMEOUT_MS = 12e4;
67831
67902
  SESSION_REUSE_TTL_MS = 30 * 60 * 1e3;
67832
67903
  MAX_TRACKED_SESSIONS = 50;
@@ -67834,6 +67905,25 @@ var init_adapter_claude_cli = __esm({
67834
67905
  protocol = "claude-cli";
67835
67906
  /** conversationKey → CLI session for --resume chaining. In-process only. */
67836
67907
  sessions = /* @__PURE__ */ new Map();
67908
+ /** Process gate: MAX_CONCURRENT_CLI turns at once, the rest queue in order. */
67909
+ inFlight = 0;
67910
+ waiters = [];
67911
+ async withSlot(fn) {
67912
+ if (this.inFlight >= MAX_CONCURRENT_CLI) {
67913
+ await new Promise((resolve) => this.waiters.push(resolve));
67914
+ }
67915
+ this.inFlight++;
67916
+ try {
67917
+ return await fn();
67918
+ } finally {
67919
+ this.inFlight--;
67920
+ this.waiters.shift()?.();
67921
+ }
67922
+ }
67923
+ /** Test hook: how many turns are running / waiting right now. */
67924
+ _gateStateForTesting() {
67925
+ return { inFlight: this.inFlight, waiting: this.waiters.length };
67926
+ }
67837
67927
  resetClient() {
67838
67928
  this.sessions.clear();
67839
67929
  }
@@ -67911,7 +68001,7 @@ User: ${overridePrompt}` : "");
67911
68001
  }
67912
68002
  if (!prompt.trim()) prompt = "(continue)";
67913
68003
  const args = [...buildArgs(opts), session2.flag, session2.id];
67914
- return this.execCli(command6, args, env3, prompt, opts.signal, onTextDelta).then((result) => {
68004
+ return this.withSlot(() => this.execCli(command6, args, env3, prompt, opts.signal, onTextDelta)).then((result) => {
67915
68005
  this.sessions.set(key2, {
67916
68006
  sessionId: session2.id,
67917
68007
  seenMessages: opts.messages.length,
@@ -67930,7 +68020,7 @@ User: ${overridePrompt}` : "");
67930
68020
  User: ${overridePrompt}` : "");
67931
68021
  const freshId = randomUUID3();
67932
68022
  const freshArgs = [...buildArgs(opts), "--session-id", freshId];
67933
- return this.execCli(command6, freshArgs, env3, freshPrompt || "(continue)", opts.signal, onTextDelta).then((result) => {
68023
+ return this.withSlot(() => this.execCli(command6, freshArgs, env3, freshPrompt || "(continue)", opts.signal, onTextDelta)).then((result) => {
67934
68024
  this.sessions.set(key2, {
67935
68025
  sessionId: freshId,
67936
68026
  seenMessages: opts.messages.length,
@@ -68014,7 +68104,7 @@ User: ${overridePrompt}` : "");
68014
68104
  }
68015
68105
  const resultText = finalResult?.result || accumulatedText;
68016
68106
  if (resultText && resultText.length < 300 && /^\s*failed to authenticate|^\s*api error: 4\d\d/i.test(resultText)) {
68017
- reject(new Error(`claude-cli subscription auth failed \u2014 run \`claude login\` to re-authenticate. CLI said: ${resultText.slice(0, 200)}`));
68107
+ reject(new Error(`claude-cli could not authenticate \u2014 run \`claude\` once in a terminal to sign in (or check its Bedrock settings). CLI said: ${resultText.slice(0, 200)}`));
68018
68108
  return;
68019
68109
  }
68020
68110
  if (code !== 0 && !accumulatedText && !finalResult) {
@@ -68248,7 +68338,7 @@ function getContextThreshold(model, percent) {
68248
68338
  }
68249
68339
  async function resolveForCall(config) {
68250
68340
  const fullConfig = await getConfig();
68251
- const providerName = config?.provider ?? fullConfig.agent?.main_provider ?? "bedrock";
68341
+ const providerName = config?.provider ?? resolveMainProviderName(fullConfig);
68252
68342
  const hasExplicitProviders = fullConfig.providers && Object.keys(fullConfig.providers).length > 0;
68253
68343
  let providers = hasExplicitProviders ? buildProviderMap(fullConfig.providers, fullConfig) : synthesizeFromLegacy(fullConfig);
68254
68344
  const resolved = resolveProvider(providerName, providers);
@@ -68318,6 +68408,7 @@ var init_model = __esm({
68318
68408
  "use strict";
68319
68409
  init_config_manager();
68320
68410
  init_registry();
68411
+ init_default_provider();
68321
68412
  init_defaults();
68322
68413
  init_model_catalog();
68323
68414
  }
@@ -69758,7 +69849,7 @@ __export(cheap_model_exports, {
69758
69849
  });
69759
69850
  function fastModelFor(config) {
69760
69851
  if (config.agent?.fast_model) return config.agent.fast_model;
69761
- const providerName = config.agent?.main_provider ?? "bedrock";
69852
+ const providerName = resolveMainProviderName(config);
69762
69853
  return MODEL_CATALOG[providerName]?.find(
69763
69854
  (model) => model.id.toLowerCase().includes("haiku")
69764
69855
  )?.id;
@@ -69770,6 +69861,7 @@ var init_cheap_model = __esm({
69770
69861
  "src/core/cheap-model.ts"() {
69771
69862
  "use strict";
69772
69863
  init_model_catalog();
69864
+ init_default_provider();
69773
69865
  }
69774
69866
  });
69775
69867
 
@@ -71589,7 +71681,20 @@ async function applySnapshot(sessionId, snapshot2, source) {
71589
71681
  if (projected !== "error") {
71590
71682
  updates.errorMessage = void 0;
71591
71683
  updates.errorKind = void 0;
71592
- clearSuppressedErrorReason(sessionId);
71684
+ const stashedStop = projected === "stopped" ? takeSuppressedErrorReason(sessionId) : null;
71685
+ if (stashedStop?.message) {
71686
+ updates.errorMessage = stashedStop.message;
71687
+ if (stashedStop.kind) updates.errorKind = stashedStop.kind;
71688
+ log.session.info("snapshot projection adopted suppressed stop reason", {
71689
+ sessionId,
71690
+ suppressedReason: stashedStop.reason,
71691
+ kind: stashedStop.kind ?? null,
71692
+ projected,
71693
+ source
71694
+ });
71695
+ } else {
71696
+ clearSuppressedErrorReason(sessionId);
71697
+ }
71593
71698
  } else {
71594
71699
  let droppedDisproven = false;
71595
71700
  if (assertsHostUnreachable(record)) {
@@ -75663,6 +75768,26 @@ var init_transcript_chain = __esm({
75663
75768
  }
75664
75769
  });
75665
75770
 
75771
+ // src/core/sessions/side-thread-warmup.ts
75772
+ function isCacheWarmupText(text) {
75773
+ return text.startsWith(CACHE_WARMUP_TAG);
75774
+ }
75775
+ function markWarmupTurnPending(sessionId) {
75776
+ pendingWarmupTurns.add(sessionId);
75777
+ }
75778
+ function consumeWarmupTurn(sessionId) {
75779
+ return pendingWarmupTurns.delete(sessionId);
75780
+ }
75781
+ var CACHE_WARMUP_TAG, CACHE_WARMUP_MESSAGE, pendingWarmupTurns;
75782
+ var init_side_thread_warmup = __esm({
75783
+ "src/core/sessions/side-thread-warmup.ts"() {
75784
+ "use strict";
75785
+ CACHE_WARMUP_TAG = "<walnut-cache-warmup>";
75786
+ 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>`;
75787
+ pendingWarmupTurns = /* @__PURE__ */ new Set();
75788
+ }
75789
+ });
75790
+
75666
75791
  // src/core/history-disk-cache.ts
75667
75792
  var history_disk_cache_exports = {};
75668
75793
  __export(history_disk_cache_exports, {
@@ -77823,6 +77948,7 @@ __export(session_lifecycle_exports, {
77823
77948
  retrySession: () => retrySession,
77824
77949
  terminateSession: () => terminateSession
77825
77950
  });
77951
+ import { randomUUID as randomUUID4 } from "crypto";
77826
77952
  async function readProviderSessionHistory(sessionId, record, nativeHost, skipSubagents = true, opts) {
77827
77953
  if (record && engineCaps(record.engine).historySource === "acp-journal") {
77828
77954
  const { readAcpSessionHistoryState: readAcpSessionHistoryState2 } = await Promise.resolve().then(() => (init_acp_session_history(), acp_session_history_exports));
@@ -77933,6 +78059,33 @@ async function changeSessionMode(existingRecord, sessionId, mode, updates = {})
77933
78059
  });
77934
78060
  return updated;
77935
78061
  }
78062
+ function normalizePinQuote(value) {
78063
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
78064
+ throw new SessionControlError("pinned_messages[].quote must be an object", 400);
78065
+ }
78066
+ const quote = value;
78067
+ const exact = quote.exact;
78068
+ if (typeof exact !== "string" || !exact.trim() || exact.length > MAX_PIN_QUOTE_CHARS) {
78069
+ throw new SessionControlError(
78070
+ `pinned_messages[].quote.exact must be a non-empty string (max ${MAX_PIN_QUOTE_CHARS} chars)`,
78071
+ 400
78072
+ );
78073
+ }
78074
+ for (const side of ["prefix", "suffix"]) {
78075
+ const context = quote[side];
78076
+ if (context !== void 0 && context !== null && (typeof context !== "string" || context.length > MAX_PIN_QUOTE_CONTEXT_CHARS)) {
78077
+ throw new SessionControlError(
78078
+ `pinned_messages[].quote.${side} must be a string (max ${MAX_PIN_QUOTE_CONTEXT_CHARS} chars)`,
78079
+ 400
78080
+ );
78081
+ }
78082
+ }
78083
+ return {
78084
+ exact,
78085
+ ...typeof quote.prefix === "string" && quote.prefix ? { prefix: quote.prefix } : {},
78086
+ ...typeof quote.suffix === "string" && quote.suffix ? { suffix: quote.suffix } : {}
78087
+ };
78088
+ }
77936
78089
  function normalizePinnedMessages(value) {
77937
78090
  if (!Array.isArray(value)) {
77938
78091
  throw new SessionControlError("pinned_messages must be an array", 400);
@@ -77941,7 +78094,9 @@ function normalizePinnedMessages(value) {
77941
78094
  throw new SessionControlError(`pinned_messages holds at most ${MAX_PINNED_MESSAGES} entries`, 400);
77942
78095
  }
77943
78096
  const out = [];
77944
- const seen = /* @__PURE__ */ new Set();
78097
+ const seenWholeMessage = /* @__PURE__ */ new Set();
78098
+ const seenQuoteId = /* @__PURE__ */ new Set();
78099
+ const seenQuoteShape = /* @__PURE__ */ new Set();
77945
78100
  for (const raw of value) {
77946
78101
  if (!raw || typeof raw !== "object") {
77947
78102
  throw new SessionControlError("each pinned_messages entry must be an object", 400);
@@ -77951,13 +78106,40 @@ function normalizePinnedMessages(value) {
77951
78106
  if (typeof msgId !== "string" || !msgId.trim() || msgId.length > MAX_PIN_ID_CHARS) {
77952
78107
  throw new SessionControlError("pinned_messages[].msgId must be a non-empty string", 400);
77953
78108
  }
77954
- if (seen.has(msgId)) continue;
77955
- seen.add(msgId);
78109
+ const rawId = entry.id;
78110
+ if (rawId !== void 0 && rawId !== null && (typeof rawId !== "string" || !rawId.trim() || rawId.length > MAX_PIN_ENTRY_ID_CHARS)) {
78111
+ throw new SessionControlError(
78112
+ `pinned_messages[].id must be a non-empty string (max ${MAX_PIN_ENTRY_ID_CHARS} chars)`,
78113
+ 400
78114
+ );
78115
+ }
78116
+ const id = typeof rawId === "string" ? rawId : void 0;
78117
+ const quote = entry.quote === void 0 || entry.quote === null ? void 0 : normalizePinQuote(entry.quote);
78118
+ let pinId = id;
78119
+ if (quote) {
78120
+ if (id && seenQuoteId.has(id)) continue;
78121
+ const shape = [msgId, quote.exact, quote.prefix ?? "", quote.suffix ?? ""].join("\0");
78122
+ if (seenQuoteShape.has(shape)) continue;
78123
+ pinId = id ?? randomUUID4();
78124
+ seenQuoteId.add(pinId);
78125
+ seenQuoteShape.add(shape);
78126
+ } else {
78127
+ if (seenWholeMessage.has(msgId)) continue;
78128
+ seenWholeMessage.add(msgId);
78129
+ }
77956
78130
  const role = entry.role === "user" || entry.role === "assistant" || entry.role === "system" ? entry.role : "assistant";
77957
78131
  const label = typeof entry.label === "string" ? entry.label.trim().slice(0, MAX_PIN_LABEL_CHARS) : "";
77958
78132
  const timestamp = typeof entry.timestamp === "string" ? entry.timestamp : void 0;
77959
78133
  const pinnedAt = typeof entry.pinnedAt === "string" ? entry.pinnedAt : (/* @__PURE__ */ new Date()).toISOString();
77960
- out.push({ msgId, label, role, ...timestamp ? { timestamp } : {}, pinnedAt });
78134
+ out.push({
78135
+ msgId,
78136
+ label,
78137
+ role,
78138
+ ...timestamp ? { timestamp } : {},
78139
+ pinnedAt,
78140
+ ...pinId ? { id: pinId } : {},
78141
+ ...quote ? { quote } : {}
78142
+ });
77961
78143
  }
77962
78144
  return out;
77963
78145
  }
@@ -78616,7 +78798,7 @@ async function readSessionRichHistory(sessionId, tail) {
78616
78798
  ...historyWindowed ? { windowed: true } : {}
78617
78799
  };
78618
78800
  }
78619
- var CLAUDE_SESSION_MODES, MAX_PINNED_MESSAGES, MAX_PIN_LABEL_CHARS, MAX_PIN_ID_CHARS, RECHECK_RPC_TIMEOUT_MS, RECHECK_TIMED_OUT, GIT_BASES, MAX_FORK_DEPTH, HISTORY_STARTUP_GRACE_MS;
78801
+ var CLAUDE_SESSION_MODES, MAX_PINNED_MESSAGES, MAX_PIN_LABEL_CHARS, MAX_PIN_ID_CHARS, MAX_PIN_ENTRY_ID_CHARS, MAX_PIN_QUOTE_CHARS, MAX_PIN_QUOTE_CONTEXT_CHARS, RECHECK_RPC_TIMEOUT_MS, RECHECK_TIMED_OUT, GIT_BASES, MAX_FORK_DEPTH, HISTORY_STARTUP_GRACE_MS;
78620
78802
  var init_session_lifecycle = __esm({
78621
78803
  "src/core/sessions/session-lifecycle.ts"() {
78622
78804
  "use strict";
@@ -78630,6 +78812,9 @@ var init_session_lifecycle = __esm({
78630
78812
  MAX_PINNED_MESSAGES = 200;
78631
78813
  MAX_PIN_LABEL_CHARS = 300;
78632
78814
  MAX_PIN_ID_CHARS = 200;
78815
+ MAX_PIN_ENTRY_ID_CHARS = 64;
78816
+ MAX_PIN_QUOTE_CHARS = 2e3;
78817
+ MAX_PIN_QUOTE_CONTEXT_CHARS = 64;
78633
78818
  RECHECK_RPC_TIMEOUT_MS = 5e3;
78634
78819
  RECHECK_TIMED_OUT = /* @__PURE__ */ Symbol("recheck-timeout");
78635
78820
  GIT_BASES = /* @__PURE__ */ new Set(["uncommitted", "previous", "remote"]);
@@ -78932,7 +79117,7 @@ __export(compact_inject_exports, {
78932
79117
  extractSessionMeta: () => extractSessionMeta,
78933
79118
  injectCompactBoundary: () => injectCompactBoundary
78934
79119
  });
78935
- import { randomUUID as randomUUID4 } from "crypto";
79120
+ import { randomUUID as randomUUID5 } from "crypto";
78936
79121
  import fsp26 from "fs/promises";
78937
79122
  async function extractSessionMeta(jsonlPath) {
78938
79123
  const content = await fsp26.readFile(jsonlPath, "utf-8");
@@ -78984,8 +79169,8 @@ async function injectCompactBoundary(jsonlPath, summary, meta) {
78984
79169
  log.session.error("Could not find last message UUID in JSONL", { jsonlPath });
78985
79170
  return null;
78986
79171
  }
78987
- const boundaryUuid = randomUUID4();
78988
- const summaryUuid = randomUUID4();
79172
+ const boundaryUuid = randomUUID5();
79173
+ const summaryUuid = randomUUID5();
78989
79174
  const now = /* @__PURE__ */ new Date();
78990
79175
  const boundaryTs = now.toISOString();
78991
79176
  const summaryTs = new Date(now.getTime() + 1e3).toISOString();
@@ -89769,6 +89954,82 @@ var init_task = __esm({
89769
89954
  }
89770
89955
  });
89771
89956
 
89957
+ // src/core/sessions/spawn-prefix.ts
89958
+ var spawn_prefix_exports = {};
89959
+ __export(spawn_prefix_exports, {
89960
+ MAX_SPAWN_PROMPT_BYTES: () => MAX_SPAWN_PROMPT_BYTES,
89961
+ parseSpawnPrefixFromArgs: () => parseSpawnPrefixFromArgs,
89962
+ readParentSpawnPrefix: () => readParentSpawnPrefix,
89963
+ spawnPrefixFromRecord: () => spawnPrefixFromRecord
89964
+ });
89965
+ function flagValue(args, flag) {
89966
+ const i8 = args.indexOf(flag);
89967
+ if (i8 < 0 || i8 + 1 >= args.length) return void 0;
89968
+ const v3 = args[i8 + 1];
89969
+ return v3 !== void 0 && !v3.startsWith("--") ? v3 : void 0;
89970
+ }
89971
+ function parseSpawnPrefixFromArgs(args) {
89972
+ const prompt = flagValue(args, "--append-system-prompt");
89973
+ return {
89974
+ appendSystemPrompt: prompt !== void 0 && Buffer.byteLength(prompt, "utf8") <= MAX_SPAWN_PROMPT_BYTES ? prompt : null,
89975
+ ...flagValue(args, "--model") ? { model: flagValue(args, "--model") } : {},
89976
+ ...EFFORTS.includes(flagValue(args, "--effort") ?? "") ? { effort: flagValue(args, "--effort") } : {},
89977
+ ...flagValue(args, "--permission-mode") ? { permissionMode: flagValue(args, "--permission-mode") } : {},
89978
+ source: "live-process"
89979
+ };
89980
+ }
89981
+ function spawnPrefixFromRecord(record) {
89982
+ const stored = record.appliedAppendSystemPrompt;
89983
+ if (stored === void 0 || stored === null) return { appendSystemPrompt: null, source: "unknown" };
89984
+ const usable = stored.length > 0 && Buffer.byteLength(stored, "utf8") <= MAX_SPAWN_PROMPT_BYTES;
89985
+ return { appendSystemPrompt: usable ? stored : null, source: "record" };
89986
+ }
89987
+ async function defaultLiveArgsReader(hostKey2, sessionId) {
89988
+ const { probeDaemonSessionArgs: probeDaemonSessionArgs2 } = await Promise.resolve().then(() => (init_daemon_connection(), daemon_connection_exports));
89989
+ return probeDaemonSessionArgs2(hostKey2, sessionId);
89990
+ }
89991
+ async function readParentSpawnPrefix(parent, deps = {}) {
89992
+ const readLiveArgs = deps.readLiveArgs ?? defaultLiveArgsReader;
89993
+ const hostKey2 = parent.host || "__local__";
89994
+ let live = null;
89995
+ try {
89996
+ live = await readLiveArgs(hostKey2, parent.claudeSessionId);
89997
+ } catch (err2) {
89998
+ log.session.debug("spawn prefix: live argv read threw", {
89999
+ sessionId: parent.claudeSessionId,
90000
+ error: err2 instanceof Error ? err2.message : String(err2)
90001
+ });
90002
+ }
90003
+ if (live && live.length > 0) {
90004
+ const prefix = parseSpawnPrefixFromArgs(live);
90005
+ const remembered = parent.appliedAppendSystemPrompt ?? void 0;
90006
+ const actual = prefix.appendSystemPrompt ?? "";
90007
+ if (remembered !== actual) {
90008
+ const persist4 = deps.persist ?? (async (sid, prompt) => {
90009
+ const { updateSessionRecord: updateSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
90010
+ return updateSessionRecord2(sid, { appliedAppendSystemPrompt: prompt });
90011
+ });
90012
+ void persist4(parent.claudeSessionId, actual).catch((err2) => {
90013
+ log.session.warn("spawn prefix: backfill of applied prompt failed", {
90014
+ sessionId: parent.claudeSessionId,
90015
+ error: err2 instanceof Error ? err2.message : String(err2)
90016
+ });
90017
+ });
90018
+ }
90019
+ return prefix;
90020
+ }
90021
+ return spawnPrefixFromRecord(parent);
90022
+ }
90023
+ var EFFORTS, MAX_SPAWN_PROMPT_BYTES;
90024
+ var init_spawn_prefix = __esm({
90025
+ "src/core/sessions/spawn-prefix.ts"() {
90026
+ "use strict";
90027
+ init_logging();
90028
+ EFFORTS = ["low", "medium", "high", "xhigh", "max"];
90029
+ MAX_SPAWN_PROMPT_BYTES = 65536;
90030
+ }
90031
+ });
90032
+
89772
90033
  // src/core/sessions/side-thread-fork.ts
89773
90034
  var side_thread_fork_exports = {};
89774
90035
  __export(side_thread_fork_exports, {
@@ -89825,6 +90086,10 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
89825
90086
  const sessionId = crypto18.randomUUID();
89826
90087
  const lane = sideThreadLaneKey(parentSid, threadId);
89827
90088
  const mode = parent.mode !== "default" ? parent.mode : void 0;
90089
+ const { readParentSpawnPrefix: readParentSpawnPrefix2 } = await Promise.resolve().then(() => (init_spawn_prefix(), spawn_prefix_exports));
90090
+ const prefix = await readParentSpawnPrefix2(parent);
90091
+ const cliModel = prefix.model ?? parent.cliModel;
90092
+ const effort = prefix.effort ?? parent.effort;
89828
90093
  await createSessionRecord(sessionId, "", parent.project ?? "", parent.cwd, {
89829
90094
  title,
89830
90095
  ...mode ? { mode } : {},
@@ -89832,8 +90097,10 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
89832
90097
  // The thread inherits the parent's launch bundle verbatim, re-applied from
89833
90098
  // the record on every cold resume.
89834
90099
  ...parent.profile ? { profile: parent.profile } : {},
89835
- ...parent.effort ? { effort: parent.effort } : {},
89836
- ...parent.cliModel ? { cliModel: parent.cliModel } : {},
90100
+ ...effort ? { effort } : {},
90101
+ ...cliModel ? { cliModel } : {},
90102
+ // '' = the parent runs without an append prompt, and so does the thread.
90103
+ appliedAppendSystemPrompt: prefix.appendSystemPrompt ?? "",
89837
90104
  lane,
89838
90105
  forkedFromSessionId: resumeFrom,
89839
90106
  initialProcessStatus: "idle",
@@ -89849,11 +90116,12 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
89849
90116
  ...mode ? { mode } : {},
89850
90117
  ...parent.host ? { host: parent.host } : {},
89851
90118
  ...parent.profile ? { profile: parent.profile } : {},
89852
- ...parent.effort ? { effort: parent.effort } : {},
90119
+ ...effort ? { effort } : {},
89853
90120
  // Must be `cliModel` (the parent's verbatim --model arg, [1m] marker
89854
90121
  // included): the CLI does NOT inherit a model across --fork-session, and
89855
90122
  // the reported `model` has lost the marker.
89856
- ...parent.cliModel ? { model: parent.cliModel } : {},
90123
+ ...cliModel ? { model: cliModel } : {},
90124
+ appendSystemPromptExact: prefix.appendSystemPrompt,
89857
90125
  lane,
89858
90126
  forkedFromSessionId: resumeFrom
89859
90127
  }, ["session-runner"], { source: "side-thread-fork" });
@@ -89862,7 +90130,9 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
89862
90130
  threadId,
89863
90131
  sessionId,
89864
90132
  resumeFrom,
89865
- initOnly: !opts?.message
90133
+ initOnly: !opts?.message,
90134
+ prefixSource: prefix.source,
90135
+ prefixPromptLength: prefix.appendSystemPrompt?.length ?? 0
89866
90136
  });
89867
90137
  return { sessionId, resumeFromSessionId: resumeFrom };
89868
90138
  }
@@ -115734,11 +116004,12 @@ __export(session_controls_exports, {
115734
116004
  applySessionModelChange: () => applySessionModelChange,
115735
116005
  computeModelOptions: () => computeModelOptions,
115736
116006
  controlErrorKind: () => controlErrorKind,
116007
+ createForkSiblingTask: () => createForkSiblingTask,
115737
116008
  forkSessionToTask: () => forkSessionToTask,
115738
116009
  handleSessionControlRelay: () => handleSessionControlRelay,
115739
116010
  modelReadBackMatches: () => modelReadBackMatches
115740
116011
  });
115741
- import { randomUUID as randomUUID5 } from "crypto";
116012
+ import { randomUUID as randomUUID6 } from "crypto";
115742
116013
  function controlErrorKind(status2) {
115743
116014
  if (status2 === 404) return "not_found";
115744
116015
  if (status2 === 405) return "method_not_allowed";
@@ -115911,6 +116182,124 @@ async function computeModelOptions(sessionId) {
115911
116182
  currentEffort: liveEffort ?? record.effectiveEffort ?? record.effort ?? null
115912
116183
  };
115913
116184
  }
116185
+ async function createForkSiblingTask(sourceTaskId, opts) {
116186
+ const {
116187
+ getTask: getTask2,
116188
+ addTask: addTask2,
116189
+ updateTask: updateTask2,
116190
+ groupTasks: groupTasks2,
116191
+ addToGroup: addToGroup2,
116192
+ renameGroup: renameGroup2
116193
+ } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
116194
+ let sourceTask;
116195
+ try {
116196
+ sourceTask = await getTask2(sourceTaskId);
116197
+ } catch {
116198
+ throw new SessionControlError(`Source task "${sourceTaskId}" not found`, 404);
116199
+ }
116200
+ const sourceBaseTitle = sourceTask.title.replace(/^Fork of\s+/i, "").replace(/\s+-\s+fork of\s+.*$/i, "").trim() || sourceTask.title;
116201
+ const explicitTitle = opts.childTitle || (opts.titlePrefix ? `${opts.titlePrefix} - fork of ${sourceBaseTitle}` : void 0);
116202
+ const autoTitle = !explicitTitle;
116203
+ const newTitle = explicitTitle || `Fork of ${sourceBaseTitle}`;
116204
+ let inheritedTier;
116205
+ if (sourceTask.pinned && sourceTask.focus_tier) {
116206
+ const { getCustomTiers: getCustomTiers2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
116207
+ const known = ["focus", "backlog", "wait"].includes(sourceTask.focus_tier) || (await getCustomTiers2()).some((t3) => t3.id === sourceTask.focus_tier);
116208
+ if (known) inheritedTier = sourceTask.focus_tier;
116209
+ else {
116210
+ log.session.warn("fork: source tier is not registered, filing the fork in Satellite", {
116211
+ sourceTaskId: sourceTask.id,
116212
+ tier: sourceTask.focus_tier
116213
+ });
116214
+ }
116215
+ }
116216
+ const { task: newFork } = await addTask2({
116217
+ title: newTitle,
116218
+ project: sourceTask.project || "",
116219
+ source: sourceTask.source,
116220
+ ...opts.description ? { description: opts.description } : {},
116221
+ // A fork is a person splitting off live work, so it joins the board like
116222
+ // any other hand-made task (Satellite = pinned, no stored tier).
116223
+ pinned: true,
116224
+ ...inheritedTier ? { focus_tier: inheritedTier } : {},
116225
+ // Forking an Ask Walnut conversation yields another Ask Walnut
116226
+ // conversation: without the flag the fork loses the amber marker AND the
116227
+ // persona drift repair (both key on task.walnut_agent, never on project).
116228
+ ...sourceTask.walnut_agent ? { walnut_agent: true } : {}
116229
+ });
116230
+ let forkGroupId;
116231
+ try {
116232
+ if (sourceTask.group_id) {
116233
+ const r8 = await addToGroup2(sourceTask.group_id, [newFork.id]);
116234
+ forkGroupId = r8.group_id;
116235
+ } else {
116236
+ const r8 = await groupTasks2([sourceTask.id, newFork.id], sourceTask.title);
116237
+ forkGroupId = r8.group_id;
116238
+ }
116239
+ } catch (err2) {
116240
+ log.session.warn("fork: failed to group source + fork", {
116241
+ sourceTaskId: sourceTask.id,
116242
+ forkTaskId: newFork.id,
116243
+ error: err2 instanceof Error ? err2.message : String(err2)
116244
+ });
116245
+ }
116246
+ let task;
116247
+ try {
116248
+ task = await getTask2(newFork.id);
116249
+ } catch {
116250
+ task = newFork;
116251
+ }
116252
+ bus.emit(EventNames.TASK_CREATED, { task }, ["web-ui", "main-agent"], { source: opts.source });
116253
+ if (autoTitle && opts.prompt?.trim()) {
116254
+ const forkId = newFork.id;
116255
+ const sourceTitle = sourceBaseTitle;
116256
+ const placeholderTitle = newTitle;
116257
+ const prompt = opts.prompt;
116258
+ const titleSource = `${opts.source}-title`;
116259
+ void (async () => {
116260
+ try {
116261
+ const { summarizeForkPrompt: summarizeForkPrompt2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
116262
+ const label = await summarizeForkPrompt2(prompt);
116263
+ if (!label) return;
116264
+ const current5 = await getTask2(forkId);
116265
+ if (current5.title !== placeholderTitle) {
116266
+ log.session.info("fork title refine skipped \u2014 title changed since fork", { taskId: forkId });
116267
+ return;
116268
+ }
116269
+ const refinedTitle = `${label} - fork of ${sourceTitle}`;
116270
+ const { task: updated } = await updateTask2(forkId, { title: refinedTitle }, { source: titleSource });
116271
+ bus.emit(EventNames.TASK_UPDATED, { task: updated }, ["web-ui", "main-agent"], { source: titleSource });
116272
+ log.session.info("fork title refined", { taskId: forkId, title: refinedTitle });
116273
+ } catch (err2) {
116274
+ log.session.warn("fork title refine failed", {
116275
+ taskId: forkId,
116276
+ error: err2 instanceof Error ? err2.message : String(err2)
116277
+ });
116278
+ }
116279
+ })();
116280
+ }
116281
+ if (forkGroupId && !sourceTask.group_id) {
116282
+ const gid = forkGroupId;
116283
+ const seedTitles = [sourceTask.title, newTitle];
116284
+ const groupSource = opts.source;
116285
+ void (async () => {
116286
+ try {
116287
+ const { summarizeGroupLabel: summarizeGroupLabel2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
116288
+ const label = await summarizeGroupLabel2(seedTitles);
116289
+ if (!label) return;
116290
+ await renameGroup2(gid, label);
116291
+ bus.emit(EventNames.TASK_GROUPS_CHANGED, { group_id: gid, label }, ["web-ui", "main-agent"], { source: groupSource });
116292
+ log.session.info("fork group label refined", { groupId: gid, label });
116293
+ } catch (err2) {
116294
+ log.session.warn("fork group label refine failed", {
116295
+ groupId: gid,
116296
+ error: err2 instanceof Error ? err2.message : String(err2)
116297
+ });
116298
+ }
116299
+ })();
116300
+ }
116301
+ return { task, ...forkGroupId ? { groupId: forkGroupId } : {} };
116302
+ }
115914
116303
  async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
115915
116304
  const { task_id, create_child_task, child_title, message, title, model, imageContext } = input;
115916
116305
  if (!task_id && !create_child_task) {
@@ -115920,14 +116309,7 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
115920
116309
  throw new SessionControlError("task_id and create_child_task are mutually exclusive", 400);
115921
116310
  }
115922
116311
  const { getSessionByClaudeId: getSessionByClaudeId2, getSessionsForTask: getSessionsForTask2, createSessionRecord: createSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
115923
- const {
115924
- getTask: getTask2,
115925
- addTask: addTask2,
115926
- updateTask: updateTask2,
115927
- groupTasks: groupTasks2,
115928
- addToGroup: addToGroup2,
115929
- renameGroup: renameGroup2
115930
- } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
116312
+ const { getTask: getTask2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
115931
116313
  const sourceRecord = await getSessionByClaudeId2(sourceSessionId);
115932
116314
  if (!sourceRecord) throw new SessionControlError("Source session not found", 404);
115933
116315
  if (!engineCaps(sourceRecord.engine).fork) {
@@ -115944,108 +116326,13 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
115944
116326
  if (!sourceRecord.taskId) {
115945
116327
  throw new SessionControlError("Source session has no task \u2014 cannot create fork task", 400);
115946
116328
  }
115947
- let sourceTask;
115948
- try {
115949
- sourceTask = await getTask2(sourceRecord.taskId);
115950
- } catch {
115951
- throw new SessionControlError(`Source task "${sourceRecord.taskId}" not found`, 404);
115952
- }
115953
- const sourceBaseTitle = sourceTask.title.replace(/^Fork of\s+/i, "").replace(/\s+-\s+fork of\s+.*$/i, "").trim() || sourceTask.title;
115954
- const autoTitle = !child_title;
115955
- const newTitle = child_title || `Fork of ${sourceBaseTitle}`;
115956
- let inheritedTier;
115957
- if (sourceTask.pinned && sourceTask.focus_tier) {
115958
- const { getCustomTiers: getCustomTiers2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
115959
- const known = ["focus", "backlog", "wait"].includes(sourceTask.focus_tier) || (await getCustomTiers2()).some((t3) => t3.id === sourceTask.focus_tier);
115960
- if (known) inheritedTier = sourceTask.focus_tier;
115961
- else {
115962
- log.session.warn("fork: source tier is not registered, filing the fork in Satellite", {
115963
- sourceTaskId: sourceTask.id,
115964
- tier: sourceTask.focus_tier
115965
- });
115966
- }
115967
- }
115968
- const { task: newFork } = await addTask2({
115969
- title: newTitle,
115970
- project: sourceTask.project || "",
115971
- source: sourceTask.source,
115972
- // A fork is a person splitting off live work, so it joins the board like
115973
- // any other hand-made task (Satellite = pinned, no stored tier).
115974
- pinned: true,
115975
- ...inheritedTier ? { focus_tier: inheritedTier } : {},
115976
- // Forking an Ask Walnut conversation yields another Ask Walnut
115977
- // conversation: without the flag the fork loses the amber marker AND the
115978
- // persona drift repair (both key on task.walnut_agent, never on project).
115979
- ...sourceTask.walnut_agent ? { walnut_agent: true } : {}
115980
- });
115981
- let forkGroupId;
115982
- try {
115983
- if (sourceTask.group_id) {
115984
- const r8 = await addToGroup2(sourceTask.group_id, [newFork.id]);
115985
- forkGroupId = r8.group_id;
115986
- } else {
115987
- const r8 = await groupTasks2([sourceTask.id, newFork.id], sourceTask.title);
115988
- forkGroupId = r8.group_id;
115989
- }
115990
- } catch (err2) {
115991
- log.session.warn("fork: failed to group source + fork", {
115992
- sourceTaskId: sourceTask.id,
115993
- forkTaskId: newFork.id,
115994
- error: err2 instanceof Error ? err2.message : String(err2)
115995
- });
115996
- }
115997
- try {
115998
- task = await getTask2(newFork.id);
115999
- } catch {
116000
- task = newFork;
116001
- }
116002
- bus.emit(EventNames.TASK_CREATED, { task }, ["web-ui", "main-agent"], { source: "fork" });
116329
+ const created = await createForkSiblingTask(sourceRecord.taskId, {
116330
+ childTitle: child_title,
116331
+ prompt: message,
116332
+ source: "fork"
116333
+ });
116334
+ task = created.task;
116003
116335
  childTaskCreated = true;
116004
- if (autoTitle && message?.trim()) {
116005
- const forkId = newFork.id;
116006
- const sourceTitle = sourceBaseTitle;
116007
- const placeholderTitle = newTitle;
116008
- void (async () => {
116009
- try {
116010
- const { summarizeForkPrompt: summarizeForkPrompt2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
116011
- const label = await summarizeForkPrompt2(message);
116012
- if (!label) return;
116013
- const current5 = await getTask2(forkId);
116014
- if (current5.title !== placeholderTitle) {
116015
- log.session.info("fork title refine skipped \u2014 title changed since fork", { taskId: forkId });
116016
- return;
116017
- }
116018
- const refinedTitle = `${label} - fork of ${sourceTitle}`;
116019
- const { task: updated } = await updateTask2(forkId, { title: refinedTitle }, { source: "fork-title" });
116020
- bus.emit(EventNames.TASK_UPDATED, { task: updated }, ["web-ui", "main-agent"], { source: "fork-title" });
116021
- log.session.info("fork title refined", { taskId: forkId, title: refinedTitle });
116022
- } catch (err2) {
116023
- log.session.warn("fork title refine failed", {
116024
- taskId: forkId,
116025
- error: err2 instanceof Error ? err2.message : String(err2)
116026
- });
116027
- }
116028
- })();
116029
- }
116030
- if (forkGroupId && !sourceTask.group_id) {
116031
- const gid = forkGroupId;
116032
- const seedTitles = [sourceTask.title, newTitle];
116033
- void (async () => {
116034
- try {
116035
- const { summarizeGroupLabel: summarizeGroupLabel2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
116036
- const label = await summarizeGroupLabel2(seedTitles);
116037
- if (!label) return;
116038
- await renameGroup2(gid, label);
116039
- bus.emit(EventNames.TASK_GROUPS_CHANGED, { group_id: gid, label }, ["web-ui", "main-agent"], { source: "fork" });
116040
- log.session.info("fork group label refined", { groupId: gid, label });
116041
- } catch (err2) {
116042
- log.session.warn("fork group label refine failed", {
116043
- groupId: gid,
116044
- error: err2 instanceof Error ? err2.message : String(err2)
116045
- });
116046
- }
116047
- })();
116048
- }
116049
116336
  } else {
116050
116337
  try {
116051
116338
  task = await getTask2(task_id);
@@ -116067,7 +116354,7 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
116067
116354
  const FORK_FOCUS_PREFIX = "This is a forked session. Focus on the NEW request below \u2014 treat it as your primary task. Do not resume or continue the parent session's previous work unless the user explicitly asks you to.\n\n";
116068
116355
  const forkMessage = `${FORK_FOCUS_PREFIX}${imageContext ?? ""}New request:
116069
116356
  ${userRequest}`;
116070
- const forkSessionId = randomUUID5();
116357
+ const forkSessionId = randomUUID6();
116071
116358
  const forkTitle = title ?? `Fork of ${sourceRecord.title ?? sourceSessionId.slice(0, 16)}`;
116072
116359
  try {
116073
116360
  await createSessionRecord2(forkSessionId, task.id, task.project ?? "", sourceRecord.cwd, {
@@ -116790,7 +117077,7 @@ __export(session_rewind_exports, {
116790
117077
  probeRewindViaDaemon: () => probeRewindViaDaemon,
116791
117078
  rewindSessionToMessage: () => rewindSessionToMessage
116792
117079
  });
116793
- import { randomUUID as randomUUID6 } from "crypto";
117080
+ import { randomUUID as randomUUID7 } from "crypto";
116794
117081
  function cutAncestorHistoryAtRewindPoint(ancestorMessages, rewoundAtMessageUuid) {
116795
117082
  if (!rewoundAtMessageUuid) {
116796
117083
  return { messages: ancestorMessages, found: false, dropped: 0 };
@@ -116991,7 +117278,7 @@ async function rewindSessionToMessage(sessionId, input, source = "web-api") {
116991
117278
  error: err2 instanceof Error ? err2.message : String(err2)
116992
117279
  });
116993
117280
  }
116994
- const rewoundId = randomUUID6();
117281
+ const rewoundId = randomUUID7();
116995
117282
  const baseTitle = (record.title ?? sessionId.slice(0, 16)).replace(/\s*\(rewound\)$/i, "");
116996
117283
  const title = `${baseTitle} (rewound)`.slice(0, 200);
116997
117284
  const { createSessionRecord: createSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
@@ -117711,6 +117998,7 @@ function parseSessionMessages(content, opts) {
117711
117998
  }
117712
117999
  }
117713
118000
  let lastPlanContent = null;
118001
+ let dropWarmupReply = false;
117714
118002
  const resultParentIds = [];
117715
118003
  const result = [];
117716
118004
  for (const [mapMsgId, msg] of messageMap) {
@@ -117788,10 +118076,18 @@ ${imgPath}` : imgPath;
117788
118076
  let text = textParts.join("\n").trim();
117789
118077
  if (!text && tools2.length === 0 && !thinking) continue;
117790
118078
  if (msg.role === "user" && text.startsWith("<")) {
118079
+ if (isCacheWarmupText(text)) {
118080
+ dropWarmupReply = true;
118081
+ continue;
118082
+ }
117791
118083
  const transformed = transformInjectedUserText(text);
117792
118084
  if (transformed === null) continue;
117793
118085
  if (transformed !== void 0) text = transformed;
117794
118086
  }
118087
+ if (msg.role === "assistant" && dropWarmupReply) {
118088
+ dropWarmupReply = false;
118089
+ if (tools2.length === 0) continue;
118090
+ }
117795
118091
  if (msg.role === "user") text = stripOutputModeWrappers(text);
117796
118092
  if (msg.role === "assistant" && !text && tools2.length === 0) continue;
117797
118093
  result.push({
@@ -118742,6 +119038,7 @@ var init_session_history = __esm({
118742
119038
  init_session_io();
118743
119039
  init_constants();
118744
119040
  init_remote_image_mirror();
119041
+ init_side_thread_warmup();
118745
119042
  init_session_file_reader();
118746
119043
  LOCAL_HOME = os19.homedir();
118747
119044
  TAIL_SEGMENT_ROLL_BYTES = 4 * 1024 * 1024;
@@ -119034,7 +119331,7 @@ var init_context_window = __esm({
119034
119331
  });
119035
119332
 
119036
119333
  // src/providers/turn-ledger.ts
119037
- import { randomUUID as randomUUID7 } from "crypto";
119334
+ import { randomUUID as randomUUID8 } from "crypto";
119038
119335
  function openTurn(sessionId) {
119039
119336
  const existing = openTurns.get(sessionId);
119040
119337
  if (existing && !existing.settled) {
@@ -119044,7 +119341,7 @@ function openTurn(sessionId) {
119044
119341
  });
119045
119342
  return { turnId: existing.turnId, promise: existing.promise };
119046
119343
  }
119047
- const turnId = randomUUID7();
119344
+ const turnId = randomUUID8();
119048
119345
  let resolve;
119049
119346
  let reject;
119050
119347
  const promise = new Promise((res, rej) => {
@@ -127038,11 +127335,39 @@ var init_claude_code_session = __esm({
127038
127335
  session2.pendingDescription = message.slice(0, 500);
127039
127336
  let appendSystemPrompt;
127040
127337
  const isFork = !!data.forkedFromSessionId;
127041
- if (data.appendSystemPrompt && !isFork) {
127042
- appendSystemPrompt = data.appendSystemPrompt;
127043
- log.session.info("using caller-provided system prompt", { taskId: taskId2, promptLength: data.appendSystemPrompt.length });
127044
- }
127045
- {
127338
+ const { parseSideLaneKey: parseSideLaneKey2 } = await Promise.resolve().then(() => (init_side_thread_fork(), side_thread_fork_exports));
127339
+ const sideIds = parseSideLaneKey2(data.lane);
127340
+ if (sideIds) {
127341
+ let exact = data.appendSystemPromptExact;
127342
+ let source = "live-or-record";
127343
+ if (exact === void 0) {
127344
+ try {
127345
+ const { getSessionByClaudeId: getSessionByClaudeId2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
127346
+ const { spawnPrefixFromRecord: spawnPrefixFromRecord2 } = await Promise.resolve().then(() => (init_spawn_prefix(), spawn_prefix_exports));
127347
+ const parentRecord = await getSessionByClaudeId2(sideIds.parentSid);
127348
+ const prefix = parentRecord ? spawnPrefixFromRecord2(parentRecord) : null;
127349
+ exact = prefix?.appendSystemPrompt ?? null;
127350
+ source = prefix?.source ?? "no-parent-record";
127351
+ } catch (err2) {
127352
+ exact = null;
127353
+ source = "lookup-failed";
127354
+ log.session.warn("side thread: parent prompt lookup failed \u2014 spawning without an append prompt", {
127355
+ parentSid: sideIds.parentSid,
127356
+ error: err2 instanceof Error ? err2.message : String(err2)
127357
+ });
127358
+ }
127359
+ }
127360
+ appendSystemPrompt = exact ?? void 0;
127361
+ log.session.info("side thread: prefix prompt resolved", {
127362
+ parentSid: sideIds.parentSid,
127363
+ source,
127364
+ promptLength: appendSystemPrompt?.length ?? 0
127365
+ });
127366
+ } else {
127367
+ if (data.appendSystemPrompt && !isFork) {
127368
+ appendSystemPrompt = data.appendSystemPrompt;
127369
+ log.session.info("using caller-provided system prompt", { taskId: taskId2, promptLength: data.appendSystemPrompt.length });
127370
+ }
127046
127371
  try {
127047
127372
  const { buildSessionContext: buildSessionContext2 } = await Promise.resolve().then(() => (init_session_context(), session_context_exports));
127048
127373
  const ctx = await buildSessionContext2(taskId2 ?? "", cwd, data.host);
@@ -127060,32 +127385,6 @@ ${ctx.systemPrompt}` : ctx.systemPrompt;
127060
127385
  log.session.warn("failed to build session context", { taskId: taskId2, error: err2 instanceof Error ? err2.message : String(err2) });
127061
127386
  }
127062
127387
  }
127063
- {
127064
- const { parseSideLaneKey: parseSideLaneKey2 } = await Promise.resolve().then(() => (init_side_thread_fork(), side_thread_fork_exports));
127065
- const sideIds = parseSideLaneKey2(data.lane);
127066
- if (sideIds) {
127067
- try {
127068
- const { getSessionByClaudeId: getSessionByClaudeId2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
127069
- const parentRecord = await getSessionByClaudeId2(sideIds.parentSid);
127070
- const inherited = parentRecord?.appliedAppendSystemPrompt;
127071
- if (inherited && Buffer.byteLength(inherited, "utf8") <= 65536) {
127072
- appendSystemPrompt = inherited;
127073
- log.session.info("side thread: inherited parent system prompt verbatim", {
127074
- parentSid: sideIds.parentSid,
127075
- promptLength: appendSystemPrompt.length
127076
- });
127077
- } else {
127078
- log.session.info("side thread: parent has no stored system prompt \u2014 fresh build (cache prefix will diverge)", {
127079
- parentSid: sideIds.parentSid
127080
- });
127081
- }
127082
- } catch (err2) {
127083
- log.session.warn("side thread: parent prompt lookup failed \u2014 fresh build", {
127084
- error: err2 instanceof Error ? err2.message : String(err2)
127085
- });
127086
- }
127087
- }
127088
- }
127089
127388
  if (data.preassignedSessionId && appendSystemPrompt && Buffer.byteLength(appendSystemPrompt, "utf8") <= 65536) {
127090
127389
  const promptToStore = appendSystemPrompt;
127091
127390
  const sidToStore = data.preassignedSessionId;
@@ -128285,7 +128584,7 @@ __export(session_requests_exports, {
128285
128584
  settleNotified: () => settleNotified,
128286
128585
  settleReplied: () => settleReplied
128287
128586
  });
128288
- import { createHash as createHash14, randomUUID as randomUUID8 } from "crypto";
128587
+ import { createHash as createHash14, randomUUID as randomUUID9 } from "crypto";
128289
128588
  import path119 from "path";
128290
128589
  function oneLine2(text, max = 160) {
128291
128590
  const flat = text.replace(/\s+/g, " ").trim();
@@ -128309,7 +128608,7 @@ function clampReplyTimeoutSecs(secs) {
128309
128608
  }
128310
128609
  async function createSessionRequest(input) {
128311
128610
  const request2 = {
128312
- id: `rq-${randomUUID8().replace(/-/g, "").slice(0, 12)}`,
128611
+ id: `rq-${randomUUID9().replace(/-/g, "").slice(0, 12)}`,
128313
128612
  fromSessionId: input.fromSessionId,
128314
128613
  ...input.toSessionId ? { toSessionId: input.toSessionId } : {},
128315
128614
  ...input.toTaskId ? { toTaskId: input.toTaskId } : {},
@@ -137268,7 +137567,7 @@ __export(mobile_launch_exports, {
137268
137567
  performMobileLaunch: () => performMobileLaunch,
137269
137568
  validateMobileLaunchBody: () => validateMobileLaunchBody
137270
137569
  });
137271
- import { randomUUID as randomUUID9 } from "crypto";
137570
+ import { randomUUID as randomUUID10 } from "crypto";
137272
137571
  function launchErrorCode(status2) {
137273
137572
  if (status2 === 404) return "not_found";
137274
137573
  if (status2 === 409) return "conflict";
@@ -137390,7 +137689,7 @@ async function performMobileLaunch(input, source) {
137390
137689
  );
137391
137690
  }
137392
137691
  }
137393
- const preassignedSessionId = engineCaps(input.engine).idProvisioning === "provider-issued" ? void 0 : randomUUID9();
137692
+ const preassignedSessionId = engineCaps(input.engine).idProvisioning === "provider-issued" ? void 0 : randomUUID10();
137394
137693
  const task = await quickStartSession({
137395
137694
  message: input.message,
137396
137695
  cwd: input.cwd,
@@ -138189,6 +138488,7 @@ __export(daemon_connection_exports, {
138189
138488
  isDaemonConnected: () => isDaemonConnected,
138190
138489
  isPooledConnection: () => isPooledConnection,
138191
138490
  probeDaemonSession: () => probeDaemonSession,
138491
+ probeDaemonSessionArgs: () => probeDaemonSessionArgs,
138192
138492
  pushDaemonHooksToAllHosts: () => pushDaemonHooksToAllHosts,
138193
138493
  setOnDaemonHostConnected: () => setOnDaemonHostConnected,
138194
138494
  setOnDaemonStatusChange: () => setOnDaemonStatusChange,
@@ -138365,6 +138665,28 @@ async function probeDaemonSession(hostKey2, sessionId) {
138365
138665
  return null;
138366
138666
  }
138367
138667
  }
138668
+ async function probeDaemonSessionArgs(hostKey2, sessionId, deadlineMs = 2e3) {
138669
+ const conn = getConnectedDaemonConnection(hostKey2);
138670
+ if (!conn) return null;
138671
+ try {
138672
+ const result = await Promise.race([
138673
+ conn.send("status", { sid: sessionId, includeArgs: true }),
138674
+ new Promise((_, reject) => setTimeout(
138675
+ () => reject(new Error(`status probe exceeded ${deadlineMs}ms`)),
138676
+ deadlineMs
138677
+ ).unref?.())
138678
+ ]);
138679
+ if (!result.ok || !result.alive || !Array.isArray(result.args)) return null;
138680
+ return result.args.filter((a8) => typeof a8 === "string");
138681
+ } catch (err2) {
138682
+ log.session.debug("probeDaemonSessionArgs: status probe failed", {
138683
+ hostKey: hostKey2,
138684
+ sessionId,
138685
+ error: err2 instanceof Error ? err2.message : String(err2)
138686
+ });
138687
+ return null;
138688
+ }
138689
+ }
138368
138690
  var execFileAsync11, MOBILE_ENQUEUE_LEDGER_MAX, recentMobileEnqueues, DaemonConnection, onPoolStatusChange, onHostConnected, hostConnectedListeners, connectionPool, connectingPromises, failureCache, FAILURE_CACHE_TTL_MS;
138369
138691
  var init_daemon_connection = __esm({
138370
138692
  "src/providers/daemon-connection.ts"() {
@@ -143710,6 +144032,7 @@ async function createSessionRecord(claudeSessionId, taskId2, project, cwd, extra
143710
144032
  ...extra?.effort ? { effort: extra.effort } : {},
143711
144033
  ...extra?.profile ? { profile: extra.profile } : {},
143712
144034
  ...extra?.lane ? { lane: extra.lane } : {},
144035
+ ...extra?.appliedAppendSystemPrompt !== void 0 ? { appliedAppendSystemPrompt: extra.appliedAppendSystemPrompt } : {},
143713
144036
  ...extra?.engine ? { engine: extra.engine } : {},
143714
144037
  ...extra?.acpRuntimeId ? { acpRuntimeId: extra.acpRuntimeId } : {},
143715
144038
  ...extra?.acpJournalPath ? { acpJournalPath: extra.acpJournalPath } : {},
@@ -143864,7 +144187,7 @@ function applyUpdateToSession(session2, updates, logLabel) {
143864
144187
  };
143865
144188
  if (count2 === 1) log.session.info("legacy status write suppressed", payload);
143866
144189
  else log.session.debug("legacy status write suppressed", payload);
143867
- if (updates.process_status === "error") {
144190
+ if (updates.process_status === "error" || updates.process_status === "stopped") {
143868
144191
  noteSuppressedErrorReason(session2.claudeSessionId, {
143869
144192
  reason: typeof reason === "string" ? reason : "unknown",
143870
144193
  message: typeof updates.errorMessage === "string" ? updates.errorMessage : void 0,
@@ -151813,14 +152136,16 @@ function isLive(r8) {
151813
152136
  function isRevivable(r8) {
151814
152137
  return !!r8.outputFile || r8.consumedOffset !== void 0;
151815
152138
  }
151816
- var STANDBY_TTL_MS, MAX_LIVE_THREADS, IDLE_SWEEP_INTERVAL_MS, THREAD_IDLE_MS, SideThreadManager, sideThreadManager;
152139
+ var STANDBY_TTL_MS, WARMED_STANDBY_TTL_MS, MAX_LIVE_THREADS, IDLE_SWEEP_INTERVAL_MS, THREAD_IDLE_MS, SideThreadManager, sideThreadManager;
151817
152140
  var init_side_thread_manager = __esm({
151818
152141
  "src/core/sessions/side-thread-manager.ts"() {
151819
152142
  "use strict";
151820
152143
  init_logging();
151821
152144
  init_side_thread_fork();
151822
152145
  init_session_controls();
152146
+ init_side_thread_warmup();
151823
152147
  STANDBY_TTL_MS = 12e4;
152148
+ WARMED_STANDBY_TTL_MS = 15 * 6e4;
151824
152149
  MAX_LIVE_THREADS = 3;
151825
152150
  IDLE_SWEEP_INTERVAL_MS = 5 * 6e4;
151826
152151
  THREAD_IDLE_MS = 30 * 6e4;
@@ -151831,6 +152156,8 @@ var init_side_thread_manager = __esm({
151831
152156
  * In-memory only: a server restart loses it, but the boot sweep archives
151832
152157
  * every standby anyway, so a record can never outlive its map entry. */
151833
152158
  standbyParentOffsets = /* @__PURE__ */ new Map();
152159
+ /** parentSid → standby sid that already ran its cache warm-up turn. */
152160
+ warmedStandbys = /* @__PURE__ */ new Map();
151834
152161
  /** One in-flight standby/create op per parent — two concurrent asks must not
151835
152162
  * both consume the same standby (they would collide on its lane rename). */
151836
152163
  inFlight = /* @__PURE__ */ new Map();
@@ -151862,6 +152189,7 @@ var init_side_thread_manager = __esm({
151862
152189
  for (const timer3 of this.standbyTimers.values()) clearTimeout(timer3);
151863
152190
  this.standbyTimers.clear();
151864
152191
  this.standbyParentOffsets.clear();
152192
+ this.warmedStandbys.clear();
151865
152193
  this.inFlight.clear();
151866
152194
  }
151867
152195
  // ── Standby ────────────────────────────────────────────────────────────────
@@ -151877,10 +152205,12 @@ var init_side_thread_manager = __esm({
151877
152205
  const existing = await this.findStandby(parentSid);
151878
152206
  if (existing) {
151879
152207
  if (await this.isStandbyUsable(parentSid, existing)) {
151880
- this.armStandbyTtl(parentSid, existing.claudeSessionId);
152208
+ const warmed = this.warmedStandbys.get(parentSid) === existing.claudeSessionId;
152209
+ this.armStandbyTtl(parentSid, existing.claudeSessionId, warmed ? WARMED_STANDBY_TTL_MS : STANDBY_TTL_MS);
151881
152210
  return existing.claudeSessionId;
151882
152211
  }
151883
152212
  void this.retireSession(existing.claudeSessionId, "side_thread_standby_stale");
152213
+ this.warmedStandbys.delete(parentSid);
151884
152214
  }
151885
152215
  const parentOffset = parent.consumedOffset;
151886
152216
  const { sessionId } = await forkSideThreadSession(parentSid, STANDBY_THREAD_ID);
@@ -151906,7 +152236,7 @@ var init_side_thread_manager = __esm({
151906
152236
  if (!parent) return false;
151907
152237
  return parent.consumedOffset === this.standbyParentOffsets.get(parentSid);
151908
152238
  }
151909
- armStandbyTtl(parentSid, sessionId) {
152239
+ armStandbyTtl(parentSid, sessionId, ttlMs = STANDBY_TTL_MS) {
151910
152240
  const prev = this.standbyTimers.get(parentSid);
151911
152241
  if (prev) clearTimeout(prev);
151912
152242
  const timer3 = setTimeout(() => {
@@ -151916,13 +152246,41 @@ var init_side_thread_manager = __esm({
151916
152246
  const record = await getSessionByClaudeId2(sessionId).catch(() => null);
151917
152247
  if (!record || parseSideLaneKey(record.lane)?.threadId !== STANDBY_THREAD_ID) return;
151918
152248
  this.standbyParentOffsets.delete(parentSid);
152249
+ this.warmedStandbys.delete(parentSid);
151919
152250
  await this.retireSession(sessionId, "side_thread_standby_ttl");
151920
152251
  }).catch(() => {
151921
152252
  });
151922
- }, STANDBY_TTL_MS);
152253
+ }, ttlMs);
151923
152254
  timer3.unref?.();
151924
152255
  this.standbyTimers.set(parentSid, timer3);
151925
152256
  }
152257
+ /**
152258
+ * Run the standby's cache warm-up turn (see side-thread-warmup.ts). Called
152259
+ * when the user starts TYPING a new question: the fork's first API call pays
152260
+ * the full prefix write no matter what, so paying it while they type turns
152261
+ * the actual question into a cached follow-up. Idempotent per standby; a
152262
+ * missing or stale standby is a no-op (the ask path will fork fresh anyway).
152263
+ */
152264
+ async warmStandby(parentSid) {
152265
+ return this.serialize(parentSid, async () => {
152266
+ const standby = await this.findStandby(parentSid);
152267
+ if (!standby) return { warmed: false, reason: "no_standby" };
152268
+ if (!await this.isStandbyUsable(parentSid, standby)) return { warmed: false, reason: "stale" };
152269
+ if (this.warmedStandbys.get(parentSid) === standby.claudeSessionId) {
152270
+ return { warmed: true, reason: "already_warm" };
152271
+ }
152272
+ const { sendMessageToSession: sendMessageToSession2 } = await Promise.resolve().then(() => (init_session_message_queue(), session_message_queue_exports));
152273
+ markWarmupTurnPending(standby.claudeSessionId);
152274
+ await sendMessageToSession2(standby.claudeSessionId, CACHE_WARMUP_MESSAGE, { source: "side-thread-warmup" });
152275
+ this.warmedStandbys.set(parentSid, standby.claudeSessionId);
152276
+ this.armStandbyTtl(parentSid, standby.claudeSessionId, WARMED_STANDBY_TTL_MS);
152277
+ log.session.info("side thread: standby cache warm-up sent", {
152278
+ parentSid,
152279
+ standbySid: standby.claudeSessionId
152280
+ });
152281
+ return { warmed: true };
152282
+ });
152283
+ }
151926
152284
  // ── Threads ────────────────────────────────────────────────────────────────
151927
152285
  /**
151928
152286
  * Open a side thread on `parentSid` and ask `question` in it.
@@ -151933,6 +152291,10 @@ var init_side_thread_manager = __esm({
151933
152291
  * question as its spawn's first turn instead — a send issued right after
151934
152292
  * SESSION_START can lose that race and cold-`--resume` an id the CLI has never
151935
152293
  * seen (see personal-ai-lane.ts LaneSession.created).
152294
+ *
152295
+ * `imageContext` (attachment paths for the CLI to Read) rides the MESSAGE only.
152296
+ * The stored entry keeps the plain question, so the chip label, the injected
152297
+ * transcript and a promoted task never carry the machine preamble.
151936
152298
  */
151937
152299
  async createThread(parentSid, input) {
151938
152300
  const question = input.question?.trim();
@@ -151940,8 +152302,11 @@ var init_side_thread_manager = __esm({
151940
152302
  return this.serialize(parentSid, async () => {
151941
152303
  const threadId = mintSideThreadId();
151942
152304
  const title = input.title?.trim() || void 0;
152305
+ const message = input.imageContext ? `${input.imageContext}
152306
+
152307
+ ${question}` : question;
151943
152308
  const consumed = await this.consumeStandby(parentSid, threadId, title);
151944
- const threadSessionId = consumed ?? (await forkSideThreadSession(parentSid, threadId, { message: question, ...title ? { title } : {} })).sessionId;
152309
+ const threadSessionId = consumed ?? (await forkSideThreadSession(parentSid, threadId, { message, ...title ? { title } : {} })).sessionId;
151945
152310
  let entry;
151946
152311
  try {
151947
152312
  const { addSideThread: addSideThread2 } = await Promise.resolve().then(() => (init_side_questions(), side_questions_exports));
@@ -151953,7 +152318,7 @@ var init_side_thread_manager = __esm({
151953
152318
  });
151954
152319
  if (consumed) {
151955
152320
  const { sendMessageToSession: sendMessageToSession2 } = await Promise.resolve().then(() => (init_session_message_queue(), session_message_queue_exports));
151956
- await sendMessageToSession2(threadSessionId, question, { source: "side-thread" });
152321
+ await sendMessageToSession2(threadSessionId, message, { source: "side-thread" });
151957
152322
  }
151958
152323
  } catch (err2) {
151959
152324
  void this.retireSession(threadSessionId, "side_thread_create_failed");
@@ -151988,6 +152353,7 @@ var init_side_thread_manager = __esm({
151988
152353
  if (!await this.isStandbyUsable(parentSid, standby)) {
151989
152354
  void this.retireSession(standby.claudeSessionId, "side_thread_standby_stale");
151990
152355
  this.standbyParentOffsets.delete(parentSid);
152356
+ this.warmedStandbys.delete(parentSid);
151991
152357
  return null;
151992
152358
  }
151993
152359
  const timer3 = this.standbyTimers.get(parentSid);
@@ -151996,6 +152362,7 @@ var init_side_thread_manager = __esm({
151996
152362
  this.standbyTimers.delete(parentSid);
151997
152363
  }
151998
152364
  this.standbyParentOffsets.delete(parentSid);
152365
+ this.warmedStandbys.delete(parentSid);
151999
152366
  const lane = sideThreadLaneKey(parentSid, threadId);
152000
152367
  const { updateSessionRecord: updateSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
152001
152368
  await updateSessionRecord2(standby.claudeSessionId, {
@@ -152182,16 +152549,30 @@ async function promoteSideThread(parentSid, threadId, opts) {
152182
152549
  const threadRecord = await getSessionByClaudeId2(sessionId);
152183
152550
  if (!threadRecord) throw new SessionControlError("Side thread session not found", 404);
152184
152551
  const parentRecord = await getSessionByClaudeId2(parentSid);
152185
- const parentTaskId = parentRecord?.taskId?.trim() || void 0;
152186
- const title = opts?.title?.trim() || entry.title?.trim() || entry.question;
152552
+ const siblingOfTaskId = parentRecord?.taskId?.trim() || void 0;
152553
+ const threadLabel = opts?.title?.trim() || entry.title?.trim() || void 0;
152187
152554
  const { addTask: addTask2, linkSession: linkSession2, linkSessionSlot: linkSessionSlot2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
152188
- const { task } = await addTask2({
152189
- title,
152190
- description: entry.question,
152191
- ...parentTaskId ? { parent_task_id: parentTaskId } : {},
152192
- // A person clicked "promote" — same board default as any hand-made task.
152193
- pinned: true
152194
- });
152555
+ let groupId;
152556
+ let task;
152557
+ if (siblingOfTaskId) {
152558
+ const created = await createForkSiblingTask(siblingOfTaskId, {
152559
+ // A known label short-circuits the background refine; without one the
152560
+ // thread's first question is what gets summarized (same as a fork's message).
152561
+ titlePrefix: threadLabel,
152562
+ prompt: entry.question,
152563
+ description: entry.question,
152564
+ source: "side-thread-promote"
152565
+ });
152566
+ task = created.task;
152567
+ groupId = created.groupId;
152568
+ } else {
152569
+ ({ task } = await addTask2({
152570
+ title: threadLabel || entry.question,
152571
+ description: entry.question,
152572
+ // A person clicked "promote" — same board default as any hand-made task.
152573
+ pinned: true
152574
+ }));
152575
+ }
152195
152576
  const stamped = await markThreadPromoted2(parentSid, threadId, task.id);
152196
152577
  if (!stamped) {
152197
152578
  log.session.warn("side thread promote: entry vanished before stamp \u2014 promote proceeds, 409 guard is unarmed", {
@@ -152205,7 +152586,7 @@ async function promoteSideThread(parentSid, threadId, opts) {
152205
152586
  project: task.project ?? "",
152206
152587
  // Clearing the lane un-hides the session (see the file header).
152207
152588
  lane: void 0,
152208
- title
152589
+ title: task.title
152209
152590
  });
152210
152591
  try {
152211
152592
  const { sessionRunner: sessionRunner2 } = await Promise.resolve().then(() => (init_claude_code_session(), claude_code_session_exports));
@@ -152224,10 +152605,18 @@ async function promoteSideThread(parentSid, threadId, opts) {
152224
152605
  error: err2 instanceof Error ? err2.message : String(err2)
152225
152606
  });
152226
152607
  }
152227
- log.session.info("side thread: promoted to task", { parentSid, threadId, sessionId, taskId: task.id });
152608
+ log.session.info("side thread: promoted to task", {
152609
+ parentSid,
152610
+ threadId,
152611
+ sessionId,
152612
+ taskId: task.id,
152613
+ siblingOfTaskId,
152614
+ groupId
152615
+ });
152228
152616
  return {
152229
152617
  taskId: task.id,
152230
- ...task.parent_task_id ? { parentTaskId: task.parent_task_id } : {},
152618
+ ...siblingOfTaskId ? { siblingOfTaskId } : {},
152619
+ ...groupId ? { groupId } : {},
152231
152620
  sessionId
152232
152621
  };
152233
152622
  }
@@ -152242,7 +152631,7 @@ var init_side_thread_promote = __esm({
152242
152631
 
152243
152632
  // src/web/routes/sessions.ts
152244
152633
  import { Router as Router16 } from "express";
152245
- import { randomUUID as randomUUID10 } from "crypto";
152634
+ import { randomUUID as randomUUID11 } from "crypto";
152246
152635
  import os27 from "os";
152247
152636
  import path142 from "path";
152248
152637
  function condenseStaleReason(msg) {
@@ -152588,7 +152977,7 @@ var init_sessions = __esm({
152588
152977
  }
152589
152978
  const isNativeEngine = !isAcpEngine(engine);
152590
152979
  const clientSessionId = typeof req.body.sessionId === "string" && UUID_RE3.test(req.body.sessionId) ? req.body.sessionId : void 0;
152591
- const preassignedSessionId = isNativeEngine ? clientSessionId ?? randomUUID10() : void 0;
152980
+ const preassignedSessionId = isNativeEngine ? clientSessionId ?? randomUUID11() : void 0;
152592
152981
  const updatedTask = await quickStartSession({
152593
152982
  message: sessionMessage,
152594
152983
  messagePrefix,
@@ -153563,18 +153952,38 @@ var init_sessions = __esm({
153563
153952
  });
153564
153953
  }
153565
153954
  });
153955
+ sessionsRouter.post("/:sessionId/side-threads/standby/warm", async (req, res) => {
153956
+ if (refuseSideThreadsOnReplica(res)) return;
153957
+ const sessionId = String(req.params.sessionId);
153958
+ try {
153959
+ const { sideThreadManager: sideThreadManager2 } = await Promise.resolve().then(() => (init_side_thread_manager(), side_thread_manager_exports));
153960
+ res.json(await sideThreadManager2.warmStandby(sessionId));
153961
+ } catch (err2) {
153962
+ log.web.warn("side thread standby warm-up failed", {
153963
+ sessionId,
153964
+ error: err2 instanceof Error ? err2.message : String(err2)
153965
+ });
153966
+ res.json({ warmed: false, reason: "error" });
153967
+ }
153968
+ });
153566
153969
  sessionsRouter.post("/:sessionId/side-threads", async (req, res, next) => {
153567
153970
  if (refuseSideThreadsOnReplica(res)) return;
153568
153971
  try {
153569
- const { question, title } = req.body ?? {};
153972
+ const { question, title, images } = req.body ?? {};
153570
153973
  if (!question || typeof question !== "string" || !question.trim()) {
153571
153974
  res.status(400).json({ error: "question (non-empty string) is required" });
153572
153975
  return;
153573
153976
  }
153977
+ let imageContext = "";
153978
+ if (images && images.length > 0) {
153979
+ const processed = await processAndSaveImages(images);
153980
+ if (processed) imageContext = buildSessionImageContext(processed.savedImages);
153981
+ }
153574
153982
  const { sideThreadManager: sideThreadManager2 } = await Promise.resolve().then(() => (init_side_thread_manager(), side_thread_manager_exports));
153575
153983
  const thread = await sideThreadManager2.createThread(String(req.params.sessionId), {
153576
153984
  question,
153577
- ...typeof title === "string" && title.trim() ? { title } : {}
153985
+ ...typeof title === "string" && title.trim() ? { title } : {},
153986
+ ...imageContext ? { imageContext } : {}
153578
153987
  });
153579
153988
  res.json({ thread });
153580
153989
  } catch (err2) {
@@ -153593,7 +154002,8 @@ var init_sessions = __esm({
153593
154002
  );
153594
154003
  res.json({
153595
154004
  taskId: result.taskId,
153596
- ...result.parentTaskId ? { parentTaskId: result.parentTaskId } : {},
154005
+ ...result.siblingOfTaskId ? { siblingOfTaskId: result.siblingOfTaskId } : {},
154006
+ ...result.groupId ? { groupId: result.groupId } : {},
153597
154007
  sessionId: result.sessionId
153598
154008
  });
153599
154009
  } catch (err2) {
@@ -154288,7 +154698,7 @@ var micro_claude_exports = {};
154288
154698
  __export(micro_claude_exports, {
154289
154699
  runMicroClaude: () => runMicroClaude
154290
154700
  });
154291
- import { randomUUID as randomUUID11 } from "crypto";
154701
+ import { randomUUID as randomUUID12 } from "crypto";
154292
154702
  async function runMicroClaude(opts) {
154293
154703
  if (opts.warm) {
154294
154704
  const run10 = await runWarmMicroClaude({
@@ -154297,7 +154707,7 @@ async function runMicroClaude(opts) {
154297
154707
  tools: opts.tools ?? [],
154298
154708
  prompt: opts.prompt,
154299
154709
  timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
154300
- toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID11()}`,
154710
+ toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID12()}`,
154301
154711
  ...opts.onBlock ? { onBlock: opts.onBlock } : {}
154302
154712
  });
154303
154713
  return { response: run10.response, costUsd: run10.costUsd, durationMs: run10.durationMs };
@@ -154307,7 +154717,7 @@ async function runMicroClaude(opts) {
154307
154717
  systemPrompt: opts.system,
154308
154718
  model: opts.model ?? DEFAULT_MODEL2,
154309
154719
  timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
154310
- toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID11()}`,
154720
+ toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID12()}`,
154311
154721
  slim: true,
154312
154722
  thinking: opts.thinking ?? false,
154313
154723
  ...opts.tools && opts.tools.length > 0 ? { tools: opts.tools } : {},
@@ -154337,7 +154747,7 @@ __export(micro_agent_exports, {
154337
154747
  async function resolveTierModel(tier) {
154338
154748
  const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
154339
154749
  const config = await getConfig2();
154340
- const provider = config.agent?.main_provider ?? "bedrock";
154750
+ const provider = resolveMainProviderName(config);
154341
154751
  const { MODEL_CATALOG: MODEL_CATALOG3 } = await Promise.resolve().then(() => (init_model_catalog(), model_catalog_exports));
154342
154752
  const entry = MODEL_CATALOG3[provider]?.find((m6) => {
154343
154753
  const id = m6.id.toLowerCase();
@@ -154406,6 +154816,7 @@ var DEFAULT_TIER, DEFAULT_MAX_TOKENS2, DEFAULT_MAX_ROUNDS, DEFAULT_TIMEOUT_MS3;
154406
154816
  var init_micro_agent = __esm({
154407
154817
  "src/agent/micro-agent.ts"() {
154408
154818
  "use strict";
154819
+ init_default_provider();
154409
154820
  DEFAULT_TIER = "sonnet";
154410
154821
  DEFAULT_MAX_TOKENS2 = 2e3;
154411
154822
  DEFAULT_MAX_ROUNDS = 3;
@@ -154421,7 +154832,7 @@ __export(task_search_agent_exports, {
154421
154832
  prewarmAgentSearchChild: () => prewarmAgentSearchChild,
154422
154833
  runTaskSearchAgent: () => runTaskSearchAgent
154423
154834
  });
154424
- import { randomUUID as randomUUID12 } from "crypto";
154835
+ import { randomUUID as randomUUID13 } from "crypto";
154425
154836
  function useCliEngine() {
154426
154837
  return process.env.WALNUT_AGENT_SEARCH_ENGINE !== "inprocess";
154427
154838
  }
@@ -154501,7 +154912,7 @@ async function claudeCliEngine(userPrompt, options) {
154501
154912
  model: options.model,
154502
154913
  timeoutMs: options.timeoutMs,
154503
154914
  tools: ["Bash"],
154504
- toolUseId: `task-search-${randomUUID12()}`,
154915
+ toolUseId: `task-search-${randomUUID13()}`,
154505
154916
  warm: true,
154506
154917
  onBlock
154507
154918
  });
@@ -155579,10 +155990,11 @@ async function buildProvidersPayload() {
155579
155990
  providers[name] = {
155580
155991
  api: prov.api,
155581
155992
  base_url: prov.base_url,
155582
- status: caps.subscriptionReady ? "ready" : "no_key",
155993
+ status: caps.installed ? "ready" : "no_key",
155583
155994
  auto_detected: !explicitNames.has(name),
155584
155995
  models: getModelsForProvider(name, prov.models),
155585
- credential_source: caps.subscriptionReady ? "subscription" : caps.installed ? "cli_no_subscription" : "cli_not_installed"
155996
+ credential_source: caps.installed ? `cli_${caps.auth.mode}` : "cli_not_installed",
155997
+ credential_detail: caps.installed ? caps.auth.label : void 0
155586
155998
  };
155587
155999
  continue;
155588
156000
  }
@@ -155641,10 +156053,11 @@ async function buildProvidersPayload() {
155641
156053
  const caps = detectClaudeCli2();
155642
156054
  providers[name] = {
155643
156055
  api: template.api,
155644
- status: caps.subscriptionReady ? "ready" : "no_key",
156056
+ status: caps.installed ? "ready" : "no_key",
155645
156057
  auto_detected: false,
155646
156058
  models: getModelsForProvider(name),
155647
- credential_source: caps.subscriptionReady ? "subscription" : caps.installed ? "cli_no_subscription" : "cli_not_installed"
156059
+ credential_source: caps.installed ? `cli_${caps.auth.mode}` : "cli_not_installed",
156060
+ credential_detail: caps.installed ? caps.auth.label : void 0
155648
156061
  };
155649
156062
  continue;
155650
156063
  }
@@ -155800,7 +156213,7 @@ var init_config11 = __esm({
155800
156213
  const {
155801
156214
  traceCredentialResolution: traceCredentialResolution2,
155802
156215
  resolveCredentials: resolveCredentials2,
155803
- readClaudeSettingsEnv: readClaudeSettingsEnv2,
156216
+ readClaudeSettingsEnv: readClaudeSettingsEnv3,
155804
156217
  readClaudeCredentialExport: readClaudeCredentialExport2
155805
156218
  } = await Promise.resolve().then(() => (init_credential_resolver(), credential_resolver_exports));
155806
156219
  const fs114 = await import("fs");
@@ -155816,7 +156229,7 @@ var init_config11 = __esm({
155816
156229
  };
155817
156230
  const trace = traceCredentialResolution2({
155818
156231
  config,
155819
- claudeEnv: readClaudeSettingsEnv2(),
156232
+ claudeEnv: readClaudeSettingsEnv3(),
155820
156233
  processEnv: process.env,
155821
156234
  awsFiles: {
155822
156235
  credentials: exists3(path185.join(home, ".aws", "credentials")),
@@ -173146,7 +173559,7 @@ __export(task_start_exports, {
173146
173559
  SessionExistsError: () => SessionExistsError,
173147
173560
  startSessionForTask: () => startSessionForTask
173148
173561
  });
173149
- import { randomUUID as randomUUID13 } from "crypto";
173562
+ import { randomUUID as randomUUID14 } from "crypto";
173150
173563
  async function startSessionForTask(params) {
173151
173564
  const { taskIdPrefix, source } = params;
173152
173565
  let task;
@@ -173197,7 +173610,7 @@ async function startSessionForTask(params) {
173197
173610
  }
173198
173611
  let message = params.message?.trim() || `Working on task: ${task.title}`;
173199
173612
  const engine = normalizeEngine(params.engine);
173200
- const preassignedSessionId = engineCaps(engine).idProvisioning === "provider-issued" ? void 0 : randomUUID13();
173613
+ const preassignedSessionId = engineCaps(engine).idProvisioning === "provider-issued" ? void 0 : randomUUID14();
173201
173614
  let requestId;
173202
173615
  if (params.expectReply) {
173203
173616
  const { resolveCaller: resolveCaller2 } = await Promise.resolve().then(() => (init_session_send_core(), session_send_core_exports));
@@ -184525,7 +184938,7 @@ async function generateLedgerDesc(task, timeoutMs = 15e3) {
184525
184938
  try {
184526
184939
  const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
184527
184940
  const config = await getConfig2();
184528
- const providerName = config.agent?.main_provider ?? "bedrock";
184941
+ const providerName = resolveMainProviderName(config);
184529
184942
  const model = cheapModelFor(providerName);
184530
184943
  const detail = (task.description ?? "").trim().slice(0, 800);
184531
184944
  const project = (task.project ?? "").trim();
@@ -184584,6 +184997,7 @@ var init_task_ledger_desc = __esm({
184584
184997
  "use strict";
184585
184998
  init_model();
184586
184999
  init_model_catalog();
185000
+ init_default_provider();
184587
185001
  init_logging();
184588
185002
  init_task_manager();
184589
185003
  MAX_DESC_LEN = 120;
@@ -184607,6 +185021,10 @@ function diffStreamedVsPersisted(streamedTextMsgIds, persistedMsgIds) {
184607
185021
  }
184608
185022
  function armStreamConvergenceCheck(sessionId, streamedTextMsgIds) {
184609
185023
  if (streamedTextMsgIds.length === 0) return;
185024
+ if (consumeWarmupTurn(sessionId)) {
185025
+ log.obs.debug("stream-convergence: skipped hidden warm-up turn", { sessionId });
185026
+ return;
185027
+ }
184610
185028
  const timer3 = setTimeout(() => {
184611
185029
  void runCheck(sessionId, streamedTextMsgIds).catch((err2) => {
184612
185030
  log.obs.warn("stream-convergence check failed", {
@@ -184672,6 +185090,7 @@ var init_stream_convergence = __esm({
184672
185090
  "src/core/observability/stream-convergence.ts"() {
184673
185091
  "use strict";
184674
185092
  init_logging();
185093
+ init_side_thread_warmup();
184675
185094
  CHECK_DELAY_MS = 15e3;
184676
185095
  INCIDENT_DEDUPE_MS = 30 * 6e4;
184677
185096
  lastIncidentAt = /* @__PURE__ */ new Map();
@@ -184922,12 +185341,17 @@ function getSystemHealth() {
184922
185341
  }
184923
185342
  async function refreshSystemHealth() {
184924
185343
  systemHealth.claudeCliAvailable = checkClaudeCliAvailable();
184925
- const cred = await resolveCredentialHealth();
185344
+ applyCredentialHealth(await resolveCredentialHealth());
185345
+ broadcastEvent("system:health", systemHealth);
185346
+ return systemHealth;
185347
+ }
185348
+ function applyCredentialHealth(cred) {
184926
185349
  systemHealth.hasReadyProvider = cred.hasReadyProvider;
184927
185350
  systemHealth.credentialSource = cred.source;
184928
185351
  systemHealth.credentialDetail = cred.detail;
184929
- broadcastEvent("system:health", systemHealth);
184930
- return systemHealth;
185352
+ systemHealth.mainProvider = cred.mainProvider;
185353
+ systemHealth.mainProviderImplicit = cred.mainProviderImplicit;
185354
+ systemHealth.claudeCliAuth = cred.claudeCliAuth;
184931
185355
  }
184932
185356
  function checkClaudeCliAvailable() {
184933
185357
  return isClaudeCliInstalled();
@@ -184937,27 +185361,36 @@ async function resolveCredentialHealth() {
184937
185361
  const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
184938
185362
  const { resolveCredentials: resolveCredentials2 } = await Promise.resolve().then(() => (init_credential_resolver(), credential_resolver_exports));
184939
185363
  const { buildProviderMap: buildProviderMap2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
185364
+ const { resolveMainProviderName: resolveMainProviderName2, mainProviderIsImplicit: mainProviderIsImplicit2 } = await Promise.resolve().then(() => (init_default_provider(), default_provider_exports));
185365
+ const { detectClaudeCli: detectClaudeCli2 } = await Promise.resolve().then(() => (init_claude_cli_detect(), claude_cli_detect_exports));
184940
185366
  const config = await getConfig2();
185367
+ const cli = detectClaudeCli2();
185368
+ const mainProvider = resolveMainProviderName2(config, cli.installed);
185369
+ const base = {
185370
+ mainProvider,
185371
+ mainProviderImplicit: mainProviderIsImplicit2(config),
185372
+ claudeCliAuth: cli.installed ? cli.auth.label : void 0
185373
+ };
185374
+ if (mainProvider === "claude_cli") {
185375
+ return cli.installed ? { ...base, hasReadyProvider: true, source: "config", detail: `claude-cli (${cli.auth.label})` } : { ...base, hasReadyProvider: false, source: "none" };
185376
+ }
184941
185377
  const cred = resolveCredentials2(config);
184942
185378
  if (cred.source !== "none") {
184943
- return { hasReadyProvider: true, source: cred.source, detail: cred.detail };
185379
+ return { ...base, hasReadyProvider: true, source: cred.source, detail: cred.detail };
184944
185380
  }
184945
185381
  const providers = buildProviderMap2(config.providers, config);
184946
185382
  for (const [, prov] of Object.entries(providers)) {
184947
185383
  if (prov.api === "bedrock" || prov.api === "ollama") continue;
184948
185384
  if (prov.api === "claude-cli") {
184949
- const { detectClaudeCli: detectClaudeCli2 } = await Promise.resolve().then(() => (init_claude_cli_detect(), claude_cli_detect_exports));
184950
- if (detectClaudeCli2().subscriptionReady) {
184951
- return { hasReadyProvider: true, source: "config", detail: "claude-cli (subscription)" };
184952
- }
185385
+ if (cli.installed) return { ...base, hasReadyProvider: true, source: "config", detail: `claude-cli (${cli.auth.label})` };
184953
185386
  continue;
184954
185387
  }
184955
185388
  const implemented = prov.api === "anthropic-messages" || prov.api === "openai-chat" || prov.api === "google-generative-ai";
184956
185389
  if (implemented && (prov.api_key || prov.bearer_token)) {
184957
- return { hasReadyProvider: true, source: "config", detail: prov.api };
185390
+ return { ...base, hasReadyProvider: true, source: "config", detail: prov.api };
184958
185391
  }
184959
185392
  }
184960
- return { hasReadyProvider: false, source: "none" };
185393
+ return { ...base, hasReadyProvider: false, source: "none" };
184961
185394
  } catch {
184962
185395
  return { hasReadyProvider: false, source: "none" };
184963
185396
  }
@@ -184996,11 +185429,11 @@ async function startServer(options = {}) {
184996
185429
  }
184997
185430
  {
184998
185431
  const cred = await resolveCredentialHealth();
184999
- systemHealth.hasReadyProvider = cred.hasReadyProvider;
185000
- systemHealth.credentialSource = cred.source;
185001
- systemHealth.credentialDetail = cred.detail;
185432
+ applyCredentialHealth(cred);
185002
185433
  if (!systemHealth.hasReadyProvider) {
185003
185434
  log.web.warn("No AI provider configured \u2014 configure one in Settings");
185435
+ } else if (cred.mainProvider === "claude_cli") {
185436
+ log.web.info("AI provider: Claude Code", { auth: cred.claudeCliAuth, implicit: cred.mainProviderImplicit });
185004
185437
  } else if (cred.source && cred.source !== "config") {
185005
185438
  log.web.info("Auto-detected Bedrock credential", { source: cred.source, detail: cred.detail });
185006
185439
  }
@@ -186280,10 +186713,9 @@ async function startServer(options = {}) {
186280
186713
  const prev = systemHealth.hasReadyProvider;
186281
186714
  const prevSource = systemHealth.credentialSource;
186282
186715
  const cred = await resolveCredentialHealth();
186283
- systemHealth.hasReadyProvider = cred.hasReadyProvider;
186284
- systemHealth.credentialSource = cred.source;
186285
- systemHealth.credentialDetail = cred.detail;
186286
- if (cred.hasReadyProvider !== prev || cred.source !== prevSource) {
186716
+ const prevProvider = systemHealth.mainProvider;
186717
+ applyCredentialHealth(cred);
186718
+ if (cred.hasReadyProvider !== prev || cred.source !== prevSource || cred.mainProvider !== prevProvider) {
186287
186719
  broadcastEvent("system:health", systemHealth);
186288
186720
  }
186289
186721
  } catch {