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/cli.js
CHANGED
|
@@ -2996,10 +2996,155 @@ var init_model_catalog = __esm({
|
|
|
2996
2996
|
}
|
|
2997
2997
|
});
|
|
2998
2998
|
|
|
2999
|
-
// src/core/
|
|
3000
|
-
|
|
3001
|
-
|
|
2999
|
+
// src/core/claude-cli-detect.ts
|
|
3000
|
+
var claude_cli_detect_exports = {};
|
|
3001
|
+
__export(claude_cli_detect_exports, {
|
|
3002
|
+
_CLAUDE_CREDENTIALS_HOME: () => _CLAUDE_CREDENTIALS_HOME,
|
|
3003
|
+
describeClaudeCliAuth: () => describeClaudeCliAuth,
|
|
3004
|
+
detectClaudeCli: () => detectClaudeCli,
|
|
3005
|
+
hasClaudeSubscriptionAuth: () => hasClaudeSubscriptionAuth,
|
|
3006
|
+
isClaudeCliInstalled: () => isClaudeCliInstalled,
|
|
3007
|
+
resolveClaudeCliExecutable: () => resolveClaudeCliExecutable
|
|
3008
|
+
});
|
|
3009
|
+
import { execFileSync } from "child_process";
|
|
3010
|
+
import fs6 from "fs";
|
|
3002
3011
|
import os3 from "os";
|
|
3012
|
+
import path8 from "path";
|
|
3013
|
+
function resolveClaudeCliExecutable(env3 = process.env) {
|
|
3014
|
+
const home = env3.HOME || os3.homedir();
|
|
3015
|
+
const pathDirs = (env3.PATH ?? "").split(path8.delimiter).filter(Boolean);
|
|
3016
|
+
const fallbackDirs = [
|
|
3017
|
+
path8.join(home, ".toolbox", "bin"),
|
|
3018
|
+
path8.join(home, ".local", "bin"),
|
|
3019
|
+
path8.join(home, ".npm-global", "bin"),
|
|
3020
|
+
path8.join(home, ".bun", "bin"),
|
|
3021
|
+
"/usr/local/bin",
|
|
3022
|
+
"/opt/homebrew/bin",
|
|
3023
|
+
"/usr/bin",
|
|
3024
|
+
"/bin"
|
|
3025
|
+
];
|
|
3026
|
+
for (const dir2 of [.../* @__PURE__ */ new Set([...pathDirs, ...fallbackDirs])]) {
|
|
3027
|
+
const candidate = path8.join(dir2, "claude");
|
|
3028
|
+
try {
|
|
3029
|
+
fs6.accessSync(candidate, fs6.constants.X_OK);
|
|
3030
|
+
return candidate;
|
|
3031
|
+
} catch {
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
return null;
|
|
3035
|
+
}
|
|
3036
|
+
function isClaudeCliInstalled() {
|
|
3037
|
+
return resolveClaudeCliExecutable() !== null;
|
|
3038
|
+
}
|
|
3039
|
+
function hasClaudeSubscriptionAuth() {
|
|
3040
|
+
if (process.platform === "darwin") {
|
|
3041
|
+
try {
|
|
3042
|
+
execFileSync("security", ["find-generic-password", "-s", KEYCHAIN_OAUTH_SERVICE], {
|
|
3043
|
+
stdio: "ignore"
|
|
3044
|
+
});
|
|
3045
|
+
return true;
|
|
3046
|
+
} catch {
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
return fileCredentialExists();
|
|
3050
|
+
}
|
|
3051
|
+
function fileCredentialExists() {
|
|
3052
|
+
try {
|
|
3053
|
+
return fs6.existsSync(CLAUDE_CREDENTIALS_FILE);
|
|
3054
|
+
} catch {
|
|
3055
|
+
return false;
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
function detectClaudeCli() {
|
|
3059
|
+
const installed2 = isClaudeCliInstalled();
|
|
3060
|
+
const subscriptionAuth = installed2 && hasClaudeSubscriptionAuth();
|
|
3061
|
+
return {
|
|
3062
|
+
installed: installed2,
|
|
3063
|
+
subscriptionAuth,
|
|
3064
|
+
subscriptionReady: installed2 && subscriptionAuth,
|
|
3065
|
+
ready: installed2,
|
|
3066
|
+
auth: installed2 ? describeClaudeCliAuth() : { mode: "unknown", label: "not installed" }
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
function describeClaudeCliAuth(env3 = process.env, settingsEnv = readClaudeSettingsEnv()) {
|
|
3070
|
+
const merged = { ...env3, ...settingsEnv };
|
|
3071
|
+
const truthy = (v3) => !!v3 && v3 !== "0" && v3.toLowerCase() !== "false";
|
|
3072
|
+
if (truthy(merged.CLAUDE_CODE_USE_BEDROCK)) {
|
|
3073
|
+
const region = merged.AWS_REGION || merged.AWS_DEFAULT_REGION;
|
|
3074
|
+
return { mode: "bedrock", label: region ? `Bedrock (${region})` : "Bedrock" };
|
|
3075
|
+
}
|
|
3076
|
+
if (truthy(merged.CLAUDE_CODE_USE_VERTEX)) return { mode: "vertex", label: "Vertex AI" };
|
|
3077
|
+
if (merged.ANTHROPIC_API_KEY || merged.ANTHROPIC_AUTH_TOKEN) return { mode: "api-key", label: "an Anthropic API key" };
|
|
3078
|
+
if (hasClaudeSubscriptionAuth()) return { mode: "subscription", label: "your Claude subscription" };
|
|
3079
|
+
return { mode: "unknown", label: "its own login" };
|
|
3080
|
+
}
|
|
3081
|
+
function readClaudeSettingsEnv() {
|
|
3082
|
+
try {
|
|
3083
|
+
const raw = fs6.readFileSync(path8.join(os3.homedir(), ".claude", "settings.json"), "utf8");
|
|
3084
|
+
const env3 = JSON.parse(raw).env ?? {};
|
|
3085
|
+
return Object.fromEntries(Object.entries(env3).filter(([, v3]) => typeof v3 === "string"));
|
|
3086
|
+
} catch {
|
|
3087
|
+
return {};
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
var KEYCHAIN_OAUTH_SERVICE, _CLAUDE_CREDENTIALS_HOME;
|
|
3091
|
+
var init_claude_cli_detect = __esm({
|
|
3092
|
+
"src/core/claude-cli-detect.ts"() {
|
|
3093
|
+
"use strict";
|
|
3094
|
+
init_constants();
|
|
3095
|
+
KEYCHAIN_OAUTH_SERVICE = "Claude Code-credentials";
|
|
3096
|
+
_CLAUDE_CREDENTIALS_HOME = os3.homedir();
|
|
3097
|
+
}
|
|
3098
|
+
});
|
|
3099
|
+
|
|
3100
|
+
// src/agent/providers/default-provider.ts
|
|
3101
|
+
var default_provider_exports = {};
|
|
3102
|
+
__export(default_provider_exports, {
|
|
3103
|
+
CLAUDE_CLI_PROVIDER: () => CLAUDE_CLI_PROVIDER,
|
|
3104
|
+
LEGACY_DEFAULT_PROVIDER: () => LEGACY_DEFAULT_PROVIDER,
|
|
3105
|
+
_resetDefaultProviderCacheForTesting: () => _resetDefaultProviderCacheForTesting,
|
|
3106
|
+
hasExplicitBedrockConfig: () => hasExplicitBedrockConfig,
|
|
3107
|
+
mainProviderIsImplicit: () => mainProviderIsImplicit,
|
|
3108
|
+
resolveMainProviderName: () => resolveMainProviderName
|
|
3109
|
+
});
|
|
3110
|
+
function claudeCliInstalledCached() {
|
|
3111
|
+
const now = Date.now();
|
|
3112
|
+
if (!cliProbe || now - cliProbe.at > CLI_PROBE_TTL_MS) {
|
|
3113
|
+
cliProbe = { at: now, installed: isClaudeCliInstalled() };
|
|
3114
|
+
}
|
|
3115
|
+
return cliProbe.installed;
|
|
3116
|
+
}
|
|
3117
|
+
function _resetDefaultProviderCacheForTesting() {
|
|
3118
|
+
cliProbe = void 0;
|
|
3119
|
+
}
|
|
3120
|
+
function hasExplicitBedrockConfig(config) {
|
|
3121
|
+
const b8 = config.providers?.bedrock;
|
|
3122
|
+
if (b8 && (b8.bearer_token || b8.aws_access_key_id || b8.aws_profile || b8.aws_credential_export)) return true;
|
|
3123
|
+
return !!config.provider?.bedrock_bearer_token;
|
|
3124
|
+
}
|
|
3125
|
+
function resolveMainProviderName(config, claudeInstalled = claudeCliInstalledCached()) {
|
|
3126
|
+
if (config.agent?.main_provider) return config.agent.main_provider;
|
|
3127
|
+
if (claudeInstalled) return CLAUDE_CLI_PROVIDER;
|
|
3128
|
+
return LEGACY_DEFAULT_PROVIDER;
|
|
3129
|
+
}
|
|
3130
|
+
function mainProviderIsImplicit(config) {
|
|
3131
|
+
return !config.agent?.main_provider;
|
|
3132
|
+
}
|
|
3133
|
+
var LEGACY_DEFAULT_PROVIDER, CLAUDE_CLI_PROVIDER, CLI_PROBE_TTL_MS, cliProbe;
|
|
3134
|
+
var init_default_provider = __esm({
|
|
3135
|
+
"src/agent/providers/default-provider.ts"() {
|
|
3136
|
+
"use strict";
|
|
3137
|
+
init_claude_cli_detect();
|
|
3138
|
+
LEGACY_DEFAULT_PROVIDER = "bedrock";
|
|
3139
|
+
CLAUDE_CLI_PROVIDER = "claude_cli";
|
|
3140
|
+
CLI_PROBE_TTL_MS = 6e4;
|
|
3141
|
+
}
|
|
3142
|
+
});
|
|
3143
|
+
|
|
3144
|
+
// src/core/ssh-config-scanner.ts
|
|
3145
|
+
import fs7 from "fs/promises";
|
|
3146
|
+
import path9 from "path";
|
|
3147
|
+
import os4 from "os";
|
|
3003
3148
|
function isGitServerHost(h8) {
|
|
3004
3149
|
if (h8.user === "git") return true;
|
|
3005
3150
|
const gitPrefix = /^(git|github|gitlab|bitbucket|codecommit|ssh\.github)\./i;
|
|
@@ -3015,10 +3160,10 @@ function shortLabelForFqdn(hostname) {
|
|
|
3015
3160
|
return `${first.slice(0, 12)}\u2026${first.slice(-8)}`;
|
|
3016
3161
|
}
|
|
3017
3162
|
async function scanSshConfig(configPath) {
|
|
3018
|
-
const sshConfigPath = configPath ??
|
|
3163
|
+
const sshConfigPath = configPath ?? path9.join(os4.homedir(), ".ssh", "config");
|
|
3019
3164
|
let mtimeMs2;
|
|
3020
3165
|
try {
|
|
3021
|
-
mtimeMs2 = (await
|
|
3166
|
+
mtimeMs2 = (await fs7.stat(sshConfigPath)).mtimeMs;
|
|
3022
3167
|
} catch {
|
|
3023
3168
|
return /* @__PURE__ */ new Map();
|
|
3024
3169
|
}
|
|
@@ -3031,7 +3176,7 @@ async function scanSshConfig(configPath) {
|
|
|
3031
3176
|
async function parseSshConfigFile(sshConfigPath) {
|
|
3032
3177
|
const discovered = /* @__PURE__ */ new Map();
|
|
3033
3178
|
try {
|
|
3034
|
-
const content = await
|
|
3179
|
+
const content = await fs7.readFile(sshConfigPath, "utf-8");
|
|
3035
3180
|
const lines = content.split("\n");
|
|
3036
3181
|
let currentHost = null;
|
|
3037
3182
|
for (const rawLine of lines) {
|
|
@@ -3132,23 +3277,24 @@ __export(config_manager_exports, {
|
|
|
3132
3277
|
updatePushTokens: () => updatePushTokens,
|
|
3133
3278
|
writeFileAtomic: () => writeFileAtomic2
|
|
3134
3279
|
});
|
|
3135
|
-
import
|
|
3280
|
+
import fs8 from "fs/promises";
|
|
3136
3281
|
import yaml from "js-yaml";
|
|
3137
|
-
function resolveAgentEngineProvider(config) {
|
|
3282
|
+
function resolveAgentEngineProvider(config, claudeInstalled) {
|
|
3138
3283
|
if (CLOUD_MODE) return "walnut-agent";
|
|
3139
3284
|
const raw = config.agent?.provider;
|
|
3140
3285
|
if (typeof raw === "string" && VALID_AGENT_ENGINE_PROVIDERS.has(raw)) {
|
|
3141
3286
|
return raw;
|
|
3142
3287
|
}
|
|
3143
|
-
if (raw
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
using: FALLBACK_AGENT_ENGINE_PROVIDER,
|
|
3147
|
-
valid: [...VALID_AGENT_ENGINE_PROVIDERS].join(", ")
|
|
3148
|
-
});
|
|
3149
|
-
return FALLBACK_AGENT_ENGINE_PROVIDER;
|
|
3288
|
+
if (raw === void 0) {
|
|
3289
|
+
const main = claudeInstalled === void 0 ? resolveMainProviderName(config) : resolveMainProviderName(config, claudeInstalled);
|
|
3290
|
+
return main === CLAUDE_CLI_PROVIDER ? "claude-code" : "walnut-agent";
|
|
3150
3291
|
}
|
|
3151
|
-
|
|
3292
|
+
log.session.warn("config-manager: agent.provider is not a known engine \u2014 using the fallback", {
|
|
3293
|
+
value: typeof raw === "string" ? raw : `<${typeof raw}>`,
|
|
3294
|
+
using: FALLBACK_AGENT_ENGINE_PROVIDER,
|
|
3295
|
+
valid: [...VALID_AGENT_ENGINE_PROVIDERS].join(", ")
|
|
3296
|
+
});
|
|
3297
|
+
return FALLBACK_AGENT_ENGINE_PROVIDER;
|
|
3152
3298
|
}
|
|
3153
3299
|
function migrateConfigToProjectOnly(config) {
|
|
3154
3300
|
let changed = false;
|
|
@@ -3221,13 +3367,13 @@ function migrateConfigToProjectOnly(config) {
|
|
|
3221
3367
|
}
|
|
3222
3368
|
async function readRawConfigContent() {
|
|
3223
3369
|
try {
|
|
3224
|
-
const primary = await
|
|
3370
|
+
const primary = await fs8.readFile(CONFIG_FILE, "utf-8");
|
|
3225
3371
|
if (primary.trim()) return primary;
|
|
3226
3372
|
throw new Error("config.yaml is empty");
|
|
3227
3373
|
} catch (primaryErr) {
|
|
3228
3374
|
let backup;
|
|
3229
3375
|
try {
|
|
3230
|
-
backup = await
|
|
3376
|
+
backup = await fs8.readFile(CONFIG_BACKUP_FILE, "utf-8");
|
|
3231
3377
|
} catch {
|
|
3232
3378
|
return null;
|
|
3233
3379
|
}
|
|
@@ -3237,7 +3383,7 @@ async function readRawConfigContent() {
|
|
|
3237
3383
|
backup: CONFIG_BACKUP_FILE
|
|
3238
3384
|
});
|
|
3239
3385
|
try {
|
|
3240
|
-
await
|
|
3386
|
+
await fs8.writeFile(CONFIG_FILE, backup, "utf-8");
|
|
3241
3387
|
} catch {
|
|
3242
3388
|
}
|
|
3243
3389
|
return backup;
|
|
@@ -3246,10 +3392,10 @@ async function readRawConfigContent() {
|
|
|
3246
3392
|
async function writeFileAtomic2(filePath, content) {
|
|
3247
3393
|
const tmp = `${filePath}.tmp-${process.pid}-${Date.now().toString(36)}`;
|
|
3248
3394
|
try {
|
|
3249
|
-
await
|
|
3250
|
-
await
|
|
3395
|
+
await fs8.writeFile(tmp, content, "utf-8");
|
|
3396
|
+
await fs8.rename(tmp, filePath);
|
|
3251
3397
|
} catch (err2) {
|
|
3252
|
-
await
|
|
3398
|
+
await fs8.rm(tmp, { force: true }).catch(() => {
|
|
3253
3399
|
});
|
|
3254
3400
|
throw err2;
|
|
3255
3401
|
}
|
|
@@ -3447,7 +3593,7 @@ async function seedConfigDefaults() {
|
|
|
3447
3593
|
const config = await getConfig();
|
|
3448
3594
|
let needsWrite = false;
|
|
3449
3595
|
try {
|
|
3450
|
-
const raw = await
|
|
3596
|
+
const raw = await fs8.readFile(CONFIG_FILE, "utf-8");
|
|
3451
3597
|
if (!raw.includes("available_models")) {
|
|
3452
3598
|
needsWrite = true;
|
|
3453
3599
|
}
|
|
@@ -3475,6 +3621,7 @@ var init_config_manager = __esm({
|
|
|
3475
3621
|
init_constants();
|
|
3476
3622
|
init_types();
|
|
3477
3623
|
init_model_catalog();
|
|
3624
|
+
init_default_provider();
|
|
3478
3625
|
init_ssh_config_scanner();
|
|
3479
3626
|
DEFAULT_CONFIG = {
|
|
3480
3627
|
version: 1,
|
|
@@ -3486,12 +3633,12 @@ var init_config_manager = __esm({
|
|
|
3486
3633
|
// so this never re-routes an established setup.
|
|
3487
3634
|
defaults: { priority: "none", platform: "local" },
|
|
3488
3635
|
provider: { type: "claude-code" },
|
|
3489
|
-
// Which ENGINE answers a Personal AI chat turn
|
|
3490
|
-
//
|
|
3491
|
-
//
|
|
3492
|
-
//
|
|
3493
|
-
//
|
|
3494
|
-
agent: {
|
|
3636
|
+
// Which ENGINE answers a Personal AI chat turn is NOT defaulted here: it follows
|
|
3637
|
+
// the AI provider (Settings → AI Provider) — see resolveAgentEngineProvider().
|
|
3638
|
+
// getConfig() spreads the parsed file OVER these defaults at the TOP level, so
|
|
3639
|
+
// anything put in this object is dropped by any config.yaml with an `agent:`
|
|
3640
|
+
// section; the resolver is what actually applies the rule.
|
|
3641
|
+
agent: {}
|
|
3495
3642
|
};
|
|
3496
3643
|
CONFIG_BACKUP_FILE = `${CONFIG_FILE}.bak`;
|
|
3497
3644
|
writeLock = Promise.resolve();
|
|
@@ -3500,15 +3647,15 @@ var init_config_manager = __esm({
|
|
|
3500
3647
|
|
|
3501
3648
|
// src/core/tmp-sweep.ts
|
|
3502
3649
|
import fsp5 from "fs/promises";
|
|
3503
|
-
import
|
|
3650
|
+
import path10 from "path";
|
|
3504
3651
|
function sweepDirs() {
|
|
3505
3652
|
return [
|
|
3506
3653
|
WALNUT_HOME,
|
|
3507
3654
|
SYNC_DIR,
|
|
3508
|
-
|
|
3509
|
-
|
|
3655
|
+
path10.join(WALNUT_HOME, "sessions"),
|
|
3656
|
+
path10.join(WALNUT_HOME, "sessions", "transcripts"),
|
|
3510
3657
|
TASKS_DIR,
|
|
3511
|
-
|
|
3658
|
+
path10.join(TASKS_DIR, "outbox"),
|
|
3512
3659
|
CONVERSATIONS_DIR,
|
|
3513
3660
|
MEMORY_DIR,
|
|
3514
3661
|
DAILY_DIR,
|
|
@@ -3529,7 +3676,7 @@ async function sweepOrphanAtomicTmpFiles() {
|
|
|
3529
3676
|
const entries = await fsp5.readdir(dir2, { withFileTypes: true });
|
|
3530
3677
|
for (const entry of entries) {
|
|
3531
3678
|
if (!entry.isFile() || !ORPHAN_RE.test(entry.name)) continue;
|
|
3532
|
-
const full =
|
|
3679
|
+
const full = path10.join(dir2, entry.name);
|
|
3533
3680
|
try {
|
|
3534
3681
|
const st = await fsp5.stat(full);
|
|
3535
3682
|
if (st.mtimeMs > cutoff) continue;
|
|
@@ -3566,10 +3713,10 @@ __export(init_exports, {
|
|
|
3566
3713
|
initDirectories: () => initDirectories
|
|
3567
3714
|
});
|
|
3568
3715
|
import fsp6 from "fs/promises";
|
|
3569
|
-
import
|
|
3716
|
+
import fs9 from "fs";
|
|
3570
3717
|
async function migrateLegacyMemoryFile() {
|
|
3571
3718
|
try {
|
|
3572
|
-
if (
|
|
3719
|
+
if (fs9.existsSync(LEGACY_MEMORY_FILE) && !fs9.existsSync(MEMORY_FILE)) {
|
|
3573
3720
|
await fsp6.rename(LEGACY_MEMORY_FILE, MEMORY_FILE);
|
|
3574
3721
|
log.memory.info("Migrated MEMORY.md into memory/ folder", { from: LEGACY_MEMORY_FILE, to: MEMORY_FILE });
|
|
3575
3722
|
}
|
|
@@ -4524,15 +4671,15 @@ __export(task_db_exports, {
|
|
|
4524
4671
|
transaction: () => transaction
|
|
4525
4672
|
});
|
|
4526
4673
|
import Database from "better-sqlite3";
|
|
4527
|
-
import
|
|
4528
|
-
import
|
|
4674
|
+
import fs10 from "fs";
|
|
4675
|
+
import path11 from "path";
|
|
4529
4676
|
import yaml2 from "js-yaml";
|
|
4530
4677
|
function getDb() {
|
|
4531
4678
|
if (db) return db;
|
|
4532
4679
|
if (initAttempted) throw initError;
|
|
4533
4680
|
initAttempted = true;
|
|
4534
4681
|
try {
|
|
4535
|
-
|
|
4682
|
+
fs10.mkdirSync(path11.dirname(TASK_DB_PATH), { recursive: true });
|
|
4536
4683
|
const handle2 = new Database(TASK_DB_PATH);
|
|
4537
4684
|
handle2.pragma("journal_mode = WAL");
|
|
4538
4685
|
handle2.pragma("busy_timeout = 5000");
|
|
@@ -4762,8 +4909,8 @@ function migrateToProjectOnly(handle2) {
|
|
|
4762
4909
|
}
|
|
4763
4910
|
try {
|
|
4764
4911
|
handle2.pragma("wal_checkpoint(TRUNCATE)");
|
|
4765
|
-
if (!
|
|
4766
|
-
|
|
4912
|
+
if (!fs10.existsSync(TASK_DB_PRE_V5_BACKUP_PATH)) {
|
|
4913
|
+
fs10.copyFileSync(TASK_DB_PATH, TASK_DB_PRE_V5_BACKUP_PATH);
|
|
4767
4914
|
log.task.info("task-db v5: wrote pre-migration backup", { path: TASK_DB_PRE_V5_BACKUP_PATH });
|
|
4768
4915
|
}
|
|
4769
4916
|
} catch (err2) {
|
|
@@ -5273,7 +5420,7 @@ var init_task_db = __esm({
|
|
|
5273
5420
|
init_constants();
|
|
5274
5421
|
init_logging();
|
|
5275
5422
|
init_phase2();
|
|
5276
|
-
TASK_DB_PATH =
|
|
5423
|
+
TASK_DB_PATH = path11.join(TASKS_DIR, "tasks.sqlite");
|
|
5277
5424
|
EXPLICIT_TASK_COLUMNS = [
|
|
5278
5425
|
"id",
|
|
5279
5426
|
"title",
|
|
@@ -5436,8 +5583,8 @@ var init_task_db = __esm({
|
|
|
5436
5583
|
});
|
|
5437
5584
|
|
|
5438
5585
|
// src/core/task-db-migration.ts
|
|
5439
|
-
import
|
|
5440
|
-
import
|
|
5586
|
+
import fs11 from "fs";
|
|
5587
|
+
import path12 from "path";
|
|
5441
5588
|
import yaml3 from "js-yaml";
|
|
5442
5589
|
async function runMigrationIfNeeded() {
|
|
5443
5590
|
const db5 = getDb();
|
|
@@ -5448,7 +5595,7 @@ async function runMigrationIfNeeded() {
|
|
|
5448
5595
|
if (existing.n > 0) {
|
|
5449
5596
|
return { migrated: false, count: existing.n };
|
|
5450
5597
|
}
|
|
5451
|
-
if (!
|
|
5598
|
+
if (!fs11.existsSync(TASKS_FILE)) {
|
|
5452
5599
|
return { migrated: false, count: 0 };
|
|
5453
5600
|
}
|
|
5454
5601
|
const store3 = await readJsonFile(TASKS_FILE, { version: 1, tasks: [] });
|
|
@@ -5605,9 +5752,9 @@ async function runMigrationIfNeeded() {
|
|
|
5605
5752
|
}
|
|
5606
5753
|
const after = db5.prepare("SELECT COUNT(*) AS n FROM tasks").get();
|
|
5607
5754
|
const count2 = after.n;
|
|
5608
|
-
const backupPath =
|
|
5755
|
+
const backupPath = path12.join(path12.dirname(TASKS_FILE), "tasks.json.migrated-from-json.backup");
|
|
5609
5756
|
try {
|
|
5610
|
-
|
|
5757
|
+
fs11.copyFileSync(TASKS_FILE, backupPath);
|
|
5611
5758
|
} catch (err2) {
|
|
5612
5759
|
log.task.warn("task-db migration: backup copy failed (non-fatal)", {
|
|
5613
5760
|
path: backupPath,
|
|
@@ -5631,9 +5778,9 @@ var init_task_db_migration = __esm({
|
|
|
5631
5778
|
});
|
|
5632
5779
|
|
|
5633
5780
|
// src/core/memory-dir-migration.ts
|
|
5634
|
-
import
|
|
5781
|
+
import fs12 from "fs";
|
|
5635
5782
|
import fsp7 from "fs/promises";
|
|
5636
|
-
import
|
|
5783
|
+
import path13 from "path";
|
|
5637
5784
|
function bodyOf(content) {
|
|
5638
5785
|
const fm = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
|
|
5639
5786
|
return fm ? content.slice(fm[0].length) : content;
|
|
@@ -5651,7 +5798,7 @@ ${legacyBody}
|
|
|
5651
5798
|
}
|
|
5652
5799
|
function mtimeMs(file) {
|
|
5653
5800
|
try {
|
|
5654
|
-
return
|
|
5801
|
+
return fs12.statSync(file).mtimeMs;
|
|
5655
5802
|
} catch {
|
|
5656
5803
|
return 0;
|
|
5657
5804
|
}
|
|
@@ -5660,24 +5807,24 @@ function findCandidates(root6) {
|
|
|
5660
5807
|
const out = [];
|
|
5661
5808
|
let level1;
|
|
5662
5809
|
try {
|
|
5663
|
-
level1 =
|
|
5810
|
+
level1 = fs12.readdirSync(root6, { withFileTypes: true });
|
|
5664
5811
|
} catch {
|
|
5665
5812
|
return out;
|
|
5666
5813
|
}
|
|
5667
5814
|
for (const cat of level1) {
|
|
5668
5815
|
if (!cat.isDirectory()) continue;
|
|
5669
|
-
const catDir =
|
|
5670
|
-
if (
|
|
5816
|
+
const catDir = path13.join(root6, cat.name);
|
|
5817
|
+
if (fs12.existsSync(path13.join(catDir, MEMORY_FILE_NAME))) continue;
|
|
5671
5818
|
let level2;
|
|
5672
5819
|
try {
|
|
5673
|
-
level2 =
|
|
5820
|
+
level2 = fs12.readdirSync(catDir, { withFileTypes: true });
|
|
5674
5821
|
} catch {
|
|
5675
5822
|
continue;
|
|
5676
5823
|
}
|
|
5677
5824
|
for (const proj of level2) {
|
|
5678
5825
|
if (!proj.isDirectory()) continue;
|
|
5679
|
-
const sourceDir =
|
|
5680
|
-
if (!
|
|
5826
|
+
const sourceDir = path13.join(catDir, proj.name);
|
|
5827
|
+
if (!fs12.existsSync(path13.join(sourceDir, MEMORY_FILE_NAME))) continue;
|
|
5681
5828
|
out.push({ category: cat.name, project: proj.name, sourceDir });
|
|
5682
5829
|
}
|
|
5683
5830
|
}
|
|
@@ -5691,11 +5838,11 @@ async function drainDir(from, to) {
|
|
|
5691
5838
|
return;
|
|
5692
5839
|
}
|
|
5693
5840
|
for (const name of entries) {
|
|
5694
|
-
const target =
|
|
5695
|
-
if (
|
|
5696
|
-
await fsp7.rename(
|
|
5841
|
+
const target = path13.join(to, name);
|
|
5842
|
+
if (fs12.existsSync(target)) continue;
|
|
5843
|
+
await fsp7.rename(path13.join(from, name), target).catch((err2) => {
|
|
5697
5844
|
log.memory.warn("memory-dir migration: entry move failed", {
|
|
5698
|
-
from:
|
|
5845
|
+
from: path13.join(from, name),
|
|
5699
5846
|
to: target,
|
|
5700
5847
|
error: String(err2)
|
|
5701
5848
|
});
|
|
@@ -5707,23 +5854,23 @@ async function drainDir(from, to) {
|
|
|
5707
5854
|
async function migrateProjectMemoryDirs() {
|
|
5708
5855
|
const result = { ran: false, moved: 0, merged: 0 };
|
|
5709
5856
|
try {
|
|
5710
|
-
if (!
|
|
5711
|
-
if (
|
|
5857
|
+
if (!fs12.existsSync(PROJECTS_MEMORY_DIR)) return result;
|
|
5858
|
+
if (fs12.existsSync(MEMORY_DIR_MIGRATION_MARKER)) return result;
|
|
5712
5859
|
result.ran = true;
|
|
5713
5860
|
const candidates = findCandidates(PROJECTS_MEMORY_DIR);
|
|
5714
5861
|
const touchedCategories = new Set(candidates.map((c8) => c8.category));
|
|
5715
5862
|
for (const c8 of candidates) {
|
|
5716
|
-
const targetDir =
|
|
5863
|
+
const targetDir = path13.join(PROJECTS_MEMORY_DIR, c8.project);
|
|
5717
5864
|
const label = `${c8.category}/${c8.project}`;
|
|
5718
5865
|
try {
|
|
5719
|
-
if (!
|
|
5866
|
+
if (!fs12.existsSync(targetDir)) {
|
|
5720
5867
|
await fsp7.rename(c8.sourceDir, targetDir);
|
|
5721
5868
|
result.moved += 1;
|
|
5722
5869
|
continue;
|
|
5723
5870
|
}
|
|
5724
|
-
const sourceMem =
|
|
5725
|
-
const targetMem =
|
|
5726
|
-
if (!
|
|
5871
|
+
const sourceMem = path13.join(c8.sourceDir, MEMORY_FILE_NAME);
|
|
5872
|
+
const targetMem = path13.join(targetDir, MEMORY_FILE_NAME);
|
|
5873
|
+
if (!fs12.existsSync(targetMem)) {
|
|
5727
5874
|
await fsp7.rename(sourceMem, targetMem);
|
|
5728
5875
|
await drainDir(c8.sourceDir, targetDir);
|
|
5729
5876
|
result.moved += 1;
|
|
@@ -5745,7 +5892,7 @@ async function migrateProjectMemoryDirs() {
|
|
|
5745
5892
|
}
|
|
5746
5893
|
}
|
|
5747
5894
|
for (const cat of touchedCategories) {
|
|
5748
|
-
await fsp7.rmdir(
|
|
5895
|
+
await fsp7.rmdir(path13.join(PROJECTS_MEMORY_DIR, cat)).catch(() => {
|
|
5749
5896
|
});
|
|
5750
5897
|
}
|
|
5751
5898
|
await fsp7.writeFile(
|
|
@@ -5773,7 +5920,7 @@ var init_memory_dir_migration = __esm({
|
|
|
5773
5920
|
"use strict";
|
|
5774
5921
|
init_constants();
|
|
5775
5922
|
init_logging();
|
|
5776
|
-
MEMORY_DIR_MIGRATION_MARKER =
|
|
5923
|
+
MEMORY_DIR_MIGRATION_MARKER = path13.join(PROJECTS_MEMORY_DIR, ".migrated-project-only");
|
|
5777
5924
|
MEMORY_FILE_NAME = "MEMORY.md";
|
|
5778
5925
|
}
|
|
5779
5926
|
});
|
|
@@ -5989,7 +6136,7 @@ __export(microsoft_todo_exports, {
|
|
|
5989
6136
|
});
|
|
5990
6137
|
import * as msal from "@azure/msal-node";
|
|
5991
6138
|
import https from "https";
|
|
5992
|
-
import
|
|
6139
|
+
import path14 from "path";
|
|
5993
6140
|
function getMsTodoConfig(config) {
|
|
5994
6141
|
return config.plugins?.["ms-todo"];
|
|
5995
6142
|
}
|
|
@@ -7208,8 +7355,8 @@ var init_microsoft_todo = __esm({
|
|
|
7208
7355
|
init_format();
|
|
7209
7356
|
init_task_manager();
|
|
7210
7357
|
init_task_remote_links();
|
|
7211
|
-
TOKENS_FILE =
|
|
7212
|
-
DELTA_FILE =
|
|
7358
|
+
TOKENS_FILE = path14.join(SYNC_DIR, "ms-todo-tokens.json");
|
|
7359
|
+
DELTA_FILE = path14.join(SYNC_DIR, "ms-todo-delta.json");
|
|
7213
7360
|
GRAPH_BASE = "https://graph.microsoft.com/v1.0";
|
|
7214
7361
|
SCOPES = ["Tasks.ReadWrite"];
|
|
7215
7362
|
PRIORITY_TO_IMPORTANCE = {
|
|
@@ -7332,8 +7479,8 @@ __export(daemon_version_check_exports, {
|
|
|
7332
7479
|
verifyDaemonBinaryVersion: () => verifyDaemonBinaryVersion
|
|
7333
7480
|
});
|
|
7334
7481
|
import { createHash } from "crypto";
|
|
7335
|
-
import
|
|
7336
|
-
import
|
|
7482
|
+
import fs13 from "fs";
|
|
7483
|
+
import path15 from "path";
|
|
7337
7484
|
import { spawnSync } from "child_process";
|
|
7338
7485
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7339
7486
|
function computeExpectedDaemonVersion() {
|
|
@@ -7342,11 +7489,11 @@ function computeExpectedDaemonVersion() {
|
|
|
7342
7489
|
const hash = createHash("sha256");
|
|
7343
7490
|
const NUL = Buffer.from([0]);
|
|
7344
7491
|
for (const rel of DAEMON_SOURCE_FILES) {
|
|
7345
|
-
const abs =
|
|
7492
|
+
const abs = path15.join(repoRoot, rel);
|
|
7346
7493
|
try {
|
|
7347
7494
|
hash.update(Buffer.from(rel));
|
|
7348
7495
|
hash.update(NUL);
|
|
7349
|
-
hash.update(
|
|
7496
|
+
hash.update(fs13.readFileSync(abs));
|
|
7350
7497
|
hash.update(NUL);
|
|
7351
7498
|
} catch {
|
|
7352
7499
|
return null;
|
|
@@ -7355,12 +7502,12 @@ function computeExpectedDaemonVersion() {
|
|
|
7355
7502
|
return "walnut-daemon-" + hash.digest("hex").slice(0, 12);
|
|
7356
7503
|
}
|
|
7357
7504
|
function findRepoRoot() {
|
|
7358
|
-
let dir2 =
|
|
7505
|
+
let dir2 = path15.dirname(fileURLToPath2(import.meta.url));
|
|
7359
7506
|
for (let i8 = 0; i8 < 10; i8++) {
|
|
7360
|
-
if (
|
|
7507
|
+
if (fs13.existsSync(path15.join(dir2, "package.json")) && fs13.existsSync(path15.join(dir2, "src", "providers", "daemon-standalone.ts"))) {
|
|
7361
7508
|
return dir2;
|
|
7362
7509
|
}
|
|
7363
|
-
const parent =
|
|
7510
|
+
const parent = path15.dirname(dir2);
|
|
7364
7511
|
if (parent === dir2) break;
|
|
7365
7512
|
dir2 = parent;
|
|
7366
7513
|
}
|
|
@@ -7369,14 +7516,14 @@ function findRepoRoot() {
|
|
|
7369
7516
|
function bunAvailable() {
|
|
7370
7517
|
const home = process.env.HOME ?? "";
|
|
7371
7518
|
const candidates = [
|
|
7372
|
-
...(process.env.PATH ?? "").split(
|
|
7373
|
-
|
|
7519
|
+
...(process.env.PATH ?? "").split(path15.delimiter).filter(Boolean).map((d8) => path15.join(d8, "bun")),
|
|
7520
|
+
path15.join(home, ".bun", "bin", "bun"),
|
|
7374
7521
|
"/opt/homebrew/bin/bun",
|
|
7375
7522
|
"/usr/local/bin/bun"
|
|
7376
7523
|
];
|
|
7377
7524
|
return candidates.some((p6) => {
|
|
7378
7525
|
try {
|
|
7379
|
-
|
|
7526
|
+
fs13.accessSync(p6, fs13.constants.X_OK);
|
|
7380
7527
|
return true;
|
|
7381
7528
|
} catch {
|
|
7382
7529
|
return false;
|
|
@@ -7386,10 +7533,10 @@ function bunAvailable() {
|
|
|
7386
7533
|
function readBuiltVersions() {
|
|
7387
7534
|
const out = /* @__PURE__ */ new Map();
|
|
7388
7535
|
try {
|
|
7389
|
-
for (const f8 of
|
|
7536
|
+
for (const f8 of fs13.readdirSync(DAEMON_BINARIES_DIR)) {
|
|
7390
7537
|
if (!f8.endsWith(".version")) continue;
|
|
7391
7538
|
const arch = f8.slice(0, -".version".length);
|
|
7392
|
-
const v3 =
|
|
7539
|
+
const v3 = fs13.readFileSync(path15.join(DAEMON_BINARIES_DIR, f8), "utf-8").trim();
|
|
7393
7540
|
if (v3) out.set(arch, v3);
|
|
7394
7541
|
}
|
|
7395
7542
|
} catch {
|
|
@@ -7849,12 +7996,12 @@ var init_daemon_fold = __esm({
|
|
|
7849
7996
|
});
|
|
7850
7997
|
|
|
7851
7998
|
// src/providers/daemon-source.ts
|
|
7852
|
-
import
|
|
7853
|
-
import
|
|
7999
|
+
import fs14 from "fs";
|
|
8000
|
+
import path16 from "path";
|
|
7854
8001
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7855
8002
|
function resolveDaemonSourceVersion() {
|
|
7856
8003
|
const here = fileURLToPath3(import.meta.url);
|
|
7857
|
-
const bundled = here.split(
|
|
8004
|
+
const bundled = here.split(path16.sep).includes("dist");
|
|
7858
8005
|
if (bundled) {
|
|
7859
8006
|
const sidecar = readSidecarDaemonVersion(here);
|
|
7860
8007
|
if (sidecar) return sidecar;
|
|
@@ -7862,32 +8009,32 @@ function resolveDaemonSourceVersion() {
|
|
|
7862
8009
|
const computed = computeExpectedDaemonVersion();
|
|
7863
8010
|
if (computed) return computed;
|
|
7864
8011
|
if (process.env.DAEMON_VERSION) return process.env.DAEMON_VERSION;
|
|
7865
|
-
let dir2 =
|
|
8012
|
+
let dir2 = path16.dirname(fileURLToPath3(import.meta.url));
|
|
7866
8013
|
for (let i8 = 0; i8 < 10; i8++) {
|
|
7867
8014
|
try {
|
|
7868
|
-
const pkg = JSON.parse(
|
|
8015
|
+
const pkg = JSON.parse(fs14.readFileSync(path16.join(dir2, "package.json"), "utf-8"));
|
|
7869
8016
|
if (pkg.name === "open-walnut" && pkg.version) return `walnut-daemon-pkg-${pkg.version}`;
|
|
7870
8017
|
} catch {
|
|
7871
8018
|
}
|
|
7872
|
-
const parent =
|
|
8019
|
+
const parent = path16.dirname(dir2);
|
|
7873
8020
|
if (parent === dir2) break;
|
|
7874
8021
|
dir2 = parent;
|
|
7875
8022
|
}
|
|
7876
8023
|
return "dev-source";
|
|
7877
8024
|
}
|
|
7878
8025
|
function readSidecarDaemonVersion(fromPath) {
|
|
7879
|
-
let dir2 =
|
|
8026
|
+
let dir2 = path16.dirname(fromPath);
|
|
7880
8027
|
for (let i8 = 0; i8 < 5; i8++) {
|
|
7881
|
-
const binDir =
|
|
8028
|
+
const binDir = path16.join(dir2, "daemon-binaries");
|
|
7882
8029
|
try {
|
|
7883
|
-
for (const name of
|
|
8030
|
+
for (const name of fs14.readdirSync(binDir)) {
|
|
7884
8031
|
if (!name.endsWith(".version")) continue;
|
|
7885
|
-
const v3 =
|
|
8032
|
+
const v3 = fs14.readFileSync(path16.join(binDir, name), "utf-8").trim();
|
|
7886
8033
|
if (v3) return v3;
|
|
7887
8034
|
}
|
|
7888
8035
|
} catch {
|
|
7889
8036
|
}
|
|
7890
|
-
const parent =
|
|
8037
|
+
const parent = path16.dirname(dir2);
|
|
7891
8038
|
if (parent === dir2) break;
|
|
7892
8039
|
dir2 = parent;
|
|
7893
8040
|
}
|
|
@@ -8815,24 +8962,25 @@ function reapSession(sid, code, reason) {
|
|
|
8815
8962
|
let jsonlAgeMs = null;
|
|
8816
8963
|
try { jsonlAgeMs = Date.now() - fs.statSync(session.jsonlPath).mtimeMs; } catch {}
|
|
8817
8964
|
const cleanExit = isTurnCompleteExit(session.jsonlPath);
|
|
8818
|
-
//
|
|
8819
|
-
//
|
|
8820
|
-
// before
|
|
8821
|
-
// it (usually orphan-poll ESRCH \u2192 code -1). Verified
|
|
8822
|
-
// line was a control_response (the reply to a
|
|
8823
|
-
// a routine shape), so isTurnCompleteExit said
|
|
8824
|
-
// the projection painted a red "Error" over a
|
|
8825
|
-
//
|
|
8826
|
-
// information the tail does not contain,
|
|
8827
|
-
//
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8965
|
+
// A DELIBERATE stop is not a failure, and the JSONL tail cannot tell us it
|
|
8966
|
+
// happened. Both writers (our idle scanner, and any stop RPC via cmdStop)
|
|
8967
|
+
// stamp intentionalStopAt before signalling; the death then lands here through
|
|
8968
|
+
// whichever path notices it (usually orphan-poll ESRCH \u2192 code -1). Verified
|
|
8969
|
+
// production case: the last line was a control_response (the reply to a
|
|
8970
|
+
// Walnut-issued control request \u2014 a routine shape), so isTurnCompleteExit said
|
|
8971
|
+
// false, exitCode stayed -1, and the projection painted a red "Error" over a
|
|
8972
|
+
// healthy, fully resumable session that was stopped on purpose. Checked FIRST
|
|
8973
|
+
// and unconditionally: the flag carries information the tail does not contain,
|
|
8974
|
+
// which is why isTurnCompleteExit is left alone.
|
|
8975
|
+
// Keep in sync with daemon-core.ts reapSession.
|
|
8976
|
+
const intentionalStop = session.intentionalStopAt != null;
|
|
8977
|
+
if (intentionalStop && code !== 0) {
|
|
8978
|
+
logMsg('info', 'reapSession: intentional stop, normalizing exit code', {
|
|
8831
8979
|
sid, pid: session.pid, originalCode: code, originalReason: reason,
|
|
8832
|
-
jsonlAgeMs, cleanExit,
|
|
8980
|
+
jsonlAgeMs, cleanExit, intentionalStopAt: session.intentionalStopAt,
|
|
8833
8981
|
});
|
|
8834
8982
|
code = 0;
|
|
8835
|
-
reason = reason + '+intentional-
|
|
8983
|
+
reason = reason + '+intentional-stop';
|
|
8836
8984
|
} else if (code !== 0 && cleanExit) {
|
|
8837
8985
|
logMsg('info', 'reapSession: turn-complete detected, normalizing exit code', {
|
|
8838
8986
|
sid, pid: session.pid, originalCode: code, originalReason: reason, jsonlAgeMs,
|
|
@@ -12486,6 +12634,15 @@ function cmdStop(ws, id, cmd) {
|
|
|
12486
12634
|
const pid = session.pid;
|
|
12487
12635
|
logMsg('info', 'cmdStop: stopping session (process group kill)', { sid, pid });
|
|
12488
12636
|
|
|
12637
|
+
// Nobody sends stop by accident, so record the intent BEFORE the first signal:
|
|
12638
|
+
// the death lands asynchronously (orphan poll \u2192 ESRCH \u2192 code -1) and by then
|
|
12639
|
+
// nothing remembers this was asked for. This path is how the SERVER's idle
|
|
12640
|
+
// reaper kills (mgr.kill() \u2192 stop RPC), not the daemon's own scan, so it needs
|
|
12641
|
+
// its own stamp: a LOCAL session auto-stopped after 2h idle surfaced as
|
|
12642
|
+
// "Session ended unexpectedly and no cause was recorded" (2026-09-03).
|
|
12643
|
+
// Keep in sync with daemon-standalone.ts cmdStop.
|
|
12644
|
+
session.intentionalStopAt = Date.now();
|
|
12645
|
+
|
|
12489
12646
|
// An explicit stop is a human decision \u2014 never undone by a pending auto-retry
|
|
12490
12647
|
// respawning the CLI minutes later. Cancel the timer AND clear the streak so
|
|
12491
12648
|
// an in-flight result line can't re-arm one either.
|
|
@@ -12522,7 +12679,7 @@ function cmdStop(ws, id, cmd) {
|
|
|
12522
12679
|
|
|
12523
12680
|
// \u2500\u2500 Status \u2500\u2500
|
|
12524
12681
|
function cmdStatus(ws, id, cmd) {
|
|
12525
|
-
const { sid } = cmd;
|
|
12682
|
+
const { sid, includeArgs } = cmd;
|
|
12526
12683
|
if (!sid) return sendError(ws, id, 'status: missing sid');
|
|
12527
12684
|
|
|
12528
12685
|
const session = sessions.get(sid);
|
|
@@ -12553,6 +12710,11 @@ function cmdStatus(ws, id, cmd) {
|
|
|
12553
12710
|
exitCode: session.exitCode,
|
|
12554
12711
|
exitReason: session.exitReason,
|
|
12555
12712
|
pendingCtrl: session.pendingCtrl,
|
|
12713
|
+
// Opt-in: the argv the live process was launched with (a fork copies it to
|
|
12714
|
+
// reproduce the parent's prompt-cache prefix). Off by default because the
|
|
12715
|
+
// health monitor polls status for every session and the argv carries the
|
|
12716
|
+
// multi-KB --append-system-prompt.
|
|
12717
|
+
...(includeArgs && session.args && session.args.length > 0 ? { args: session.args } : {}),
|
|
12556
12718
|
});
|
|
12557
12719
|
}
|
|
12558
12720
|
|
|
@@ -14605,7 +14767,7 @@ function scanIdleSessions() {
|
|
|
14605
14767
|
// end" for any session whose last line isn't a type:result \u2014 and the
|
|
14606
14768
|
// projection then shows a red Error for our own routine reclamation.
|
|
14607
14769
|
// Keep in sync with daemon-standalone.ts scanIdleSessions.
|
|
14608
|
-
session.
|
|
14770
|
+
session.intentionalStopAt = now;
|
|
14609
14771
|
killSessionProcessGroup(pid, sid);
|
|
14610
14772
|
}
|
|
14611
14773
|
}
|
|
@@ -14986,7 +15148,7 @@ if (action === '--start') {
|
|
|
14986
15148
|
});
|
|
14987
15149
|
|
|
14988
15150
|
// src/core/jsonl-tailer.ts
|
|
14989
|
-
import
|
|
15151
|
+
import fs15 from "fs";
|
|
14990
15152
|
var init_jsonl_tailer = __esm({
|
|
14991
15153
|
"src/core/jsonl-tailer.ts"() {
|
|
14992
15154
|
"use strict";
|
|
@@ -14995,10 +15157,10 @@ var init_jsonl_tailer = __esm({
|
|
|
14995
15157
|
});
|
|
14996
15158
|
|
|
14997
15159
|
// src/providers/session-io.ts
|
|
14998
|
-
import
|
|
15160
|
+
import fs16 from "fs";
|
|
14999
15161
|
import fsp8 from "fs/promises";
|
|
15000
|
-
import
|
|
15001
|
-
import { execFileSync } from "child_process";
|
|
15162
|
+
import path17 from "path";
|
|
15163
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
15002
15164
|
function shellQuote(s4) {
|
|
15003
15165
|
return "'" + s4.replace(/'/g, "'\\''") + "'";
|
|
15004
15166
|
}
|
|
@@ -15043,7 +15205,7 @@ function findRelativeImageNames(text, opts) {
|
|
|
15043
15205
|
function findLocalImagePaths(text) {
|
|
15044
15206
|
return findImagePaths(text).filter((p6) => {
|
|
15045
15207
|
try {
|
|
15046
|
-
return
|
|
15208
|
+
return fs16.statSync(p6).isFile();
|
|
15047
15209
|
} catch {
|
|
15048
15210
|
return false;
|
|
15049
15211
|
}
|
|
@@ -15289,19 +15451,19 @@ function createDaemonCore(deps) {
|
|
|
15289
15451
|
jsonlAgeMs = clock() - fs121.statSync(session2.jsonlPath).mtimeMs;
|
|
15290
15452
|
} catch {
|
|
15291
15453
|
}
|
|
15292
|
-
const
|
|
15293
|
-
if (
|
|
15294
|
-
logger2("info", "reapSession: intentional
|
|
15454
|
+
const intentionalStop = session2.intentionalStopAt != null;
|
|
15455
|
+
if (intentionalStop && code !== 0) {
|
|
15456
|
+
logger2("info", "reapSession: intentional stop, normalizing exit code", {
|
|
15295
15457
|
sid,
|
|
15296
15458
|
pid: session2.pid,
|
|
15297
15459
|
originalCode: code,
|
|
15298
15460
|
originalReason: reason,
|
|
15299
15461
|
jsonlAgeMs,
|
|
15300
15462
|
cleanExit,
|
|
15301
|
-
|
|
15463
|
+
intentionalStopAt: session2.intentionalStopAt
|
|
15302
15464
|
});
|
|
15303
15465
|
code = 0;
|
|
15304
|
-
reason = reason + "+intentional-
|
|
15466
|
+
reason = reason + "+intentional-stop";
|
|
15305
15467
|
} else if (cleanExit && code !== 0) {
|
|
15306
15468
|
logger2("info", "reapSession: turn-complete detected, normalizing exit code", {
|
|
15307
15469
|
sid,
|
|
@@ -16088,83 +16250,6 @@ var init_daemon_core = __esm({
|
|
|
16088
16250
|
}
|
|
16089
16251
|
});
|
|
16090
16252
|
|
|
16091
|
-
// src/core/claude-cli-detect.ts
|
|
16092
|
-
var claude_cli_detect_exports = {};
|
|
16093
|
-
__export(claude_cli_detect_exports, {
|
|
16094
|
-
_CLAUDE_CREDENTIALS_HOME: () => _CLAUDE_CREDENTIALS_HOME,
|
|
16095
|
-
detectClaudeCli: () => detectClaudeCli,
|
|
16096
|
-
hasClaudeSubscriptionAuth: () => hasClaudeSubscriptionAuth,
|
|
16097
|
-
isClaudeCliInstalled: () => isClaudeCliInstalled,
|
|
16098
|
-
resolveClaudeCliExecutable: () => resolveClaudeCliExecutable
|
|
16099
|
-
});
|
|
16100
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
16101
|
-
import fs16 from "fs";
|
|
16102
|
-
import os4 from "os";
|
|
16103
|
-
import path17 from "path";
|
|
16104
|
-
function resolveClaudeCliExecutable(env3 = process.env) {
|
|
16105
|
-
const home = env3.HOME || os4.homedir();
|
|
16106
|
-
const pathDirs = (env3.PATH ?? "").split(path17.delimiter).filter(Boolean);
|
|
16107
|
-
const fallbackDirs = [
|
|
16108
|
-
path17.join(home, ".toolbox", "bin"),
|
|
16109
|
-
path17.join(home, ".local", "bin"),
|
|
16110
|
-
path17.join(home, ".npm-global", "bin"),
|
|
16111
|
-
path17.join(home, ".bun", "bin"),
|
|
16112
|
-
"/usr/local/bin",
|
|
16113
|
-
"/opt/homebrew/bin",
|
|
16114
|
-
"/usr/bin",
|
|
16115
|
-
"/bin"
|
|
16116
|
-
];
|
|
16117
|
-
for (const dir2 of [.../* @__PURE__ */ new Set([...pathDirs, ...fallbackDirs])]) {
|
|
16118
|
-
const candidate = path17.join(dir2, "claude");
|
|
16119
|
-
try {
|
|
16120
|
-
fs16.accessSync(candidate, fs16.constants.X_OK);
|
|
16121
|
-
return candidate;
|
|
16122
|
-
} catch {
|
|
16123
|
-
}
|
|
16124
|
-
}
|
|
16125
|
-
return null;
|
|
16126
|
-
}
|
|
16127
|
-
function isClaudeCliInstalled() {
|
|
16128
|
-
return resolveClaudeCliExecutable() !== null;
|
|
16129
|
-
}
|
|
16130
|
-
function hasClaudeSubscriptionAuth() {
|
|
16131
|
-
if (process.platform === "darwin") {
|
|
16132
|
-
try {
|
|
16133
|
-
execFileSync2("security", ["find-generic-password", "-s", KEYCHAIN_OAUTH_SERVICE], {
|
|
16134
|
-
stdio: "ignore"
|
|
16135
|
-
});
|
|
16136
|
-
return true;
|
|
16137
|
-
} catch {
|
|
16138
|
-
}
|
|
16139
|
-
}
|
|
16140
|
-
return fileCredentialExists();
|
|
16141
|
-
}
|
|
16142
|
-
function fileCredentialExists() {
|
|
16143
|
-
try {
|
|
16144
|
-
return fs16.existsSync(CLAUDE_CREDENTIALS_FILE);
|
|
16145
|
-
} catch {
|
|
16146
|
-
return false;
|
|
16147
|
-
}
|
|
16148
|
-
}
|
|
16149
|
-
function detectClaudeCli() {
|
|
16150
|
-
const installed2 = isClaudeCliInstalled();
|
|
16151
|
-
const subscriptionAuth = installed2 && hasClaudeSubscriptionAuth();
|
|
16152
|
-
return {
|
|
16153
|
-
installed: installed2,
|
|
16154
|
-
subscriptionAuth,
|
|
16155
|
-
subscriptionReady: installed2 && subscriptionAuth
|
|
16156
|
-
};
|
|
16157
|
-
}
|
|
16158
|
-
var KEYCHAIN_OAUTH_SERVICE, _CLAUDE_CREDENTIALS_HOME;
|
|
16159
|
-
var init_claude_cli_detect = __esm({
|
|
16160
|
-
"src/core/claude-cli-detect.ts"() {
|
|
16161
|
-
"use strict";
|
|
16162
|
-
init_constants();
|
|
16163
|
-
KEYCHAIN_OAUTH_SERVICE = "Claude Code-credentials";
|
|
16164
|
-
_CLAUDE_CREDENTIALS_HOME = os4.homedir();
|
|
16165
|
-
}
|
|
16166
|
-
});
|
|
16167
|
-
|
|
16168
16253
|
// src/providers/daemon-ownership.ts
|
|
16169
16254
|
import fs17 from "fs";
|
|
16170
16255
|
import os5 from "os";
|
|
@@ -23524,7 +23609,7 @@ __export(credential_resolver_exports, {
|
|
|
23524
23609
|
DEFAULT_REGION: () => DEFAULT_REGION,
|
|
23525
23610
|
hasResolvableCredential: () => hasResolvableCredential,
|
|
23526
23611
|
readClaudeCredentialExport: () => readClaudeCredentialExport,
|
|
23527
|
-
readClaudeSettingsEnv: () =>
|
|
23612
|
+
readClaudeSettingsEnv: () => readClaudeSettingsEnv2,
|
|
23528
23613
|
resolveCredentialFrom: () => resolveCredentialFrom,
|
|
23529
23614
|
resolveCredentials: () => resolveCredentials,
|
|
23530
23615
|
traceCredentialResolution: () => traceCredentialResolution
|
|
@@ -23679,7 +23764,7 @@ function traceCredentialResolution(inputs) {
|
|
|
23679
23764
|
region: { value: winner.region ?? DEFAULT_REGION, source: regionSource }
|
|
23680
23765
|
};
|
|
23681
23766
|
}
|
|
23682
|
-
function
|
|
23767
|
+
function readClaudeSettingsEnv2(settingsFile = CLAUDE_SETTINGS_FILE) {
|
|
23683
23768
|
try {
|
|
23684
23769
|
const raw = fs30.readFileSync(settingsFile, "utf-8");
|
|
23685
23770
|
const parsed = JSON.parse(raw);
|
|
@@ -23719,7 +23804,7 @@ function probeAwsFiles() {
|
|
|
23719
23804
|
function resolveCredentials(config) {
|
|
23720
23805
|
const result = resolveCredentialFrom({
|
|
23721
23806
|
config,
|
|
23722
|
-
claudeEnv:
|
|
23807
|
+
claudeEnv: readClaudeSettingsEnv2(),
|
|
23723
23808
|
processEnv: process.env,
|
|
23724
23809
|
awsFiles: probeAwsFiles(),
|
|
23725
23810
|
claudeCredentialExport: readClaudeCredentialExport()
|
|
@@ -66501,10 +66586,9 @@ function buildArgs(opts) {
|
|
|
66501
66586
|
// Input stays default (text): we write the whole prompt to stdin and close.
|
|
66502
66587
|
// (stream-json INPUT would force us into a JSON envelope for no gain here.)
|
|
66503
66588
|
"--tools",
|
|
66504
|
-
""
|
|
66589
|
+
""
|
|
66505
66590
|
// disable ALL built-in tools (deny-all)
|
|
66506
|
-
|
|
66507
|
-
FORCE_SUBSCRIPTION_SETTINGS
|
|
66591
|
+
// No --settings override: the user's settings.json (and so their auth) applies as-is.
|
|
66508
66592
|
];
|
|
66509
66593
|
let systemText = systemToText(opts.system).trim();
|
|
66510
66594
|
if (opts.tools && opts.tools.length > 0) {
|
|
@@ -66533,7 +66617,7 @@ function systemToText(system) {
|
|
|
66533
66617
|
}
|
|
66534
66618
|
function buildSpawnEnv(base = process.env) {
|
|
66535
66619
|
const env3 = { ...base };
|
|
66536
|
-
for (const key2 of
|
|
66620
|
+
for (const key2 of ENV_TO_STRIP) {
|
|
66537
66621
|
delete env3[key2];
|
|
66538
66622
|
}
|
|
66539
66623
|
env3.CLAUDE_CODE_ENTRYPOINT = WALNUT_UTILITY_ENTRYPOINT;
|
|
@@ -66595,7 +66679,7 @@ function normalizeCliModel(model) {
|
|
|
66595
66679
|
}
|
|
66596
66680
|
return void 0;
|
|
66597
66681
|
}
|
|
66598
|
-
var
|
|
66682
|
+
var ENV_TO_STRIP, MAX_CONCURRENT_CLI, CLI_TURN_TIMEOUT_MS, SESSION_REUSE_TTL_MS, MAX_TRACKED_SESSIONS, ClaudeCliAdapter;
|
|
66599
66683
|
var init_adapter_claude_cli = __esm({
|
|
66600
66684
|
"src/agent/providers/adapter-claude-cli.ts"() {
|
|
66601
66685
|
"use strict";
|
|
@@ -66605,24 +66689,11 @@ var init_adapter_claude_cli = __esm({
|
|
|
66605
66689
|
init_logging();
|
|
66606
66690
|
init_claude_cli_detect();
|
|
66607
66691
|
init_claude_cli_protocol();
|
|
66608
|
-
|
|
66609
|
-
|
|
66610
|
-
|
|
66611
|
-
|
|
66612
|
-
|
|
66613
|
-
"AWS_ACCESS_KEY_ID",
|
|
66614
|
-
"AWS_SECRET_ACCESS_KEY",
|
|
66615
|
-
"AWS_SESSION_TOKEN",
|
|
66616
|
-
"AWS_PROFILE",
|
|
66617
|
-
"CLAUDE_CODE_USE_BEDROCK",
|
|
66618
|
-
"CLAUDE_CODE_USE_VERTEX",
|
|
66619
|
-
// Nested-session marker — inline-subagent strips this too so the child doesn't
|
|
66620
|
-
// think it's running inside another Claude session.
|
|
66621
|
-
"CLAUDECODE"
|
|
66622
|
-
];
|
|
66623
|
-
FORCE_SUBSCRIPTION_SETTINGS = JSON.stringify({
|
|
66624
|
-
env: { CLAUDE_CODE_USE_BEDROCK: "", CLAUDE_CODE_USE_VERTEX: "" }
|
|
66625
|
-
});
|
|
66692
|
+
ENV_TO_STRIP = ["CLAUDECODE"];
|
|
66693
|
+
MAX_CONCURRENT_CLI = (() => {
|
|
66694
|
+
const n6 = Number(process.env.WALNUT_CLAUDE_CLI_CONCURRENCY);
|
|
66695
|
+
return Number.isFinite(n6) && n6 > 0 ? Math.floor(n6) : 3;
|
|
66696
|
+
})();
|
|
66626
66697
|
CLI_TURN_TIMEOUT_MS = 12e4;
|
|
66627
66698
|
SESSION_REUSE_TTL_MS = 30 * 60 * 1e3;
|
|
66628
66699
|
MAX_TRACKED_SESSIONS = 50;
|
|
@@ -66630,6 +66701,25 @@ var init_adapter_claude_cli = __esm({
|
|
|
66630
66701
|
protocol = "claude-cli";
|
|
66631
66702
|
/** conversationKey → CLI session for --resume chaining. In-process only. */
|
|
66632
66703
|
sessions = /* @__PURE__ */ new Map();
|
|
66704
|
+
/** Process gate: MAX_CONCURRENT_CLI turns at once, the rest queue in order. */
|
|
66705
|
+
inFlight = 0;
|
|
66706
|
+
waiters = [];
|
|
66707
|
+
async withSlot(fn) {
|
|
66708
|
+
if (this.inFlight >= MAX_CONCURRENT_CLI) {
|
|
66709
|
+
await new Promise((resolve) => this.waiters.push(resolve));
|
|
66710
|
+
}
|
|
66711
|
+
this.inFlight++;
|
|
66712
|
+
try {
|
|
66713
|
+
return await fn();
|
|
66714
|
+
} finally {
|
|
66715
|
+
this.inFlight--;
|
|
66716
|
+
this.waiters.shift()?.();
|
|
66717
|
+
}
|
|
66718
|
+
}
|
|
66719
|
+
/** Test hook: how many turns are running / waiting right now. */
|
|
66720
|
+
_gateStateForTesting() {
|
|
66721
|
+
return { inFlight: this.inFlight, waiting: this.waiters.length };
|
|
66722
|
+
}
|
|
66633
66723
|
resetClient() {
|
|
66634
66724
|
this.sessions.clear();
|
|
66635
66725
|
}
|
|
@@ -66707,7 +66797,7 @@ User: ${overridePrompt}` : "");
|
|
|
66707
66797
|
}
|
|
66708
66798
|
if (!prompt.trim()) prompt = "(continue)";
|
|
66709
66799
|
const args = [...buildArgs(opts), session2.flag, session2.id];
|
|
66710
|
-
return this.execCli(command6, args, env3, prompt, opts.signal, onTextDelta).then((result) => {
|
|
66800
|
+
return this.withSlot(() => this.execCli(command6, args, env3, prompt, opts.signal, onTextDelta)).then((result) => {
|
|
66711
66801
|
this.sessions.set(key2, {
|
|
66712
66802
|
sessionId: session2.id,
|
|
66713
66803
|
seenMessages: opts.messages.length,
|
|
@@ -66726,7 +66816,7 @@ User: ${overridePrompt}` : "");
|
|
|
66726
66816
|
User: ${overridePrompt}` : "");
|
|
66727
66817
|
const freshId = randomUUID3();
|
|
66728
66818
|
const freshArgs = [...buildArgs(opts), "--session-id", freshId];
|
|
66729
|
-
return this.execCli(command6, freshArgs, env3, freshPrompt || "(continue)", opts.signal, onTextDelta).then((result) => {
|
|
66819
|
+
return this.withSlot(() => this.execCli(command6, freshArgs, env3, freshPrompt || "(continue)", opts.signal, onTextDelta)).then((result) => {
|
|
66730
66820
|
this.sessions.set(key2, {
|
|
66731
66821
|
sessionId: freshId,
|
|
66732
66822
|
seenMessages: opts.messages.length,
|
|
@@ -66810,7 +66900,7 @@ User: ${overridePrompt}` : "");
|
|
|
66810
66900
|
}
|
|
66811
66901
|
const resultText = finalResult?.result || accumulatedText;
|
|
66812
66902
|
if (resultText && resultText.length < 300 && /^\s*failed to authenticate|^\s*api error: 4\d\d/i.test(resultText)) {
|
|
66813
|
-
reject(new Error(`claude-cli
|
|
66903
|
+
reject(new Error(`claude-cli could not authenticate \u2014 run \`claude\` once in a terminal to sign in (or check its Bedrock settings). CLI said: ${resultText.slice(0, 200)}`));
|
|
66814
66904
|
return;
|
|
66815
66905
|
}
|
|
66816
66906
|
if (code !== 0 && !accumulatedText && !finalResult) {
|
|
@@ -67044,7 +67134,7 @@ function getContextThreshold(model, percent) {
|
|
|
67044
67134
|
}
|
|
67045
67135
|
async function resolveForCall(config) {
|
|
67046
67136
|
const fullConfig = await getConfig();
|
|
67047
|
-
const providerName = config?.provider ?? fullConfig
|
|
67137
|
+
const providerName = config?.provider ?? resolveMainProviderName(fullConfig);
|
|
67048
67138
|
const hasExplicitProviders = fullConfig.providers && Object.keys(fullConfig.providers).length > 0;
|
|
67049
67139
|
let providers = hasExplicitProviders ? buildProviderMap(fullConfig.providers, fullConfig) : synthesizeFromLegacy(fullConfig);
|
|
67050
67140
|
const resolved = resolveProvider(providerName, providers);
|
|
@@ -67114,6 +67204,7 @@ var init_model = __esm({
|
|
|
67114
67204
|
"use strict";
|
|
67115
67205
|
init_config_manager();
|
|
67116
67206
|
init_registry2();
|
|
67207
|
+
init_default_provider();
|
|
67117
67208
|
init_defaults();
|
|
67118
67209
|
init_model_catalog();
|
|
67119
67210
|
}
|
|
@@ -68554,7 +68645,7 @@ __export(cheap_model_exports, {
|
|
|
68554
68645
|
});
|
|
68555
68646
|
function fastModelFor(config) {
|
|
68556
68647
|
if (config.agent?.fast_model) return config.agent.fast_model;
|
|
68557
|
-
const providerName = config
|
|
68648
|
+
const providerName = resolveMainProviderName(config);
|
|
68558
68649
|
return MODEL_CATALOG[providerName]?.find(
|
|
68559
68650
|
(model) => model.id.toLowerCase().includes("haiku")
|
|
68560
68651
|
)?.id;
|
|
@@ -68566,6 +68657,7 @@ var init_cheap_model = __esm({
|
|
|
68566
68657
|
"src/core/cheap-model.ts"() {
|
|
68567
68658
|
"use strict";
|
|
68568
68659
|
init_model_catalog();
|
|
68660
|
+
init_default_provider();
|
|
68569
68661
|
}
|
|
68570
68662
|
});
|
|
68571
68663
|
|
|
@@ -70385,7 +70477,20 @@ async function applySnapshot(sessionId, snapshot2, source) {
|
|
|
70385
70477
|
if (projected !== "error") {
|
|
70386
70478
|
updates.errorMessage = void 0;
|
|
70387
70479
|
updates.errorKind = void 0;
|
|
70388
|
-
|
|
70480
|
+
const stashedStop = projected === "stopped" ? takeSuppressedErrorReason(sessionId) : null;
|
|
70481
|
+
if (stashedStop?.message) {
|
|
70482
|
+
updates.errorMessage = stashedStop.message;
|
|
70483
|
+
if (stashedStop.kind) updates.errorKind = stashedStop.kind;
|
|
70484
|
+
log.session.info("snapshot projection adopted suppressed stop reason", {
|
|
70485
|
+
sessionId,
|
|
70486
|
+
suppressedReason: stashedStop.reason,
|
|
70487
|
+
kind: stashedStop.kind ?? null,
|
|
70488
|
+
projected,
|
|
70489
|
+
source
|
|
70490
|
+
});
|
|
70491
|
+
} else {
|
|
70492
|
+
clearSuppressedErrorReason(sessionId);
|
|
70493
|
+
}
|
|
70389
70494
|
} else {
|
|
70390
70495
|
let droppedDisproven = false;
|
|
70391
70496
|
if (assertsHostUnreachable(record)) {
|
|
@@ -74459,6 +74564,26 @@ var init_transcript_chain = __esm({
|
|
|
74459
74564
|
}
|
|
74460
74565
|
});
|
|
74461
74566
|
|
|
74567
|
+
// src/core/sessions/side-thread-warmup.ts
|
|
74568
|
+
function isCacheWarmupText(text) {
|
|
74569
|
+
return text.startsWith(CACHE_WARMUP_TAG);
|
|
74570
|
+
}
|
|
74571
|
+
function markWarmupTurnPending(sessionId) {
|
|
74572
|
+
pendingWarmupTurns.add(sessionId);
|
|
74573
|
+
}
|
|
74574
|
+
function consumeWarmupTurn(sessionId) {
|
|
74575
|
+
return pendingWarmupTurns.delete(sessionId);
|
|
74576
|
+
}
|
|
74577
|
+
var CACHE_WARMUP_TAG, CACHE_WARMUP_MESSAGE, pendingWarmupTurns;
|
|
74578
|
+
var init_side_thread_warmup = __esm({
|
|
74579
|
+
"src/core/sessions/side-thread-warmup.ts"() {
|
|
74580
|
+
"use strict";
|
|
74581
|
+
CACHE_WARMUP_TAG = "<walnut-cache-warmup>";
|
|
74582
|
+
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>`;
|
|
74583
|
+
pendingWarmupTurns = /* @__PURE__ */ new Set();
|
|
74584
|
+
}
|
|
74585
|
+
});
|
|
74586
|
+
|
|
74462
74587
|
// src/core/history-disk-cache.ts
|
|
74463
74588
|
var history_disk_cache_exports = {};
|
|
74464
74589
|
__export(history_disk_cache_exports, {
|
|
@@ -76619,6 +76744,7 @@ __export(session_lifecycle_exports, {
|
|
|
76619
76744
|
retrySession: () => retrySession,
|
|
76620
76745
|
terminateSession: () => terminateSession
|
|
76621
76746
|
});
|
|
76747
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
76622
76748
|
async function readProviderSessionHistory(sessionId, record, nativeHost, skipSubagents = true, opts) {
|
|
76623
76749
|
if (record && engineCaps(record.engine).historySource === "acp-journal") {
|
|
76624
76750
|
const { readAcpSessionHistoryState: readAcpSessionHistoryState2 } = await Promise.resolve().then(() => (init_acp_session_history(), acp_session_history_exports));
|
|
@@ -76729,6 +76855,33 @@ async function changeSessionMode(existingRecord, sessionId, mode, updates = {})
|
|
|
76729
76855
|
});
|
|
76730
76856
|
return updated;
|
|
76731
76857
|
}
|
|
76858
|
+
function normalizePinQuote(value) {
|
|
76859
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
76860
|
+
throw new SessionControlError("pinned_messages[].quote must be an object", 400);
|
|
76861
|
+
}
|
|
76862
|
+
const quote = value;
|
|
76863
|
+
const exact = quote.exact;
|
|
76864
|
+
if (typeof exact !== "string" || !exact.trim() || exact.length > MAX_PIN_QUOTE_CHARS) {
|
|
76865
|
+
throw new SessionControlError(
|
|
76866
|
+
`pinned_messages[].quote.exact must be a non-empty string (max ${MAX_PIN_QUOTE_CHARS} chars)`,
|
|
76867
|
+
400
|
|
76868
|
+
);
|
|
76869
|
+
}
|
|
76870
|
+
for (const side of ["prefix", "suffix"]) {
|
|
76871
|
+
const context = quote[side];
|
|
76872
|
+
if (context !== void 0 && context !== null && (typeof context !== "string" || context.length > MAX_PIN_QUOTE_CONTEXT_CHARS)) {
|
|
76873
|
+
throw new SessionControlError(
|
|
76874
|
+
`pinned_messages[].quote.${side} must be a string (max ${MAX_PIN_QUOTE_CONTEXT_CHARS} chars)`,
|
|
76875
|
+
400
|
|
76876
|
+
);
|
|
76877
|
+
}
|
|
76878
|
+
}
|
|
76879
|
+
return {
|
|
76880
|
+
exact,
|
|
76881
|
+
...typeof quote.prefix === "string" && quote.prefix ? { prefix: quote.prefix } : {},
|
|
76882
|
+
...typeof quote.suffix === "string" && quote.suffix ? { suffix: quote.suffix } : {}
|
|
76883
|
+
};
|
|
76884
|
+
}
|
|
76732
76885
|
function normalizePinnedMessages(value) {
|
|
76733
76886
|
if (!Array.isArray(value)) {
|
|
76734
76887
|
throw new SessionControlError("pinned_messages must be an array", 400);
|
|
@@ -76737,7 +76890,9 @@ function normalizePinnedMessages(value) {
|
|
|
76737
76890
|
throw new SessionControlError(`pinned_messages holds at most ${MAX_PINNED_MESSAGES} entries`, 400);
|
|
76738
76891
|
}
|
|
76739
76892
|
const out = [];
|
|
76740
|
-
const
|
|
76893
|
+
const seenWholeMessage = /* @__PURE__ */ new Set();
|
|
76894
|
+
const seenQuoteId = /* @__PURE__ */ new Set();
|
|
76895
|
+
const seenQuoteShape = /* @__PURE__ */ new Set();
|
|
76741
76896
|
for (const raw of value) {
|
|
76742
76897
|
if (!raw || typeof raw !== "object") {
|
|
76743
76898
|
throw new SessionControlError("each pinned_messages entry must be an object", 400);
|
|
@@ -76747,13 +76902,40 @@ function normalizePinnedMessages(value) {
|
|
|
76747
76902
|
if (typeof msgId !== "string" || !msgId.trim() || msgId.length > MAX_PIN_ID_CHARS) {
|
|
76748
76903
|
throw new SessionControlError("pinned_messages[].msgId must be a non-empty string", 400);
|
|
76749
76904
|
}
|
|
76750
|
-
|
|
76751
|
-
|
|
76905
|
+
const rawId = entry.id;
|
|
76906
|
+
if (rawId !== void 0 && rawId !== null && (typeof rawId !== "string" || !rawId.trim() || rawId.length > MAX_PIN_ENTRY_ID_CHARS)) {
|
|
76907
|
+
throw new SessionControlError(
|
|
76908
|
+
`pinned_messages[].id must be a non-empty string (max ${MAX_PIN_ENTRY_ID_CHARS} chars)`,
|
|
76909
|
+
400
|
|
76910
|
+
);
|
|
76911
|
+
}
|
|
76912
|
+
const id = typeof rawId === "string" ? rawId : void 0;
|
|
76913
|
+
const quote = entry.quote === void 0 || entry.quote === null ? void 0 : normalizePinQuote(entry.quote);
|
|
76914
|
+
let pinId = id;
|
|
76915
|
+
if (quote) {
|
|
76916
|
+
if (id && seenQuoteId.has(id)) continue;
|
|
76917
|
+
const shape = [msgId, quote.exact, quote.prefix ?? "", quote.suffix ?? ""].join("\0");
|
|
76918
|
+
if (seenQuoteShape.has(shape)) continue;
|
|
76919
|
+
pinId = id ?? randomUUID4();
|
|
76920
|
+
seenQuoteId.add(pinId);
|
|
76921
|
+
seenQuoteShape.add(shape);
|
|
76922
|
+
} else {
|
|
76923
|
+
if (seenWholeMessage.has(msgId)) continue;
|
|
76924
|
+
seenWholeMessage.add(msgId);
|
|
76925
|
+
}
|
|
76752
76926
|
const role = entry.role === "user" || entry.role === "assistant" || entry.role === "system" ? entry.role : "assistant";
|
|
76753
76927
|
const label = typeof entry.label === "string" ? entry.label.trim().slice(0, MAX_PIN_LABEL_CHARS) : "";
|
|
76754
76928
|
const timestamp = typeof entry.timestamp === "string" ? entry.timestamp : void 0;
|
|
76755
76929
|
const pinnedAt = typeof entry.pinnedAt === "string" ? entry.pinnedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
76756
|
-
out.push({
|
|
76930
|
+
out.push({
|
|
76931
|
+
msgId,
|
|
76932
|
+
label,
|
|
76933
|
+
role,
|
|
76934
|
+
...timestamp ? { timestamp } : {},
|
|
76935
|
+
pinnedAt,
|
|
76936
|
+
...pinId ? { id: pinId } : {},
|
|
76937
|
+
...quote ? { quote } : {}
|
|
76938
|
+
});
|
|
76757
76939
|
}
|
|
76758
76940
|
return out;
|
|
76759
76941
|
}
|
|
@@ -77412,7 +77594,7 @@ async function readSessionRichHistory(sessionId, tail) {
|
|
|
77412
77594
|
...historyWindowed ? { windowed: true } : {}
|
|
77413
77595
|
};
|
|
77414
77596
|
}
|
|
77415
|
-
var CLAUDE_SESSION_MODES, MAX_PINNED_MESSAGES, MAX_PIN_LABEL_CHARS, MAX_PIN_ID_CHARS, RECHECK_RPC_TIMEOUT_MS, RECHECK_TIMED_OUT, GIT_BASES, MAX_FORK_DEPTH, HISTORY_STARTUP_GRACE_MS;
|
|
77597
|
+
var CLAUDE_SESSION_MODES, MAX_PINNED_MESSAGES, MAX_PIN_LABEL_CHARS, MAX_PIN_ID_CHARS, MAX_PIN_ENTRY_ID_CHARS, MAX_PIN_QUOTE_CHARS, MAX_PIN_QUOTE_CONTEXT_CHARS, RECHECK_RPC_TIMEOUT_MS, RECHECK_TIMED_OUT, GIT_BASES, MAX_FORK_DEPTH, HISTORY_STARTUP_GRACE_MS;
|
|
77416
77598
|
var init_session_lifecycle = __esm({
|
|
77417
77599
|
"src/core/sessions/session-lifecycle.ts"() {
|
|
77418
77600
|
"use strict";
|
|
@@ -77426,6 +77608,9 @@ var init_session_lifecycle = __esm({
|
|
|
77426
77608
|
MAX_PINNED_MESSAGES = 200;
|
|
77427
77609
|
MAX_PIN_LABEL_CHARS = 300;
|
|
77428
77610
|
MAX_PIN_ID_CHARS = 200;
|
|
77611
|
+
MAX_PIN_ENTRY_ID_CHARS = 64;
|
|
77612
|
+
MAX_PIN_QUOTE_CHARS = 2e3;
|
|
77613
|
+
MAX_PIN_QUOTE_CONTEXT_CHARS = 64;
|
|
77429
77614
|
RECHECK_RPC_TIMEOUT_MS = 5e3;
|
|
77430
77615
|
RECHECK_TIMED_OUT = /* @__PURE__ */ Symbol("recheck-timeout");
|
|
77431
77616
|
GIT_BASES = /* @__PURE__ */ new Set(["uncommitted", "previous", "remote"]);
|
|
@@ -77728,7 +77913,7 @@ __export(compact_inject_exports, {
|
|
|
77728
77913
|
extractSessionMeta: () => extractSessionMeta,
|
|
77729
77914
|
injectCompactBoundary: () => injectCompactBoundary
|
|
77730
77915
|
});
|
|
77731
|
-
import { randomUUID as
|
|
77916
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
77732
77917
|
import fsp26 from "fs/promises";
|
|
77733
77918
|
async function extractSessionMeta(jsonlPath) {
|
|
77734
77919
|
const content = await fsp26.readFile(jsonlPath, "utf-8");
|
|
@@ -77780,8 +77965,8 @@ async function injectCompactBoundary(jsonlPath, summary, meta) {
|
|
|
77780
77965
|
log.session.error("Could not find last message UUID in JSONL", { jsonlPath });
|
|
77781
77966
|
return null;
|
|
77782
77967
|
}
|
|
77783
|
-
const boundaryUuid =
|
|
77784
|
-
const summaryUuid =
|
|
77968
|
+
const boundaryUuid = randomUUID5();
|
|
77969
|
+
const summaryUuid = randomUUID5();
|
|
77785
77970
|
const now = /* @__PURE__ */ new Date();
|
|
77786
77971
|
const boundaryTs = now.toISOString();
|
|
77787
77972
|
const summaryTs = new Date(now.getTime() + 1e3).toISOString();
|
|
@@ -88565,6 +88750,82 @@ var init_task = __esm({
|
|
|
88565
88750
|
}
|
|
88566
88751
|
});
|
|
88567
88752
|
|
|
88753
|
+
// src/core/sessions/spawn-prefix.ts
|
|
88754
|
+
var spawn_prefix_exports = {};
|
|
88755
|
+
__export(spawn_prefix_exports, {
|
|
88756
|
+
MAX_SPAWN_PROMPT_BYTES: () => MAX_SPAWN_PROMPT_BYTES,
|
|
88757
|
+
parseSpawnPrefixFromArgs: () => parseSpawnPrefixFromArgs,
|
|
88758
|
+
readParentSpawnPrefix: () => readParentSpawnPrefix,
|
|
88759
|
+
spawnPrefixFromRecord: () => spawnPrefixFromRecord
|
|
88760
|
+
});
|
|
88761
|
+
function flagValue(args, flag) {
|
|
88762
|
+
const i8 = args.indexOf(flag);
|
|
88763
|
+
if (i8 < 0 || i8 + 1 >= args.length) return void 0;
|
|
88764
|
+
const v3 = args[i8 + 1];
|
|
88765
|
+
return v3 !== void 0 && !v3.startsWith("--") ? v3 : void 0;
|
|
88766
|
+
}
|
|
88767
|
+
function parseSpawnPrefixFromArgs(args) {
|
|
88768
|
+
const prompt = flagValue(args, "--append-system-prompt");
|
|
88769
|
+
return {
|
|
88770
|
+
appendSystemPrompt: prompt !== void 0 && Buffer.byteLength(prompt, "utf8") <= MAX_SPAWN_PROMPT_BYTES ? prompt : null,
|
|
88771
|
+
...flagValue(args, "--model") ? { model: flagValue(args, "--model") } : {},
|
|
88772
|
+
...EFFORTS.includes(flagValue(args, "--effort") ?? "") ? { effort: flagValue(args, "--effort") } : {},
|
|
88773
|
+
...flagValue(args, "--permission-mode") ? { permissionMode: flagValue(args, "--permission-mode") } : {},
|
|
88774
|
+
source: "live-process"
|
|
88775
|
+
};
|
|
88776
|
+
}
|
|
88777
|
+
function spawnPrefixFromRecord(record) {
|
|
88778
|
+
const stored = record.appliedAppendSystemPrompt;
|
|
88779
|
+
if (stored === void 0 || stored === null) return { appendSystemPrompt: null, source: "unknown" };
|
|
88780
|
+
const usable = stored.length > 0 && Buffer.byteLength(stored, "utf8") <= MAX_SPAWN_PROMPT_BYTES;
|
|
88781
|
+
return { appendSystemPrompt: usable ? stored : null, source: "record" };
|
|
88782
|
+
}
|
|
88783
|
+
async function defaultLiveArgsReader(hostKey2, sessionId) {
|
|
88784
|
+
const { probeDaemonSessionArgs: probeDaemonSessionArgs2 } = await Promise.resolve().then(() => (init_daemon_connection(), daemon_connection_exports));
|
|
88785
|
+
return probeDaemonSessionArgs2(hostKey2, sessionId);
|
|
88786
|
+
}
|
|
88787
|
+
async function readParentSpawnPrefix(parent, deps = {}) {
|
|
88788
|
+
const readLiveArgs = deps.readLiveArgs ?? defaultLiveArgsReader;
|
|
88789
|
+
const hostKey2 = parent.host || "__local__";
|
|
88790
|
+
let live = null;
|
|
88791
|
+
try {
|
|
88792
|
+
live = await readLiveArgs(hostKey2, parent.claudeSessionId);
|
|
88793
|
+
} catch (err2) {
|
|
88794
|
+
log.session.debug("spawn prefix: live argv read threw", {
|
|
88795
|
+
sessionId: parent.claudeSessionId,
|
|
88796
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
88797
|
+
});
|
|
88798
|
+
}
|
|
88799
|
+
if (live && live.length > 0) {
|
|
88800
|
+
const prefix = parseSpawnPrefixFromArgs(live);
|
|
88801
|
+
const remembered = parent.appliedAppendSystemPrompt ?? void 0;
|
|
88802
|
+
const actual = prefix.appendSystemPrompt ?? "";
|
|
88803
|
+
if (remembered !== actual) {
|
|
88804
|
+
const persist4 = deps.persist ?? (async (sid, prompt) => {
|
|
88805
|
+
const { updateSessionRecord: updateSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
|
|
88806
|
+
return updateSessionRecord2(sid, { appliedAppendSystemPrompt: prompt });
|
|
88807
|
+
});
|
|
88808
|
+
void persist4(parent.claudeSessionId, actual).catch((err2) => {
|
|
88809
|
+
log.session.warn("spawn prefix: backfill of applied prompt failed", {
|
|
88810
|
+
sessionId: parent.claudeSessionId,
|
|
88811
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
88812
|
+
});
|
|
88813
|
+
});
|
|
88814
|
+
}
|
|
88815
|
+
return prefix;
|
|
88816
|
+
}
|
|
88817
|
+
return spawnPrefixFromRecord(parent);
|
|
88818
|
+
}
|
|
88819
|
+
var EFFORTS, MAX_SPAWN_PROMPT_BYTES;
|
|
88820
|
+
var init_spawn_prefix = __esm({
|
|
88821
|
+
"src/core/sessions/spawn-prefix.ts"() {
|
|
88822
|
+
"use strict";
|
|
88823
|
+
init_logging();
|
|
88824
|
+
EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
88825
|
+
MAX_SPAWN_PROMPT_BYTES = 65536;
|
|
88826
|
+
}
|
|
88827
|
+
});
|
|
88828
|
+
|
|
88568
88829
|
// src/core/sessions/side-thread-fork.ts
|
|
88569
88830
|
var side_thread_fork_exports = {};
|
|
88570
88831
|
__export(side_thread_fork_exports, {
|
|
@@ -88621,6 +88882,10 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
|
|
|
88621
88882
|
const sessionId = crypto16.randomUUID();
|
|
88622
88883
|
const lane = sideThreadLaneKey(parentSid, threadId);
|
|
88623
88884
|
const mode = parent.mode !== "default" ? parent.mode : void 0;
|
|
88885
|
+
const { readParentSpawnPrefix: readParentSpawnPrefix2 } = await Promise.resolve().then(() => (init_spawn_prefix(), spawn_prefix_exports));
|
|
88886
|
+
const prefix = await readParentSpawnPrefix2(parent);
|
|
88887
|
+
const cliModel = prefix.model ?? parent.cliModel;
|
|
88888
|
+
const effort = prefix.effort ?? parent.effort;
|
|
88624
88889
|
await createSessionRecord(sessionId, "", parent.project ?? "", parent.cwd, {
|
|
88625
88890
|
title,
|
|
88626
88891
|
...mode ? { mode } : {},
|
|
@@ -88628,8 +88893,10 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
|
|
|
88628
88893
|
// The thread inherits the parent's launch bundle verbatim, re-applied from
|
|
88629
88894
|
// the record on every cold resume.
|
|
88630
88895
|
...parent.profile ? { profile: parent.profile } : {},
|
|
88631
|
-
...
|
|
88632
|
-
...
|
|
88896
|
+
...effort ? { effort } : {},
|
|
88897
|
+
...cliModel ? { cliModel } : {},
|
|
88898
|
+
// '' = the parent runs without an append prompt, and so does the thread.
|
|
88899
|
+
appliedAppendSystemPrompt: prefix.appendSystemPrompt ?? "",
|
|
88633
88900
|
lane,
|
|
88634
88901
|
forkedFromSessionId: resumeFrom,
|
|
88635
88902
|
initialProcessStatus: "idle",
|
|
@@ -88645,11 +88912,12 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
|
|
|
88645
88912
|
...mode ? { mode } : {},
|
|
88646
88913
|
...parent.host ? { host: parent.host } : {},
|
|
88647
88914
|
...parent.profile ? { profile: parent.profile } : {},
|
|
88648
|
-
...
|
|
88915
|
+
...effort ? { effort } : {},
|
|
88649
88916
|
// Must be `cliModel` (the parent's verbatim --model arg, [1m] marker
|
|
88650
88917
|
// included): the CLI does NOT inherit a model across --fork-session, and
|
|
88651
88918
|
// the reported `model` has lost the marker.
|
|
88652
|
-
...
|
|
88919
|
+
...cliModel ? { model: cliModel } : {},
|
|
88920
|
+
appendSystemPromptExact: prefix.appendSystemPrompt,
|
|
88653
88921
|
lane,
|
|
88654
88922
|
forkedFromSessionId: resumeFrom
|
|
88655
88923
|
}, ["session-runner"], { source: "side-thread-fork" });
|
|
@@ -88658,7 +88926,9 @@ async function forkSideThreadSession(parentSid, threadId, opts) {
|
|
|
88658
88926
|
threadId,
|
|
88659
88927
|
sessionId,
|
|
88660
88928
|
resumeFrom,
|
|
88661
|
-
initOnly: !opts?.message
|
|
88929
|
+
initOnly: !opts?.message,
|
|
88930
|
+
prefixSource: prefix.source,
|
|
88931
|
+
prefixPromptLength: prefix.appendSystemPrompt?.length ?? 0
|
|
88662
88932
|
});
|
|
88663
88933
|
return { sessionId, resumeFromSessionId: resumeFrom };
|
|
88664
88934
|
}
|
|
@@ -100914,14 +101184,16 @@ function isLive(r8) {
|
|
|
100914
101184
|
function isRevivable(r8) {
|
|
100915
101185
|
return !!r8.outputFile || r8.consumedOffset !== void 0;
|
|
100916
101186
|
}
|
|
100917
|
-
var STANDBY_TTL_MS, MAX_LIVE_THREADS, IDLE_SWEEP_INTERVAL_MS, THREAD_IDLE_MS, SideThreadManager, sideThreadManager;
|
|
101187
|
+
var STANDBY_TTL_MS, WARMED_STANDBY_TTL_MS, MAX_LIVE_THREADS, IDLE_SWEEP_INTERVAL_MS, THREAD_IDLE_MS, SideThreadManager, sideThreadManager;
|
|
100918
101188
|
var init_side_thread_manager = __esm({
|
|
100919
101189
|
"src/core/sessions/side-thread-manager.ts"() {
|
|
100920
101190
|
"use strict";
|
|
100921
101191
|
init_logging();
|
|
100922
101192
|
init_side_thread_fork();
|
|
100923
101193
|
init_session_controls();
|
|
101194
|
+
init_side_thread_warmup();
|
|
100924
101195
|
STANDBY_TTL_MS = 12e4;
|
|
101196
|
+
WARMED_STANDBY_TTL_MS = 15 * 6e4;
|
|
100925
101197
|
MAX_LIVE_THREADS = 3;
|
|
100926
101198
|
IDLE_SWEEP_INTERVAL_MS = 5 * 6e4;
|
|
100927
101199
|
THREAD_IDLE_MS = 30 * 6e4;
|
|
@@ -100932,6 +101204,8 @@ var init_side_thread_manager = __esm({
|
|
|
100932
101204
|
* In-memory only: a server restart loses it, but the boot sweep archives
|
|
100933
101205
|
* every standby anyway, so a record can never outlive its map entry. */
|
|
100934
101206
|
standbyParentOffsets = /* @__PURE__ */ new Map();
|
|
101207
|
+
/** parentSid → standby sid that already ran its cache warm-up turn. */
|
|
101208
|
+
warmedStandbys = /* @__PURE__ */ new Map();
|
|
100935
101209
|
/** One in-flight standby/create op per parent — two concurrent asks must not
|
|
100936
101210
|
* both consume the same standby (they would collide on its lane rename). */
|
|
100937
101211
|
inFlight = /* @__PURE__ */ new Map();
|
|
@@ -100963,6 +101237,7 @@ var init_side_thread_manager = __esm({
|
|
|
100963
101237
|
for (const timer3 of this.standbyTimers.values()) clearTimeout(timer3);
|
|
100964
101238
|
this.standbyTimers.clear();
|
|
100965
101239
|
this.standbyParentOffsets.clear();
|
|
101240
|
+
this.warmedStandbys.clear();
|
|
100966
101241
|
this.inFlight.clear();
|
|
100967
101242
|
}
|
|
100968
101243
|
// ── Standby ────────────────────────────────────────────────────────────────
|
|
@@ -100978,10 +101253,12 @@ var init_side_thread_manager = __esm({
|
|
|
100978
101253
|
const existing = await this.findStandby(parentSid);
|
|
100979
101254
|
if (existing) {
|
|
100980
101255
|
if (await this.isStandbyUsable(parentSid, existing)) {
|
|
100981
|
-
this.
|
|
101256
|
+
const warmed = this.warmedStandbys.get(parentSid) === existing.claudeSessionId;
|
|
101257
|
+
this.armStandbyTtl(parentSid, existing.claudeSessionId, warmed ? WARMED_STANDBY_TTL_MS : STANDBY_TTL_MS);
|
|
100982
101258
|
return existing.claudeSessionId;
|
|
100983
101259
|
}
|
|
100984
101260
|
void this.retireSession(existing.claudeSessionId, "side_thread_standby_stale");
|
|
101261
|
+
this.warmedStandbys.delete(parentSid);
|
|
100985
101262
|
}
|
|
100986
101263
|
const parentOffset = parent.consumedOffset;
|
|
100987
101264
|
const { sessionId } = await forkSideThreadSession(parentSid, STANDBY_THREAD_ID);
|
|
@@ -101007,7 +101284,7 @@ var init_side_thread_manager = __esm({
|
|
|
101007
101284
|
if (!parent) return false;
|
|
101008
101285
|
return parent.consumedOffset === this.standbyParentOffsets.get(parentSid);
|
|
101009
101286
|
}
|
|
101010
|
-
armStandbyTtl(parentSid, sessionId) {
|
|
101287
|
+
armStandbyTtl(parentSid, sessionId, ttlMs = STANDBY_TTL_MS) {
|
|
101011
101288
|
const prev = this.standbyTimers.get(parentSid);
|
|
101012
101289
|
if (prev) clearTimeout(prev);
|
|
101013
101290
|
const timer3 = setTimeout(() => {
|
|
@@ -101017,13 +101294,41 @@ var init_side_thread_manager = __esm({
|
|
|
101017
101294
|
const record = await getSessionByClaudeId2(sessionId).catch(() => null);
|
|
101018
101295
|
if (!record || parseSideLaneKey(record.lane)?.threadId !== STANDBY_THREAD_ID) return;
|
|
101019
101296
|
this.standbyParentOffsets.delete(parentSid);
|
|
101297
|
+
this.warmedStandbys.delete(parentSid);
|
|
101020
101298
|
await this.retireSession(sessionId, "side_thread_standby_ttl");
|
|
101021
101299
|
}).catch(() => {
|
|
101022
101300
|
});
|
|
101023
|
-
},
|
|
101301
|
+
}, ttlMs);
|
|
101024
101302
|
timer3.unref?.();
|
|
101025
101303
|
this.standbyTimers.set(parentSid, timer3);
|
|
101026
101304
|
}
|
|
101305
|
+
/**
|
|
101306
|
+
* Run the standby's cache warm-up turn (see side-thread-warmup.ts). Called
|
|
101307
|
+
* when the user starts TYPING a new question: the fork's first API call pays
|
|
101308
|
+
* the full prefix write no matter what, so paying it while they type turns
|
|
101309
|
+
* the actual question into a cached follow-up. Idempotent per standby; a
|
|
101310
|
+
* missing or stale standby is a no-op (the ask path will fork fresh anyway).
|
|
101311
|
+
*/
|
|
101312
|
+
async warmStandby(parentSid) {
|
|
101313
|
+
return this.serialize(parentSid, async () => {
|
|
101314
|
+
const standby = await this.findStandby(parentSid);
|
|
101315
|
+
if (!standby) return { warmed: false, reason: "no_standby" };
|
|
101316
|
+
if (!await this.isStandbyUsable(parentSid, standby)) return { warmed: false, reason: "stale" };
|
|
101317
|
+
if (this.warmedStandbys.get(parentSid) === standby.claudeSessionId) {
|
|
101318
|
+
return { warmed: true, reason: "already_warm" };
|
|
101319
|
+
}
|
|
101320
|
+
const { sendMessageToSession: sendMessageToSession2 } = await Promise.resolve().then(() => (init_session_message_queue(), session_message_queue_exports));
|
|
101321
|
+
markWarmupTurnPending(standby.claudeSessionId);
|
|
101322
|
+
await sendMessageToSession2(standby.claudeSessionId, CACHE_WARMUP_MESSAGE, { source: "side-thread-warmup" });
|
|
101323
|
+
this.warmedStandbys.set(parentSid, standby.claudeSessionId);
|
|
101324
|
+
this.armStandbyTtl(parentSid, standby.claudeSessionId, WARMED_STANDBY_TTL_MS);
|
|
101325
|
+
log.session.info("side thread: standby cache warm-up sent", {
|
|
101326
|
+
parentSid,
|
|
101327
|
+
standbySid: standby.claudeSessionId
|
|
101328
|
+
});
|
|
101329
|
+
return { warmed: true };
|
|
101330
|
+
});
|
|
101331
|
+
}
|
|
101027
101332
|
// ── Threads ────────────────────────────────────────────────────────────────
|
|
101028
101333
|
/**
|
|
101029
101334
|
* Open a side thread on `parentSid` and ask `question` in it.
|
|
@@ -101034,6 +101339,10 @@ var init_side_thread_manager = __esm({
|
|
|
101034
101339
|
* question as its spawn's first turn instead — a send issued right after
|
|
101035
101340
|
* SESSION_START can lose that race and cold-`--resume` an id the CLI has never
|
|
101036
101341
|
* seen (see personal-ai-lane.ts LaneSession.created).
|
|
101342
|
+
*
|
|
101343
|
+
* `imageContext` (attachment paths for the CLI to Read) rides the MESSAGE only.
|
|
101344
|
+
* The stored entry keeps the plain question, so the chip label, the injected
|
|
101345
|
+
* transcript and a promoted task never carry the machine preamble.
|
|
101037
101346
|
*/
|
|
101038
101347
|
async createThread(parentSid, input) {
|
|
101039
101348
|
const question = input.question?.trim();
|
|
@@ -101041,8 +101350,11 @@ var init_side_thread_manager = __esm({
|
|
|
101041
101350
|
return this.serialize(parentSid, async () => {
|
|
101042
101351
|
const threadId = mintSideThreadId();
|
|
101043
101352
|
const title = input.title?.trim() || void 0;
|
|
101353
|
+
const message = input.imageContext ? `${input.imageContext}
|
|
101354
|
+
|
|
101355
|
+
${question}` : question;
|
|
101044
101356
|
const consumed = await this.consumeStandby(parentSid, threadId, title);
|
|
101045
|
-
const threadSessionId = consumed ?? (await forkSideThreadSession(parentSid, threadId, { message
|
|
101357
|
+
const threadSessionId = consumed ?? (await forkSideThreadSession(parentSid, threadId, { message, ...title ? { title } : {} })).sessionId;
|
|
101046
101358
|
let entry;
|
|
101047
101359
|
try {
|
|
101048
101360
|
const { addSideThread: addSideThread2 } = await Promise.resolve().then(() => (init_side_questions(), side_questions_exports));
|
|
@@ -101054,7 +101366,7 @@ var init_side_thread_manager = __esm({
|
|
|
101054
101366
|
});
|
|
101055
101367
|
if (consumed) {
|
|
101056
101368
|
const { sendMessageToSession: sendMessageToSession2 } = await Promise.resolve().then(() => (init_session_message_queue(), session_message_queue_exports));
|
|
101057
|
-
await sendMessageToSession2(threadSessionId,
|
|
101369
|
+
await sendMessageToSession2(threadSessionId, message, { source: "side-thread" });
|
|
101058
101370
|
}
|
|
101059
101371
|
} catch (err2) {
|
|
101060
101372
|
void this.retireSession(threadSessionId, "side_thread_create_failed");
|
|
@@ -101089,6 +101401,7 @@ var init_side_thread_manager = __esm({
|
|
|
101089
101401
|
if (!await this.isStandbyUsable(parentSid, standby)) {
|
|
101090
101402
|
void this.retireSession(standby.claudeSessionId, "side_thread_standby_stale");
|
|
101091
101403
|
this.standbyParentOffsets.delete(parentSid);
|
|
101404
|
+
this.warmedStandbys.delete(parentSid);
|
|
101092
101405
|
return null;
|
|
101093
101406
|
}
|
|
101094
101407
|
const timer3 = this.standbyTimers.get(parentSid);
|
|
@@ -101097,6 +101410,7 @@ var init_side_thread_manager = __esm({
|
|
|
101097
101410
|
this.standbyTimers.delete(parentSid);
|
|
101098
101411
|
}
|
|
101099
101412
|
this.standbyParentOffsets.delete(parentSid);
|
|
101413
|
+
this.warmedStandbys.delete(parentSid);
|
|
101100
101414
|
const lane = sideThreadLaneKey(parentSid, threadId);
|
|
101101
101415
|
const { updateSessionRecord: updateSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
|
|
101102
101416
|
await updateSessionRecord2(standby.claudeSessionId, {
|
|
@@ -101283,16 +101597,30 @@ async function promoteSideThread(parentSid, threadId, opts) {
|
|
|
101283
101597
|
const threadRecord = await getSessionByClaudeId2(sessionId);
|
|
101284
101598
|
if (!threadRecord) throw new SessionControlError("Side thread session not found", 404);
|
|
101285
101599
|
const parentRecord = await getSessionByClaudeId2(parentSid);
|
|
101286
|
-
const
|
|
101287
|
-
const
|
|
101600
|
+
const siblingOfTaskId = parentRecord?.taskId?.trim() || void 0;
|
|
101601
|
+
const threadLabel = opts?.title?.trim() || entry.title?.trim() || void 0;
|
|
101288
101602
|
const { addTask: addTask2, linkSession: linkSession2, linkSessionSlot: linkSessionSlot2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
|
|
101289
|
-
|
|
101290
|
-
|
|
101291
|
-
|
|
101292
|
-
|
|
101293
|
-
|
|
101294
|
-
|
|
101295
|
-
|
|
101603
|
+
let groupId;
|
|
101604
|
+
let task;
|
|
101605
|
+
if (siblingOfTaskId) {
|
|
101606
|
+
const created = await createForkSiblingTask(siblingOfTaskId, {
|
|
101607
|
+
// A known label short-circuits the background refine; without one the
|
|
101608
|
+
// thread's first question is what gets summarized (same as a fork's message).
|
|
101609
|
+
titlePrefix: threadLabel,
|
|
101610
|
+
prompt: entry.question,
|
|
101611
|
+
description: entry.question,
|
|
101612
|
+
source: "side-thread-promote"
|
|
101613
|
+
});
|
|
101614
|
+
task = created.task;
|
|
101615
|
+
groupId = created.groupId;
|
|
101616
|
+
} else {
|
|
101617
|
+
({ task } = await addTask2({
|
|
101618
|
+
title: threadLabel || entry.question,
|
|
101619
|
+
description: entry.question,
|
|
101620
|
+
// A person clicked "promote" — same board default as any hand-made task.
|
|
101621
|
+
pinned: true
|
|
101622
|
+
}));
|
|
101623
|
+
}
|
|
101296
101624
|
const stamped = await markThreadPromoted2(parentSid, threadId, task.id);
|
|
101297
101625
|
if (!stamped) {
|
|
101298
101626
|
log.session.warn("side thread promote: entry vanished before stamp \u2014 promote proceeds, 409 guard is unarmed", {
|
|
@@ -101306,7 +101634,7 @@ async function promoteSideThread(parentSid, threadId, opts) {
|
|
|
101306
101634
|
project: task.project ?? "",
|
|
101307
101635
|
// Clearing the lane un-hides the session (see the file header).
|
|
101308
101636
|
lane: void 0,
|
|
101309
|
-
title
|
|
101637
|
+
title: task.title
|
|
101310
101638
|
});
|
|
101311
101639
|
try {
|
|
101312
101640
|
const { sessionRunner: sessionRunner2 } = await Promise.resolve().then(() => (init_claude_code_session(), claude_code_session_exports));
|
|
@@ -101325,10 +101653,18 @@ async function promoteSideThread(parentSid, threadId, opts) {
|
|
|
101325
101653
|
error: err2 instanceof Error ? err2.message : String(err2)
|
|
101326
101654
|
});
|
|
101327
101655
|
}
|
|
101328
|
-
log.session.info("side thread: promoted to task", {
|
|
101656
|
+
log.session.info("side thread: promoted to task", {
|
|
101657
|
+
parentSid,
|
|
101658
|
+
threadId,
|
|
101659
|
+
sessionId,
|
|
101660
|
+
taskId: task.id,
|
|
101661
|
+
siblingOfTaskId,
|
|
101662
|
+
groupId
|
|
101663
|
+
});
|
|
101329
101664
|
return {
|
|
101330
101665
|
taskId: task.id,
|
|
101331
|
-
...
|
|
101666
|
+
...siblingOfTaskId ? { siblingOfTaskId } : {},
|
|
101667
|
+
...groupId ? { groupId } : {},
|
|
101332
101668
|
sessionId
|
|
101333
101669
|
};
|
|
101334
101670
|
}
|
|
@@ -101343,7 +101679,7 @@ var init_side_thread_promote = __esm({
|
|
|
101343
101679
|
|
|
101344
101680
|
// src/web/routes/sessions.ts
|
|
101345
101681
|
import { Router as Router10 } from "express";
|
|
101346
|
-
import { randomUUID as
|
|
101682
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
101347
101683
|
import os19 from "os";
|
|
101348
101684
|
import path91 from "path";
|
|
101349
101685
|
function condenseStaleReason(msg) {
|
|
@@ -101689,7 +102025,7 @@ var init_sessions2 = __esm({
|
|
|
101689
102025
|
}
|
|
101690
102026
|
const isNativeEngine = !isAcpEngine(engine);
|
|
101691
102027
|
const clientSessionId = typeof req.body.sessionId === "string" && UUID_RE2.test(req.body.sessionId) ? req.body.sessionId : void 0;
|
|
101692
|
-
const preassignedSessionId = isNativeEngine ? clientSessionId ??
|
|
102028
|
+
const preassignedSessionId = isNativeEngine ? clientSessionId ?? randomUUID6() : void 0;
|
|
101693
102029
|
const updatedTask = await quickStartSession({
|
|
101694
102030
|
message: sessionMessage,
|
|
101695
102031
|
messagePrefix,
|
|
@@ -102664,18 +103000,38 @@ var init_sessions2 = __esm({
|
|
|
102664
103000
|
});
|
|
102665
103001
|
}
|
|
102666
103002
|
});
|
|
103003
|
+
sessionsRouter.post("/:sessionId/side-threads/standby/warm", async (req, res) => {
|
|
103004
|
+
if (refuseSideThreadsOnReplica(res)) return;
|
|
103005
|
+
const sessionId = String(req.params.sessionId);
|
|
103006
|
+
try {
|
|
103007
|
+
const { sideThreadManager: sideThreadManager2 } = await Promise.resolve().then(() => (init_side_thread_manager(), side_thread_manager_exports));
|
|
103008
|
+
res.json(await sideThreadManager2.warmStandby(sessionId));
|
|
103009
|
+
} catch (err2) {
|
|
103010
|
+
log.web.warn("side thread standby warm-up failed", {
|
|
103011
|
+
sessionId,
|
|
103012
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
103013
|
+
});
|
|
103014
|
+
res.json({ warmed: false, reason: "error" });
|
|
103015
|
+
}
|
|
103016
|
+
});
|
|
102667
103017
|
sessionsRouter.post("/:sessionId/side-threads", async (req, res, next) => {
|
|
102668
103018
|
if (refuseSideThreadsOnReplica(res)) return;
|
|
102669
103019
|
try {
|
|
102670
|
-
const { question, title } = req.body ?? {};
|
|
103020
|
+
const { question, title, images } = req.body ?? {};
|
|
102671
103021
|
if (!question || typeof question !== "string" || !question.trim()) {
|
|
102672
103022
|
res.status(400).json({ error: "question (non-empty string) is required" });
|
|
102673
103023
|
return;
|
|
102674
103024
|
}
|
|
103025
|
+
let imageContext = "";
|
|
103026
|
+
if (images && images.length > 0) {
|
|
103027
|
+
const processed = await processAndSaveImages(images);
|
|
103028
|
+
if (processed) imageContext = buildSessionImageContext(processed.savedImages);
|
|
103029
|
+
}
|
|
102675
103030
|
const { sideThreadManager: sideThreadManager2 } = await Promise.resolve().then(() => (init_side_thread_manager(), side_thread_manager_exports));
|
|
102676
103031
|
const thread = await sideThreadManager2.createThread(String(req.params.sessionId), {
|
|
102677
103032
|
question,
|
|
102678
|
-
...typeof title === "string" && title.trim() ? { title } : {}
|
|
103033
|
+
...typeof title === "string" && title.trim() ? { title } : {},
|
|
103034
|
+
...imageContext ? { imageContext } : {}
|
|
102679
103035
|
});
|
|
102680
103036
|
res.json({ thread });
|
|
102681
103037
|
} catch (err2) {
|
|
@@ -102694,7 +103050,8 @@ var init_sessions2 = __esm({
|
|
|
102694
103050
|
);
|
|
102695
103051
|
res.json({
|
|
102696
103052
|
taskId: result.taskId,
|
|
102697
|
-
...result.
|
|
103053
|
+
...result.siblingOfTaskId ? { siblingOfTaskId: result.siblingOfTaskId } : {},
|
|
103054
|
+
...result.groupId ? { groupId: result.groupId } : {},
|
|
102698
103055
|
sessionId: result.sessionId
|
|
102699
103056
|
});
|
|
102700
103057
|
} catch (err2) {
|
|
@@ -103389,7 +103746,7 @@ var micro_claude_exports = {};
|
|
|
103389
103746
|
__export(micro_claude_exports, {
|
|
103390
103747
|
runMicroClaude: () => runMicroClaude
|
|
103391
103748
|
});
|
|
103392
|
-
import { randomUUID as
|
|
103749
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
103393
103750
|
async function runMicroClaude(opts) {
|
|
103394
103751
|
if (opts.warm) {
|
|
103395
103752
|
const run10 = await runWarmMicroClaude({
|
|
@@ -103398,7 +103755,7 @@ async function runMicroClaude(opts) {
|
|
|
103398
103755
|
tools: opts.tools ?? [],
|
|
103399
103756
|
prompt: opts.prompt,
|
|
103400
103757
|
timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
|
|
103401
|
-
toolUseId: opts.toolUseId ?? `micro-claude-${
|
|
103758
|
+
toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID7()}`,
|
|
103402
103759
|
...opts.onBlock ? { onBlock: opts.onBlock } : {}
|
|
103403
103760
|
});
|
|
103404
103761
|
return { response: run10.response, costUsd: run10.costUsd, durationMs: run10.durationMs };
|
|
@@ -103408,7 +103765,7 @@ async function runMicroClaude(opts) {
|
|
|
103408
103765
|
systemPrompt: opts.system,
|
|
103409
103766
|
model: opts.model ?? DEFAULT_MODEL2,
|
|
103410
103767
|
timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
|
|
103411
|
-
toolUseId: opts.toolUseId ?? `micro-claude-${
|
|
103768
|
+
toolUseId: opts.toolUseId ?? `micro-claude-${randomUUID7()}`,
|
|
103412
103769
|
slim: true,
|
|
103413
103770
|
thinking: opts.thinking ?? false,
|
|
103414
103771
|
...opts.tools && opts.tools.length > 0 ? { tools: opts.tools } : {},
|
|
@@ -104233,7 +104590,7 @@ __export(micro_agent_exports, {
|
|
|
104233
104590
|
async function resolveTierModel(tier) {
|
|
104234
104591
|
const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
|
|
104235
104592
|
const config = await getConfig2();
|
|
104236
|
-
const provider = config
|
|
104593
|
+
const provider = resolveMainProviderName(config);
|
|
104237
104594
|
const { MODEL_CATALOG: MODEL_CATALOG3 } = await Promise.resolve().then(() => (init_model_catalog(), model_catalog_exports));
|
|
104238
104595
|
const entry = MODEL_CATALOG3[provider]?.find((m6) => {
|
|
104239
104596
|
const id = m6.id.toLowerCase();
|
|
@@ -104302,6 +104659,7 @@ var DEFAULT_TIER, DEFAULT_MAX_TOKENS2, DEFAULT_MAX_ROUNDS, DEFAULT_TIMEOUT_MS3;
|
|
|
104302
104659
|
var init_micro_agent = __esm({
|
|
104303
104660
|
"src/agent/micro-agent.ts"() {
|
|
104304
104661
|
"use strict";
|
|
104662
|
+
init_default_provider();
|
|
104305
104663
|
DEFAULT_TIER = "sonnet";
|
|
104306
104664
|
DEFAULT_MAX_TOKENS2 = 2e3;
|
|
104307
104665
|
DEFAULT_MAX_ROUNDS = 3;
|
|
@@ -104317,7 +104675,7 @@ __export(task_search_agent_exports, {
|
|
|
104317
104675
|
prewarmAgentSearchChild: () => prewarmAgentSearchChild,
|
|
104318
104676
|
runTaskSearchAgent: () => runTaskSearchAgent
|
|
104319
104677
|
});
|
|
104320
|
-
import { randomUUID as
|
|
104678
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
104321
104679
|
function useCliEngine() {
|
|
104322
104680
|
return process.env.WALNUT_AGENT_SEARCH_ENGINE !== "inprocess";
|
|
104323
104681
|
}
|
|
@@ -104397,7 +104755,7 @@ async function claudeCliEngine(userPrompt, options) {
|
|
|
104397
104755
|
model: options.model,
|
|
104398
104756
|
timeoutMs: options.timeoutMs,
|
|
104399
104757
|
tools: ["Bash"],
|
|
104400
|
-
toolUseId: `task-search-${
|
|
104758
|
+
toolUseId: `task-search-${randomUUID8()}`,
|
|
104401
104759
|
warm: true,
|
|
104402
104760
|
onBlock
|
|
104403
104761
|
});
|
|
@@ -105660,10 +106018,11 @@ async function buildProvidersPayload() {
|
|
|
105660
106018
|
providers[name] = {
|
|
105661
106019
|
api: prov.api,
|
|
105662
106020
|
base_url: prov.base_url,
|
|
105663
|
-
status: caps.
|
|
106021
|
+
status: caps.installed ? "ready" : "no_key",
|
|
105664
106022
|
auto_detected: !explicitNames.has(name),
|
|
105665
106023
|
models: getModelsForProvider(name, prov.models),
|
|
105666
|
-
credential_source: caps.
|
|
106024
|
+
credential_source: caps.installed ? `cli_${caps.auth.mode}` : "cli_not_installed",
|
|
106025
|
+
credential_detail: caps.installed ? caps.auth.label : void 0
|
|
105667
106026
|
};
|
|
105668
106027
|
continue;
|
|
105669
106028
|
}
|
|
@@ -105722,10 +106081,11 @@ async function buildProvidersPayload() {
|
|
|
105722
106081
|
const caps = detectClaudeCli2();
|
|
105723
106082
|
providers[name] = {
|
|
105724
106083
|
api: template.api,
|
|
105725
|
-
status: caps.
|
|
106084
|
+
status: caps.installed ? "ready" : "no_key",
|
|
105726
106085
|
auto_detected: false,
|
|
105727
106086
|
models: getModelsForProvider(name),
|
|
105728
|
-
credential_source: caps.
|
|
106087
|
+
credential_source: caps.installed ? `cli_${caps.auth.mode}` : "cli_not_installed",
|
|
106088
|
+
credential_detail: caps.installed ? caps.auth.label : void 0
|
|
105729
106089
|
};
|
|
105730
106090
|
continue;
|
|
105731
106091
|
}
|
|
@@ -105881,7 +106241,7 @@ var init_config11 = __esm({
|
|
|
105881
106241
|
const {
|
|
105882
106242
|
traceCredentialResolution: traceCredentialResolution2,
|
|
105883
106243
|
resolveCredentials: resolveCredentials2,
|
|
105884
|
-
readClaudeSettingsEnv:
|
|
106244
|
+
readClaudeSettingsEnv: readClaudeSettingsEnv3,
|
|
105885
106245
|
readClaudeCredentialExport: readClaudeCredentialExport2
|
|
105886
106246
|
} = await Promise.resolve().then(() => (init_credential_resolver(), credential_resolver_exports));
|
|
105887
106247
|
const fs121 = await import("fs");
|
|
@@ -105897,7 +106257,7 @@ var init_config11 = __esm({
|
|
|
105897
106257
|
};
|
|
105898
106258
|
const trace = traceCredentialResolution2({
|
|
105899
106259
|
config,
|
|
105900
|
-
claudeEnv:
|
|
106260
|
+
claudeEnv: readClaudeSettingsEnv3(),
|
|
105901
106261
|
processEnv: process.env,
|
|
105902
106262
|
awsFiles: {
|
|
105903
106263
|
credentials: exists3(path193.join(home, ".aws", "credentials")),
|
|
@@ -133332,7 +133692,7 @@ __export(session_requests_exports, {
|
|
|
133332
133692
|
settleNotified: () => settleNotified,
|
|
133333
133693
|
settleReplied: () => settleReplied
|
|
133334
133694
|
});
|
|
133335
|
-
import { createHash as createHash18, randomUUID as
|
|
133695
|
+
import { createHash as createHash18, randomUUID as randomUUID9 } from "crypto";
|
|
133336
133696
|
import path137 from "path";
|
|
133337
133697
|
function oneLine(text, max = 160) {
|
|
133338
133698
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
@@ -133356,7 +133716,7 @@ function clampReplyTimeoutSecs(secs) {
|
|
|
133356
133716
|
}
|
|
133357
133717
|
async function createSessionRequest(input) {
|
|
133358
133718
|
const request3 = {
|
|
133359
|
-
id: `rq-${
|
|
133719
|
+
id: `rq-${randomUUID9().replace(/-/g, "").slice(0, 12)}`,
|
|
133360
133720
|
fromSessionId: input.fromSessionId,
|
|
133361
133721
|
...input.toSessionId ? { toSessionId: input.toSessionId } : {},
|
|
133362
133722
|
...input.toTaskId ? { toTaskId: input.toTaskId } : {},
|
|
@@ -133789,7 +134149,7 @@ __export(task_start_exports, {
|
|
|
133789
134149
|
SessionExistsError: () => SessionExistsError,
|
|
133790
134150
|
startSessionForTask: () => startSessionForTask
|
|
133791
134151
|
});
|
|
133792
|
-
import { randomUUID as
|
|
134152
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
133793
134153
|
async function startSessionForTask(params) {
|
|
133794
134154
|
const { taskIdPrefix, source } = params;
|
|
133795
134155
|
let task;
|
|
@@ -133840,7 +134200,7 @@ async function startSessionForTask(params) {
|
|
|
133840
134200
|
}
|
|
133841
134201
|
let message = params.message?.trim() || `Working on task: ${task.title}`;
|
|
133842
134202
|
const engine = normalizeEngine(params.engine);
|
|
133843
|
-
const preassignedSessionId = engineCaps(engine).idProvisioning === "provider-issued" ? void 0 :
|
|
134203
|
+
const preassignedSessionId = engineCaps(engine).idProvisioning === "provider-issued" ? void 0 : randomUUID10();
|
|
133844
134204
|
let requestId;
|
|
133845
134205
|
if (params.expectReply) {
|
|
133846
134206
|
const { resolveCaller: resolveCaller2 } = await Promise.resolve().then(() => (init_session_send_core(), session_send_core_exports));
|
|
@@ -149338,7 +149698,7 @@ async function generateLedgerDesc(task, timeoutMs = 15e3) {
|
|
|
149338
149698
|
try {
|
|
149339
149699
|
const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
|
|
149340
149700
|
const config = await getConfig2();
|
|
149341
|
-
const providerName = config
|
|
149701
|
+
const providerName = resolveMainProviderName(config);
|
|
149342
149702
|
const model = cheapModelFor(providerName);
|
|
149343
149703
|
const detail = (task.description ?? "").trim().slice(0, 800);
|
|
149344
149704
|
const project = (task.project ?? "").trim();
|
|
@@ -149397,6 +149757,7 @@ var init_task_ledger_desc = __esm({
|
|
|
149397
149757
|
"use strict";
|
|
149398
149758
|
init_model();
|
|
149399
149759
|
init_model_catalog();
|
|
149760
|
+
init_default_provider();
|
|
149400
149761
|
init_logging();
|
|
149401
149762
|
init_task_manager();
|
|
149402
149763
|
MAX_DESC_LEN = 120;
|
|
@@ -149420,6 +149781,10 @@ function diffStreamedVsPersisted(streamedTextMsgIds, persistedMsgIds) {
|
|
|
149420
149781
|
}
|
|
149421
149782
|
function armStreamConvergenceCheck(sessionId, streamedTextMsgIds) {
|
|
149422
149783
|
if (streamedTextMsgIds.length === 0) return;
|
|
149784
|
+
if (consumeWarmupTurn(sessionId)) {
|
|
149785
|
+
log.obs.debug("stream-convergence: skipped hidden warm-up turn", { sessionId });
|
|
149786
|
+
return;
|
|
149787
|
+
}
|
|
149423
149788
|
const timer3 = setTimeout(() => {
|
|
149424
149789
|
void runCheck(sessionId, streamedTextMsgIds).catch((err2) => {
|
|
149425
149790
|
log.obs.warn("stream-convergence check failed", {
|
|
@@ -149485,6 +149850,7 @@ var init_stream_convergence = __esm({
|
|
|
149485
149850
|
"src/core/observability/stream-convergence.ts"() {
|
|
149486
149851
|
"use strict";
|
|
149487
149852
|
init_logging();
|
|
149853
|
+
init_side_thread_warmup();
|
|
149488
149854
|
CHECK_DELAY_MS = 15e3;
|
|
149489
149855
|
INCIDENT_DEDUPE_MS = 30 * 6e4;
|
|
149490
149856
|
lastIncidentAt = /* @__PURE__ */ new Map();
|
|
@@ -149735,12 +150101,17 @@ function getSystemHealth() {
|
|
|
149735
150101
|
}
|
|
149736
150102
|
async function refreshSystemHealth() {
|
|
149737
150103
|
systemHealth.claudeCliAvailable = checkClaudeCliAvailable();
|
|
149738
|
-
|
|
150104
|
+
applyCredentialHealth(await resolveCredentialHealth());
|
|
150105
|
+
broadcastEvent("system:health", systemHealth);
|
|
150106
|
+
return systemHealth;
|
|
150107
|
+
}
|
|
150108
|
+
function applyCredentialHealth(cred) {
|
|
149739
150109
|
systemHealth.hasReadyProvider = cred.hasReadyProvider;
|
|
149740
150110
|
systemHealth.credentialSource = cred.source;
|
|
149741
150111
|
systemHealth.credentialDetail = cred.detail;
|
|
149742
|
-
|
|
149743
|
-
|
|
150112
|
+
systemHealth.mainProvider = cred.mainProvider;
|
|
150113
|
+
systemHealth.mainProviderImplicit = cred.mainProviderImplicit;
|
|
150114
|
+
systemHealth.claudeCliAuth = cred.claudeCliAuth;
|
|
149744
150115
|
}
|
|
149745
150116
|
function checkClaudeCliAvailable() {
|
|
149746
150117
|
return isClaudeCliInstalled();
|
|
@@ -149750,27 +150121,36 @@ async function resolveCredentialHealth() {
|
|
|
149750
150121
|
const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
|
|
149751
150122
|
const { resolveCredentials: resolveCredentials2 } = await Promise.resolve().then(() => (init_credential_resolver(), credential_resolver_exports));
|
|
149752
150123
|
const { buildProviderMap: buildProviderMap2 } = await Promise.resolve().then(() => (init_registry2(), registry_exports));
|
|
150124
|
+
const { resolveMainProviderName: resolveMainProviderName2, mainProviderIsImplicit: mainProviderIsImplicit2 } = await Promise.resolve().then(() => (init_default_provider(), default_provider_exports));
|
|
150125
|
+
const { detectClaudeCli: detectClaudeCli2 } = await Promise.resolve().then(() => (init_claude_cli_detect(), claude_cli_detect_exports));
|
|
149753
150126
|
const config = await getConfig2();
|
|
150127
|
+
const cli = detectClaudeCli2();
|
|
150128
|
+
const mainProvider = resolveMainProviderName2(config, cli.installed);
|
|
150129
|
+
const base = {
|
|
150130
|
+
mainProvider,
|
|
150131
|
+
mainProviderImplicit: mainProviderIsImplicit2(config),
|
|
150132
|
+
claudeCliAuth: cli.installed ? cli.auth.label : void 0
|
|
150133
|
+
};
|
|
150134
|
+
if (mainProvider === "claude_cli") {
|
|
150135
|
+
return cli.installed ? { ...base, hasReadyProvider: true, source: "config", detail: `claude-cli (${cli.auth.label})` } : { ...base, hasReadyProvider: false, source: "none" };
|
|
150136
|
+
}
|
|
149754
150137
|
const cred = resolveCredentials2(config);
|
|
149755
150138
|
if (cred.source !== "none") {
|
|
149756
|
-
return { hasReadyProvider: true, source: cred.source, detail: cred.detail };
|
|
150139
|
+
return { ...base, hasReadyProvider: true, source: cred.source, detail: cred.detail };
|
|
149757
150140
|
}
|
|
149758
150141
|
const providers = buildProviderMap2(config.providers, config);
|
|
149759
150142
|
for (const [, prov] of Object.entries(providers)) {
|
|
149760
150143
|
if (prov.api === "bedrock" || prov.api === "ollama") continue;
|
|
149761
150144
|
if (prov.api === "claude-cli") {
|
|
149762
|
-
|
|
149763
|
-
if (detectClaudeCli2().subscriptionReady) {
|
|
149764
|
-
return { hasReadyProvider: true, source: "config", detail: "claude-cli (subscription)" };
|
|
149765
|
-
}
|
|
150145
|
+
if (cli.installed) return { ...base, hasReadyProvider: true, source: "config", detail: `claude-cli (${cli.auth.label})` };
|
|
149766
150146
|
continue;
|
|
149767
150147
|
}
|
|
149768
150148
|
const implemented = prov.api === "anthropic-messages" || prov.api === "openai-chat" || prov.api === "google-generative-ai";
|
|
149769
150149
|
if (implemented && (prov.api_key || prov.bearer_token)) {
|
|
149770
|
-
return { hasReadyProvider: true, source: "config", detail: prov.api };
|
|
150150
|
+
return { ...base, hasReadyProvider: true, source: "config", detail: prov.api };
|
|
149771
150151
|
}
|
|
149772
150152
|
}
|
|
149773
|
-
return { hasReadyProvider: false, source: "none" };
|
|
150153
|
+
return { ...base, hasReadyProvider: false, source: "none" };
|
|
149774
150154
|
} catch {
|
|
149775
150155
|
return { hasReadyProvider: false, source: "none" };
|
|
149776
150156
|
}
|
|
@@ -149809,11 +150189,11 @@ async function startServer(options = {}) {
|
|
|
149809
150189
|
}
|
|
149810
150190
|
{
|
|
149811
150191
|
const cred = await resolveCredentialHealth();
|
|
149812
|
-
|
|
149813
|
-
systemHealth.credentialSource = cred.source;
|
|
149814
|
-
systemHealth.credentialDetail = cred.detail;
|
|
150192
|
+
applyCredentialHealth(cred);
|
|
149815
150193
|
if (!systemHealth.hasReadyProvider) {
|
|
149816
150194
|
log.web.warn("No AI provider configured \u2014 configure one in Settings");
|
|
150195
|
+
} else if (cred.mainProvider === "claude_cli") {
|
|
150196
|
+
log.web.info("AI provider: Claude Code", { auth: cred.claudeCliAuth, implicit: cred.mainProviderImplicit });
|
|
149817
150197
|
} else if (cred.source && cred.source !== "config") {
|
|
149818
150198
|
log.web.info("Auto-detected Bedrock credential", { source: cred.source, detail: cred.detail });
|
|
149819
150199
|
}
|
|
@@ -151093,10 +151473,9 @@ async function startServer(options = {}) {
|
|
|
151093
151473
|
const prev = systemHealth.hasReadyProvider;
|
|
151094
151474
|
const prevSource = systemHealth.credentialSource;
|
|
151095
151475
|
const cred = await resolveCredentialHealth();
|
|
151096
|
-
|
|
151097
|
-
|
|
151098
|
-
|
|
151099
|
-
if (cred.hasReadyProvider !== prev || cred.source !== prevSource) {
|
|
151476
|
+
const prevProvider = systemHealth.mainProvider;
|
|
151477
|
+
applyCredentialHealth(cred);
|
|
151478
|
+
if (cred.hasReadyProvider !== prev || cred.source !== prevSource || cred.mainProvider !== prevProvider) {
|
|
151100
151479
|
broadcastEvent("system:health", systemHealth);
|
|
151101
151480
|
}
|
|
151102
151481
|
} catch {
|
|
@@ -158523,11 +158902,12 @@ __export(session_controls_exports, {
|
|
|
158523
158902
|
applySessionModelChange: () => applySessionModelChange,
|
|
158524
158903
|
computeModelOptions: () => computeModelOptions,
|
|
158525
158904
|
controlErrorKind: () => controlErrorKind,
|
|
158905
|
+
createForkSiblingTask: () => createForkSiblingTask,
|
|
158526
158906
|
forkSessionToTask: () => forkSessionToTask,
|
|
158527
158907
|
handleSessionControlRelay: () => handleSessionControlRelay,
|
|
158528
158908
|
modelReadBackMatches: () => modelReadBackMatches
|
|
158529
158909
|
});
|
|
158530
|
-
import { randomUUID as
|
|
158910
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
158531
158911
|
function controlErrorKind(status2) {
|
|
158532
158912
|
if (status2 === 404) return "not_found";
|
|
158533
158913
|
if (status2 === 405) return "method_not_allowed";
|
|
@@ -158700,6 +159080,124 @@ async function computeModelOptions(sessionId) {
|
|
|
158700
159080
|
currentEffort: liveEffort ?? record.effectiveEffort ?? record.effort ?? null
|
|
158701
159081
|
};
|
|
158702
159082
|
}
|
|
159083
|
+
async function createForkSiblingTask(sourceTaskId, opts) {
|
|
159084
|
+
const {
|
|
159085
|
+
getTask: getTask2,
|
|
159086
|
+
addTask: addTask2,
|
|
159087
|
+
updateTask: updateTask2,
|
|
159088
|
+
groupTasks: groupTasks2,
|
|
159089
|
+
addToGroup: addToGroup2,
|
|
159090
|
+
renameGroup: renameGroup2
|
|
159091
|
+
} = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
|
|
159092
|
+
let sourceTask;
|
|
159093
|
+
try {
|
|
159094
|
+
sourceTask = await getTask2(sourceTaskId);
|
|
159095
|
+
} catch {
|
|
159096
|
+
throw new SessionControlError(`Source task "${sourceTaskId}" not found`, 404);
|
|
159097
|
+
}
|
|
159098
|
+
const sourceBaseTitle = sourceTask.title.replace(/^Fork of\s+/i, "").replace(/\s+-\s+fork of\s+.*$/i, "").trim() || sourceTask.title;
|
|
159099
|
+
const explicitTitle = opts.childTitle || (opts.titlePrefix ? `${opts.titlePrefix} - fork of ${sourceBaseTitle}` : void 0);
|
|
159100
|
+
const autoTitle = !explicitTitle;
|
|
159101
|
+
const newTitle = explicitTitle || `Fork of ${sourceBaseTitle}`;
|
|
159102
|
+
let inheritedTier;
|
|
159103
|
+
if (sourceTask.pinned && sourceTask.focus_tier) {
|
|
159104
|
+
const { getCustomTiers: getCustomTiers2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
|
|
159105
|
+
const known = ["focus", "backlog", "wait"].includes(sourceTask.focus_tier) || (await getCustomTiers2()).some((t3) => t3.id === sourceTask.focus_tier);
|
|
159106
|
+
if (known) inheritedTier = sourceTask.focus_tier;
|
|
159107
|
+
else {
|
|
159108
|
+
log.session.warn("fork: source tier is not registered, filing the fork in Satellite", {
|
|
159109
|
+
sourceTaskId: sourceTask.id,
|
|
159110
|
+
tier: sourceTask.focus_tier
|
|
159111
|
+
});
|
|
159112
|
+
}
|
|
159113
|
+
}
|
|
159114
|
+
const { task: newFork } = await addTask2({
|
|
159115
|
+
title: newTitle,
|
|
159116
|
+
project: sourceTask.project || "",
|
|
159117
|
+
source: sourceTask.source,
|
|
159118
|
+
...opts.description ? { description: opts.description } : {},
|
|
159119
|
+
// A fork is a person splitting off live work, so it joins the board like
|
|
159120
|
+
// any other hand-made task (Satellite = pinned, no stored tier).
|
|
159121
|
+
pinned: true,
|
|
159122
|
+
...inheritedTier ? { focus_tier: inheritedTier } : {},
|
|
159123
|
+
// Forking an Ask Walnut conversation yields another Ask Walnut
|
|
159124
|
+
// conversation: without the flag the fork loses the amber marker AND the
|
|
159125
|
+
// persona drift repair (both key on task.walnut_agent, never on project).
|
|
159126
|
+
...sourceTask.walnut_agent ? { walnut_agent: true } : {}
|
|
159127
|
+
});
|
|
159128
|
+
let forkGroupId;
|
|
159129
|
+
try {
|
|
159130
|
+
if (sourceTask.group_id) {
|
|
159131
|
+
const r8 = await addToGroup2(sourceTask.group_id, [newFork.id]);
|
|
159132
|
+
forkGroupId = r8.group_id;
|
|
159133
|
+
} else {
|
|
159134
|
+
const r8 = await groupTasks2([sourceTask.id, newFork.id], sourceTask.title);
|
|
159135
|
+
forkGroupId = r8.group_id;
|
|
159136
|
+
}
|
|
159137
|
+
} catch (err2) {
|
|
159138
|
+
log.session.warn("fork: failed to group source + fork", {
|
|
159139
|
+
sourceTaskId: sourceTask.id,
|
|
159140
|
+
forkTaskId: newFork.id,
|
|
159141
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
159142
|
+
});
|
|
159143
|
+
}
|
|
159144
|
+
let task;
|
|
159145
|
+
try {
|
|
159146
|
+
task = await getTask2(newFork.id);
|
|
159147
|
+
} catch {
|
|
159148
|
+
task = newFork;
|
|
159149
|
+
}
|
|
159150
|
+
bus.emit(EventNames.TASK_CREATED, { task }, ["web-ui", "main-agent"], { source: opts.source });
|
|
159151
|
+
if (autoTitle && opts.prompt?.trim()) {
|
|
159152
|
+
const forkId = newFork.id;
|
|
159153
|
+
const sourceTitle = sourceBaseTitle;
|
|
159154
|
+
const placeholderTitle = newTitle;
|
|
159155
|
+
const prompt = opts.prompt;
|
|
159156
|
+
const titleSource = `${opts.source}-title`;
|
|
159157
|
+
void (async () => {
|
|
159158
|
+
try {
|
|
159159
|
+
const { summarizeForkPrompt: summarizeForkPrompt2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
|
|
159160
|
+
const label = await summarizeForkPrompt2(prompt);
|
|
159161
|
+
if (!label) return;
|
|
159162
|
+
const current5 = await getTask2(forkId);
|
|
159163
|
+
if (current5.title !== placeholderTitle) {
|
|
159164
|
+
log.session.info("fork title refine skipped \u2014 title changed since fork", { taskId: forkId });
|
|
159165
|
+
return;
|
|
159166
|
+
}
|
|
159167
|
+
const refinedTitle = `${label} - fork of ${sourceTitle}`;
|
|
159168
|
+
const { task: updated } = await updateTask2(forkId, { title: refinedTitle }, { source: titleSource });
|
|
159169
|
+
bus.emit(EventNames.TASK_UPDATED, { task: updated }, ["web-ui", "main-agent"], { source: titleSource });
|
|
159170
|
+
log.session.info("fork title refined", { taskId: forkId, title: refinedTitle });
|
|
159171
|
+
} catch (err2) {
|
|
159172
|
+
log.session.warn("fork title refine failed", {
|
|
159173
|
+
taskId: forkId,
|
|
159174
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
159175
|
+
});
|
|
159176
|
+
}
|
|
159177
|
+
})();
|
|
159178
|
+
}
|
|
159179
|
+
if (forkGroupId && !sourceTask.group_id) {
|
|
159180
|
+
const gid = forkGroupId;
|
|
159181
|
+
const seedTitles = [sourceTask.title, newTitle];
|
|
159182
|
+
const groupSource = opts.source;
|
|
159183
|
+
void (async () => {
|
|
159184
|
+
try {
|
|
159185
|
+
const { summarizeGroupLabel: summarizeGroupLabel2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
|
|
159186
|
+
const label = await summarizeGroupLabel2(seedTitles);
|
|
159187
|
+
if (!label) return;
|
|
159188
|
+
await renameGroup2(gid, label);
|
|
159189
|
+
bus.emit(EventNames.TASK_GROUPS_CHANGED, { group_id: gid, label }, ["web-ui", "main-agent"], { source: groupSource });
|
|
159190
|
+
log.session.info("fork group label refined", { groupId: gid, label });
|
|
159191
|
+
} catch (err2) {
|
|
159192
|
+
log.session.warn("fork group label refine failed", {
|
|
159193
|
+
groupId: gid,
|
|
159194
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
159195
|
+
});
|
|
159196
|
+
}
|
|
159197
|
+
})();
|
|
159198
|
+
}
|
|
159199
|
+
return { task, ...forkGroupId ? { groupId: forkGroupId } : {} };
|
|
159200
|
+
}
|
|
158703
159201
|
async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
|
|
158704
159202
|
const { task_id, create_child_task, child_title, message, title, model, imageContext } = input;
|
|
158705
159203
|
if (!task_id && !create_child_task) {
|
|
@@ -158709,14 +159207,7 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
|
|
|
158709
159207
|
throw new SessionControlError("task_id and create_child_task are mutually exclusive", 400);
|
|
158710
159208
|
}
|
|
158711
159209
|
const { getSessionByClaudeId: getSessionByClaudeId2, getSessionsForTask: getSessionsForTask2, createSessionRecord: createSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
|
|
158712
|
-
const {
|
|
158713
|
-
getTask: getTask2,
|
|
158714
|
-
addTask: addTask2,
|
|
158715
|
-
updateTask: updateTask2,
|
|
158716
|
-
groupTasks: groupTasks2,
|
|
158717
|
-
addToGroup: addToGroup2,
|
|
158718
|
-
renameGroup: renameGroup2
|
|
158719
|
-
} = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
|
|
159210
|
+
const { getTask: getTask2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
|
|
158720
159211
|
const sourceRecord = await getSessionByClaudeId2(sourceSessionId);
|
|
158721
159212
|
if (!sourceRecord) throw new SessionControlError("Source session not found", 404);
|
|
158722
159213
|
if (!engineCaps(sourceRecord.engine).fork) {
|
|
@@ -158733,108 +159224,13 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
|
|
|
158733
159224
|
if (!sourceRecord.taskId) {
|
|
158734
159225
|
throw new SessionControlError("Source session has no task \u2014 cannot create fork task", 400);
|
|
158735
159226
|
}
|
|
158736
|
-
|
|
158737
|
-
|
|
158738
|
-
|
|
158739
|
-
|
|
158740
|
-
|
|
158741
|
-
|
|
158742
|
-
const sourceBaseTitle = sourceTask.title.replace(/^Fork of\s+/i, "").replace(/\s+-\s+fork of\s+.*$/i, "").trim() || sourceTask.title;
|
|
158743
|
-
const autoTitle = !child_title;
|
|
158744
|
-
const newTitle = child_title || `Fork of ${sourceBaseTitle}`;
|
|
158745
|
-
let inheritedTier;
|
|
158746
|
-
if (sourceTask.pinned && sourceTask.focus_tier) {
|
|
158747
|
-
const { getCustomTiers: getCustomTiers2 } = await Promise.resolve().then(() => (init_task_manager(), task_manager_exports));
|
|
158748
|
-
const known = ["focus", "backlog", "wait"].includes(sourceTask.focus_tier) || (await getCustomTiers2()).some((t3) => t3.id === sourceTask.focus_tier);
|
|
158749
|
-
if (known) inheritedTier = sourceTask.focus_tier;
|
|
158750
|
-
else {
|
|
158751
|
-
log.session.warn("fork: source tier is not registered, filing the fork in Satellite", {
|
|
158752
|
-
sourceTaskId: sourceTask.id,
|
|
158753
|
-
tier: sourceTask.focus_tier
|
|
158754
|
-
});
|
|
158755
|
-
}
|
|
158756
|
-
}
|
|
158757
|
-
const { task: newFork } = await addTask2({
|
|
158758
|
-
title: newTitle,
|
|
158759
|
-
project: sourceTask.project || "",
|
|
158760
|
-
source: sourceTask.source,
|
|
158761
|
-
// A fork is a person splitting off live work, so it joins the board like
|
|
158762
|
-
// any other hand-made task (Satellite = pinned, no stored tier).
|
|
158763
|
-
pinned: true,
|
|
158764
|
-
...inheritedTier ? { focus_tier: inheritedTier } : {},
|
|
158765
|
-
// Forking an Ask Walnut conversation yields another Ask Walnut
|
|
158766
|
-
// conversation: without the flag the fork loses the amber marker AND the
|
|
158767
|
-
// persona drift repair (both key on task.walnut_agent, never on project).
|
|
158768
|
-
...sourceTask.walnut_agent ? { walnut_agent: true } : {}
|
|
158769
|
-
});
|
|
158770
|
-
let forkGroupId;
|
|
158771
|
-
try {
|
|
158772
|
-
if (sourceTask.group_id) {
|
|
158773
|
-
const r8 = await addToGroup2(sourceTask.group_id, [newFork.id]);
|
|
158774
|
-
forkGroupId = r8.group_id;
|
|
158775
|
-
} else {
|
|
158776
|
-
const r8 = await groupTasks2([sourceTask.id, newFork.id], sourceTask.title);
|
|
158777
|
-
forkGroupId = r8.group_id;
|
|
158778
|
-
}
|
|
158779
|
-
} catch (err2) {
|
|
158780
|
-
log.session.warn("fork: failed to group source + fork", {
|
|
158781
|
-
sourceTaskId: sourceTask.id,
|
|
158782
|
-
forkTaskId: newFork.id,
|
|
158783
|
-
error: err2 instanceof Error ? err2.message : String(err2)
|
|
158784
|
-
});
|
|
158785
|
-
}
|
|
158786
|
-
try {
|
|
158787
|
-
task = await getTask2(newFork.id);
|
|
158788
|
-
} catch {
|
|
158789
|
-
task = newFork;
|
|
158790
|
-
}
|
|
158791
|
-
bus.emit(EventNames.TASK_CREATED, { task }, ["web-ui", "main-agent"], { source: "fork" });
|
|
159227
|
+
const created = await createForkSiblingTask(sourceRecord.taskId, {
|
|
159228
|
+
childTitle: child_title,
|
|
159229
|
+
prompt: message,
|
|
159230
|
+
source: "fork"
|
|
159231
|
+
});
|
|
159232
|
+
task = created.task;
|
|
158792
159233
|
childTaskCreated = true;
|
|
158793
|
-
if (autoTitle && message?.trim()) {
|
|
158794
|
-
const forkId = newFork.id;
|
|
158795
|
-
const sourceTitle = sourceBaseTitle;
|
|
158796
|
-
const placeholderTitle = newTitle;
|
|
158797
|
-
void (async () => {
|
|
158798
|
-
try {
|
|
158799
|
-
const { summarizeForkPrompt: summarizeForkPrompt2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
|
|
158800
|
-
const label = await summarizeForkPrompt2(message);
|
|
158801
|
-
if (!label) return;
|
|
158802
|
-
const current5 = await getTask2(forkId);
|
|
158803
|
-
if (current5.title !== placeholderTitle) {
|
|
158804
|
-
log.session.info("fork title refine skipped \u2014 title changed since fork", { taskId: forkId });
|
|
158805
|
-
return;
|
|
158806
|
-
}
|
|
158807
|
-
const refinedTitle = `${label} - fork of ${sourceTitle}`;
|
|
158808
|
-
const { task: updated } = await updateTask2(forkId, { title: refinedTitle }, { source: "fork-title" });
|
|
158809
|
-
bus.emit(EventNames.TASK_UPDATED, { task: updated }, ["web-ui", "main-agent"], { source: "fork-title" });
|
|
158810
|
-
log.session.info("fork title refined", { taskId: forkId, title: refinedTitle });
|
|
158811
|
-
} catch (err2) {
|
|
158812
|
-
log.session.warn("fork title refine failed", {
|
|
158813
|
-
taskId: forkId,
|
|
158814
|
-
error: err2 instanceof Error ? err2.message : String(err2)
|
|
158815
|
-
});
|
|
158816
|
-
}
|
|
158817
|
-
})();
|
|
158818
|
-
}
|
|
158819
|
-
if (forkGroupId && !sourceTask.group_id) {
|
|
158820
|
-
const gid = forkGroupId;
|
|
158821
|
-
const seedTitles = [sourceTask.title, newTitle];
|
|
158822
|
-
void (async () => {
|
|
158823
|
-
try {
|
|
158824
|
-
const { summarizeGroupLabel: summarizeGroupLabel2 } = await Promise.resolve().then(() => (init_fork_title(), fork_title_exports));
|
|
158825
|
-
const label = await summarizeGroupLabel2(seedTitles);
|
|
158826
|
-
if (!label) return;
|
|
158827
|
-
await renameGroup2(gid, label);
|
|
158828
|
-
bus.emit(EventNames.TASK_GROUPS_CHANGED, { group_id: gid, label }, ["web-ui", "main-agent"], { source: "fork" });
|
|
158829
|
-
log.session.info("fork group label refined", { groupId: gid, label });
|
|
158830
|
-
} catch (err2) {
|
|
158831
|
-
log.session.warn("fork group label refine failed", {
|
|
158832
|
-
groupId: gid,
|
|
158833
|
-
error: err2 instanceof Error ? err2.message : String(err2)
|
|
158834
|
-
});
|
|
158835
|
-
}
|
|
158836
|
-
})();
|
|
158837
|
-
}
|
|
158838
159234
|
} else {
|
|
158839
159235
|
try {
|
|
158840
159236
|
task = await getTask2(task_id);
|
|
@@ -158856,7 +159252,7 @@ async function forkSessionToTask(sourceSessionId, input, source = "web-api") {
|
|
|
158856
159252
|
const FORK_FOCUS_PREFIX = "This is a forked session. Focus on the NEW request below \u2014 treat it as your primary task. Do not resume or continue the parent session's previous work unless the user explicitly asks you to.\n\n";
|
|
158857
159253
|
const forkMessage = `${FORK_FOCUS_PREFIX}${imageContext ?? ""}New request:
|
|
158858
159254
|
${userRequest}`;
|
|
158859
|
-
const forkSessionId =
|
|
159255
|
+
const forkSessionId = randomUUID11();
|
|
158860
159256
|
const forkTitle = title ?? `Fork of ${sourceRecord.title ?? sourceSessionId.slice(0, 16)}`;
|
|
158861
159257
|
try {
|
|
158862
159258
|
await createSessionRecord2(forkSessionId, task.id, task.project ?? "", sourceRecord.cwd, {
|
|
@@ -159579,7 +159975,7 @@ __export(session_rewind_exports, {
|
|
|
159579
159975
|
probeRewindViaDaemon: () => probeRewindViaDaemon,
|
|
159580
159976
|
rewindSessionToMessage: () => rewindSessionToMessage
|
|
159581
159977
|
});
|
|
159582
|
-
import { randomUUID as
|
|
159978
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
159583
159979
|
function cutAncestorHistoryAtRewindPoint(ancestorMessages, rewoundAtMessageUuid) {
|
|
159584
159980
|
if (!rewoundAtMessageUuid) {
|
|
159585
159981
|
return { messages: ancestorMessages, found: false, dropped: 0 };
|
|
@@ -159780,7 +160176,7 @@ async function rewindSessionToMessage(sessionId, input, source = "web-api") {
|
|
|
159780
160176
|
error: err2 instanceof Error ? err2.message : String(err2)
|
|
159781
160177
|
});
|
|
159782
160178
|
}
|
|
159783
|
-
const rewoundId =
|
|
160179
|
+
const rewoundId = randomUUID12();
|
|
159784
160180
|
const baseTitle = (record.title ?? sessionId.slice(0, 16)).replace(/\s*\(rewound\)$/i, "");
|
|
159785
160181
|
const title = `${baseTitle} (rewound)`.slice(0, 200);
|
|
159786
160182
|
const { createSessionRecord: createSessionRecord2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
|
|
@@ -160500,6 +160896,7 @@ function parseSessionMessages(content, opts) {
|
|
|
160500
160896
|
}
|
|
160501
160897
|
}
|
|
160502
160898
|
let lastPlanContent = null;
|
|
160899
|
+
let dropWarmupReply = false;
|
|
160503
160900
|
const resultParentIds = [];
|
|
160504
160901
|
const result = [];
|
|
160505
160902
|
for (const [mapMsgId, msg] of messageMap) {
|
|
@@ -160577,10 +160974,18 @@ ${imgPath}` : imgPath;
|
|
|
160577
160974
|
let text = textParts.join("\n").trim();
|
|
160578
160975
|
if (!text && tools2.length === 0 && !thinking) continue;
|
|
160579
160976
|
if (msg.role === "user" && text.startsWith("<")) {
|
|
160977
|
+
if (isCacheWarmupText(text)) {
|
|
160978
|
+
dropWarmupReply = true;
|
|
160979
|
+
continue;
|
|
160980
|
+
}
|
|
160580
160981
|
const transformed = transformInjectedUserText(text);
|
|
160581
160982
|
if (transformed === null) continue;
|
|
160582
160983
|
if (transformed !== void 0) text = transformed;
|
|
160583
160984
|
}
|
|
160985
|
+
if (msg.role === "assistant" && dropWarmupReply) {
|
|
160986
|
+
dropWarmupReply = false;
|
|
160987
|
+
if (tools2.length === 0) continue;
|
|
160988
|
+
}
|
|
160584
160989
|
if (msg.role === "user") text = stripOutputModeWrappers(text);
|
|
160585
160990
|
if (msg.role === "assistant" && !text && tools2.length === 0) continue;
|
|
160586
160991
|
result.push({
|
|
@@ -161531,6 +161936,7 @@ var init_session_history = __esm({
|
|
|
161531
161936
|
init_session_io();
|
|
161532
161937
|
init_constants();
|
|
161533
161938
|
init_remote_image_mirror();
|
|
161939
|
+
init_side_thread_warmup();
|
|
161534
161940
|
init_session_file_reader();
|
|
161535
161941
|
LOCAL_HOME = os36.homedir();
|
|
161536
161942
|
TAIL_SEGMENT_ROLL_BYTES = 4 * 1024 * 1024;
|
|
@@ -161678,7 +162084,7 @@ var init_context_window = __esm({
|
|
|
161678
162084
|
});
|
|
161679
162085
|
|
|
161680
162086
|
// src/providers/turn-ledger.ts
|
|
161681
|
-
import { randomUUID as
|
|
162087
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
161682
162088
|
function openTurn(sessionId) {
|
|
161683
162089
|
const existing = openTurns.get(sessionId);
|
|
161684
162090
|
if (existing && !existing.settled) {
|
|
@@ -161688,7 +162094,7 @@ function openTurn(sessionId) {
|
|
|
161688
162094
|
});
|
|
161689
162095
|
return { turnId: existing.turnId, promise: existing.promise };
|
|
161690
162096
|
}
|
|
161691
|
-
const turnId =
|
|
162097
|
+
const turnId = randomUUID13();
|
|
161692
162098
|
let resolve;
|
|
161693
162099
|
let reject;
|
|
161694
162100
|
const promise = new Promise((res, rej) => {
|
|
@@ -168436,11 +168842,39 @@ var init_claude_code_session = __esm({
|
|
|
168436
168842
|
session2.pendingDescription = message.slice(0, 500);
|
|
168437
168843
|
let appendSystemPrompt;
|
|
168438
168844
|
const isFork = !!data.forkedFromSessionId;
|
|
168439
|
-
|
|
168440
|
-
|
|
168441
|
-
|
|
168442
|
-
|
|
168443
|
-
|
|
168845
|
+
const { parseSideLaneKey: parseSideLaneKey2 } = await Promise.resolve().then(() => (init_side_thread_fork(), side_thread_fork_exports));
|
|
168846
|
+
const sideIds = parseSideLaneKey2(data.lane);
|
|
168847
|
+
if (sideIds) {
|
|
168848
|
+
let exact = data.appendSystemPromptExact;
|
|
168849
|
+
let source = "live-or-record";
|
|
168850
|
+
if (exact === void 0) {
|
|
168851
|
+
try {
|
|
168852
|
+
const { getSessionByClaudeId: getSessionByClaudeId2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
|
|
168853
|
+
const { spawnPrefixFromRecord: spawnPrefixFromRecord2 } = await Promise.resolve().then(() => (init_spawn_prefix(), spawn_prefix_exports));
|
|
168854
|
+
const parentRecord = await getSessionByClaudeId2(sideIds.parentSid);
|
|
168855
|
+
const prefix = parentRecord ? spawnPrefixFromRecord2(parentRecord) : null;
|
|
168856
|
+
exact = prefix?.appendSystemPrompt ?? null;
|
|
168857
|
+
source = prefix?.source ?? "no-parent-record";
|
|
168858
|
+
} catch (err2) {
|
|
168859
|
+
exact = null;
|
|
168860
|
+
source = "lookup-failed";
|
|
168861
|
+
log.session.warn("side thread: parent prompt lookup failed \u2014 spawning without an append prompt", {
|
|
168862
|
+
parentSid: sideIds.parentSid,
|
|
168863
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
168864
|
+
});
|
|
168865
|
+
}
|
|
168866
|
+
}
|
|
168867
|
+
appendSystemPrompt = exact ?? void 0;
|
|
168868
|
+
log.session.info("side thread: prefix prompt resolved", {
|
|
168869
|
+
parentSid: sideIds.parentSid,
|
|
168870
|
+
source,
|
|
168871
|
+
promptLength: appendSystemPrompt?.length ?? 0
|
|
168872
|
+
});
|
|
168873
|
+
} else {
|
|
168874
|
+
if (data.appendSystemPrompt && !isFork) {
|
|
168875
|
+
appendSystemPrompt = data.appendSystemPrompt;
|
|
168876
|
+
log.session.info("using caller-provided system prompt", { taskId: taskId2, promptLength: data.appendSystemPrompt.length });
|
|
168877
|
+
}
|
|
168444
168878
|
try {
|
|
168445
168879
|
const { buildSessionContext: buildSessionContext2 } = await Promise.resolve().then(() => (init_session_context(), session_context_exports));
|
|
168446
168880
|
const ctx = await buildSessionContext2(taskId2 ?? "", cwd, data.host);
|
|
@@ -168458,32 +168892,6 @@ ${ctx.systemPrompt}` : ctx.systemPrompt;
|
|
|
168458
168892
|
log.session.warn("failed to build session context", { taskId: taskId2, error: err2 instanceof Error ? err2.message : String(err2) });
|
|
168459
168893
|
}
|
|
168460
168894
|
}
|
|
168461
|
-
{
|
|
168462
|
-
const { parseSideLaneKey: parseSideLaneKey2 } = await Promise.resolve().then(() => (init_side_thread_fork(), side_thread_fork_exports));
|
|
168463
|
-
const sideIds = parseSideLaneKey2(data.lane);
|
|
168464
|
-
if (sideIds) {
|
|
168465
|
-
try {
|
|
168466
|
-
const { getSessionByClaudeId: getSessionByClaudeId2 } = await Promise.resolve().then(() => (init_session_tracker(), session_tracker_exports));
|
|
168467
|
-
const parentRecord = await getSessionByClaudeId2(sideIds.parentSid);
|
|
168468
|
-
const inherited = parentRecord?.appliedAppendSystemPrompt;
|
|
168469
|
-
if (inherited && Buffer.byteLength(inherited, "utf8") <= 65536) {
|
|
168470
|
-
appendSystemPrompt = inherited;
|
|
168471
|
-
log.session.info("side thread: inherited parent system prompt verbatim", {
|
|
168472
|
-
parentSid: sideIds.parentSid,
|
|
168473
|
-
promptLength: appendSystemPrompt.length
|
|
168474
|
-
});
|
|
168475
|
-
} else {
|
|
168476
|
-
log.session.info("side thread: parent has no stored system prompt \u2014 fresh build (cache prefix will diverge)", {
|
|
168477
|
-
parentSid: sideIds.parentSid
|
|
168478
|
-
});
|
|
168479
|
-
}
|
|
168480
|
-
} catch (err2) {
|
|
168481
|
-
log.session.warn("side thread: parent prompt lookup failed \u2014 fresh build", {
|
|
168482
|
-
error: err2 instanceof Error ? err2.message : String(err2)
|
|
168483
|
-
});
|
|
168484
|
-
}
|
|
168485
|
-
}
|
|
168486
|
-
}
|
|
168487
168895
|
if (data.preassignedSessionId && appendSystemPrompt && Buffer.byteLength(appendSystemPrompt, "utf8") <= 65536) {
|
|
168488
168896
|
const promptToStore = appendSystemPrompt;
|
|
168489
168897
|
const sidToStore = data.preassignedSessionId;
|
|
@@ -177482,7 +177890,7 @@ __export(mobile_launch_exports, {
|
|
|
177482
177890
|
performMobileLaunch: () => performMobileLaunch,
|
|
177483
177891
|
validateMobileLaunchBody: () => validateMobileLaunchBody
|
|
177484
177892
|
});
|
|
177485
|
-
import { randomUUID as
|
|
177893
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
177486
177894
|
function launchErrorCode(status2) {
|
|
177487
177895
|
if (status2 === 404) return "not_found";
|
|
177488
177896
|
if (status2 === 409) return "conflict";
|
|
@@ -177604,7 +178012,7 @@ async function performMobileLaunch(input, source) {
|
|
|
177604
178012
|
);
|
|
177605
178013
|
}
|
|
177606
178014
|
}
|
|
177607
|
-
const preassignedSessionId = engineCaps(input.engine).idProvisioning === "provider-issued" ? void 0 :
|
|
178015
|
+
const preassignedSessionId = engineCaps(input.engine).idProvisioning === "provider-issued" ? void 0 : randomUUID14();
|
|
177608
178016
|
const task = await quickStartSession({
|
|
177609
178017
|
message: input.message,
|
|
177610
178018
|
cwd: input.cwd,
|
|
@@ -178031,6 +178439,7 @@ __export(daemon_connection_exports, {
|
|
|
178031
178439
|
isDaemonConnected: () => isDaemonConnected,
|
|
178032
178440
|
isPooledConnection: () => isPooledConnection,
|
|
178033
178441
|
probeDaemonSession: () => probeDaemonSession,
|
|
178442
|
+
probeDaemonSessionArgs: () => probeDaemonSessionArgs,
|
|
178034
178443
|
pushDaemonHooksToAllHosts: () => pushDaemonHooksToAllHosts,
|
|
178035
178444
|
setOnDaemonHostConnected: () => setOnDaemonHostConnected,
|
|
178036
178445
|
setOnDaemonStatusChange: () => setOnDaemonStatusChange,
|
|
@@ -178207,6 +178616,28 @@ async function probeDaemonSession(hostKey2, sessionId) {
|
|
|
178207
178616
|
return null;
|
|
178208
178617
|
}
|
|
178209
178618
|
}
|
|
178619
|
+
async function probeDaemonSessionArgs(hostKey2, sessionId, deadlineMs = 2e3) {
|
|
178620
|
+
const conn = getConnectedDaemonConnection(hostKey2);
|
|
178621
|
+
if (!conn) return null;
|
|
178622
|
+
try {
|
|
178623
|
+
const result = await Promise.race([
|
|
178624
|
+
conn.send("status", { sid: sessionId, includeArgs: true }),
|
|
178625
|
+
new Promise((_, reject) => setTimeout(
|
|
178626
|
+
() => reject(new Error(`status probe exceeded ${deadlineMs}ms`)),
|
|
178627
|
+
deadlineMs
|
|
178628
|
+
).unref?.())
|
|
178629
|
+
]);
|
|
178630
|
+
if (!result.ok || !result.alive || !Array.isArray(result.args)) return null;
|
|
178631
|
+
return result.args.filter((a8) => typeof a8 === "string");
|
|
178632
|
+
} catch (err2) {
|
|
178633
|
+
log.session.debug("probeDaemonSessionArgs: status probe failed", {
|
|
178634
|
+
hostKey: hostKey2,
|
|
178635
|
+
sessionId,
|
|
178636
|
+
error: err2 instanceof Error ? err2.message : String(err2)
|
|
178637
|
+
});
|
|
178638
|
+
return null;
|
|
178639
|
+
}
|
|
178640
|
+
}
|
|
178210
178641
|
var execFileAsync23, MOBILE_ENQUEUE_LEDGER_MAX, recentMobileEnqueues, DaemonConnection, onPoolStatusChange, onHostConnected, hostConnectedListeners, connectionPool, connectingPromises, failureCache, FAILURE_CACHE_TTL_MS;
|
|
178211
178642
|
var init_daemon_connection = __esm({
|
|
178212
178643
|
"src/providers/daemon-connection.ts"() {
|
|
@@ -182774,6 +183205,7 @@ async function createSessionRecord(claudeSessionId, taskId2, project, cwd, extra
|
|
|
182774
183205
|
...extra?.effort ? { effort: extra.effort } : {},
|
|
182775
183206
|
...extra?.profile ? { profile: extra.profile } : {},
|
|
182776
183207
|
...extra?.lane ? { lane: extra.lane } : {},
|
|
183208
|
+
...extra?.appliedAppendSystemPrompt !== void 0 ? { appliedAppendSystemPrompt: extra.appliedAppendSystemPrompt } : {},
|
|
182777
183209
|
...extra?.engine ? { engine: extra.engine } : {},
|
|
182778
183210
|
...extra?.acpRuntimeId ? { acpRuntimeId: extra.acpRuntimeId } : {},
|
|
182779
183211
|
...extra?.acpJournalPath ? { acpJournalPath: extra.acpJournalPath } : {},
|
|
@@ -182928,7 +183360,7 @@ function applyUpdateToSession(session2, updates, logLabel) {
|
|
|
182928
183360
|
};
|
|
182929
183361
|
if (count2 === 1) log.session.info("legacy status write suppressed", payload);
|
|
182930
183362
|
else log.session.debug("legacy status write suppressed", payload);
|
|
182931
|
-
if (updates.process_status === "error") {
|
|
183363
|
+
if (updates.process_status === "error" || updates.process_status === "stopped") {
|
|
182932
183364
|
noteSuppressedErrorReason(session2.claudeSessionId, {
|
|
182933
183365
|
reason: typeof reason === "string" ? reason : "unknown",
|
|
182934
183366
|
message: typeof updates.errorMessage === "string" ? updates.errorMessage : void 0,
|