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.
- package/README.md +2 -5
- package/dist/cli-fast.js +69 -46
- package/dist/cli.js +882 -450
- package/dist/daemon-binaries/acp-worker.js.version +1 -1
- package/dist/integrations/calendar/index.js +42 -36
- package/dist/integrations/jira/index.js +100 -78
- package/dist/integrations/ms-todo/index.js +94 -72
- package/dist/web/server.js +930 -498
- package/dist/web/static/assets/{OfficePreview-C8OMbtiq.js → OfficePreview-DcPiG7nq.js} +2 -2
- package/dist/web/static/assets/{docx-preview-hDgtrOpv.js → docx-preview-BWSwr6o8.js} +1 -1
- package/dist/web/static/assets/index-1mQq6abn.css +32 -0
- package/dist/web/static/assets/{index-CPHXL-wy.js → index-A2G2h66W.js} +1 -1
- package/dist/web/static/assets/{index-B2-N8jTH.js → index-BWTJQD8q.js} +1 -1
- package/dist/web/static/assets/{index-CBHRNnCk.js → index-BcxyDA3F.js} +1 -1
- package/dist/web/static/assets/{index-EowLf_Xf.js → index-CEldo7Tq.js} +1 -1
- package/dist/web/static/assets/{index-KY36bfpA.js → index-CYY_N36O.js} +1 -1
- package/dist/web/static/assets/{index-ph8Hxff-.js → index-CakB6vm_.js} +1 -1
- package/dist/web/static/assets/{index-D81YKipC.js → index-Ch4LQw8-.js} +1 -1
- package/dist/web/static/assets/{index-BAb6UVet.js → index-CnmikCRj.js} +1 -1
- package/dist/web/static/assets/{index-Caj09uf7.js → index-CtytO7Ce.js} +1 -1
- package/dist/web/static/assets/{index-gv-TJwHT.js → index-CvUyfkEn.js} +1 -1
- package/dist/web/static/assets/{index-D1uUk0AQ.js → index-Cw6d8sbx.js} +289 -289
- package/dist/web/static/assets/{index-CWIydpPr.js → index-DK9AxMTA.js} +1 -1
- package/dist/web/static/assets/{index-DtRt21Zi.js → index-DPeOcZuH.js} +1 -1
- package/dist/web/static/assets/{index-nRBC-NJN.js → index-DUWox7Cm.js} +1 -1
- package/dist/web/static/assets/{index-bfZj1PAc.js → index-Zb9B6znf.js} +1 -1
- package/dist/web/static/assets/{index-C5-vT4kT.js → index-a6-uB8ph.js} +1 -1
- package/dist/web/static/assets/{index-VcSnwuHG.js → index-aAfs_cWd.js} +1 -1
- package/dist/web/static/assets/{index-B35fidy6.js → index-xpwLXfWl.js} +1 -1
- package/dist/web/static/assets/{jszip.min-DyrlNEg4.js → jszip.min-BXx8_j4Y.js} +1 -1
- package/dist/web/static/assets/{pptx-preview.es-DjND6y6O.js → pptx-preview.es-ByVfeUXp.js} +1 -1
- package/dist/web/static/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/static/assets/index-Btn4AoIy.css +0 -32
package/dist/web/server.js
CHANGED
|
@@ -2018,10 +2018,155 @@ var init_model_catalog = __esm({
|
|
|
2018
2018
|
}
|
|
2019
2019
|
});
|
|
2020
2020
|
|
|
2021
|
-
// src/core/
|
|
2022
|
-
|
|
2023
|
-
|
|
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 ??
|
|
2185
|
+
const sshConfigPath = configPath ?? path4.join(os3.homedir(), ".ssh", "config");
|
|
2041
2186
|
let mtimeMs2;
|
|
2042
2187
|
try {
|
|
2043
|
-
mtimeMs2 = (await
|
|
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
|
|
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
|
|
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
|
|
2166
|
-
|
|
2167
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2272
|
-
await
|
|
2417
|
+
await fs5.writeFile(tmp, content, "utf-8");
|
|
2418
|
+
await fs5.rename(tmp, filePath);
|
|
2273
2419
|
} catch (err2) {
|
|
2274
|
-
await
|
|
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
|
|
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
|
|
2512
|
-
//
|
|
2513
|
-
//
|
|
2514
|
-
//
|
|
2515
|
-
//
|
|
2516
|
-
agent: {
|
|
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
|
|
2671
|
+
import fs6 from "fs";
|
|
2525
2672
|
import fsp2 from "fs/promises";
|
|
2526
|
-
import
|
|
2673
|
+
import path5 from "path";
|
|
2527
2674
|
function lockDir(filePath) {
|
|
2528
2675
|
return filePath + ".lock";
|
|
2529
2676
|
}
|
|
2530
2677
|
function pidFile(lock) {
|
|
2531
|
-
return
|
|
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(
|
|
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
|
|
2620
|
-
import
|
|
2766
|
+
import fs7 from "fs/promises";
|
|
2767
|
+
import path6 from "path";
|
|
2621
2768
|
function authFilePath() {
|
|
2622
|
-
return
|
|
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
|
|
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
|
|
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
|
|
2828
|
+
await fs7.mkdir(path6.dirname(file), { recursive: true });
|
|
2682
2829
|
const tmp = `${file}.tmp-${process.pid}`;
|
|
2683
|
-
await
|
|
2684
|
-
await
|
|
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
|
|
2688
|
-
await
|
|
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
|
|
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
|
|
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
|
|
4897
|
-
import
|
|
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 ||
|
|
5124
|
+
return process.env.OPEN_WALNUT_HOME || path7.join(os4.homedir(), ".open-walnut");
|
|
4978
5125
|
}
|
|
4979
5126
|
function isInsideWalnutDataDir(filePath) {
|
|
4980
|
-
const dataDir2 =
|
|
4981
|
-
const abs =
|
|
4982
|
-
return abs === dataDir2 || abs.startsWith(dataDir2 +
|
|
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 =
|
|
5018
|
-
let dir2 =
|
|
5019
|
-
if (dir2 !== boundary && !dir2.startsWith(boundary +
|
|
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(
|
|
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 =
|
|
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 =
|
|
5059
|
-
const 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 ??
|
|
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 =
|
|
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(
|
|
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
|
|
5149
|
-
import
|
|
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 =
|
|
5153
|
-
await
|
|
5154
|
-
const tmpFile =
|
|
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
|
|
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
|
|
5164
|
-
if (options.mode !== void 0) await
|
|
5310
|
+
await fs8.rename(tmpFile, filePath);
|
|
5311
|
+
if (options.mode !== void 0) await fs8.chmod(filePath, options.mode);
|
|
5165
5312
|
} catch (err2) {
|
|
5166
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
5390
|
-
import
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
5425
|
-
|
|
5426
|
-
|
|
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
|
|
5593
|
+
import path11 from "path";
|
|
5447
5594
|
function sweepDirs() {
|
|
5448
5595
|
return [
|
|
5449
5596
|
WALNUT_HOME,
|
|
5450
5597
|
SYNC_DIR,
|
|
5451
|
-
|
|
5452
|
-
|
|
5598
|
+
path11.join(WALNUT_HOME, "sessions"),
|
|
5599
|
+
path11.join(WALNUT_HOME, "sessions", "transcripts"),
|
|
5453
5600
|
TASKS_DIR,
|
|
5454
|
-
|
|
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 =
|
|
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
|
|
5659
|
+
import fs10 from "fs";
|
|
5513
5660
|
async function migrateLegacyMemoryFile() {
|
|
5514
5661
|
try {
|
|
5515
|
-
if (
|
|
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
|
|
6061
|
-
import
|
|
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
|
-
|
|
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 (!
|
|
6299
|
-
|
|
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 =
|
|
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
|
|
6973
|
-
import
|
|
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 (!
|
|
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 =
|
|
7288
|
+
const backupPath = path13.join(path13.dirname(TASKS_FILE), "tasks.json.migrated-from-json.backup");
|
|
7142
7289
|
try {
|
|
7143
|
-
|
|
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
|
|
7314
|
+
import fs13 from "fs";
|
|
7168
7315
|
import fsp7 from "fs/promises";
|
|
7169
|
-
import
|
|
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
|
|
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 =
|
|
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 =
|
|
7203
|
-
if (
|
|
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 =
|
|
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 =
|
|
7213
|
-
if (!
|
|
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 =
|
|
7228
|
-
if (
|
|
7229
|
-
await fsp7.rename(
|
|
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:
|
|
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 (!
|
|
7244
|
-
if (
|
|
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 =
|
|
7396
|
+
const targetDir = path14.join(PROJECTS_MEMORY_DIR, c8.project);
|
|
7250
7397
|
const label = `${c8.category}/${c8.project}`;
|
|
7251
7398
|
try {
|
|
7252
|
-
if (!
|
|
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 =
|
|
7258
|
-
const targetMem =
|
|
7259
|
-
if (!
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
8745
|
-
DELTA_FILE =
|
|
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
|
|
8864
|
-
import
|
|
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 =
|
|
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(
|
|
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 =
|
|
9033
|
+
let dir2 = path16.dirname(fileURLToPath2(import.meta.url));
|
|
8887
9034
|
for (let i8 = 0; i8 < 10; i8++) {
|
|
8888
|
-
if (
|
|
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 =
|
|
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
|
|
9237
|
-
import
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
9410
|
+
let dir2 = path17.dirname(fromPath);
|
|
9264
9411
|
for (let i8 = 0; i8 < 5; i8++) {
|
|
9265
|
-
const binDir =
|
|
9412
|
+
const binDir = path17.join(dir2, "daemon-binaries");
|
|
9266
9413
|
try {
|
|
9267
|
-
for (const name of
|
|
9414
|
+
for (const name of fs15.readdirSync(binDir)) {
|
|
9268
9415
|
if (!name.endsWith(".version")) continue;
|
|
9269
|
-
const v3 =
|
|
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 =
|
|
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
|
-
//
|
|
10203
|
-
//
|
|
10204
|
-
// before
|
|
10205
|
-
// it (usually orphan-poll ESRCH \u2192 code -1). Verified
|
|
10206
|
-
// line was a control_response (the reply to a
|
|
10207
|
-
// a routine shape), so isTurnCompleteExit said
|
|
10208
|
-
// the projection painted a red "Error" over a
|
|
10209
|
-
//
|
|
10210
|
-
// information the tail does not contain,
|
|
10211
|
-
//
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
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,
|
|
10364
|
+
jsonlAgeMs, cleanExit, intentionalStopAt: session.intentionalStopAt,
|
|
10217
10365
|
});
|
|
10218
10366
|
code = 0;
|
|
10219
|
-
reason = reason + '+intentional-
|
|
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.
|
|
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
|
|
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
|
|
16544
|
+
import fs17 from "fs";
|
|
16383
16545
|
import fsp8 from "fs/promises";
|
|
16384
|
-
import
|
|
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
|
|
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
|
|
16677
|
-
if (
|
|
16678
|
-
logger2("info", "reapSession: intentional
|
|
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
|
-
|
|
16847
|
+
intentionalStopAt: session2.intentionalStopAt
|
|
16686
16848
|
});
|
|
16687
16849
|
code = 0;
|
|
16688
|
-
reason = reason + "+intentional-
|
|
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: () =>
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
67813
|
-
|
|
67814
|
-
|
|
67815
|
-
|
|
67816
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
77955
|
-
|
|
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({
|
|
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
|
|
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 =
|
|
78988
|
-
const summaryUuid =
|
|
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
|
-
...
|
|
89836
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
|
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
|
-
|
|
115948
|
-
|
|
115949
|
-
|
|
115950
|
-
|
|
115951
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
127042
|
-
|
|
127043
|
-
|
|
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
|
|
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-${
|
|
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
|
|
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 :
|
|
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.
|
|
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
|
-
},
|
|
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
|
|
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,
|
|
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
|
|
152186
|
-
const
|
|
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
|
-
|
|
152189
|
-
|
|
152190
|
-
|
|
152191
|
-
|
|
152192
|
-
|
|
152193
|
-
|
|
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", {
|
|
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
|
-
...
|
|
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
|
|
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 ??
|
|
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.
|
|
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
|
|
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-${
|
|
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-${
|
|
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
|
|
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
|
|
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-${
|
|
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.
|
|
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.
|
|
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.
|
|
156056
|
+
status: caps.installed ? "ready" : "no_key",
|
|
155645
156057
|
auto_detected: false,
|
|
155646
156058
|
models: getModelsForProvider(name),
|
|
155647
|
-
credential_source: caps.
|
|
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:
|
|
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:
|
|
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
|
|
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 :
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
184930
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186284
|
-
|
|
186285
|
-
|
|
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 {
|