open-walnut 0.3.0
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/LICENSE +21 -0
- package/README.md +339 -0
- package/bin/open-walnut.js +2 -0
- package/bin/walnut.js +2 -0
- package/dist/cli.js +137849 -0
- package/dist/daemon-binaries/acp-worker.js +44 -0
- package/dist/daemon-binaries/acp-worker.js.version +1 -0
- package/dist/data/skills/daily-standup/SKILL.md +16 -0
- package/dist/data/skills/install-plugin/SKILL.md +118 -0
- package/dist/data/skills/learn/SKILL.md +46 -0
- package/dist/data/skills/register-repo/SKILL.md +20 -0
- package/dist/data/skills/setup-cloud-companion/SKILL.md +326 -0
- package/dist/data/skills/triage/SKILL.md +23 -0
- package/dist/data/skills/walnut-calendar/SKILL.md +92 -0
- package/dist/data/skills/walnut-manage-repos/SKILL.md +107 -0
- package/dist/data/skills/walnut-peer-sessions/SKILL.md +61 -0
- package/dist/data/skills/walnut-register-repo/SKILL.md +72 -0
- package/dist/data/skills/walnut-register-repo/references/yaml-format.md +111 -0
- package/dist/data/skills/walnut-register-repo/scripts/validate-repo.py +177 -0
- package/dist/data/skills/walnut-routines/SKILL.md +65 -0
- package/dist/data/skills/weekly-review/SKILL.md +22 -0
- package/dist/data/walnut-calendar.swift +213 -0
- package/dist/data/walnut-extract.swift +80 -0
- package/dist/hooks/on-compact.js +976 -0
- package/dist/hooks/on-stop.js +1329 -0
- package/dist/integrations/jira/index.js +4200 -0
- package/dist/integrations/jira/manifest.json +26 -0
- package/dist/integrations/local/index.js +51 -0
- package/dist/integrations/local/manifest.json +10 -0
- package/dist/integrations/ms-todo/index.js +4672 -0
- package/dist/integrations/ms-todo/manifest.json +17 -0
- package/dist/session-server/index.js +1382 -0
- package/dist/web/server.js +135275 -0
- package/dist/web/static/assets/apl-B4CMkyY2.js +1 -0
- package/dist/web/static/assets/asciiarmor-Df11BRmG.js +1 -0
- package/dist/web/static/assets/asn1-EdZsLKOL.js +1 -0
- package/dist/web/static/assets/asterisk-B-8jnY81.js +1 -0
- package/dist/web/static/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/dist/web/static/assets/clike-B9uivgTg.js +1 -0
- package/dist/web/static/assets/clojure-BMjYHr_A.js +1 -0
- package/dist/web/static/assets/cmake-BQqOBYOt.js +1 -0
- package/dist/web/static/assets/cobol-CWcv1MsR.js +1 -0
- package/dist/web/static/assets/coffeescript-S37ZYGWr.js +1 -0
- package/dist/web/static/assets/commonlisp-DBKNyK5s.js +1 -0
- package/dist/web/static/assets/crystal-SjHAIU92.js +1 -0
- package/dist/web/static/assets/css-BnMrqG3P.js +1 -0
- package/dist/web/static/assets/cypher-C_CwsFkJ.js +1 -0
- package/dist/web/static/assets/d-pRatUO7H.js +1 -0
- package/dist/web/static/assets/diff-DbItnlRl.js +1 -0
- package/dist/web/static/assets/dockerfile-BKs6k2Af.js +1 -0
- package/dist/web/static/assets/dtd-DF_7sFjM.js +1 -0
- package/dist/web/static/assets/dylan-DwRh75JA.js +1 -0
- package/dist/web/static/assets/ebnf-CDyGwa7X.js +1 -0
- package/dist/web/static/assets/ecl-Cabwm37j.js +1 -0
- package/dist/web/static/assets/eiffel-CnydiIhH.js +1 -0
- package/dist/web/static/assets/elm-vLlmbW-K.js +1 -0
- package/dist/web/static/assets/erlang-BNw1qcRV.js +1 -0
- package/dist/web/static/assets/factor-kuTfRLto.js +1 -0
- package/dist/web/static/assets/fcl-Kvtd6kyn.js +1 -0
- package/dist/web/static/assets/forth-Ffai-XNe.js +1 -0
- package/dist/web/static/assets/fortran-DYz_wnZ1.js +1 -0
- package/dist/web/static/assets/gas-Bneqetm1.js +1 -0
- package/dist/web/static/assets/gherkin-heZmZLOM.js +1 -0
- package/dist/web/static/assets/groovy-D9Dt4D0W.js +1 -0
- package/dist/web/static/assets/haskell-Cw1EW3IL.js +1 -0
- package/dist/web/static/assets/haxe-H-WmDvRZ.js +1 -0
- package/dist/web/static/assets/http-DBlCnlav.js +1 -0
- package/dist/web/static/assets/idl-BEugSyMb.js +1 -0
- package/dist/web/static/assets/index-B3K5yAdA.js +1 -0
- package/dist/web/static/assets/index-B7OvpXOs.js +632 -0
- package/dist/web/static/assets/index-C07UNJIJ.js +1 -0
- package/dist/web/static/assets/index-C8k-3wfu.js +2 -0
- package/dist/web/static/assets/index-CBXGsT3t.js +1 -0
- package/dist/web/static/assets/index-CUrO3qpX.js +1 -0
- package/dist/web/static/assets/index-CpTvBBAa.js +1 -0
- package/dist/web/static/assets/index-D4J6slTf.js +1 -0
- package/dist/web/static/assets/index-DUgC5K_q.js +3 -0
- package/dist/web/static/assets/index-DhqXE1wa.js +1 -0
- package/dist/web/static/assets/index-DkGqHTCk.js +1 -0
- package/dist/web/static/assets/index-DsxFiBic.js +1 -0
- package/dist/web/static/assets/index-Dz3umkfm.js +1 -0
- package/dist/web/static/assets/index-G2v0oaqp.js +1 -0
- package/dist/web/static/assets/index-GNDKJKwy.js +1 -0
- package/dist/web/static/assets/index-U1CVtNUH.css +32 -0
- package/dist/web/static/assets/index-ft4kpQXq.js +1 -0
- package/dist/web/static/assets/index-ls7h0u-p.js +1 -0
- package/dist/web/static/assets/index-nL5l5WPW.js +7 -0
- package/dist/web/static/assets/javascript-iXu5QeM3.js +1 -0
- package/dist/web/static/assets/julia-DuME0IfC.js +1 -0
- package/dist/web/static/assets/livescript-BwQOo05w.js +1 -0
- package/dist/web/static/assets/lua-VAEuO923.js +1 -0
- package/dist/web/static/assets/mathematica-DTrFuWx2.js +1 -0
- package/dist/web/static/assets/mbox-CNhZ1qSd.js +1 -0
- package/dist/web/static/assets/mirc-CjQqDB4T.js +1 -0
- package/dist/web/static/assets/mllike-CXdrOF99.js +1 -0
- package/dist/web/static/assets/modelica-Dc1JOy9r.js +1 -0
- package/dist/web/static/assets/mscgen-BA5vi2Kp.js +1 -0
- package/dist/web/static/assets/mumps-BT43cFF4.js +1 -0
- package/dist/web/static/assets/nginx-DdIZxoE0.js +1 -0
- package/dist/web/static/assets/nsis-LdVXkNf5.js +1 -0
- package/dist/web/static/assets/ntriples-BfvgReVJ.js +1 -0
- package/dist/web/static/assets/octave-Ck1zUtKM.js +1 -0
- package/dist/web/static/assets/oz-BzwKVEFT.js +1 -0
- package/dist/web/static/assets/pascal--L3eBynH.js +1 -0
- package/dist/web/static/assets/perl-CdXCOZ3F.js +1 -0
- package/dist/web/static/assets/pig-CevX1Tat.js +1 -0
- package/dist/web/static/assets/powershell-CFHJl5sT.js +1 -0
- package/dist/web/static/assets/properties-C78fOPTZ.js +1 -0
- package/dist/web/static/assets/protobuf-ChK-085T.js +1 -0
- package/dist/web/static/assets/pug-DeIclll2.js +1 -0
- package/dist/web/static/assets/puppet-DMA9R1ak.js +1 -0
- package/dist/web/static/assets/python-BuPzkPfP.js +1 -0
- package/dist/web/static/assets/q-pXgVlZs6.js +1 -0
- package/dist/web/static/assets/r-B6wPVr8A.js +1 -0
- package/dist/web/static/assets/rpm-CTu-6PCP.js +1 -0
- package/dist/web/static/assets/ruby-B2Rjki9n.js +1 -0
- package/dist/web/static/assets/sas-B4kiWyti.js +1 -0
- package/dist/web/static/assets/scheme-C41bIUwD.js +1 -0
- package/dist/web/static/assets/shell-CjFT_Tl9.js +1 -0
- package/dist/web/static/assets/sieve-C3Gn_uJK.js +1 -0
- package/dist/web/static/assets/simple-mode-GW_nhZxv.js +1 -0
- package/dist/web/static/assets/smalltalk-CnHTOXQT.js +1 -0
- package/dist/web/static/assets/solr-DehyRSwq.js +1 -0
- package/dist/web/static/assets/sparql-DkYu6x3z.js +1 -0
- package/dist/web/static/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/dist/web/static/assets/sql-D0XecflT.js +1 -0
- package/dist/web/static/assets/stex-C3f8Ysf7.js +1 -0
- package/dist/web/static/assets/stylus-B533Al4x.js +1 -0
- package/dist/web/static/assets/swift-BzpIVaGY.js +1 -0
- package/dist/web/static/assets/tcl-DVfN8rqt.js +1 -0
- package/dist/web/static/assets/textile-CnDTJFAw.js +1 -0
- package/dist/web/static/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/dist/web/static/assets/tiki-DGYXhP31.js +1 -0
- package/dist/web/static/assets/toml-Bm5Em-hy.js +1 -0
- package/dist/web/static/assets/troff-wAsdV37c.js +1 -0
- package/dist/web/static/assets/ttcn-CfJYG6tj.js +1 -0
- package/dist/web/static/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/dist/web/static/assets/turtle-B1tBg_DP.js +1 -0
- package/dist/web/static/assets/vb-CmGdzxic.js +1 -0
- package/dist/web/static/assets/vbscript-BuJXcnF6.js +1 -0
- package/dist/web/static/assets/velocity-D8B20fx6.js +1 -0
- package/dist/web/static/assets/verilog-C6RDOZhf.js +1 -0
- package/dist/web/static/assets/vhdl-lSbBsy5d.js +1 -0
- package/dist/web/static/assets/webidl-ZXfAyPTL.js +1 -0
- package/dist/web/static/assets/xquery-DzFWVndE.js +1 -0
- package/dist/web/static/assets/yacas-BJ4BC0dw.js +1 -0
- package/dist/web/static/assets/z80-Hz9HOZM7.js +1 -0
- package/dist/web/static/index.html +22 -0
- package/dist/web/static/walnut-icon.png +0 -0
- package/dist/workers/git-compaction-worker.js +898 -0
- package/dist/workers/qmd-index-worker.js +97162 -0
- package/package.json +143 -0
- package/patches/@anthropic-ai+claude-agent-sdk+0.2.50.patch +13 -0
- package/scripts/patch-qmd.mjs +171 -0
- package/scripts/postinstall.mjs +120 -0
|
@@ -0,0 +1,1382 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/constants.ts
|
|
12
|
+
var constants_exports = {};
|
|
13
|
+
__export(constants_exports, {
|
|
14
|
+
ARCHIVE_DIR: () => ARCHIVE_DIR,
|
|
15
|
+
BUILTIN_COMMANDS_DIR: () => BUILTIN_COMMANDS_DIR,
|
|
16
|
+
BUILTIN_SKILLS_DIR: () => BUILTIN_SKILLS_DIR,
|
|
17
|
+
CHAT_HISTORY_FILE: () => CHAT_HISTORY_FILE,
|
|
18
|
+
CLAUDE_CREDENTIALS_FILE: () => CLAUDE_CREDENTIALS_FILE,
|
|
19
|
+
CLAUDE_HOME: () => CLAUDE_HOME,
|
|
20
|
+
CLAUDE_PLUGINS_DIR: () => CLAUDE_PLUGINS_DIR,
|
|
21
|
+
CLAUDE_SETTINGS_FILE: () => CLAUDE_SETTINGS_FILE,
|
|
22
|
+
CLAUDE_SKILLS_DIR: () => CLAUDE_SKILLS_DIR,
|
|
23
|
+
CLOUD_MODE: () => CLOUD_MODE,
|
|
24
|
+
COMMANDS_DIR: () => COMMANDS_DIR,
|
|
25
|
+
COMPACTION_DIR: () => COMPACTION_DIR,
|
|
26
|
+
CONFIG_FILE: () => CONFIG_FILE,
|
|
27
|
+
CONFIG_SHARE_DIR: () => CONFIG_SHARE_DIR,
|
|
28
|
+
CONVERSATIONS_DIR: () => CONVERSATIONS_DIR,
|
|
29
|
+
CRON_FILE: () => CRON_FILE,
|
|
30
|
+
DAEMON_BINARIES_DIR: () => DAEMON_BINARIES_DIR,
|
|
31
|
+
DAILY_DIR: () => DAILY_DIR,
|
|
32
|
+
DTACH_SOCKET_DIR: () => DTACH_SOCKET_DIR,
|
|
33
|
+
FREQUENT_DIRS_FILE: () => FREQUENT_DIRS_FILE,
|
|
34
|
+
GLOBAL_NOTES_FILE: () => GLOBAL_NOTES_FILE,
|
|
35
|
+
GLOBAL_SKILLS_DIR: () => GLOBAL_SKILLS_DIR,
|
|
36
|
+
HEARTBEAT_FILE: () => HEARTBEAT_FILE,
|
|
37
|
+
HISTORY_CACHE_DIR: () => HISTORY_CACHE_DIR,
|
|
38
|
+
HOOK_LOG_FILE: () => HOOK_LOG_FILE,
|
|
39
|
+
HOST_MODEL_CATALOG_FILE: () => HOST_MODEL_CATALOG_FILE,
|
|
40
|
+
IMAGES_DIR: () => IMAGES_DIR,
|
|
41
|
+
IS_EPHEMERAL: () => IS_EPHEMERAL,
|
|
42
|
+
LEGACY_MEMORY_FILE: () => LEGACY_MEMORY_FILE,
|
|
43
|
+
LOG_DIR: () => LOG_DIR,
|
|
44
|
+
LOG_PREFIX: () => LOG_PREFIX,
|
|
45
|
+
MEMORY_DIR: () => MEMORY_DIR,
|
|
46
|
+
MEMORY_FILE: () => MEMORY_FILE,
|
|
47
|
+
MEMORY_INDEX_FILE: () => MEMORY_INDEX_FILE,
|
|
48
|
+
MENTION_DIRS_FILE: () => MENTION_DIRS_FILE,
|
|
49
|
+
NOTES_AGENTS_FILE: () => NOTES_AGENTS_FILE,
|
|
50
|
+
NOTES_CLAUDE_FILE: () => NOTES_CLAUDE_FILE,
|
|
51
|
+
NOTES_DIR: () => NOTES_DIR,
|
|
52
|
+
PASTES_DIR: () => PASTES_DIR,
|
|
53
|
+
PLUGIN_STORES_DIR: () => PLUGIN_STORES_DIR,
|
|
54
|
+
PROJECTION_CACHE_DIR: () => PROJECTION_CACHE_DIR,
|
|
55
|
+
PROJECTS_DIR: () => PROJECTS_DIR,
|
|
56
|
+
PROJECTS_MEMORY_DIR: () => PROJECTS_MEMORY_DIR,
|
|
57
|
+
QUICK_START_MESSAGE_HARD_LIMIT: () => QUICK_START_MESSAGE_HARD_LIMIT,
|
|
58
|
+
QUICK_START_MESSAGE_SPILL_LIMIT: () => QUICK_START_MESSAGE_SPILL_LIMIT,
|
|
59
|
+
RECORDINGS_DIR: () => RECORDINGS_DIR,
|
|
60
|
+
REMOTE_IMAGES_DIR: () => REMOTE_IMAGES_DIR,
|
|
61
|
+
REPOSITORIES_DIR: () => REPOSITORIES_DIR,
|
|
62
|
+
REPOS_MEMORY_DIR: () => REPOS_MEMORY_DIR,
|
|
63
|
+
SESSIONS_DIR: () => SESSIONS_DIR,
|
|
64
|
+
SESSIONS_FILE: () => SESSIONS_FILE,
|
|
65
|
+
SESSION_QUEUE_FILE: () => SESSION_QUEUE_FILE,
|
|
66
|
+
SESSION_STREAMS_DIR: () => SESSION_STREAMS_DIR,
|
|
67
|
+
SKILL_SETTINGS_FILE: () => SKILL_SETTINGS_FILE,
|
|
68
|
+
STT_VOCAB_FILE: () => STT_VOCAB_FILE,
|
|
69
|
+
SYNC_DIR: () => SYNC_DIR,
|
|
70
|
+
TASKS_DIR: () => TASKS_DIR,
|
|
71
|
+
TASKS_FILE: () => TASKS_FILE,
|
|
72
|
+
TASK_QUEUE_DIR: () => TASK_QUEUE_DIR,
|
|
73
|
+
TIMELINE_DIR: () => TIMELINE_DIR,
|
|
74
|
+
TMP_DIR: () => TMP_DIR,
|
|
75
|
+
TOPICS_DIR: () => TOPICS_DIR,
|
|
76
|
+
TRANSCRIPT_CACHE_DIR: () => TRANSCRIPT_CACHE_DIR,
|
|
77
|
+
UI_PREFS_FILE: () => UI_PREFS_FILE,
|
|
78
|
+
USAGE_DB_FILE: () => USAGE_DB_FILE,
|
|
79
|
+
USER_FILE: () => USER_FILE,
|
|
80
|
+
WALNUT_HOME: () => WALNUT_HOME,
|
|
81
|
+
WALNUT_INSTALL_DIR: () => WALNUT_INSTALL_DIR,
|
|
82
|
+
WORKING_MEMORY_FILE: () => WORKING_MEMORY_FILE,
|
|
83
|
+
agentDailyDir: () => agentDailyDir,
|
|
84
|
+
agentMemoryDir: () => agentMemoryDir,
|
|
85
|
+
assertNotProductionPath: () => assertNotProductionPath,
|
|
86
|
+
chatHistoryFile: () => chatHistoryFile,
|
|
87
|
+
conversationDir: () => conversationDir,
|
|
88
|
+
conversationFile: () => conversationFile,
|
|
89
|
+
conversationIndexFile: () => conversationIndexFile,
|
|
90
|
+
isValidSessionUuid: () => isValidSessionUuid,
|
|
91
|
+
validateAgentId: () => validateAgentId,
|
|
92
|
+
validateConversationId: () => validateConversationId,
|
|
93
|
+
workingMemoryFile: () => workingMemoryFile
|
|
94
|
+
});
|
|
95
|
+
import path2 from "path";
|
|
96
|
+
import fs2 from "fs";
|
|
97
|
+
import os from "os";
|
|
98
|
+
import { fileURLToPath } from "url";
|
|
99
|
+
function resolveOpenWalnutHome() {
|
|
100
|
+
const envHome = process.env.OPEN_WALNUT_HOME;
|
|
101
|
+
const productionHome = path2.join(os.homedir(), ".open-walnut");
|
|
102
|
+
const isTestEnv = !!(process.env.VITEST || process.env.VITEST_WORKER_ID || process.env.NODE_ENV === "test");
|
|
103
|
+
if (isTestEnv) {
|
|
104
|
+
if (envHome && envHome !== productionHome && !envHome.startsWith(productionHome + path2.sep)) {
|
|
105
|
+
assertNotProductionPath(envHome);
|
|
106
|
+
return envHome;
|
|
107
|
+
}
|
|
108
|
+
const testHome = path2.join(os.tmpdir(), `open-walnut-test-${process.pid}`);
|
|
109
|
+
process.env.OPEN_WALNUT_HOME = testHome;
|
|
110
|
+
return testHome;
|
|
111
|
+
}
|
|
112
|
+
if (!envHome) return productionHome;
|
|
113
|
+
if (process.argv.includes("--_ephemeral-child")) return envHome;
|
|
114
|
+
if (isEphemeralTmpDir(envHome)) {
|
|
115
|
+
process.stderr.write(
|
|
116
|
+
`WARNING: OPEN_WALNUT_HOME=${envHome} looks like a leaked ephemeral temp dir.
|
|
117
|
+
Overriding to ${productionHome}. (Only --_ephemeral-child processes may use one.)
|
|
118
|
+
`
|
|
119
|
+
);
|
|
120
|
+
process.env.OPEN_WALNUT_HOME = productionHome;
|
|
121
|
+
return productionHome;
|
|
122
|
+
}
|
|
123
|
+
return envHome;
|
|
124
|
+
}
|
|
125
|
+
function assertNotProductionPath(inputPath) {
|
|
126
|
+
const isTestEnv = !!(process.env.VITEST || process.env.VITEST_WORKER_ID || process.env.NODE_ENV === "test");
|
|
127
|
+
if (!isTestEnv) return;
|
|
128
|
+
const resolved = path2.resolve(inputPath);
|
|
129
|
+
const prodHome = path2.join(os.homedir(), ".open-walnut");
|
|
130
|
+
if (resolved === prodHome || resolved.startsWith(prodHome + path2.sep)) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`SAFETY: Test process attempted to use production path: ${resolved}
|
|
133
|
+
This would destroy real user data in ~/.open-walnut/.
|
|
134
|
+
Set OPEN_WALNUT_HOME to a temp directory or let constants.ts auto-assign one.`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function isEphemeralTmpDir(inputPath) {
|
|
139
|
+
if (!/[\\/]open-walnut-[^\\/]+$/.test(inputPath)) return false;
|
|
140
|
+
const tmpDirs = /* @__PURE__ */ new Set();
|
|
141
|
+
tmpDirs.add(os.tmpdir());
|
|
142
|
+
try {
|
|
143
|
+
tmpDirs.add(fs2.realpathSync(os.tmpdir()));
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
tmpDirs.add(fs2.realpathSync("/tmp"));
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
const parent = path2.dirname(path2.resolve(inputPath));
|
|
151
|
+
if (tmpDirs.has(parent)) return true;
|
|
152
|
+
try {
|
|
153
|
+
return tmpDirs.has(fs2.realpathSync(parent));
|
|
154
|
+
} catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function validateAgentId(agentId) {
|
|
159
|
+
if (!/^[a-z0-9_-]{1,64}$/i.test(agentId)) {
|
|
160
|
+
throw new Error(`Invalid agentId: ${JSON.stringify(agentId)}`);
|
|
161
|
+
}
|
|
162
|
+
return agentId;
|
|
163
|
+
}
|
|
164
|
+
function chatHistoryFile(agentId) {
|
|
165
|
+
if (!agentId || agentId === "general") return CHAT_HISTORY_FILE;
|
|
166
|
+
return path2.join(WALNUT_HOME, `chat-history-${agentId}.json`);
|
|
167
|
+
}
|
|
168
|
+
function conversationDir(agentId) {
|
|
169
|
+
return path2.join(CONVERSATIONS_DIR, validateAgentId(agentId));
|
|
170
|
+
}
|
|
171
|
+
function conversationIndexFile(agentId) {
|
|
172
|
+
return path2.join(conversationDir(agentId), "_index.json");
|
|
173
|
+
}
|
|
174
|
+
function conversationFile(agentId, conversationId) {
|
|
175
|
+
return path2.join(conversationDir(agentId), `${conversationId}.json`);
|
|
176
|
+
}
|
|
177
|
+
function validateConversationId(id) {
|
|
178
|
+
if (!/^conv-[a-z0-9-]{1,64}$/i.test(id)) {
|
|
179
|
+
throw new Error(`Invalid conversationId: ${JSON.stringify(id)}`);
|
|
180
|
+
}
|
|
181
|
+
return id;
|
|
182
|
+
}
|
|
183
|
+
function isValidSessionUuid(id) {
|
|
184
|
+
return typeof id === "string" && SESSION_UUID_RE.test(id);
|
|
185
|
+
}
|
|
186
|
+
function agentMemoryDir(agentId) {
|
|
187
|
+
if (!agentId || agentId === "general") return WALNUT_HOME;
|
|
188
|
+
return path2.join(MEMORY_DIR, "agents", agentId);
|
|
189
|
+
}
|
|
190
|
+
function agentDailyDir(agentId) {
|
|
191
|
+
if (!agentId || agentId === "general") return DAILY_DIR;
|
|
192
|
+
return path2.join(MEMORY_DIR, "agents", agentId, "daily");
|
|
193
|
+
}
|
|
194
|
+
function workingMemoryFile(agentId, conversationId) {
|
|
195
|
+
return path2.join(conversationDir(agentId), `${validateConversationId(conversationId)}.working-memory.md`);
|
|
196
|
+
}
|
|
197
|
+
var WALNUT_HOME, IS_EPHEMERAL, CLOUD_MODE, TASKS_DIR, TASKS_FILE, ARCHIVE_DIR, MEMORY_DIR, SESSIONS_DIR, PROJECTS_DIR, CONFIG_FILE, SYNC_DIR, PLUGIN_STORES_DIR, SESSIONS_FILE, CLAUDE_HOME, HISTORY_CACHE_DIR, PROJECTION_CACHE_DIR, TRANSCRIPT_CACHE_DIR, TASK_QUEUE_DIR, HOST_MODEL_CATALOG_FILE, HOOK_LOG_FILE, DAILY_DIR, MEMORY_FILE, USER_FILE, LEGACY_MEMORY_FILE, PROJECTS_MEMORY_DIR, CHAT_HISTORY_FILE, CONVERSATIONS_DIR, SESSION_UUID_RE, GLOBAL_SKILLS_DIR, SKILL_SETTINGS_FILE, CLAUDE_SKILLS_DIR, CLAUDE_SETTINGS_FILE, CLAUDE_PLUGINS_DIR, CLAUDE_CREDENTIALS_FILE, CRON_FILE, USAGE_DB_FILE, LOG_DIR, SESSION_STREAMS_DIR, DTACH_SOCKET_DIR, SESSION_QUEUE_FILE, IMAGES_DIR, REMOTE_IMAGES_DIR, PASTES_DIR, HEARTBEAT_FILE, COMMANDS_DIR, BUILTIN_COMMANDS_DIR, BUILTIN_SKILLS_DIR, FREQUENT_DIRS_FILE, MENTION_DIRS_FILE, NOTES_DIR, GLOBAL_NOTES_FILE, NOTES_AGENTS_FILE, NOTES_CLAUDE_FILE, REPOSITORIES_DIR, REPOS_MEMORY_DIR, TOPICS_DIR, COMPACTION_DIR, MEMORY_INDEX_FILE, WORKING_MEMORY_FILE, TIMELINE_DIR, CONFIG_SHARE_DIR, UI_PREFS_FILE, STT_VOCAB_FILE, TMP_DIR, RECORDINGS_DIR, LOG_PREFIX, QUICK_START_MESSAGE_SPILL_LIMIT, QUICK_START_MESSAGE_HARD_LIMIT, DAEMON_BINARIES_DIR, WALNUT_INSTALL_DIR;
|
|
198
|
+
var init_constants = __esm({
|
|
199
|
+
"src/constants.ts"() {
|
|
200
|
+
"use strict";
|
|
201
|
+
WALNUT_HOME = resolveOpenWalnutHome();
|
|
202
|
+
IS_EPHEMERAL = process.argv.includes("--_ephemeral-child");
|
|
203
|
+
CLOUD_MODE = process.env.WALNUT_CLOUD_MODE === "1";
|
|
204
|
+
TASKS_DIR = path2.join(WALNUT_HOME, "tasks");
|
|
205
|
+
TASKS_FILE = path2.join(TASKS_DIR, "tasks.json");
|
|
206
|
+
ARCHIVE_DIR = path2.join(TASKS_DIR, "archive");
|
|
207
|
+
MEMORY_DIR = path2.join(WALNUT_HOME, "memory");
|
|
208
|
+
SESSIONS_DIR = path2.join(MEMORY_DIR, "sessions");
|
|
209
|
+
PROJECTS_DIR = path2.join(MEMORY_DIR, "projects");
|
|
210
|
+
CONFIG_FILE = path2.join(WALNUT_HOME, "config.yaml");
|
|
211
|
+
SYNC_DIR = path2.join(WALNUT_HOME, "sync");
|
|
212
|
+
PLUGIN_STORES_DIR = path2.join(WALNUT_HOME, "plugin-stores");
|
|
213
|
+
SESSIONS_FILE = path2.join(WALNUT_HOME, "sessions.json");
|
|
214
|
+
CLAUDE_HOME = path2.join(os.homedir(), ".claude");
|
|
215
|
+
HISTORY_CACHE_DIR = path2.join(WALNUT_HOME, "cache", "history");
|
|
216
|
+
PROJECTION_CACHE_DIR = path2.join(WALNUT_HOME, "cache", "projections");
|
|
217
|
+
TRANSCRIPT_CACHE_DIR = path2.join(WALNUT_HOME, "cache", "transcripts");
|
|
218
|
+
TASK_QUEUE_DIR = path2.join(WALNUT_HOME, "cache", "task-queue");
|
|
219
|
+
HOST_MODEL_CATALOG_FILE = path2.join(WALNUT_HOME, "cache", "host-model-catalogs.json");
|
|
220
|
+
HOOK_LOG_FILE = path2.join(WALNUT_HOME, "hook-errors.log");
|
|
221
|
+
DAILY_DIR = path2.join(MEMORY_DIR, "daily");
|
|
222
|
+
MEMORY_FILE = path2.join(MEMORY_DIR, "MEMORY.md");
|
|
223
|
+
USER_FILE = path2.join(MEMORY_DIR, "USER.md");
|
|
224
|
+
LEGACY_MEMORY_FILE = path2.join(WALNUT_HOME, "MEMORY.md");
|
|
225
|
+
PROJECTS_MEMORY_DIR = path2.join(MEMORY_DIR, "projects");
|
|
226
|
+
CHAT_HISTORY_FILE = path2.join(WALNUT_HOME, "chat-history.json");
|
|
227
|
+
CONVERSATIONS_DIR = path2.join(WALNUT_HOME, "conversations");
|
|
228
|
+
SESSION_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
229
|
+
GLOBAL_SKILLS_DIR = path2.join(WALNUT_HOME, "skills");
|
|
230
|
+
SKILL_SETTINGS_FILE = path2.join(WALNUT_HOME, "skill-settings.json");
|
|
231
|
+
CLAUDE_SKILLS_DIR = path2.join(CLAUDE_HOME, "skills");
|
|
232
|
+
CLAUDE_SETTINGS_FILE = path2.join(CLAUDE_HOME, "settings.json");
|
|
233
|
+
CLAUDE_PLUGINS_DIR = path2.join(CLAUDE_HOME, "plugins");
|
|
234
|
+
CLAUDE_CREDENTIALS_FILE = path2.join(CLAUDE_HOME, ".credentials.json");
|
|
235
|
+
CRON_FILE = path2.join(WALNUT_HOME, "cron-jobs.json");
|
|
236
|
+
USAGE_DB_FILE = path2.join(WALNUT_HOME, "usage.sqlite");
|
|
237
|
+
LOG_DIR = process.env.WALNUT_DAEMON_DIR || "/tmp/open-walnut";
|
|
238
|
+
SESSION_STREAMS_DIR = path2.join(LOG_DIR, "streams");
|
|
239
|
+
DTACH_SOCKET_DIR = path2.join(LOG_DIR, "term");
|
|
240
|
+
SESSION_QUEUE_FILE = path2.join(WALNUT_HOME, "session-message-queue.json");
|
|
241
|
+
IMAGES_DIR = path2.join(LOG_DIR, "images");
|
|
242
|
+
REMOTE_IMAGES_DIR = path2.join(IMAGES_DIR, "remote");
|
|
243
|
+
PASTES_DIR = path2.join(LOG_DIR, "pastes");
|
|
244
|
+
HEARTBEAT_FILE = path2.join(WALNUT_HOME, "HEARTBEAT.md");
|
|
245
|
+
COMMANDS_DIR = path2.join(WALNUT_HOME, "commands");
|
|
246
|
+
BUILTIN_COMMANDS_DIR = (() => {
|
|
247
|
+
let dir = path2.dirname(fileURLToPath(import.meta.url));
|
|
248
|
+
for (let i = 0; i < 5; i++) {
|
|
249
|
+
const candidate = path2.join(dir, "data", "slash-commands");
|
|
250
|
+
try {
|
|
251
|
+
if (fs2.statSync(candidate).isDirectory()) return candidate;
|
|
252
|
+
} catch {
|
|
253
|
+
}
|
|
254
|
+
dir = path2.dirname(dir);
|
|
255
|
+
}
|
|
256
|
+
return path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "data", "slash-commands");
|
|
257
|
+
})();
|
|
258
|
+
BUILTIN_SKILLS_DIR = (() => {
|
|
259
|
+
let dir = path2.dirname(fileURLToPath(import.meta.url));
|
|
260
|
+
for (let i = 0; i < 5; i++) {
|
|
261
|
+
const candidate = path2.join(dir, "data", "skills");
|
|
262
|
+
try {
|
|
263
|
+
if (fs2.statSync(candidate).isDirectory()) return candidate;
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
dir = path2.dirname(dir);
|
|
267
|
+
}
|
|
268
|
+
return path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "data", "skills");
|
|
269
|
+
})();
|
|
270
|
+
FREQUENT_DIRS_FILE = path2.join(WALNUT_HOME, "frequent-directories.json");
|
|
271
|
+
MENTION_DIRS_FILE = path2.join(WALNUT_HOME, "mention-directories.json");
|
|
272
|
+
NOTES_DIR = path2.join(WALNUT_HOME, "notes");
|
|
273
|
+
GLOBAL_NOTES_FILE = path2.join(NOTES_DIR, "global-notes.md");
|
|
274
|
+
NOTES_AGENTS_FILE = path2.join(NOTES_DIR, "AGENTS.md");
|
|
275
|
+
NOTES_CLAUDE_FILE = path2.join(NOTES_DIR, "CLAUDE.md");
|
|
276
|
+
REPOSITORIES_DIR = path2.join(WALNUT_HOME, "repositories");
|
|
277
|
+
REPOS_MEMORY_DIR = path2.join(GLOBAL_SKILLS_DIR, "repos");
|
|
278
|
+
TOPICS_DIR = path2.join(MEMORY_DIR, "topics");
|
|
279
|
+
COMPACTION_DIR = path2.join(MEMORY_DIR, "compaction");
|
|
280
|
+
MEMORY_INDEX_FILE = path2.join(MEMORY_DIR, "index.md");
|
|
281
|
+
WORKING_MEMORY_FILE = path2.join(MEMORY_DIR, "working-memory.md");
|
|
282
|
+
TIMELINE_DIR = path2.join(WALNUT_HOME, "timeline");
|
|
283
|
+
CONFIG_SHARE_DIR = path2.join(WALNUT_HOME, "config", "share");
|
|
284
|
+
UI_PREFS_FILE = path2.join(CONFIG_SHARE_DIR, "ui-prefs.json");
|
|
285
|
+
STT_VOCAB_FILE = path2.join(CONFIG_SHARE_DIR, "stt-vocab.txt");
|
|
286
|
+
TMP_DIR = path2.join(WALNUT_HOME, "tmp");
|
|
287
|
+
RECORDINGS_DIR = path2.join(TMP_DIR, "recordings");
|
|
288
|
+
LOG_PREFIX = "open-walnut-";
|
|
289
|
+
QUICK_START_MESSAGE_SPILL_LIMIT = 25e3;
|
|
290
|
+
QUICK_START_MESSAGE_HARD_LIMIT = 2e6;
|
|
291
|
+
DAEMON_BINARIES_DIR = (() => {
|
|
292
|
+
let dir = path2.dirname(fileURLToPath(import.meta.url));
|
|
293
|
+
for (let i = 0; i < 5; i++) {
|
|
294
|
+
const candidate = path2.join(dir, "dist", "daemon-binaries");
|
|
295
|
+
try {
|
|
296
|
+
if (fs2.statSync(candidate).isDirectory()) return candidate;
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
299
|
+
dir = path2.dirname(dir);
|
|
300
|
+
}
|
|
301
|
+
return path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "dist", "daemon-binaries");
|
|
302
|
+
})();
|
|
303
|
+
WALNUT_INSTALL_DIR = (() => {
|
|
304
|
+
let dir = path2.dirname(fileURLToPath(import.meta.url));
|
|
305
|
+
for (let i = 0; i < 6; i++) {
|
|
306
|
+
try {
|
|
307
|
+
const pkg = JSON.parse(fs2.readFileSync(path2.join(dir, "package.json"), "utf8"));
|
|
308
|
+
if (pkg.name === "open-walnut" && fs2.statSync(path2.join(dir, ".git")).isDirectory()) return dir;
|
|
309
|
+
} catch {
|
|
310
|
+
}
|
|
311
|
+
const parent = path2.dirname(dir);
|
|
312
|
+
if (parent === dir) break;
|
|
313
|
+
dir = parent;
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
})();
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// src/session-server/server.ts
|
|
321
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
322
|
+
import { createServer } from "http";
|
|
323
|
+
|
|
324
|
+
// src/session-server/sdk-session.ts
|
|
325
|
+
import crypto from "crypto";
|
|
326
|
+
|
|
327
|
+
// src/core/usage/cost-watermark.ts
|
|
328
|
+
function costIncrement(cumulativeTotal, watermark) {
|
|
329
|
+
if (cumulativeTotal === void 0 || !(cumulativeTotal > 0)) {
|
|
330
|
+
return { delta: 0, watermark };
|
|
331
|
+
}
|
|
332
|
+
const delta = cumulativeTotal - watermark;
|
|
333
|
+
if (delta <= 0) {
|
|
334
|
+
return { delta: 0, watermark };
|
|
335
|
+
}
|
|
336
|
+
return { delta, watermark: cumulativeTotal };
|
|
337
|
+
}
|
|
338
|
+
var CostWatermark = class {
|
|
339
|
+
watermark = 0;
|
|
340
|
+
/** Forget billed cost — call on every process spawn/resume (fresh total at 0). */
|
|
341
|
+
reset() {
|
|
342
|
+
this.watermark = 0;
|
|
343
|
+
}
|
|
344
|
+
/** Bill the increment since the last result; advances the watermark. */
|
|
345
|
+
bill(cumulativeTotal) {
|
|
346
|
+
const r = costIncrement(cumulativeTotal, this.watermark);
|
|
347
|
+
this.watermark = r.watermark;
|
|
348
|
+
return r.delta;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// src/session-server/sdk-session.ts
|
|
353
|
+
var SDK_PERMISSION_MODE = {
|
|
354
|
+
bypass: "bypassPermissions",
|
|
355
|
+
accept: "acceptEdits",
|
|
356
|
+
plan: "plan",
|
|
357
|
+
dontAsk: "dontAsk",
|
|
358
|
+
default: "default"
|
|
359
|
+
};
|
|
360
|
+
function applySdkPermissionMode(options, mode) {
|
|
361
|
+
if (!mode) return;
|
|
362
|
+
const resolved = SDK_PERMISSION_MODE[mode];
|
|
363
|
+
if (!resolved) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`Permission mode "${mode}" is not supported by the SDK session server (supported: ${Object.keys(SDK_PERMISSION_MODE).join(", ")}). Use a CLI session for this mode.`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
options.permissionMode = resolved;
|
|
369
|
+
if (mode === "bypass") options.allowDangerouslySkipPermissions = true;
|
|
370
|
+
}
|
|
371
|
+
function createMessageChannel() {
|
|
372
|
+
const queue = [];
|
|
373
|
+
let resolve = null;
|
|
374
|
+
let done = false;
|
|
375
|
+
const iterable = {
|
|
376
|
+
[Symbol.asyncIterator]() {
|
|
377
|
+
return {
|
|
378
|
+
next() {
|
|
379
|
+
if (queue.length > 0) {
|
|
380
|
+
return Promise.resolve({ value: queue.shift(), done: false });
|
|
381
|
+
}
|
|
382
|
+
if (done) {
|
|
383
|
+
return Promise.resolve({ value: void 0, done: true });
|
|
384
|
+
}
|
|
385
|
+
return new Promise((r) => {
|
|
386
|
+
resolve = r;
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
return {
|
|
393
|
+
push(msg) {
|
|
394
|
+
if (done) return;
|
|
395
|
+
if (resolve) {
|
|
396
|
+
const r = resolve;
|
|
397
|
+
resolve = null;
|
|
398
|
+
r({ value: msg, done: false });
|
|
399
|
+
} else {
|
|
400
|
+
queue.push(msg);
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
end() {
|
|
404
|
+
done = true;
|
|
405
|
+
if (resolve) {
|
|
406
|
+
const r = resolve;
|
|
407
|
+
resolve = null;
|
|
408
|
+
r({ value: void 0, done: true });
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
iterable
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
var SdkSession = class {
|
|
415
|
+
id;
|
|
416
|
+
queryHandle = null;
|
|
417
|
+
channel;
|
|
418
|
+
abortController = new AbortController();
|
|
419
|
+
emitEvent;
|
|
420
|
+
_status = "idle";
|
|
421
|
+
_cwd;
|
|
422
|
+
_mode;
|
|
423
|
+
_sessionId;
|
|
424
|
+
/** Converts the SDK's cumulative total_cost_usd into a billable per-result
|
|
425
|
+
* increment. The total is a running total per query(); a resume starts a fresh
|
|
426
|
+
* query() whose total restarts at 0, so we reset on every start()/resume().
|
|
427
|
+
* Without this the whole running total was billed every turn (the 13×
|
|
428
|
+
* session-cost inflation bug). See core/usage/cost-watermark.ts. */
|
|
429
|
+
_costWatermark = new CostWatermark();
|
|
430
|
+
/** Map of pending interactive requests (question/permission) by requestId */
|
|
431
|
+
pendingInteractions = /* @__PURE__ */ new Map();
|
|
432
|
+
constructor(id, emitEvent) {
|
|
433
|
+
this.id = id;
|
|
434
|
+
this.emitEvent = emitEvent;
|
|
435
|
+
this.channel = createMessageChannel();
|
|
436
|
+
this._mode = "default";
|
|
437
|
+
}
|
|
438
|
+
get status() {
|
|
439
|
+
return this._status;
|
|
440
|
+
}
|
|
441
|
+
get cwd() {
|
|
442
|
+
return this._cwd;
|
|
443
|
+
}
|
|
444
|
+
get mode() {
|
|
445
|
+
return this._mode;
|
|
446
|
+
}
|
|
447
|
+
get sessionId() {
|
|
448
|
+
return this._sessionId;
|
|
449
|
+
}
|
|
450
|
+
/** Billable cost increment since the last result; advances the per-query
|
|
451
|
+
* watermark. Returns 0 for replayed/stale results. See cost-watermark.ts. */
|
|
452
|
+
billableCostDelta(totalCostUsd) {
|
|
453
|
+
return this._costWatermark.bill(totalCostUsd);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Start a new query (or resume) and begin streaming events.
|
|
457
|
+
*/
|
|
458
|
+
async start(params) {
|
|
459
|
+
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
460
|
+
this._cwd = params.cwd;
|
|
461
|
+
this._mode = params.mode ?? "default";
|
|
462
|
+
this._sessionId = params.sessionId;
|
|
463
|
+
this._status = "running";
|
|
464
|
+
this._costWatermark.reset();
|
|
465
|
+
const options = {
|
|
466
|
+
cwd: params.cwd,
|
|
467
|
+
includePartialMessages: true,
|
|
468
|
+
abortController: this.abortController
|
|
469
|
+
};
|
|
470
|
+
applySdkPermissionMode(options, params.mode);
|
|
471
|
+
if (params.systemPrompt) {
|
|
472
|
+
options.systemPrompt = {
|
|
473
|
+
type: "preset",
|
|
474
|
+
preset: "claude_code",
|
|
475
|
+
append: params.systemPrompt
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
if (params.sessionId) {
|
|
479
|
+
options.resume = params.sessionId;
|
|
480
|
+
}
|
|
481
|
+
options.canUseTool = this.createCanUseTool();
|
|
482
|
+
const initialMessage = {
|
|
483
|
+
type: "user",
|
|
484
|
+
message: { role: "user", content: params.message },
|
|
485
|
+
parent_tool_use_id: null,
|
|
486
|
+
session_id: this._sessionId ?? ""
|
|
487
|
+
};
|
|
488
|
+
this.channel = createMessageChannel();
|
|
489
|
+
this.queryHandle = query({
|
|
490
|
+
prompt: params.message,
|
|
491
|
+
options
|
|
492
|
+
});
|
|
493
|
+
this.processOutput().catch((err) => {
|
|
494
|
+
this.emitEvent("session:error", {
|
|
495
|
+
sessionId: this._sessionId ?? this.id,
|
|
496
|
+
error: err instanceof Error ? err.message : String(err)
|
|
497
|
+
});
|
|
498
|
+
this._status = "error";
|
|
499
|
+
});
|
|
500
|
+
return this._sessionId ?? this.id;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Send a follow-up message to the running session.
|
|
504
|
+
*
|
|
505
|
+
* If the session is idle (previous turn completed), starts a new query
|
|
506
|
+
* with `resume: sessionId` — the SDK picks up the existing conversation.
|
|
507
|
+
* If still running mid-turn, uses `streamInput` to inject the message.
|
|
508
|
+
*/
|
|
509
|
+
async send(message) {
|
|
510
|
+
if (this._status === "idle" && this._sessionId) {
|
|
511
|
+
await this.resume(message);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (!this.queryHandle) {
|
|
515
|
+
throw new Error("Session not started");
|
|
516
|
+
}
|
|
517
|
+
this._status = "running";
|
|
518
|
+
const msg = {
|
|
519
|
+
type: "user",
|
|
520
|
+
message: { role: "user", content: message },
|
|
521
|
+
parent_tool_use_id: null,
|
|
522
|
+
session_id: this._sessionId ?? ""
|
|
523
|
+
};
|
|
524
|
+
await this.queryHandle.streamInput((async function* () {
|
|
525
|
+
yield msg;
|
|
526
|
+
})());
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Resume the session with a new query after the previous turn completed.
|
|
530
|
+
*/
|
|
531
|
+
async resume(message) {
|
|
532
|
+
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
533
|
+
this._status = "running";
|
|
534
|
+
this._costWatermark.reset();
|
|
535
|
+
this.abortController = new AbortController();
|
|
536
|
+
const options = {
|
|
537
|
+
cwd: this._cwd,
|
|
538
|
+
includePartialMessages: true,
|
|
539
|
+
abortController: this.abortController,
|
|
540
|
+
resume: this._sessionId
|
|
541
|
+
};
|
|
542
|
+
applySdkPermissionMode(options, this._mode);
|
|
543
|
+
options.canUseTool = this.createCanUseTool();
|
|
544
|
+
this.queryHandle = query({
|
|
545
|
+
prompt: message,
|
|
546
|
+
options
|
|
547
|
+
});
|
|
548
|
+
this.processOutput().catch((err) => {
|
|
549
|
+
this.emitEvent("session:error", {
|
|
550
|
+
sessionId: this._sessionId ?? this.id,
|
|
551
|
+
error: err instanceof Error ? err.message : String(err)
|
|
552
|
+
});
|
|
553
|
+
this._status = "error";
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Interrupt the current turn.
|
|
558
|
+
*/
|
|
559
|
+
async interrupt() {
|
|
560
|
+
if (this.queryHandle) {
|
|
561
|
+
await this.queryHandle.interrupt();
|
|
562
|
+
}
|
|
563
|
+
this._status = "idle";
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Change permission mode.
|
|
567
|
+
*/
|
|
568
|
+
async setMode(mode) {
|
|
569
|
+
this._mode = mode;
|
|
570
|
+
if (this.queryHandle) {
|
|
571
|
+
await this.queryHandle.setPermissionMode(mode);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Stop the session entirely.
|
|
576
|
+
*/
|
|
577
|
+
stop() {
|
|
578
|
+
this.abortController.abort();
|
|
579
|
+
if (this.queryHandle) {
|
|
580
|
+
this.queryHandle.close();
|
|
581
|
+
this.queryHandle = null;
|
|
582
|
+
}
|
|
583
|
+
this.channel.end();
|
|
584
|
+
this._status = "idle";
|
|
585
|
+
for (const [, pending] of this.pendingInteractions) {
|
|
586
|
+
pending.reject(new Error("Session stopped"));
|
|
587
|
+
}
|
|
588
|
+
this.pendingInteractions.clear();
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Resolve a pending AskUserQuestion interaction.
|
|
592
|
+
*/
|
|
593
|
+
resolveQuestion(questionId, answers) {
|
|
594
|
+
const pending = this.pendingInteractions.get(questionId);
|
|
595
|
+
if (pending) {
|
|
596
|
+
this.pendingInteractions.delete(questionId);
|
|
597
|
+
pending.resolve(answers);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Resolve a pending permission request.
|
|
602
|
+
*/
|
|
603
|
+
resolvePermission(requestId, allow, message) {
|
|
604
|
+
const pending = this.pendingInteractions.get(requestId);
|
|
605
|
+
if (pending) {
|
|
606
|
+
this.pendingInteractions.delete(requestId);
|
|
607
|
+
pending.resolve({ allow, message });
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
// ── Private ──
|
|
611
|
+
createCanUseTool() {
|
|
612
|
+
return async (toolName, input, options) => {
|
|
613
|
+
if (toolName === "AskUserQuestion") {
|
|
614
|
+
const questionId = `q-${crypto.randomBytes(4).toString("hex")}`;
|
|
615
|
+
const questions = input.questions;
|
|
616
|
+
this.emitEvent("session:ask-question", {
|
|
617
|
+
sessionId: this._sessionId ?? this.id,
|
|
618
|
+
questionId,
|
|
619
|
+
questions
|
|
620
|
+
});
|
|
621
|
+
const answers = await new Promise((resolve, reject) => {
|
|
622
|
+
this.pendingInteractions.set(questionId, { resolve, reject });
|
|
623
|
+
});
|
|
624
|
+
return {
|
|
625
|
+
behavior: "allow",
|
|
626
|
+
updatedInput: { ...input, answers }
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
if (toolName === "ExitPlanMode") {
|
|
630
|
+
const planContent = typeof input.plan === "string" ? input.plan : "";
|
|
631
|
+
this.emitEvent("session:plan-complete", {
|
|
632
|
+
sessionId: this._sessionId ?? this.id,
|
|
633
|
+
planContent
|
|
634
|
+
});
|
|
635
|
+
return { behavior: "allow" };
|
|
636
|
+
}
|
|
637
|
+
if (this._mode === "bypass") {
|
|
638
|
+
return { behavior: "allow" };
|
|
639
|
+
}
|
|
640
|
+
if (this._mode === "accept") {
|
|
641
|
+
const editTools = ["Write", "Edit", "NotebookEdit"];
|
|
642
|
+
if (editTools.includes(toolName)) {
|
|
643
|
+
return { behavior: "allow" };
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (this._mode === "dontAsk") {
|
|
647
|
+
return {
|
|
648
|
+
behavior: "deny",
|
|
649
|
+
message: `Denied by "Don't Ask" mode \u2014 ${toolName} is not pre-approved. Switch modes to allow it.`
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
const requestId = `perm-${crypto.randomBytes(4).toString("hex")}`;
|
|
653
|
+
this.emitEvent("session:permission-request", {
|
|
654
|
+
sessionId: this._sessionId ?? this.id,
|
|
655
|
+
requestId,
|
|
656
|
+
toolName,
|
|
657
|
+
input,
|
|
658
|
+
suggestions: options.suggestions
|
|
659
|
+
});
|
|
660
|
+
const result = await new Promise((resolve, reject) => {
|
|
661
|
+
this.pendingInteractions.set(requestId, {
|
|
662
|
+
resolve,
|
|
663
|
+
reject
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
if (result.allow) {
|
|
667
|
+
return { behavior: "allow" };
|
|
668
|
+
} else {
|
|
669
|
+
return { behavior: "deny", message: result.message ?? "Permission denied" };
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Process the output stream from the SDK query.
|
|
675
|
+
* Maps SDK messages to session server events.
|
|
676
|
+
*/
|
|
677
|
+
async processOutput() {
|
|
678
|
+
if (!this.queryHandle) return;
|
|
679
|
+
try {
|
|
680
|
+
for await (const msg of this.queryHandle) {
|
|
681
|
+
this.handleMessage(msg);
|
|
682
|
+
}
|
|
683
|
+
} catch (err) {
|
|
684
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
throw err;
|
|
688
|
+
} finally {
|
|
689
|
+
this._status = "idle";
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
handleMessage(msg) {
|
|
693
|
+
const sessionId = this._sessionId ?? this.id;
|
|
694
|
+
switch (msg.type) {
|
|
695
|
+
case "system": {
|
|
696
|
+
const sys = msg;
|
|
697
|
+
if ("subtype" in sys) {
|
|
698
|
+
if (sys.subtype === "init") {
|
|
699
|
+
const init = sys;
|
|
700
|
+
this._sessionId = init.session_id;
|
|
701
|
+
this.emitEvent("session:init", {
|
|
702
|
+
sessionId: init.session_id,
|
|
703
|
+
model: init.model,
|
|
704
|
+
cwd: init.cwd,
|
|
705
|
+
tools: init.tools
|
|
706
|
+
});
|
|
707
|
+
} else if (sys.subtype === "compact_boundary") {
|
|
708
|
+
const compact = sys;
|
|
709
|
+
this.emitEvent("session:compact", {
|
|
710
|
+
sessionId,
|
|
711
|
+
trigger: compact.compact_metadata.trigger,
|
|
712
|
+
preTokens: compact.compact_metadata.pre_tokens
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
case "assistant": {
|
|
719
|
+
const assistant = msg;
|
|
720
|
+
if (!assistant.message?.content) break;
|
|
721
|
+
for (const block of assistant.message.content) {
|
|
722
|
+
if (block.type === "text" && "text" in block) {
|
|
723
|
+
this.emitEvent("session:text-delta", {
|
|
724
|
+
sessionId,
|
|
725
|
+
delta: block.text
|
|
726
|
+
});
|
|
727
|
+
} else if (block.type === "tool_use") {
|
|
728
|
+
this.emitEvent("session:tool-use", {
|
|
729
|
+
sessionId,
|
|
730
|
+
toolUseId: block.id,
|
|
731
|
+
name: block.name,
|
|
732
|
+
input: block.input,
|
|
733
|
+
parentToolUseId: assistant.parent_tool_use_id ?? void 0
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
case "user": {
|
|
740
|
+
const user = msg;
|
|
741
|
+
if ("isReplay" in user && user.isReplay) break;
|
|
742
|
+
if (user.message?.content && Array.isArray(user.message.content)) {
|
|
743
|
+
for (const block of user.message.content) {
|
|
744
|
+
if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_result") {
|
|
745
|
+
const content = "content" in block ? typeof block.content === "string" ? block.content : JSON.stringify(block.content) : "";
|
|
746
|
+
this.emitEvent("session:tool-result", {
|
|
747
|
+
sessionId,
|
|
748
|
+
toolUseId: "tool_use_id" in block ? block.tool_use_id : "",
|
|
749
|
+
result: typeof content === "string" ? content.slice(0, 2e3) : ""
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
break;
|
|
755
|
+
}
|
|
756
|
+
case "stream_event": {
|
|
757
|
+
const partial = msg;
|
|
758
|
+
if (partial.event?.type === "content_block_delta") {
|
|
759
|
+
const delta = partial.event;
|
|
760
|
+
if (delta.delta?.type === "text_delta" && delta.delta.text) {
|
|
761
|
+
this.emitEvent("session:text-delta", {
|
|
762
|
+
sessionId,
|
|
763
|
+
delta: delta.delta.text
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
case "result": {
|
|
770
|
+
const result = msg;
|
|
771
|
+
this._sessionId = result.session_id;
|
|
772
|
+
let subtype = "success";
|
|
773
|
+
let resultText = "";
|
|
774
|
+
if (result.subtype === "success") {
|
|
775
|
+
subtype = "success";
|
|
776
|
+
resultText = result.result ?? "";
|
|
777
|
+
} else {
|
|
778
|
+
const errorResult = result;
|
|
779
|
+
if (errorResult.subtype === "error_max_turns") {
|
|
780
|
+
subtype = "error_max_turns";
|
|
781
|
+
} else if (errorResult.subtype === "error_max_budget_usd") {
|
|
782
|
+
subtype = "error_max_budget";
|
|
783
|
+
} else {
|
|
784
|
+
subtype = "error";
|
|
785
|
+
}
|
|
786
|
+
resultText = errorResult.errors?.join("\n") ?? "";
|
|
787
|
+
}
|
|
788
|
+
this.emitEvent("session:result", {
|
|
789
|
+
sessionId: result.session_id,
|
|
790
|
+
result: resultText,
|
|
791
|
+
subtype,
|
|
792
|
+
cost: result.total_cost_usd,
|
|
793
|
+
costDelta: this.billableCostDelta(result.total_cost_usd),
|
|
794
|
+
duration: result.duration_ms,
|
|
795
|
+
usage: result.usage ? {
|
|
796
|
+
input_tokens: result.usage.input_tokens,
|
|
797
|
+
output_tokens: result.usage.output_tokens
|
|
798
|
+
} : void 0,
|
|
799
|
+
modelUsage: result.modelUsage
|
|
800
|
+
});
|
|
801
|
+
this._status = "idle";
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
// src/session-server/state.ts
|
|
809
|
+
import fs from "fs";
|
|
810
|
+
import path from "path";
|
|
811
|
+
var STATE_FILE = "session-server-state.json";
|
|
812
|
+
var StateManager = class {
|
|
813
|
+
filePath;
|
|
814
|
+
sessions = /* @__PURE__ */ new Map();
|
|
815
|
+
constructor(dataDir) {
|
|
816
|
+
this.filePath = path.join(dataDir, STATE_FILE);
|
|
817
|
+
this.load();
|
|
818
|
+
}
|
|
819
|
+
/** Add or update a session. */
|
|
820
|
+
set(sessionId, data) {
|
|
821
|
+
this.sessions.set(sessionId, { sessionId, ...data });
|
|
822
|
+
this.save();
|
|
823
|
+
}
|
|
824
|
+
/** Remove a session. */
|
|
825
|
+
remove(sessionId) {
|
|
826
|
+
this.sessions.delete(sessionId);
|
|
827
|
+
this.save();
|
|
828
|
+
}
|
|
829
|
+
/** Get all persisted sessions. */
|
|
830
|
+
getAll() {
|
|
831
|
+
return Array.from(this.sessions.values());
|
|
832
|
+
}
|
|
833
|
+
/** Clear all sessions. */
|
|
834
|
+
clear() {
|
|
835
|
+
this.sessions.clear();
|
|
836
|
+
this.save();
|
|
837
|
+
}
|
|
838
|
+
load() {
|
|
839
|
+
try {
|
|
840
|
+
if (fs.existsSync(this.filePath)) {
|
|
841
|
+
const raw = fs.readFileSync(this.filePath, "utf-8");
|
|
842
|
+
const state = JSON.parse(raw);
|
|
843
|
+
for (const s of state.sessions) {
|
|
844
|
+
this.sessions.set(s.sessionId, s);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
} catch {
|
|
848
|
+
this.sessions.clear();
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
save() {
|
|
852
|
+
try {
|
|
853
|
+
const dir = path.dirname(this.filePath);
|
|
854
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
855
|
+
const state = {
|
|
856
|
+
sessions: Array.from(this.sessions.values())
|
|
857
|
+
};
|
|
858
|
+
fs.writeFileSync(this.filePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
859
|
+
} catch {
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
// src/session-server/server.ts
|
|
865
|
+
var SessionServer = class {
|
|
866
|
+
constructor(options) {
|
|
867
|
+
this.options = options;
|
|
868
|
+
this.state = new StateManager(options.dataDir);
|
|
869
|
+
}
|
|
870
|
+
wss = null;
|
|
871
|
+
httpServer = null;
|
|
872
|
+
sessions = /* @__PURE__ */ new Map();
|
|
873
|
+
state;
|
|
874
|
+
clients = /* @__PURE__ */ new Set();
|
|
875
|
+
/** Start the WebSocket server. Returns the actual port. */
|
|
876
|
+
async start() {
|
|
877
|
+
const persisted = this.state.getAll();
|
|
878
|
+
for (const p of persisted) {
|
|
879
|
+
this.state.remove(p.sessionId);
|
|
880
|
+
}
|
|
881
|
+
return new Promise((resolve, reject) => {
|
|
882
|
+
this.httpServer = createServer();
|
|
883
|
+
this.wss = new WebSocketServer({ server: this.httpServer });
|
|
884
|
+
this.wss.on("connection", (ws) => {
|
|
885
|
+
this.clients.add(ws);
|
|
886
|
+
ws.on("close", () => this.clients.delete(ws));
|
|
887
|
+
ws.on("message", (raw) => {
|
|
888
|
+
try {
|
|
889
|
+
const frame = JSON.parse(raw.toString());
|
|
890
|
+
if (frame.type === "cmd") {
|
|
891
|
+
this.handleCommand(ws, frame);
|
|
892
|
+
}
|
|
893
|
+
} catch {
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
});
|
|
897
|
+
this.httpServer.listen(this.options.port, () => {
|
|
898
|
+
const addr = this.httpServer.address();
|
|
899
|
+
if (typeof addr === "object" && addr) {
|
|
900
|
+
resolve(addr.port);
|
|
901
|
+
} else {
|
|
902
|
+
reject(new Error("Failed to get server address"));
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
this.httpServer.on("error", reject);
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
/** Stop the server and clean up all sessions. */
|
|
909
|
+
async stop() {
|
|
910
|
+
for (const [, session] of this.sessions) {
|
|
911
|
+
session.stop();
|
|
912
|
+
}
|
|
913
|
+
this.sessions.clear();
|
|
914
|
+
for (const ws of this.clients) {
|
|
915
|
+
ws.close();
|
|
916
|
+
}
|
|
917
|
+
this.clients.clear();
|
|
918
|
+
return new Promise((resolve) => {
|
|
919
|
+
if (this.wss) {
|
|
920
|
+
this.wss.close(() => {
|
|
921
|
+
if (this.httpServer) {
|
|
922
|
+
this.httpServer.close(() => resolve());
|
|
923
|
+
} else {
|
|
924
|
+
resolve();
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
} else {
|
|
928
|
+
resolve();
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
async handleCommand(ws, cmd) {
|
|
933
|
+
try {
|
|
934
|
+
switch (cmd.method) {
|
|
935
|
+
case "session.start":
|
|
936
|
+
await this.handleSessionStart(ws, cmd);
|
|
937
|
+
break;
|
|
938
|
+
case "session.send":
|
|
939
|
+
await this.handleSessionSend(ws, cmd);
|
|
940
|
+
break;
|
|
941
|
+
case "session.interrupt":
|
|
942
|
+
await this.handleSessionInterrupt(ws, cmd);
|
|
943
|
+
break;
|
|
944
|
+
case "session.setMode":
|
|
945
|
+
await this.handleSessionSetMode(ws, cmd);
|
|
946
|
+
break;
|
|
947
|
+
case "session.stop":
|
|
948
|
+
await this.handleSessionStop(ws, cmd);
|
|
949
|
+
break;
|
|
950
|
+
case "session.respondToQuestion":
|
|
951
|
+
this.handleRespondToQuestion(ws, cmd);
|
|
952
|
+
break;
|
|
953
|
+
case "session.respondToPermission":
|
|
954
|
+
this.handleRespondToPermission(ws, cmd);
|
|
955
|
+
break;
|
|
956
|
+
case "session.list":
|
|
957
|
+
this.handleSessionList(ws, cmd);
|
|
958
|
+
break;
|
|
959
|
+
case "ping":
|
|
960
|
+
this.sendResponse(ws, cmd.id, true, { ok: true });
|
|
961
|
+
break;
|
|
962
|
+
default:
|
|
963
|
+
this.sendResponse(ws, cmd.id, false, void 0, `Unknown method: ${cmd.method}`);
|
|
964
|
+
}
|
|
965
|
+
} catch (err) {
|
|
966
|
+
this.sendResponse(ws, cmd.id, false, void 0, err instanceof Error ? err.message : String(err));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
async handleSessionStart(ws, cmd) {
|
|
970
|
+
const params = cmd.params;
|
|
971
|
+
const sessionId = params.sessionId ?? `sdk-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
972
|
+
const emitEvent = (name, data) => {
|
|
973
|
+
this.broadcastEvent(sessionId, name, data);
|
|
974
|
+
};
|
|
975
|
+
const session = new SdkSession(sessionId, emitEvent);
|
|
976
|
+
this.sessions.set(sessionId, session);
|
|
977
|
+
this.state.set(sessionId, {
|
|
978
|
+
cwd: params.cwd,
|
|
979
|
+
mode: params.mode,
|
|
980
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
981
|
+
});
|
|
982
|
+
this.sendResponse(ws, cmd.id, true, { sessionId });
|
|
983
|
+
session.start({
|
|
984
|
+
message: params.message,
|
|
985
|
+
cwd: params.cwd,
|
|
986
|
+
mode: params.mode,
|
|
987
|
+
systemPrompt: params.systemPrompt,
|
|
988
|
+
sessionId: params.sessionId
|
|
989
|
+
}).catch((err) => {
|
|
990
|
+
this.broadcastEvent(sessionId, "session:error", {
|
|
991
|
+
sessionId,
|
|
992
|
+
error: err instanceof Error ? err.message : String(err)
|
|
993
|
+
});
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
async handleSessionSend(ws, cmd) {
|
|
997
|
+
const params = cmd.params;
|
|
998
|
+
const session = this.sessions.get(params.sessionId);
|
|
999
|
+
if (!session) {
|
|
1000
|
+
this.sendResponse(ws, cmd.id, false, void 0, `Session not found: ${params.sessionId}`);
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
this.sendResponse(ws, cmd.id, true, { ok: true });
|
|
1004
|
+
session.send(params.message).catch((err) => {
|
|
1005
|
+
this.broadcastEvent(params.sessionId, "session:error", {
|
|
1006
|
+
sessionId: params.sessionId,
|
|
1007
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1008
|
+
});
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
async handleSessionInterrupt(ws, cmd) {
|
|
1012
|
+
const params = cmd.params;
|
|
1013
|
+
const session = this.sessions.get(params.sessionId);
|
|
1014
|
+
if (!session) {
|
|
1015
|
+
this.sendResponse(ws, cmd.id, false, void 0, `Session not found: ${params.sessionId}`);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
await session.interrupt();
|
|
1019
|
+
this.sendResponse(ws, cmd.id, true, { ok: true });
|
|
1020
|
+
}
|
|
1021
|
+
async handleSessionSetMode(ws, cmd) {
|
|
1022
|
+
const params = cmd.params;
|
|
1023
|
+
const session = this.sessions.get(params.sessionId);
|
|
1024
|
+
if (!session) {
|
|
1025
|
+
this.sendResponse(ws, cmd.id, false, void 0, `Session not found: ${params.sessionId}`);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
await session.setMode(params.mode);
|
|
1029
|
+
this.sendResponse(ws, cmd.id, true, { ok: true });
|
|
1030
|
+
}
|
|
1031
|
+
async handleSessionStop(ws, cmd) {
|
|
1032
|
+
const params = cmd.params;
|
|
1033
|
+
const session = this.sessions.get(params.sessionId);
|
|
1034
|
+
if (session) {
|
|
1035
|
+
session.stop();
|
|
1036
|
+
this.sessions.delete(params.sessionId);
|
|
1037
|
+
this.state.remove(params.sessionId);
|
|
1038
|
+
}
|
|
1039
|
+
this.sendResponse(ws, cmd.id, true, { ok: true });
|
|
1040
|
+
}
|
|
1041
|
+
handleRespondToQuestion(ws, cmd) {
|
|
1042
|
+
const params = cmd.params;
|
|
1043
|
+
const session = this.sessions.get(params.sessionId);
|
|
1044
|
+
if (session) {
|
|
1045
|
+
session.resolveQuestion(params.questionId, params.answers);
|
|
1046
|
+
}
|
|
1047
|
+
this.sendResponse(ws, cmd.id, true, { ok: true });
|
|
1048
|
+
}
|
|
1049
|
+
handleRespondToPermission(ws, cmd) {
|
|
1050
|
+
const params = cmd.params;
|
|
1051
|
+
const session = this.sessions.get(params.sessionId);
|
|
1052
|
+
if (session) {
|
|
1053
|
+
session.resolvePermission(params.requestId, params.allow, params.message);
|
|
1054
|
+
}
|
|
1055
|
+
this.sendResponse(ws, cmd.id, true, { ok: true });
|
|
1056
|
+
}
|
|
1057
|
+
handleSessionList(ws, cmd) {
|
|
1058
|
+
const sessions = [];
|
|
1059
|
+
for (const [id, session] of this.sessions) {
|
|
1060
|
+
sessions.push({
|
|
1061
|
+
sessionId: session.sessionId ?? id,
|
|
1062
|
+
status: session.status,
|
|
1063
|
+
cwd: session.cwd,
|
|
1064
|
+
mode: session.mode
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
this.sendResponse(ws, cmd.id, true, { sessions });
|
|
1068
|
+
}
|
|
1069
|
+
sendResponse(ws, id, ok, data, error) {
|
|
1070
|
+
const frame = { type: "res", id, ok };
|
|
1071
|
+
if (data !== void 0) frame.data = data;
|
|
1072
|
+
if (error !== void 0) frame.error = error;
|
|
1073
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
1074
|
+
ws.send(JSON.stringify(frame));
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
broadcastEvent(sessionId, name, data) {
|
|
1078
|
+
const frame = { type: "event", sessionId, name, data };
|
|
1079
|
+
const payload = JSON.stringify(frame);
|
|
1080
|
+
for (const ws of this.clients) {
|
|
1081
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
1082
|
+
ws.send(payload);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
// src/logging/logger.ts
|
|
1089
|
+
init_constants();
|
|
1090
|
+
import fs3 from "fs";
|
|
1091
|
+
import fsp from "fs/promises";
|
|
1092
|
+
import path3 from "path";
|
|
1093
|
+
|
|
1094
|
+
// src/logging/redact.ts
|
|
1095
|
+
var REDACTED = "[REDACTED]";
|
|
1096
|
+
var patterns = [
|
|
1097
|
+
// PEM private key blocks (multiline)
|
|
1098
|
+
{
|
|
1099
|
+
re: /-----BEGIN\s[\w\s]*PRIVATE KEY-----[\s\S]*?-----END\s[\w\s]*PRIVATE KEY-----/g,
|
|
1100
|
+
replacement: REDACTED
|
|
1101
|
+
},
|
|
1102
|
+
// Bearer tokens: Authorization: Bearer <token>
|
|
1103
|
+
{
|
|
1104
|
+
re: /(Bearer\s+)\S+/gi,
|
|
1105
|
+
replacement: `$1${REDACTED}`
|
|
1106
|
+
},
|
|
1107
|
+
// OpenAI / Anthropic style keys: sk-... (at least 20 chars after prefix)
|
|
1108
|
+
{
|
|
1109
|
+
re: /\bsk-[A-Za-z0-9_-]{20,}\b/g,
|
|
1110
|
+
replacement: REDACTED
|
|
1111
|
+
},
|
|
1112
|
+
// AWS access key IDs: AKIA...
|
|
1113
|
+
{
|
|
1114
|
+
re: /\bAKIA[A-Z0-9]{12,}\b/g,
|
|
1115
|
+
replacement: REDACTED
|
|
1116
|
+
},
|
|
1117
|
+
// AWS secret access key in key=value form
|
|
1118
|
+
{
|
|
1119
|
+
re: /(aws_secret_access_key\s*[=:]\s*)\S+/gi,
|
|
1120
|
+
replacement: `$1${REDACTED}`
|
|
1121
|
+
},
|
|
1122
|
+
// AWS session token in key=value form
|
|
1123
|
+
{
|
|
1124
|
+
re: /(aws_session_token\s*[=:]\s*)\S+/gi,
|
|
1125
|
+
replacement: `$1${REDACTED}`
|
|
1126
|
+
},
|
|
1127
|
+
// Credentials in URL userinfo: https://user:secret@host/...
|
|
1128
|
+
// Motivating shape: execSync embeds the whole command in error.message, so a
|
|
1129
|
+
// failed `git remote add origin https://walnut:<device-token>@host/git/data`
|
|
1130
|
+
// otherwise logs the cloud companion's long-lived credential verbatim.
|
|
1131
|
+
{
|
|
1132
|
+
re: /(https?:\/\/[^/\s:@]+:)[^@/\s]+@/gi,
|
|
1133
|
+
replacement: `$1${REDACTED}@`
|
|
1134
|
+
},
|
|
1135
|
+
// Generic secrets: password=, secret=, token=, api_key=, apikey=
|
|
1136
|
+
{
|
|
1137
|
+
re: /((?:password|secret|token|api_key|apikey)\s*[=:]\s*)\S+/gi,
|
|
1138
|
+
replacement: `$1${REDACTED}`
|
|
1139
|
+
}
|
|
1140
|
+
];
|
|
1141
|
+
function redactSensitiveText(text) {
|
|
1142
|
+
let result = text;
|
|
1143
|
+
for (const { re, replacement } of patterns) {
|
|
1144
|
+
result = result.replace(re, replacement);
|
|
1145
|
+
}
|
|
1146
|
+
return result;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// src/logging/logger.ts
|
|
1150
|
+
var dirEnsured = false;
|
|
1151
|
+
function ensureLogDir() {
|
|
1152
|
+
if (dirEnsured) return;
|
|
1153
|
+
try {
|
|
1154
|
+
fs3.mkdirSync(LOG_DIR, { recursive: true });
|
|
1155
|
+
dirEnsured = true;
|
|
1156
|
+
} catch {
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
function todayFileName() {
|
|
1160
|
+
const d = /* @__PURE__ */ new Date();
|
|
1161
|
+
const yyyy = d.getFullYear();
|
|
1162
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
1163
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
1164
|
+
return `${LOG_PREFIX}${yyyy}-${mm}-${dd}.log`;
|
|
1165
|
+
}
|
|
1166
|
+
function logFilePath() {
|
|
1167
|
+
return path3.join(LOG_DIR, todayFileName());
|
|
1168
|
+
}
|
|
1169
|
+
var FLUSH_INTERVAL_MS = 2e3;
|
|
1170
|
+
var buffer = [];
|
|
1171
|
+
var flushTimer = null;
|
|
1172
|
+
var flushing = false;
|
|
1173
|
+
function startFlushTimer() {
|
|
1174
|
+
if (flushTimer) return;
|
|
1175
|
+
flushTimer = setInterval(() => {
|
|
1176
|
+
void flushLogBuffer();
|
|
1177
|
+
}, FLUSH_INTERVAL_MS);
|
|
1178
|
+
if (flushTimer && typeof flushTimer === "object" && "unref" in flushTimer) {
|
|
1179
|
+
flushTimer.unref();
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
async function flushLogBuffer() {
|
|
1183
|
+
if (flushing || buffer.length === 0) return;
|
|
1184
|
+
flushing = true;
|
|
1185
|
+
const batch = buffer;
|
|
1186
|
+
buffer = [];
|
|
1187
|
+
try {
|
|
1188
|
+
ensureLogDir();
|
|
1189
|
+
await fsp.appendFile(logFilePath(), batch.join(""), "utf-8");
|
|
1190
|
+
} catch {
|
|
1191
|
+
} finally {
|
|
1192
|
+
flushing = false;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
function flushLogBufferSync() {
|
|
1196
|
+
if (buffer.length === 0) return;
|
|
1197
|
+
const batch = buffer;
|
|
1198
|
+
buffer = [];
|
|
1199
|
+
try {
|
|
1200
|
+
ensureLogDir();
|
|
1201
|
+
fs3.appendFileSync(logFilePath(), batch.join(""), "utf-8");
|
|
1202
|
+
} catch {
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
process.on("beforeExit", flushLogBufferSync);
|
|
1206
|
+
process.on("exit", flushLogBufferSync);
|
|
1207
|
+
function writeLogEntry(entry) {
|
|
1208
|
+
const line = redactSensitiveText(JSON.stringify(entry)) + "\n";
|
|
1209
|
+
buffer.push(line);
|
|
1210
|
+
startFlushTimer();
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// src/logging/subsystem.ts
|
|
1214
|
+
import chalk from "chalk";
|
|
1215
|
+
|
|
1216
|
+
// src/logging/levels.ts
|
|
1217
|
+
var LOG_LEVEL_ORDER = {
|
|
1218
|
+
trace: 0,
|
|
1219
|
+
debug: 1,
|
|
1220
|
+
info: 2,
|
|
1221
|
+
warn: 3,
|
|
1222
|
+
error: 4,
|
|
1223
|
+
fatal: 5
|
|
1224
|
+
};
|
|
1225
|
+
var DEFAULT_LEVEL = "info";
|
|
1226
|
+
function resolveConfiguredLevel() {
|
|
1227
|
+
const raw = process.env.WALNUT_LOG_LEVEL?.toLowerCase().trim();
|
|
1228
|
+
if (raw && raw in LOG_LEVEL_ORDER) return raw;
|
|
1229
|
+
return DEFAULT_LEVEL;
|
|
1230
|
+
}
|
|
1231
|
+
var CONFIGURED_LOG_LEVEL = resolveConfiguredLevel();
|
|
1232
|
+
function shouldLog(messageLevel, configuredLevel = CONFIGURED_LOG_LEVEL) {
|
|
1233
|
+
return LOG_LEVEL_ORDER[messageLevel] >= LOG_LEVEL_ORDER[configuredLevel];
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// src/logging/subsystem.ts
|
|
1237
|
+
var levelColor = {
|
|
1238
|
+
trace: chalk.gray,
|
|
1239
|
+
debug: chalk.cyan,
|
|
1240
|
+
info: chalk.green,
|
|
1241
|
+
warn: chalk.yellow,
|
|
1242
|
+
error: chalk.red,
|
|
1243
|
+
fatal: chalk.bgRed.white
|
|
1244
|
+
};
|
|
1245
|
+
var levelLabel = {
|
|
1246
|
+
trace: "TRC",
|
|
1247
|
+
debug: "DBG",
|
|
1248
|
+
info: "INF",
|
|
1249
|
+
warn: "WRN",
|
|
1250
|
+
error: "ERR",
|
|
1251
|
+
fatal: "FTL"
|
|
1252
|
+
};
|
|
1253
|
+
function timeStamp(d) {
|
|
1254
|
+
const hh = String(d.getHours()).padStart(2, "0");
|
|
1255
|
+
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
1256
|
+
const ss = String(d.getSeconds()).padStart(2, "0");
|
|
1257
|
+
return `${hh}:${mm}:${ss}`;
|
|
1258
|
+
}
|
|
1259
|
+
function formatMeta(meta) {
|
|
1260
|
+
if (!meta || Object.keys(meta).length === 0) return "";
|
|
1261
|
+
return " " + chalk.dim(JSON.stringify(meta));
|
|
1262
|
+
}
|
|
1263
|
+
var ERROR_SINK_KEY = /* @__PURE__ */ Symbol.for("open-walnut.errorNotificationSink");
|
|
1264
|
+
var inErrorSink = false;
|
|
1265
|
+
function forwardToErrorSink(subsystem, message, meta) {
|
|
1266
|
+
const sink = globalThis[ERROR_SINK_KEY];
|
|
1267
|
+
if (!sink || inErrorSink) return;
|
|
1268
|
+
if (subsystem.startsWith("notif")) return;
|
|
1269
|
+
inErrorSink = true;
|
|
1270
|
+
try {
|
|
1271
|
+
sink({ subsystem, message, meta });
|
|
1272
|
+
} catch {
|
|
1273
|
+
} finally {
|
|
1274
|
+
inErrorSink = false;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
function emit(level, subsystem, message, meta) {
|
|
1278
|
+
if (!shouldLog(level)) return;
|
|
1279
|
+
const now = /* @__PURE__ */ new Date();
|
|
1280
|
+
writeLogEntry({
|
|
1281
|
+
time: now.toISOString(),
|
|
1282
|
+
level,
|
|
1283
|
+
subsystem,
|
|
1284
|
+
message,
|
|
1285
|
+
...meta && Object.keys(meta).length > 0 ? meta : {}
|
|
1286
|
+
});
|
|
1287
|
+
if (level === "error" || level === "fatal") {
|
|
1288
|
+
forwardToErrorSink(subsystem, message, meta);
|
|
1289
|
+
}
|
|
1290
|
+
try {
|
|
1291
|
+
const colorFn = levelColor[level];
|
|
1292
|
+
const tag = chalk.bold(`[${subsystem}]`);
|
|
1293
|
+
const lbl = colorFn(levelLabel[level]);
|
|
1294
|
+
const ts = chalk.dim(timeStamp(now));
|
|
1295
|
+
const metaStr = formatMeta(meta);
|
|
1296
|
+
const line = redactSensitiveText(`${ts} ${lbl} ${tag} ${message}${metaStr}
|
|
1297
|
+
`);
|
|
1298
|
+
process.stderr.write(line);
|
|
1299
|
+
} catch {
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
function createSubsystemLogger(subsystem) {
|
|
1303
|
+
const logger = {
|
|
1304
|
+
trace: (msg, meta) => emit("trace", subsystem, msg, meta),
|
|
1305
|
+
debug: (msg, meta) => emit("debug", subsystem, msg, meta),
|
|
1306
|
+
info: (msg, meta) => emit("info", subsystem, msg, meta),
|
|
1307
|
+
warn: (msg, meta) => emit("warn", subsystem, msg, meta),
|
|
1308
|
+
error: (msg, meta) => emit("error", subsystem, msg, meta),
|
|
1309
|
+
fatal: (msg, meta) => emit("fatal", subsystem, msg, meta),
|
|
1310
|
+
child(subName) {
|
|
1311
|
+
return createSubsystemLogger(`${subsystem}/${subName}`);
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
return logger;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// src/logging/index.ts
|
|
1318
|
+
var log = {
|
|
1319
|
+
bus: createSubsystemLogger("bus"),
|
|
1320
|
+
agent: createSubsystemLogger("agent"),
|
|
1321
|
+
session: createSubsystemLogger("session"),
|
|
1322
|
+
subagent: createSubsystemLogger("subagent"),
|
|
1323
|
+
web: createSubsystemLogger("web"),
|
|
1324
|
+
ws: createSubsystemLogger("ws"),
|
|
1325
|
+
hook: createSubsystemLogger("hook"),
|
|
1326
|
+
task: createSubsystemLogger("task"),
|
|
1327
|
+
memory: createSubsystemLogger("memory"),
|
|
1328
|
+
cron: createSubsystemLogger("cron"),
|
|
1329
|
+
notif: createSubsystemLogger("notif"),
|
|
1330
|
+
usage: createSubsystemLogger("usage"),
|
|
1331
|
+
heartbeat: createSubsystemLogger("heartbeat"),
|
|
1332
|
+
git: createSubsystemLogger("git"),
|
|
1333
|
+
browser: createSubsystemLogger("browser"),
|
|
1334
|
+
audio: createSubsystemLogger("audio"),
|
|
1335
|
+
stt: createSubsystemLogger("stt"),
|
|
1336
|
+
obs: createSubsystemLogger("obs"),
|
|
1337
|
+
calendar: createSubsystemLogger("calendar")
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1340
|
+
// src/session-server/index.ts
|
|
1341
|
+
var DEFAULT_PORT = 7890;
|
|
1342
|
+
async function startSessionServer(options) {
|
|
1343
|
+
delete process.env.CLAUDECODE;
|
|
1344
|
+
const server = new SessionServer({
|
|
1345
|
+
port: options.port,
|
|
1346
|
+
dataDir: options.dataDir
|
|
1347
|
+
});
|
|
1348
|
+
const actualPort = await server.start();
|
|
1349
|
+
return { port: actualPort, server };
|
|
1350
|
+
}
|
|
1351
|
+
async function runSessionServer(options) {
|
|
1352
|
+
const { WALNUT_HOME: WALNUT_HOME2 } = await Promise.resolve().then(() => (init_constants(), constants_exports));
|
|
1353
|
+
const port = parseInt(options.port ?? String(DEFAULT_PORT), 10);
|
|
1354
|
+
const dataDir = options.dataDir ?? WALNUT_HOME2;
|
|
1355
|
+
const { port: actualPort } = await startSessionServer({ port, dataDir });
|
|
1356
|
+
console.log(JSON.stringify({ port: actualPort, pid: process.pid }));
|
|
1357
|
+
const shutdown = () => {
|
|
1358
|
+
log.session.info("shutting down session server");
|
|
1359
|
+
process.exit(0);
|
|
1360
|
+
};
|
|
1361
|
+
process.on("SIGINT", shutdown);
|
|
1362
|
+
process.on("SIGTERM", shutdown);
|
|
1363
|
+
}
|
|
1364
|
+
var isMainModule = process.argv[1]?.endsWith("session-server/index.js") || process.argv[1]?.endsWith("session-server/index.mjs");
|
|
1365
|
+
if (isMainModule) {
|
|
1366
|
+
const args = process.argv.slice(2);
|
|
1367
|
+
const portIdx = args.indexOf("--port");
|
|
1368
|
+
const dataDirIdx = args.indexOf("--data-dir");
|
|
1369
|
+
const options = {};
|
|
1370
|
+
if (portIdx !== -1 && args[portIdx + 1]) options.port = args[portIdx + 1];
|
|
1371
|
+
if (dataDirIdx !== -1 && args[dataDirIdx + 1]) options.dataDir = args[dataDirIdx + 1];
|
|
1372
|
+
runSessionServer(options).catch((err) => {
|
|
1373
|
+
console.error("Fatal:", err);
|
|
1374
|
+
log.session.fatal("fatal error in session server", { error: err instanceof Error ? err.message : String(err), stack: err instanceof Error ? err.stack : void 0 });
|
|
1375
|
+
process.exit(1);
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
export {
|
|
1379
|
+
runSessionServer,
|
|
1380
|
+
startSessionServer
|
|
1381
|
+
};
|
|
1382
|
+
//# sourceMappingURL=index.js.map
|