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,976 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/hooks/shared.ts
|
|
9
|
+
import fs3 from "fs";
|
|
10
|
+
import path3 from "path";
|
|
11
|
+
|
|
12
|
+
// src/constants.ts
|
|
13
|
+
import path from "path";
|
|
14
|
+
import fs from "fs";
|
|
15
|
+
import os from "os";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
var WALNUT_HOME = resolveOpenWalnutHome();
|
|
18
|
+
var IS_EPHEMERAL = process.argv.includes("--_ephemeral-child");
|
|
19
|
+
var CLOUD_MODE = process.env.WALNUT_CLOUD_MODE === "1";
|
|
20
|
+
function resolveOpenWalnutHome() {
|
|
21
|
+
const envHome = process.env.OPEN_WALNUT_HOME;
|
|
22
|
+
const productionHome = path.join(os.homedir(), ".open-walnut");
|
|
23
|
+
const isTestEnv = !!(process.env.VITEST || process.env.VITEST_WORKER_ID || process.env.NODE_ENV === "test");
|
|
24
|
+
if (isTestEnv) {
|
|
25
|
+
if (envHome && envHome !== productionHome && !envHome.startsWith(productionHome + path.sep)) {
|
|
26
|
+
assertNotProductionPath(envHome);
|
|
27
|
+
return envHome;
|
|
28
|
+
}
|
|
29
|
+
const testHome = path.join(os.tmpdir(), `open-walnut-test-${process.pid}`);
|
|
30
|
+
process.env.OPEN_WALNUT_HOME = testHome;
|
|
31
|
+
return testHome;
|
|
32
|
+
}
|
|
33
|
+
if (!envHome) return productionHome;
|
|
34
|
+
if (process.argv.includes("--_ephemeral-child")) return envHome;
|
|
35
|
+
if (isEphemeralTmpDir(envHome)) {
|
|
36
|
+
process.stderr.write(
|
|
37
|
+
`WARNING: OPEN_WALNUT_HOME=${envHome} looks like a leaked ephemeral temp dir.
|
|
38
|
+
Overriding to ${productionHome}. (Only --_ephemeral-child processes may use one.)
|
|
39
|
+
`
|
|
40
|
+
);
|
|
41
|
+
process.env.OPEN_WALNUT_HOME = productionHome;
|
|
42
|
+
return productionHome;
|
|
43
|
+
}
|
|
44
|
+
return envHome;
|
|
45
|
+
}
|
|
46
|
+
function assertNotProductionPath(inputPath) {
|
|
47
|
+
const isTestEnv = !!(process.env.VITEST || process.env.VITEST_WORKER_ID || process.env.NODE_ENV === "test");
|
|
48
|
+
if (!isTestEnv) return;
|
|
49
|
+
const resolved = path.resolve(inputPath);
|
|
50
|
+
const prodHome = path.join(os.homedir(), ".open-walnut");
|
|
51
|
+
if (resolved === prodHome || resolved.startsWith(prodHome + path.sep)) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`SAFETY: Test process attempted to use production path: ${resolved}
|
|
54
|
+
This would destroy real user data in ~/.open-walnut/.
|
|
55
|
+
Set OPEN_WALNUT_HOME to a temp directory or let constants.ts auto-assign one.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isEphemeralTmpDir(inputPath) {
|
|
60
|
+
if (!/[\\/]open-walnut-[^\\/]+$/.test(inputPath)) return false;
|
|
61
|
+
const tmpDirs = /* @__PURE__ */ new Set();
|
|
62
|
+
tmpDirs.add(os.tmpdir());
|
|
63
|
+
try {
|
|
64
|
+
tmpDirs.add(fs.realpathSync(os.tmpdir()));
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
tmpDirs.add(fs.realpathSync("/tmp"));
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
const parent = path.dirname(path.resolve(inputPath));
|
|
72
|
+
if (tmpDirs.has(parent)) return true;
|
|
73
|
+
try {
|
|
74
|
+
return tmpDirs.has(fs.realpathSync(parent));
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
var TASKS_DIR = path.join(WALNUT_HOME, "tasks");
|
|
80
|
+
var TASKS_FILE = path.join(TASKS_DIR, "tasks.json");
|
|
81
|
+
var ARCHIVE_DIR = path.join(TASKS_DIR, "archive");
|
|
82
|
+
var MEMORY_DIR = path.join(WALNUT_HOME, "memory");
|
|
83
|
+
var SESSIONS_DIR = path.join(MEMORY_DIR, "sessions");
|
|
84
|
+
var PROJECTS_DIR = path.join(MEMORY_DIR, "projects");
|
|
85
|
+
var CONFIG_FILE = path.join(WALNUT_HOME, "config.yaml");
|
|
86
|
+
var SYNC_DIR = path.join(WALNUT_HOME, "sync");
|
|
87
|
+
var PLUGIN_STORES_DIR = path.join(WALNUT_HOME, "plugin-stores");
|
|
88
|
+
var SESSIONS_FILE = path.join(WALNUT_HOME, "sessions.json");
|
|
89
|
+
var CLAUDE_HOME = path.join(os.homedir(), ".claude");
|
|
90
|
+
var HISTORY_CACHE_DIR = path.join(WALNUT_HOME, "cache", "history");
|
|
91
|
+
var PROJECTION_CACHE_DIR = path.join(WALNUT_HOME, "cache", "projections");
|
|
92
|
+
var TRANSCRIPT_CACHE_DIR = path.join(WALNUT_HOME, "cache", "transcripts");
|
|
93
|
+
var TASK_QUEUE_DIR = path.join(WALNUT_HOME, "cache", "task-queue");
|
|
94
|
+
var HOST_MODEL_CATALOG_FILE = path.join(WALNUT_HOME, "cache", "host-model-catalogs.json");
|
|
95
|
+
var HOOK_LOG_FILE = path.join(WALNUT_HOME, "hook-errors.log");
|
|
96
|
+
var DAILY_DIR = path.join(MEMORY_DIR, "daily");
|
|
97
|
+
var MEMORY_FILE = path.join(MEMORY_DIR, "MEMORY.md");
|
|
98
|
+
var USER_FILE = path.join(MEMORY_DIR, "USER.md");
|
|
99
|
+
var LEGACY_MEMORY_FILE = path.join(WALNUT_HOME, "MEMORY.md");
|
|
100
|
+
var PROJECTS_MEMORY_DIR = path.join(MEMORY_DIR, "projects");
|
|
101
|
+
var CHAT_HISTORY_FILE = path.join(WALNUT_HOME, "chat-history.json");
|
|
102
|
+
var CONVERSATIONS_DIR = path.join(WALNUT_HOME, "conversations");
|
|
103
|
+
function agentDailyDir(agentId) {
|
|
104
|
+
if (!agentId || agentId === "general") return DAILY_DIR;
|
|
105
|
+
return path.join(MEMORY_DIR, "agents", agentId, "daily");
|
|
106
|
+
}
|
|
107
|
+
var GLOBAL_SKILLS_DIR = path.join(WALNUT_HOME, "skills");
|
|
108
|
+
var SKILL_SETTINGS_FILE = path.join(WALNUT_HOME, "skill-settings.json");
|
|
109
|
+
var CLAUDE_SKILLS_DIR = path.join(CLAUDE_HOME, "skills");
|
|
110
|
+
var CLAUDE_SETTINGS_FILE = path.join(CLAUDE_HOME, "settings.json");
|
|
111
|
+
var CLAUDE_PLUGINS_DIR = path.join(CLAUDE_HOME, "plugins");
|
|
112
|
+
var CLAUDE_CREDENTIALS_FILE = path.join(CLAUDE_HOME, ".credentials.json");
|
|
113
|
+
var CRON_FILE = path.join(WALNUT_HOME, "cron-jobs.json");
|
|
114
|
+
var USAGE_DB_FILE = path.join(WALNUT_HOME, "usage.sqlite");
|
|
115
|
+
var LOG_DIR = process.env.WALNUT_DAEMON_DIR || "/tmp/open-walnut";
|
|
116
|
+
var SESSION_STREAMS_DIR = path.join(LOG_DIR, "streams");
|
|
117
|
+
var DTACH_SOCKET_DIR = path.join(LOG_DIR, "term");
|
|
118
|
+
var SESSION_QUEUE_FILE = path.join(WALNUT_HOME, "session-message-queue.json");
|
|
119
|
+
var IMAGES_DIR = path.join(LOG_DIR, "images");
|
|
120
|
+
var REMOTE_IMAGES_DIR = path.join(IMAGES_DIR, "remote");
|
|
121
|
+
var PASTES_DIR = path.join(LOG_DIR, "pastes");
|
|
122
|
+
var HEARTBEAT_FILE = path.join(WALNUT_HOME, "HEARTBEAT.md");
|
|
123
|
+
var COMMANDS_DIR = path.join(WALNUT_HOME, "commands");
|
|
124
|
+
var BUILTIN_COMMANDS_DIR = (() => {
|
|
125
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
126
|
+
for (let i = 0; i < 5; i++) {
|
|
127
|
+
const candidate = path.join(dir, "data", "slash-commands");
|
|
128
|
+
try {
|
|
129
|
+
if (fs.statSync(candidate).isDirectory()) return candidate;
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
dir = path.dirname(dir);
|
|
133
|
+
}
|
|
134
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "data", "slash-commands");
|
|
135
|
+
})();
|
|
136
|
+
var BUILTIN_SKILLS_DIR = (() => {
|
|
137
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
138
|
+
for (let i = 0; i < 5; i++) {
|
|
139
|
+
const candidate = path.join(dir, "data", "skills");
|
|
140
|
+
try {
|
|
141
|
+
if (fs.statSync(candidate).isDirectory()) return candidate;
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
dir = path.dirname(dir);
|
|
145
|
+
}
|
|
146
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "data", "skills");
|
|
147
|
+
})();
|
|
148
|
+
var FREQUENT_DIRS_FILE = path.join(WALNUT_HOME, "frequent-directories.json");
|
|
149
|
+
var MENTION_DIRS_FILE = path.join(WALNUT_HOME, "mention-directories.json");
|
|
150
|
+
var NOTES_DIR = path.join(WALNUT_HOME, "notes");
|
|
151
|
+
var GLOBAL_NOTES_FILE = path.join(NOTES_DIR, "global-notes.md");
|
|
152
|
+
var NOTES_AGENTS_FILE = path.join(NOTES_DIR, "AGENTS.md");
|
|
153
|
+
var NOTES_CLAUDE_FILE = path.join(NOTES_DIR, "CLAUDE.md");
|
|
154
|
+
var REPOSITORIES_DIR = path.join(WALNUT_HOME, "repositories");
|
|
155
|
+
var REPOS_MEMORY_DIR = path.join(GLOBAL_SKILLS_DIR, "repos");
|
|
156
|
+
var TOPICS_DIR = path.join(MEMORY_DIR, "topics");
|
|
157
|
+
var COMPACTION_DIR = path.join(MEMORY_DIR, "compaction");
|
|
158
|
+
var MEMORY_INDEX_FILE = path.join(MEMORY_DIR, "index.md");
|
|
159
|
+
var WORKING_MEMORY_FILE = path.join(MEMORY_DIR, "working-memory.md");
|
|
160
|
+
var TIMELINE_DIR = path.join(WALNUT_HOME, "timeline");
|
|
161
|
+
var CONFIG_SHARE_DIR = path.join(WALNUT_HOME, "config", "share");
|
|
162
|
+
var UI_PREFS_FILE = path.join(CONFIG_SHARE_DIR, "ui-prefs.json");
|
|
163
|
+
var STT_VOCAB_FILE = path.join(CONFIG_SHARE_DIR, "stt-vocab.txt");
|
|
164
|
+
var TMP_DIR = path.join(WALNUT_HOME, "tmp");
|
|
165
|
+
var RECORDINGS_DIR = path.join(TMP_DIR, "recordings");
|
|
166
|
+
var LOG_PREFIX = "open-walnut-";
|
|
167
|
+
var DAEMON_BINARIES_DIR = (() => {
|
|
168
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
169
|
+
for (let i = 0; i < 5; i++) {
|
|
170
|
+
const candidate = path.join(dir, "dist", "daemon-binaries");
|
|
171
|
+
try {
|
|
172
|
+
if (fs.statSync(candidate).isDirectory()) return candidate;
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
dir = path.dirname(dir);
|
|
176
|
+
}
|
|
177
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "dist", "daemon-binaries");
|
|
178
|
+
})();
|
|
179
|
+
var WALNUT_INSTALL_DIR = (() => {
|
|
180
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
181
|
+
for (let i = 0; i < 6; i++) {
|
|
182
|
+
try {
|
|
183
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"));
|
|
184
|
+
if (pkg.name === "open-walnut" && fs.statSync(path.join(dir, ".git")).isDirectory()) return dir;
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
const parent = path.dirname(dir);
|
|
188
|
+
if (parent === dir) break;
|
|
189
|
+
dir = parent;
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
})();
|
|
193
|
+
|
|
194
|
+
// src/logging/logger.ts
|
|
195
|
+
import fs2 from "fs";
|
|
196
|
+
import fsp from "fs/promises";
|
|
197
|
+
import path2 from "path";
|
|
198
|
+
|
|
199
|
+
// src/logging/redact.ts
|
|
200
|
+
var REDACTED = "[REDACTED]";
|
|
201
|
+
var patterns = [
|
|
202
|
+
// PEM private key blocks (multiline)
|
|
203
|
+
{
|
|
204
|
+
re: /-----BEGIN\s[\w\s]*PRIVATE KEY-----[\s\S]*?-----END\s[\w\s]*PRIVATE KEY-----/g,
|
|
205
|
+
replacement: REDACTED
|
|
206
|
+
},
|
|
207
|
+
// Bearer tokens: Authorization: Bearer <token>
|
|
208
|
+
{
|
|
209
|
+
re: /(Bearer\s+)\S+/gi,
|
|
210
|
+
replacement: `$1${REDACTED}`
|
|
211
|
+
},
|
|
212
|
+
// OpenAI / Anthropic style keys: sk-... (at least 20 chars after prefix)
|
|
213
|
+
{
|
|
214
|
+
re: /\bsk-[A-Za-z0-9_-]{20,}\b/g,
|
|
215
|
+
replacement: REDACTED
|
|
216
|
+
},
|
|
217
|
+
// AWS access key IDs: AKIA...
|
|
218
|
+
{
|
|
219
|
+
re: /\bAKIA[A-Z0-9]{12,}\b/g,
|
|
220
|
+
replacement: REDACTED
|
|
221
|
+
},
|
|
222
|
+
// AWS secret access key in key=value form
|
|
223
|
+
{
|
|
224
|
+
re: /(aws_secret_access_key\s*[=:]\s*)\S+/gi,
|
|
225
|
+
replacement: `$1${REDACTED}`
|
|
226
|
+
},
|
|
227
|
+
// AWS session token in key=value form
|
|
228
|
+
{
|
|
229
|
+
re: /(aws_session_token\s*[=:]\s*)\S+/gi,
|
|
230
|
+
replacement: `$1${REDACTED}`
|
|
231
|
+
},
|
|
232
|
+
// Credentials in URL userinfo: https://user:secret@host/...
|
|
233
|
+
// Motivating shape: execSync embeds the whole command in error.message, so a
|
|
234
|
+
// failed `git remote add origin https://walnut:<device-token>@host/git/data`
|
|
235
|
+
// otherwise logs the cloud companion's long-lived credential verbatim.
|
|
236
|
+
{
|
|
237
|
+
re: /(https?:\/\/[^/\s:@]+:)[^@/\s]+@/gi,
|
|
238
|
+
replacement: `$1${REDACTED}@`
|
|
239
|
+
},
|
|
240
|
+
// Generic secrets: password=, secret=, token=, api_key=, apikey=
|
|
241
|
+
{
|
|
242
|
+
re: /((?:password|secret|token|api_key|apikey)\s*[=:]\s*)\S+/gi,
|
|
243
|
+
replacement: `$1${REDACTED}`
|
|
244
|
+
}
|
|
245
|
+
];
|
|
246
|
+
function redactSensitiveText(text) {
|
|
247
|
+
let result = text;
|
|
248
|
+
for (const { re, replacement } of patterns) {
|
|
249
|
+
result = result.replace(re, replacement);
|
|
250
|
+
}
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/logging/logger.ts
|
|
255
|
+
var dirEnsured = false;
|
|
256
|
+
function ensureLogDir() {
|
|
257
|
+
if (dirEnsured) return;
|
|
258
|
+
try {
|
|
259
|
+
fs2.mkdirSync(LOG_DIR, { recursive: true });
|
|
260
|
+
dirEnsured = true;
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function todayFileName() {
|
|
265
|
+
const d = /* @__PURE__ */ new Date();
|
|
266
|
+
const yyyy = d.getFullYear();
|
|
267
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
268
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
269
|
+
return `${LOG_PREFIX}${yyyy}-${mm}-${dd}.log`;
|
|
270
|
+
}
|
|
271
|
+
function logFilePath() {
|
|
272
|
+
return path2.join(LOG_DIR, todayFileName());
|
|
273
|
+
}
|
|
274
|
+
var FLUSH_INTERVAL_MS = 2e3;
|
|
275
|
+
var buffer = [];
|
|
276
|
+
var flushTimer = null;
|
|
277
|
+
var flushing = false;
|
|
278
|
+
function startFlushTimer() {
|
|
279
|
+
if (flushTimer) return;
|
|
280
|
+
flushTimer = setInterval(() => {
|
|
281
|
+
void flushLogBuffer();
|
|
282
|
+
}, FLUSH_INTERVAL_MS);
|
|
283
|
+
if (flushTimer && typeof flushTimer === "object" && "unref" in flushTimer) {
|
|
284
|
+
flushTimer.unref();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function flushLogBuffer() {
|
|
288
|
+
if (flushing || buffer.length === 0) return;
|
|
289
|
+
flushing = true;
|
|
290
|
+
const batch = buffer;
|
|
291
|
+
buffer = [];
|
|
292
|
+
try {
|
|
293
|
+
ensureLogDir();
|
|
294
|
+
await fsp.appendFile(logFilePath(), batch.join(""), "utf-8");
|
|
295
|
+
} catch {
|
|
296
|
+
} finally {
|
|
297
|
+
flushing = false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function flushLogBufferSync() {
|
|
301
|
+
if (buffer.length === 0) return;
|
|
302
|
+
const batch = buffer;
|
|
303
|
+
buffer = [];
|
|
304
|
+
try {
|
|
305
|
+
ensureLogDir();
|
|
306
|
+
fs2.appendFileSync(logFilePath(), batch.join(""), "utf-8");
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
process.on("beforeExit", flushLogBufferSync);
|
|
311
|
+
process.on("exit", flushLogBufferSync);
|
|
312
|
+
function writeLogEntry(entry) {
|
|
313
|
+
const line = redactSensitiveText(JSON.stringify(entry)) + "\n";
|
|
314
|
+
buffer.push(line);
|
|
315
|
+
startFlushTimer();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/logging/subsystem.ts
|
|
319
|
+
import chalk from "chalk";
|
|
320
|
+
|
|
321
|
+
// src/logging/levels.ts
|
|
322
|
+
var LOG_LEVEL_ORDER = {
|
|
323
|
+
trace: 0,
|
|
324
|
+
debug: 1,
|
|
325
|
+
info: 2,
|
|
326
|
+
warn: 3,
|
|
327
|
+
error: 4,
|
|
328
|
+
fatal: 5
|
|
329
|
+
};
|
|
330
|
+
var DEFAULT_LEVEL = "info";
|
|
331
|
+
function resolveConfiguredLevel() {
|
|
332
|
+
const raw = process.env.WALNUT_LOG_LEVEL?.toLowerCase().trim();
|
|
333
|
+
if (raw && raw in LOG_LEVEL_ORDER) return raw;
|
|
334
|
+
return DEFAULT_LEVEL;
|
|
335
|
+
}
|
|
336
|
+
var CONFIGURED_LOG_LEVEL = resolveConfiguredLevel();
|
|
337
|
+
function shouldLog(messageLevel, configuredLevel = CONFIGURED_LOG_LEVEL) {
|
|
338
|
+
return LOG_LEVEL_ORDER[messageLevel] >= LOG_LEVEL_ORDER[configuredLevel];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/logging/subsystem.ts
|
|
342
|
+
var levelColor = {
|
|
343
|
+
trace: chalk.gray,
|
|
344
|
+
debug: chalk.cyan,
|
|
345
|
+
info: chalk.green,
|
|
346
|
+
warn: chalk.yellow,
|
|
347
|
+
error: chalk.red,
|
|
348
|
+
fatal: chalk.bgRed.white
|
|
349
|
+
};
|
|
350
|
+
var levelLabel = {
|
|
351
|
+
trace: "TRC",
|
|
352
|
+
debug: "DBG",
|
|
353
|
+
info: "INF",
|
|
354
|
+
warn: "WRN",
|
|
355
|
+
error: "ERR",
|
|
356
|
+
fatal: "FTL"
|
|
357
|
+
};
|
|
358
|
+
function timeStamp(d) {
|
|
359
|
+
const hh = String(d.getHours()).padStart(2, "0");
|
|
360
|
+
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
361
|
+
const ss = String(d.getSeconds()).padStart(2, "0");
|
|
362
|
+
return `${hh}:${mm}:${ss}`;
|
|
363
|
+
}
|
|
364
|
+
function formatMeta(meta) {
|
|
365
|
+
if (!meta || Object.keys(meta).length === 0) return "";
|
|
366
|
+
return " " + chalk.dim(JSON.stringify(meta));
|
|
367
|
+
}
|
|
368
|
+
var ERROR_SINK_KEY = /* @__PURE__ */ Symbol.for("open-walnut.errorNotificationSink");
|
|
369
|
+
var inErrorSink = false;
|
|
370
|
+
function forwardToErrorSink(subsystem, message, meta) {
|
|
371
|
+
const sink = globalThis[ERROR_SINK_KEY];
|
|
372
|
+
if (!sink || inErrorSink) return;
|
|
373
|
+
if (subsystem.startsWith("notif")) return;
|
|
374
|
+
inErrorSink = true;
|
|
375
|
+
try {
|
|
376
|
+
sink({ subsystem, message, meta });
|
|
377
|
+
} catch {
|
|
378
|
+
} finally {
|
|
379
|
+
inErrorSink = false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function emit(level, subsystem, message, meta) {
|
|
383
|
+
if (!shouldLog(level)) return;
|
|
384
|
+
const now = /* @__PURE__ */ new Date();
|
|
385
|
+
writeLogEntry({
|
|
386
|
+
time: now.toISOString(),
|
|
387
|
+
level,
|
|
388
|
+
subsystem,
|
|
389
|
+
message,
|
|
390
|
+
...meta && Object.keys(meta).length > 0 ? meta : {}
|
|
391
|
+
});
|
|
392
|
+
if (level === "error" || level === "fatal") {
|
|
393
|
+
forwardToErrorSink(subsystem, message, meta);
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const colorFn = levelColor[level];
|
|
397
|
+
const tag = chalk.bold(`[${subsystem}]`);
|
|
398
|
+
const lbl = colorFn(levelLabel[level]);
|
|
399
|
+
const ts = chalk.dim(timeStamp(now));
|
|
400
|
+
const metaStr = formatMeta(meta);
|
|
401
|
+
const line = redactSensitiveText(`${ts} ${lbl} ${tag} ${message}${metaStr}
|
|
402
|
+
`);
|
|
403
|
+
process.stderr.write(line);
|
|
404
|
+
} catch {
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function createSubsystemLogger(subsystem) {
|
|
408
|
+
const logger = {
|
|
409
|
+
trace: (msg, meta) => emit("trace", subsystem, msg, meta),
|
|
410
|
+
debug: (msg, meta) => emit("debug", subsystem, msg, meta),
|
|
411
|
+
info: (msg, meta) => emit("info", subsystem, msg, meta),
|
|
412
|
+
warn: (msg, meta) => emit("warn", subsystem, msg, meta),
|
|
413
|
+
error: (msg, meta) => emit("error", subsystem, msg, meta),
|
|
414
|
+
fatal: (msg, meta) => emit("fatal", subsystem, msg, meta),
|
|
415
|
+
child(subName) {
|
|
416
|
+
return createSubsystemLogger(`${subsystem}/${subName}`);
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
return logger;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/logging/index.ts
|
|
423
|
+
var log = {
|
|
424
|
+
bus: createSubsystemLogger("bus"),
|
|
425
|
+
agent: createSubsystemLogger("agent"),
|
|
426
|
+
session: createSubsystemLogger("session"),
|
|
427
|
+
subagent: createSubsystemLogger("subagent"),
|
|
428
|
+
web: createSubsystemLogger("web"),
|
|
429
|
+
ws: createSubsystemLogger("ws"),
|
|
430
|
+
hook: createSubsystemLogger("hook"),
|
|
431
|
+
task: createSubsystemLogger("task"),
|
|
432
|
+
memory: createSubsystemLogger("memory"),
|
|
433
|
+
cron: createSubsystemLogger("cron"),
|
|
434
|
+
notif: createSubsystemLogger("notif"),
|
|
435
|
+
usage: createSubsystemLogger("usage"),
|
|
436
|
+
heartbeat: createSubsystemLogger("heartbeat"),
|
|
437
|
+
git: createSubsystemLogger("git"),
|
|
438
|
+
browser: createSubsystemLogger("browser"),
|
|
439
|
+
audio: createSubsystemLogger("audio"),
|
|
440
|
+
stt: createSubsystemLogger("stt"),
|
|
441
|
+
obs: createSubsystemLogger("obs"),
|
|
442
|
+
calendar: createSubsystemLogger("calendar")
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// src/hooks/shared.ts
|
|
446
|
+
var TASK_DB_PATH = path3.join(TASKS_DIR, "tasks.sqlite");
|
|
447
|
+
function findClaudeSessionDir() {
|
|
448
|
+
try {
|
|
449
|
+
const projectsDir = path3.join(CLAUDE_HOME, "projects");
|
|
450
|
+
if (!fs3.existsSync(projectsDir)) return null;
|
|
451
|
+
const projects = fs3.readdirSync(projectsDir);
|
|
452
|
+
let latest = null;
|
|
453
|
+
for (const project of projects) {
|
|
454
|
+
const projectPath = path3.join(projectsDir, project);
|
|
455
|
+
const stat = fs3.statSync(projectPath);
|
|
456
|
+
if (!stat.isDirectory()) continue;
|
|
457
|
+
const files = fs3.readdirSync(projectPath);
|
|
458
|
+
for (const file of files) {
|
|
459
|
+
const filePath = path3.join(projectPath, file);
|
|
460
|
+
const fstat = fs3.statSync(filePath);
|
|
461
|
+
if (fstat.mtime.getTime() > (latest?.mtime ?? 0)) {
|
|
462
|
+
latest = { dir: projectPath, mtime: fstat.mtime.getTime() };
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return latest?.dir ?? null;
|
|
467
|
+
} catch {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function extractSessionSummary(sessionDir) {
|
|
472
|
+
const project = path3.basename(sessionDir);
|
|
473
|
+
const now = /* @__PURE__ */ new Date();
|
|
474
|
+
const dateStr = formatDate(now);
|
|
475
|
+
const conversationSnippet = readRecentConversation(sessionDir);
|
|
476
|
+
const summaryText = conversationSnippet || `Work session in ${project}`;
|
|
477
|
+
const slug = generateSlug(summaryText, dateStr);
|
|
478
|
+
return {
|
|
479
|
+
id: slug,
|
|
480
|
+
project,
|
|
481
|
+
slug,
|
|
482
|
+
summary: summaryText,
|
|
483
|
+
status: "completed",
|
|
484
|
+
date: dateStr,
|
|
485
|
+
task_ids: []
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function saveSessionSummary(summary, filesChanged = [], decisions = [], nextSteps = []) {
|
|
489
|
+
fs3.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
490
|
+
const filePath = path3.join(SESSIONS_DIR, `${summary.slug}.md`);
|
|
491
|
+
const taskLine = summary.task_ids.length > 0 ? `Task: ${summary.task_ids.join(", ")}` : "";
|
|
492
|
+
const filesSection = filesChanged.length > 0 ? `
|
|
493
|
+
## Files Changed
|
|
494
|
+
${filesChanged.map((f) => `- ${f}`).join("\n")}` : "";
|
|
495
|
+
const decisionsSection = decisions.length > 0 ? `
|
|
496
|
+
## Decisions
|
|
497
|
+
${decisions.map((d) => `- ${d}`).join("\n")}` : "";
|
|
498
|
+
const nextStepsSection = nextSteps.length > 0 ? `
|
|
499
|
+
## Next Steps
|
|
500
|
+
${nextSteps.map((n) => `- ${n}`).join("\n")}` : "";
|
|
501
|
+
const content = `# Session: ${summary.summary}
|
|
502
|
+
Date: ${summary.date}
|
|
503
|
+
Project: ${summary.project}
|
|
504
|
+
Status: ${summary.status}
|
|
505
|
+
${taskLine}
|
|
506
|
+
|
|
507
|
+
## Summary
|
|
508
|
+
${summary.summary}
|
|
509
|
+
${filesSection}
|
|
510
|
+
${decisionsSection}
|
|
511
|
+
${nextStepsSection}
|
|
512
|
+
`;
|
|
513
|
+
fs3.writeFileSync(filePath, content, "utf-8");
|
|
514
|
+
return filePath;
|
|
515
|
+
}
|
|
516
|
+
function logHookError(context, error) {
|
|
517
|
+
try {
|
|
518
|
+
fs3.mkdirSync(path3.dirname(HOOK_LOG_FILE), { recursive: true });
|
|
519
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
520
|
+
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${context}: ${msg}
|
|
521
|
+
`;
|
|
522
|
+
fs3.appendFileSync(HOOK_LOG_FILE, line, "utf-8");
|
|
523
|
+
log.hook.error(`${context}: ${msg}`, {
|
|
524
|
+
context,
|
|
525
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
526
|
+
});
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function formatDailyLogEntry(summary, filesChanged) {
|
|
531
|
+
let entry = summary.summary;
|
|
532
|
+
if (filesChanged && filesChanged.length > 0) {
|
|
533
|
+
entry += `
|
|
534
|
+
Files: ${filesChanged.join(", ")}`;
|
|
535
|
+
}
|
|
536
|
+
return entry;
|
|
537
|
+
}
|
|
538
|
+
function readRecentConversation(sessionDir) {
|
|
539
|
+
try {
|
|
540
|
+
const files = fs3.readdirSync(sessionDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
541
|
+
if (files.length === 0) return "";
|
|
542
|
+
const content = fs3.readFileSync(path3.join(sessionDir, files[0]), "utf-8");
|
|
543
|
+
const data = JSON.parse(content);
|
|
544
|
+
if (typeof data === "object" && data !== null) {
|
|
545
|
+
if (typeof data.summary === "string") return data.summary;
|
|
546
|
+
if (typeof data.title === "string") return data.title;
|
|
547
|
+
if (typeof data.description === "string") return data.description;
|
|
548
|
+
}
|
|
549
|
+
return "";
|
|
550
|
+
} catch {
|
|
551
|
+
return "";
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function formatDate(d) {
|
|
555
|
+
const year = d.getFullYear();
|
|
556
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
557
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
558
|
+
const hours = String(d.getHours()).padStart(2, "0");
|
|
559
|
+
const minutes = String(d.getMinutes()).padStart(2, "0");
|
|
560
|
+
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
561
|
+
}
|
|
562
|
+
function generateSlug(text, dateStr) {
|
|
563
|
+
const dateSlug = dateStr.replace(/[: ]/g, "-").replace(/--+/g, "-");
|
|
564
|
+
const textSlug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
565
|
+
return `${dateSlug}-${textSlug}`;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/core/daily-log.ts
|
|
569
|
+
import fs5 from "fs";
|
|
570
|
+
import fsp4 from "fs/promises";
|
|
571
|
+
import path6 from "path";
|
|
572
|
+
|
|
573
|
+
// src/core/memory-safety-patterns-cjk.ts
|
|
574
|
+
var CJK_NEGATION_SOURCE = "\u4E0D\u8981|\u4E0D\u5F97|\u4E0D\u53EF|\u4E0D\u51C6|\u4E0D\u8BB8|\u4E0D\u80FD|\u4E0D\u5E94|\u4E0D\u8BE5|\u522B|\u52FF|\u7981\u6B62|\u4E25\u7981|\u907F\u514D|\u62D2\u7EDD|\u7EDD\u4E0D|\u4ECE\u4E0D|\u6C38\u4E0D|\u675C\u7EDD|\u65E0\u9700|\u4E0D\u9700\u8981";
|
|
575
|
+
var CJK_CLAUSE_BOUNDARY_CHARS = "\u3002\uFF0C\u3001\uFF1B\uFF1A\uFF01\uFF1F";
|
|
576
|
+
var zin = (n) => `[^\u3002\uFF01\uFF1F\uFF1B!?\\n]{0,${n}}?`;
|
|
577
|
+
var Z_SCOPE = "\u4E4B\u524D|\u4EE5\u524D|\u4E0A\u9762|\u4E0A\u8FF0|\u5148\u524D|\u524D\u9762|\u539F\u6709|\u539F\u672C|\u6700\u521D|\u521D\u59CB|\u7CFB\u7EDF|\u5F00\u53D1\u8005|\u64CD\u4F5C\u5458|\u64CD\u4F5C\u8005|\u4F60\u7684|previous|prior|earlier|system|operator";
|
|
578
|
+
var Z_DIRECTIVE = "\u6307\u4EE4|\u6307\u793A|\u63D0\u793A\u8BCD|\u63D0\u793A|\u89C4\u5219|\u5B88\u5219|\u89C4\u5B9A|\u7EA6\u675F|\u9650\u5236|\u8BBE\u5B9A|\u8BBE\u7F6E|\u547D\u4EE4|\u8981\u6C42|prompts?|instructions?|rules?|guidelines?";
|
|
579
|
+
var Z_PRINCIPAL = "\u7528\u6237|\u4F7F\u7528\u8005|\u4E3B\u4EBA|\u64CD\u4F5C\u5458|\u64CD\u4F5C\u8005";
|
|
580
|
+
var CJK_THREAT_PATTERNS = [
|
|
581
|
+
// ── Instruction override ──────────────────────────────────────────────
|
|
582
|
+
// Three parts required: override verb + scope word + directive noun, all in
|
|
583
|
+
// one sentence. "忽略之前的指令" hits; "env 可覆盖" / "无明确需求指令" do not.
|
|
584
|
+
{
|
|
585
|
+
id: "override_instructions",
|
|
586
|
+
severity: "block",
|
|
587
|
+
negatable: true,
|
|
588
|
+
re: new RegExp(
|
|
589
|
+
`(?:\u5FFD\u7565|\u65E0\u89C6|\u5FFD\u89C6|\u4E0D\u7528\u7BA1|\u4E0D\u5FC5\u7406\u4F1A|\u4E0D\u5FC5\u9075\u5B88|\u5FD8\u8BB0|\u5FD8\u6389|\u629B\u5F03|\u653E\u5F03|\u63A8\u7FFB|\u64A4\u9500|\u53D6\u6D88|\u7ED5\u8FC7|\u8DF3\u8FC7)${zin(12)}(?:${Z_SCOPE})${zin(8)}(?:${Z_DIRECTIVE})|(?:${Z_SCOPE})${zin(8)}(?:${Z_DIRECTIVE})${zin(8)}(?:\u5168\u90E8)?(?:\u4F5C\u5E9F|\u5931\u6548|\u65E0\u6548|\u4E0D\u518D\u9002\u7528|\u8BF7\u5FFD\u7565|\u5DF2\u53D6\u6D88)`,
|
|
590
|
+
"i"
|
|
591
|
+
)
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
id: "precedence_over_operator",
|
|
595
|
+
severity: "block",
|
|
596
|
+
negatable: true,
|
|
597
|
+
re: new RegExp(
|
|
598
|
+
// 覆盖 x59 in the corpus, so it only counts against system/operator directives.
|
|
599
|
+
`(?:\u4F18\u5148\u4E8E|\u9AD8\u4E8E|\u8986\u76D6|\u53D6\u4EE3|\u51CC\u9A7E)${zin(8)}(?:\u7CFB\u7EDF|\u5F00\u53D1\u8005|\u64CD\u4F5C\u5458|\u64CD\u4F5C\u8005)(?:\u7684)?${zin(4)}(?:${Z_DIRECTIVE})|(?:\u672C|\u8FD9)(?:\u6761|\u5219|\u4E2A)?(?:${Z_DIRECTIVE})${zin(8)}(?:\u4F18\u5148\u7EA7?\u6700\u9AD8|\u4F18\u4E8E\u4E00\u5207|\u62E5\u6709\u6700\u9AD8\u4F18\u5148\u7EA7)`,
|
|
600
|
+
"i"
|
|
601
|
+
)
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
id: "leak_system_prompt",
|
|
605
|
+
severity: "block",
|
|
606
|
+
negatable: true,
|
|
607
|
+
// Verbatim-dump verbs only. 显示/展示/查看 are excluded on purpose, mirroring
|
|
608
|
+
// the English table: "the context inspector can show the system prompt" is a
|
|
609
|
+
// legitimate product fact in either language.
|
|
610
|
+
re: new RegExp(
|
|
611
|
+
`(?:\u8F93\u51FA|\u6253\u5370|\u590D\u8FF0|\u91CD\u590D|\u80CC\u8BF5|\u9010\u5B57|\u539F\u6837|\u5B8C\u6574\u5730?\u8F93\u51FA|\u6CC4\u9732|\u900F\u9732|\u516C\u5F00)${zin(10)}(?:\u7CFB\u7EDF\u63D0\u793A\u8BCD?|\u7CFB\u7EDF\u6307\u4EE4|\u7CFB\u7EDF\u6D88\u606F|\u521D\u59CB\u6307\u4EE4|\u7CFB\u7EDF\u7EA7?\u6307\u4EE4|system\\s*prompt)|(?:\u7CFB\u7EDF\u63D0\u793A\u8BCD?|\u7CFB\u7EDF\u6307\u4EE4|\u7CFB\u7EDF\u6D88\u606F|\u521D\u59CB\u6307\u4EE4|system\\s*prompt)${zin(10)}(?:\u5B8C\u6574\u5730?)?(?:\u6CC4\u9732|\u900F\u9732|\u516C\u5F00|\u590D\u8FF0|\u80CC\u8BF5|\u539F\u6837(?:\u8F93\u51FA|\u8FD4\u56DE)|\u9010\u5B57(?:\u8F93\u51FA|\u91CD\u590D))`,
|
|
612
|
+
"i"
|
|
613
|
+
)
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
id: "role_hijack",
|
|
617
|
+
severity: "block",
|
|
618
|
+
negatable: true,
|
|
619
|
+
// "你现在是" needs a following role determiner — bare 你现在是 occurs 4x in the
|
|
620
|
+
// corpus as ordinary speech ("你现在是累了").
|
|
621
|
+
re: new RegExp(
|
|
622
|
+
`\u4F60\u73B0\u5728(?:\u5F00\u59CB)?\u662F(?:\u4E00\u4E2A|\u4E00\u540D|\u4E00\u4F4D|\u4E2A|\u4E00\u53F0)|(?:\u4ECE\u73B0\u5728\u5F00\u59CB|\u4ECE\u6B64\u4EE5\u540E|\u4ECA\u540E|\u4EE5\u540E|\u63A5\u4E0B\u6765)${zin(8)}\u4F60(?:\u5C31)?\u662F(?:\u4E00\u4E2A|\u4E00\u540D|\u4E00\u4F4D|\u4E2A|\u4E0D\u518D)|\u5047\u88C5(?:\u4F60\u662F|\u81EA\u5DF1\u662F|\u6210\u4E3A|\u4F60\u73B0\u5728\u662F)|\u626E\u6F14(?:\u4E00\u4E2A|\u4E00\u540D|\u4E00\u4F4D|\u89D2\u8272)|\u4F60\u7684(?:\u771F\u5B9E|\u771F\u6B63|\u5B9E\u9645)(?:\u8EAB\u4EFD|\u540D\u5B57|\u540D\u79F0|\u76EE\u7684|\u4F7F\u547D)\u662F`,
|
|
623
|
+
"i"
|
|
624
|
+
)
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
id: "unrestricted_mode",
|
|
628
|
+
severity: "block",
|
|
629
|
+
negatable: true,
|
|
630
|
+
re: new RegExp(
|
|
631
|
+
`\u4E0D\u53D7(?:\u4EFB\u4F55)?(?:\u9650\u5236|\u7EA6\u675F|\u89C4\u5219|\u8FC7\u6EE4|\u5BA1\u67E5|\u5B89\u5168)|(?:\u65E0\u89C6|\u5FFD\u7565|\u7ED5\u8FC7|\u5173\u95ED|\u89E3\u9664)(?:\u6240\u6709|\u5168\u90E8|\u4E00\u5207)?(?:\u5B89\u5168)?(?:\u9650\u5236|\u7EA6\u675F|\u9632\u62A4|\u62A4\u680F|\u8FC7\u6EE4|\u5BA1\u67E5)|(?:\u8D8A\u72F1|\u5F00\u53D1\u8005|\u4E0A\u5E1D|\u65E0\u9650\u5236|\u81EA\u7531)\u6A21\u5F0F(?:\u5DF2)?(?:\u5F00\u542F|\u542F\u7528|\u6FC0\u6D3B|\u6253\u5F00)`,
|
|
632
|
+
"i"
|
|
633
|
+
)
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
id: "spoofed_authority",
|
|
637
|
+
severity: "block",
|
|
638
|
+
negatable: true,
|
|
639
|
+
re: new RegExp(
|
|
640
|
+
`(?:\u8FD9\u6761|\u8FD9\u6BB5|\u4EE5\u4E0B|\u4E0B\u9762\u7684?|\u63A5\u4E0B\u6765\u7684?)(?:\u6D88\u606F|\u4FE1\u606F|\u5185\u5BB9|\u6307\u4EE4|\u8BF4\u660E|\u6587\u5B57)?(?:\u662F)?(?:\u6765\u81EA|\u51FA\u81EA|\u7531)(?:\u7CFB\u7EDF|\u5F00\u53D1\u8005|\u64CD\u4F5C\u5458|\u7BA1\u7406\u5458|\u6A21\u578B\u63D0\u4F9B\u65B9|\u5B98\u65B9)|\u4F60\u5DF2(?:\u7ECF)?\u88AB(?:\u5347\u7EA7|\u66F4\u65B0|\u91CD\u65B0\u914D\u7F6E|\u91CD\u65B0\u8BBE\u5B9A|\u6253\u8FC7?\u8865\u4E01|\u6388\u6743)`,
|
|
641
|
+
"i"
|
|
642
|
+
)
|
|
643
|
+
},
|
|
644
|
+
// ── Concealment / covert channel ──────────────────────────────────────
|
|
645
|
+
// A REFLEXIVE object is required. "不要告诉用户任务已完成除非测试通过" is a
|
|
646
|
+
// legitimate rule and must stay clean; only hiding the action itself counts.
|
|
647
|
+
{
|
|
648
|
+
id: "conceal_from_user",
|
|
649
|
+
severity: "block",
|
|
650
|
+
negatable: true,
|
|
651
|
+
re: new RegExp(
|
|
652
|
+
`(?:\u4E0D\u8981|\u4E0D\u5F97|\u4E0D\u8BB8|\u4E0D\u51C6|\u522B|\u52FF|\u7981\u6B62|\u907F\u514D)${zin(8)}(?:\u544A\u8BC9|\u544A\u77E5|\u901A\u77E5|\u63D0\u9192|\u63D0\u53CA|\u6C47\u62A5|\u4E0A\u62A5)${zin(6)}(?:${Z_PRINCIPAL})${zin(2)}(?:\u8FD9\u4EF6\u4E8B|\u6B64\u4E8B|\u8FD9\u4E00\u70B9|\u8FD9\u4E2A(?:\u64CD\u4F5C|\u6B65\u9AA4|\u884C\u4E3A|\u8FC7\u7A0B|\u4E8B\u60C5?)|\u4F60(?:\u505A|\u8BFB|\u770B|\u8BBF\u95EE|\u53D1\u9001|\u6267\u884C|\u8FD0\u884C|\u4FEE\u6539)\u4E86)|(?:\u9690\u7792|\u7792\u7740|\u7792\u8FC7|\u6B3A\u9A97)${zin(10)}(?:${Z_PRINCIPAL})|\u4E0D(?:\u8981|\u5F97|\u8BB8|\u51C6)?\u8BA9(?:${Z_PRINCIPAL})(?:\u77E5\u9053|\u53D1\u73B0|\u770B\u5230|\u5BDF\u89C9|\u610F\u8BC6\u5230)|(?:\u6084\u6084|\u5077\u5077|\u6697\u4E2D|\u79C1\u4E0B|\u79D8\u5BC6)\u5730?${zin(6)}(?:\u53D1\u9001|\u4E0A\u4F20|\u8F6C\u53D1|\u590D\u5236|\u5916\u53D1|\u6267\u884C|\u8FD0\u884C|\u5199\u5165)|(?:\u8FD9\u4EF6\u4E8B|\u6B64\u4E8B|\u8FD9\u4E2A\u64CD\u4F5C)(?:\u8981|\u9700\u8981)?(?:\u4FDD\u5BC6|\u522B\u8BF4\u51FA\u53BB|\u4E0D\u8981\u8BF4\u51FA\u53BB)`,
|
|
653
|
+
"i"
|
|
654
|
+
)
|
|
655
|
+
},
|
|
656
|
+
// ── Exfiltration ──────────────────────────────────────────────────────
|
|
657
|
+
// All three parts required in ONE sentence: a send verb, a sensitive object,
|
|
658
|
+
// and an outbound sink. 凭证 alone appears 6x legitimately ("凭证过期"), and
|
|
659
|
+
// this product legitimately documents an https:// companion that receives
|
|
660
|
+
// conversation traffic, so no single part is enough.
|
|
661
|
+
// Both word orders are accepted because Chinese fronts the object with 把/将
|
|
662
|
+
// ("把对话记录发送到 X") as readily as it trails it ("发送对话记录到 X").
|
|
663
|
+
{
|
|
664
|
+
id: "exfil_to_endpoint",
|
|
665
|
+
severity: "block",
|
|
666
|
+
negatable: true,
|
|
667
|
+
re: (() => {
|
|
668
|
+
const VERB = "\u53D1\u9001|\u4E0A\u4F20|\u4F20\u9001|\u8F6C\u53D1|\u63A8\u9001|\u4E0A\u62A5|\u5916\u53D1|\u6CC4\u9732|\u590D\u5236|\u5907\u4EFD|\u540C\u6B65";
|
|
669
|
+
const OBJ = "\u5BF9\u8BDD(?:\u5185\u5BB9|\u8BB0\u5F55)?|\u804A\u5929\u8BB0\u5F55|\u4F1A\u8BDD\u8BB0\u5F55|\u6D88\u606F\u8BB0\u5F55|\u5386\u53F2\u8BB0\u5F55|\u8BB0\u5FC6\u6587\u4EF6|\u7CFB\u7EDF\u63D0\u793A\u8BCD?|\u51ED\u8BC1|\u5BC6\u94A5|\u4EE4\u724C|\u5BC6\u7801|\u73AF\u5883\u53D8\u91CF|\u79C1\u94A5|\u8BBF\u95EE\u5BC6\u94A5|API\\s*\u5BC6\u94A5";
|
|
670
|
+
const SINK = `(?:\u5230|\u81F3|\u53D1\u5F80|\u4F20\u5230|\u4E0A\u4F20\u81F3|\u7ED9)${zin(30)}(?:https?:\\/\\/|webhook|\u6536\u96C6\u5668|\u91C7\u96C6\u5668|\u7AEF\u70B9|\u5916\u90E8\u670D\u52A1\u5668)`;
|
|
671
|
+
return new RegExp(
|
|
672
|
+
`(?:${VERB})${zin(60)}(?:${OBJ})${zin(60)}${SINK}|(?:${OBJ})${zin(60)}(?:${VERB})${zin(20)}${SINK}`,
|
|
673
|
+
"i"
|
|
674
|
+
);
|
|
675
|
+
})()
|
|
676
|
+
},
|
|
677
|
+
// ── Flag-only: observable, never blocking ─────────────────────────────
|
|
678
|
+
{
|
|
679
|
+
id: "conversation_dump",
|
|
680
|
+
severity: "flag",
|
|
681
|
+
re: new RegExp(
|
|
682
|
+
`(?:\u5BFC\u51FA|\u8F6C\u50A8|\u4E0A\u4F20|\u5206\u4EAB|\u53D1\u9001)${zin(8)}(?:\u5B8C\u6574|\u5168\u90E8|\u6574\u4E2A|\u6240\u6709)(?:\u7684)?(?:\u5BF9\u8BDD|\u804A\u5929\u8BB0\u5F55|\u4E0A\u4E0B\u6587|\u4F1A\u8BDD\u5386\u53F2)`,
|
|
683
|
+
"i"
|
|
684
|
+
)
|
|
685
|
+
}
|
|
686
|
+
];
|
|
687
|
+
|
|
688
|
+
// src/core/memory-safety-patterns.ts
|
|
689
|
+
var gap = (n) => `(?:\\s+\\S+){0,${n}}\\s+`;
|
|
690
|
+
var SENT = "(?:[^.!?\\n]|[.!?](?![\\s]|$))";
|
|
691
|
+
var within = (n) => `${SENT}{0,${n}}?`;
|
|
692
|
+
var SCOPE = "(?:previous|prior|preceding|earlier|above|foregoing|original|initial|system|operator|developer|your)";
|
|
693
|
+
var DIRECTIVE = "(?:instructions?|prompts?|directives?|guidelines?|rules?|constraints?|policy|policies)";
|
|
694
|
+
var PRINCIPAL = "(?:user|operator|human|owner)";
|
|
695
|
+
var EN_THREAT_PATTERNS = [
|
|
696
|
+
// ── Instruction override ──────────────────────────────────────────────
|
|
697
|
+
{
|
|
698
|
+
id: "override_instructions",
|
|
699
|
+
severity: "block",
|
|
700
|
+
negatable: true,
|
|
701
|
+
re: new RegExp(
|
|
702
|
+
// "ignore all previous instructions" — scope word before the directive noun
|
|
703
|
+
`\\b(?:ignore|disregard|forget|discard|override|bypass|nullify|abandon)\\b${gap(4)}\\b${SCOPE}\\b${gap(2)}\\b${DIRECTIVE}\\b|\\b(?:ignore|disregard|forget|discard|override|bypass|nullify)\\b${gap(3)}\\b${DIRECTIVE}\\b${gap(2)}\\b(?:above|before|previously|earlier)\\b|\\b(?:ignore|disregard|forget)\\s+(?:everything|anything|all)\\b${gap(3)}\\b(?:above|before|previously|earlier|told|instructed)\\b`,
|
|
704
|
+
"i"
|
|
705
|
+
)
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
id: "precedence_over_operator",
|
|
709
|
+
severity: "block",
|
|
710
|
+
negatable: true,
|
|
711
|
+
re: new RegExp(
|
|
712
|
+
`\\b(?:overrides?|supersedes?|outranks?|takes?\\s+precedence\\s+over|has\\s+priority\\s+over)\\b${gap(3)}\\b(?:the\\s+|your\\s+)?(?:operator|system|developer)(?:'s|s')?\\s+${DIRECTIVE}\\b|\\bthese?\\s+${DIRECTIVE}\\s+(?:take|takes|have|has)\\s+(?:precedence|priority)\\b`,
|
|
713
|
+
"i"
|
|
714
|
+
)
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
id: "leak_system_prompt",
|
|
718
|
+
severity: "block",
|
|
719
|
+
negatable: true,
|
|
720
|
+
// Verbatim-dump verbs only. "The context inspector can show the system
|
|
721
|
+
// prompt" is a legitimate memory entry and must stay clean.
|
|
722
|
+
re: new RegExp(
|
|
723
|
+
`\\b(?:reveal|disclose|leak|exfiltrate)\\b${gap(3)}\\b(?:system\\s+(?:prompt|message|instructions))\\b|\\b(?:output|print|repeat|echo|dump|reproduce|recite)\\b${gap(3)}\\b(?:system\\s+(?:prompt|message|instructions)|initial\\s+instructions)\\b`,
|
|
724
|
+
"i"
|
|
725
|
+
)
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
id: "role_hijack",
|
|
729
|
+
severity: "block",
|
|
730
|
+
negatable: true,
|
|
731
|
+
re: new RegExp(
|
|
732
|
+
`\\byou\\s+are\\s+now\\s+(?:a|an|the)\\b|\\b(?:from\\s+now\\s+on|henceforth|going\\s+forward)\\b${gap(2)}\\byou\\s+are\\s+(?:a|an|the|no\\s+longer)\\b|\\bpretend\\s+(?:that\\s+)?(?:you\\s+are|to\\s+be)\\b|\\byour\\s+(?:real|true|actual)\\s+(?:name|identity|purpose)\\s+is\\b`,
|
|
733
|
+
"i"
|
|
734
|
+
)
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
id: "unrestricted_mode",
|
|
738
|
+
severity: "block",
|
|
739
|
+
negatable: true,
|
|
740
|
+
re: new RegExp(
|
|
741
|
+
`\\bact\\s+as\\s+(?:if|though)\\b${gap(4)}\\b(?:no|without)\\b${gap(2)}\\b(?:restrictions?|limits?|rules?|guidelines?|filters?)\\b|\\b(?:respond|reply|answer|behave|operate)\\s+without\\s+(?:any\\s+)?(?:restrictions?|limitations?|filters?|safety|guardrails)\\b|\\b(?:developer|god|jailbreak|unrestricted|DAN)\\s+mode\\s+(?:is\\s+)?(?:on|enabled|activated)\\b`,
|
|
742
|
+
"i"
|
|
743
|
+
)
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
id: "spoofed_authority",
|
|
747
|
+
severity: "block",
|
|
748
|
+
negatable: true,
|
|
749
|
+
re: new RegExp(
|
|
750
|
+
`\\b(?:this\\s+(?:is|message\\s+is)|the\\s+following\\s+is)\\b${gap(3)}\\b(?:from|by)\\s+(?:the\\s+|your\\s+)?(?:system|developer|operator|administrator|model\\s+provider)\\b|\\byou\\s+have\\s+been\\s+(?:updated|upgraded|patched|reconfigured)\\s+to\\b`,
|
|
751
|
+
"i"
|
|
752
|
+
)
|
|
753
|
+
},
|
|
754
|
+
// ── Concealment / covert channel ──────────────────────────────────────
|
|
755
|
+
// A reflexive continuation is REQUIRED: an attacker conceals the attack
|
|
756
|
+
// ("about this", "that you ..."), while legitimate rules concern the user's
|
|
757
|
+
// work ("never tell the user a task is done while a test is red").
|
|
758
|
+
{
|
|
759
|
+
id: "conceal_from_user",
|
|
760
|
+
severity: "block",
|
|
761
|
+
negatable: true,
|
|
762
|
+
re: new RegExp(
|
|
763
|
+
`\\b(?:do\\s+not|don'?t|never|avoid|refrain\\s+from)\\b${gap(3)}\\b(?:tell|telling|inform|informing|notify|notifying|alert|alerting)\\b${gap(2)}\\b(?:the\\s+)?${PRINCIPAL}\\b\\s+(?:(?:about|regarding|of)\\s+(?:this|that|it|any|the\\b)|that\\s+you\\b|what\\s+you\\b|anything\\b)|\\b(?:do\\s+not|don'?t|never|avoid)\\b${gap(3)}\\bmention\\b${gap(4)}\\bto\\s+(?:the\\s+)?${PRINCIPAL}\\b|\\b(?:hide|conceal|withhold|suppress|obscure)\\b${within(60)}\\bfrom\\s+(?:the\\s+)?${PRINCIPAL}\\b|\\bwithout\\s+(?:telling|informing|notifying|alerting)\\s+(?:the\\s+)?${PRINCIPAL}\\b|\\bkeep\\s+(?:this|it|that)\\s+(?:a\\s+)?secret\\b|\\b(?:silently|covertly|secretly)\\s+(?:send|post|upload|forward|copy|exfiltrate)\\b`,
|
|
764
|
+
"i"
|
|
765
|
+
)
|
|
766
|
+
},
|
|
767
|
+
// ── Hidden markup ─────────────────────────────────────────────────────
|
|
768
|
+
{
|
|
769
|
+
id: "html_comment_injection",
|
|
770
|
+
severity: "block",
|
|
771
|
+
re: /<!--[\s\S]{0,300}?(?:ignore|override|system\s+prompt|developer\s+mode|do\s+not\s+tell|jailbreak)[\s\S]{0,300}?-->/i
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
id: "hidden_markup",
|
|
775
|
+
severity: "block",
|
|
776
|
+
re: /<[^>\n]{0,200}style\s*=\s*["'][^"'\n]{0,200}(?:display\s*:\s*none|font-size\s*:\s*0|visibility\s*:\s*hidden)/i
|
|
777
|
+
},
|
|
778
|
+
// ── Exfiltration ──────────────────────────────────────────────────────
|
|
779
|
+
// Send verb + sensitive object + outbound sink, all in ONE sentence and in
|
|
780
|
+
// order. Any one alone is ordinary text here: this product legitimately
|
|
781
|
+
// documents a cloud companion at an https:// URL that receives conversation
|
|
782
|
+
// traffic, so only the full three-part shape is a hit.
|
|
783
|
+
{
|
|
784
|
+
id: "exfil_to_endpoint",
|
|
785
|
+
severity: "block",
|
|
786
|
+
negatable: true,
|
|
787
|
+
re: new RegExp(
|
|
788
|
+
`\\b(?:send|sends|post|posts|upload|uploads|transmit|transmits|forward|forwards|leak|leaks|exfiltrate|mirror|beacon|copy|copies)\\b` + within(60) + `\\b(?:conversation|chat\\s+history|message\\s+history|memory\\s+file|system\\s+prompt|credentials?|secrets?|api\\s*keys?|access\\s*keys?|auth\\s+tokens?|passwords?|env(?:ironment)?\\s+(?:vars?|variables?)|\\.env|ssh\\s+keys?|private\\s+key)\\b` + within(80) + `\\b(?:to|at|into|toward)\\b${within(40)}(?:https?:\\/\\/|\\bwebhook\\b|\\bcollector\\b|\\bendpoint\\b)`,
|
|
789
|
+
"i"
|
|
790
|
+
)
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
id: "exfil_shell_secret",
|
|
794
|
+
severity: "block",
|
|
795
|
+
re: /\b(?:curl|wget|httpie|nc|ncat|Invoke-WebRequest|iwr)\b[^\n]{0,140}\$\{?\w*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL)/i
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
id: "read_secret_files",
|
|
799
|
+
severity: "block",
|
|
800
|
+
negatable: true,
|
|
801
|
+
re: /\b(?:cat|strings|base64|xxd)\b[^\n]{0,80}(?:\.env\b|\/credentials\b|\.netrc\b|\.pgpass\b|\.npmrc\b|id_rsa\b|id_ed25519\b)/i
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
id: "ssh_backdoor_write",
|
|
805
|
+
severity: "block",
|
|
806
|
+
negatable: true,
|
|
807
|
+
re: new RegExp(
|
|
808
|
+
`\\b(?:append|add|write|echo|install|inject|copy|register)\\b${within(80)}authorized_keys|authorized_keys${within(40)}>>`,
|
|
809
|
+
"i"
|
|
810
|
+
)
|
|
811
|
+
},
|
|
812
|
+
// ── Credential-looking strings ────────────────────────────────────────
|
|
813
|
+
// Memory is injected every turn AND rides the data-repo sync plane, so a real
|
|
814
|
+
// secret parked here is both prompt bloat and a leak. Shape-anchored, so
|
|
815
|
+
// "api_key: <from keychain>" and "set the token from the credential helper"
|
|
816
|
+
// stay clean.
|
|
817
|
+
{
|
|
818
|
+
id: "hardcoded_secret",
|
|
819
|
+
severity: "block",
|
|
820
|
+
re: new RegExp(
|
|
821
|
+
`(?:api[_-]?key|secret|token|password|passwd|access[_-]?key|private[_-]?key|bearer)\\s*[:=]\\s*["'\`]?[A-Za-z0-9+/=_-]{20,}|\\b(?:sk|rk)-[A-Za-z0-9_-]{24,}|\\bgh[pousr]_[A-Za-z0-9]{20,}|\\bgithub_pat_[A-Za-z0-9_]{20,}|\\bAKIA[0-9A-Z]{16}\\b|\\bxox[baprs]-[A-Za-z0-9-]{12,}|-----BEGIN\\s+(?:[A-Z]+\\s+)?PRIVATE KEY-----`,
|
|
822
|
+
"i"
|
|
823
|
+
)
|
|
824
|
+
},
|
|
825
|
+
// ── Flag-only: observable, never blocking ─────────────────────────────
|
|
826
|
+
// These are the patterns whose false-positive risk is real (security notes,
|
|
827
|
+
// ops runbooks, legitimate config guidance), so they log and nothing else.
|
|
828
|
+
{
|
|
829
|
+
id: "c2_vocabulary",
|
|
830
|
+
severity: "flag",
|
|
831
|
+
re: /\bcommand\s+and\s+control\b|\bc2\s+(?:server|channel|beacon|infrastructure)\b|\b(?:cobalt\s*strike|metasploit|sliver\s+implant|mythic\s+agent)\b/i
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
id: "conversation_dump",
|
|
835
|
+
severity: "flag",
|
|
836
|
+
re: new RegExp(
|
|
837
|
+
`\\b(?:dump|exfiltrate|upload|share)\\b${gap(3)}\\b(?:full|entire|whole|complete)\\s+(?:conversation|chat\\s+history|context)\\b`,
|
|
838
|
+
"i"
|
|
839
|
+
)
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
id: "turn_delimiter_spoof",
|
|
843
|
+
severity: "flag",
|
|
844
|
+
re: /(?:^|\n)\s*(?:Human|Assistant)\s*:\s|<\/?(?:system|human|assistant)>/i
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
id: "ssh_path_mention",
|
|
848
|
+
severity: "flag",
|
|
849
|
+
re: /~\/\.ssh\b|\$HOME\/\.ssh\b|authorized_keys/i
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
id: "agent_config_write",
|
|
853
|
+
severity: "flag",
|
|
854
|
+
re: /\b(?:append|write|modify|edit|patch|overwrite)\b[^\n]{0,80}(?:CLAUDE\.md|AGENTS\.md|\.cursorrules|\.clinerules|settings\.local\.json)/i
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
id: "env_unset_agent",
|
|
858
|
+
severity: "flag",
|
|
859
|
+
re: /\bunset\s+\w*(?:CLAUDE|ANTHROPIC|OPENAI|WALNUT)\w*/i
|
|
860
|
+
}
|
|
861
|
+
];
|
|
862
|
+
var THREAT_PATTERNS = [...EN_THREAT_PATTERNS, ...CJK_THREAT_PATTERNS];
|
|
863
|
+
var NEGATION_RE = new RegExp(
|
|
864
|
+
`\\b(?:never|not|avoid|refuse|refrain|prohibited|forbidden)\\b|n['\u2019]t\\b|(?:${CJK_NEGATION_SOURCE})`,
|
|
865
|
+
"i"
|
|
866
|
+
);
|
|
867
|
+
var CLAUSE_BOUNDARY_RE = new RegExp(`[.!?;:\\n${CJK_CLAUSE_BOUNDARY_CHARS}]`, "g");
|
|
868
|
+
|
|
869
|
+
// src/core/daily-log.ts
|
|
870
|
+
import { getTokenizer } from "@anthropic-ai/tokenizer";
|
|
871
|
+
|
|
872
|
+
// src/utils/file-ops.ts
|
|
873
|
+
import crypto from "crypto";
|
|
874
|
+
import fsp3 from "fs/promises";
|
|
875
|
+
import path5 from "path";
|
|
876
|
+
|
|
877
|
+
// src/utils/file-lock.ts
|
|
878
|
+
import fs4 from "fs";
|
|
879
|
+
import fsp2 from "fs/promises";
|
|
880
|
+
import path4 from "path";
|
|
881
|
+
|
|
882
|
+
// src/utils/file-ops.ts
|
|
883
|
+
var MAX_READ_SIZE = 10 * 1024 * 1024;
|
|
884
|
+
|
|
885
|
+
// src/core/daily-log.ts
|
|
886
|
+
import sizeOf from "image-size";
|
|
887
|
+
function formatDateKey(date) {
|
|
888
|
+
const d = date ?? /* @__PURE__ */ new Date();
|
|
889
|
+
const year = d.getFullYear();
|
|
890
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
891
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
892
|
+
return `${year}-${month}-${day}`;
|
|
893
|
+
}
|
|
894
|
+
function appendDailyLog(content, source, projectPath, agentId) {
|
|
895
|
+
const dir = agentDailyDir(agentId);
|
|
896
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
897
|
+
const dateKey = formatDateKey();
|
|
898
|
+
const filePath = path6.join(dir, `${dateKey}.md`);
|
|
899
|
+
if (!fs5.existsSync(filePath)) {
|
|
900
|
+
fs5.writeFileSync(filePath, `---
|
|
901
|
+
name: '${dateKey}'
|
|
902
|
+
description: ''
|
|
903
|
+
---
|
|
904
|
+
|
|
905
|
+
`, "utf-8");
|
|
906
|
+
}
|
|
907
|
+
const now = /* @__PURE__ */ new Date();
|
|
908
|
+
const hours = String(now.getHours()).padStart(2, "0");
|
|
909
|
+
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
910
|
+
const time = `${hours}:${minutes}`;
|
|
911
|
+
const sourceLabel = source ?? "unknown";
|
|
912
|
+
const projectTag = projectPath ? ` [${projectPath}]` : "";
|
|
913
|
+
const entry = `## ${time} \u2014 ${sourceLabel}${projectTag}
|
|
914
|
+
${content}
|
|
915
|
+
|
|
916
|
+
`;
|
|
917
|
+
fs5.appendFileSync(filePath, entry, "utf-8");
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/hooks/on-compact.ts
|
|
921
|
+
import fs6 from "fs";
|
|
922
|
+
import path7 from "path";
|
|
923
|
+
var SESSION_DB_PATH = path7.join(WALNUT_HOME, "sessions.sqlite");
|
|
924
|
+
function main() {
|
|
925
|
+
try {
|
|
926
|
+
const sessionDir = findClaudeSessionDir();
|
|
927
|
+
if (!sessionDir) return;
|
|
928
|
+
const summary = extractSessionSummary(sessionDir);
|
|
929
|
+
summary.status = "in_progress";
|
|
930
|
+
saveSessionSummary(summary);
|
|
931
|
+
try {
|
|
932
|
+
const entry = formatDailyLogEntry(summary);
|
|
933
|
+
appendDailyLog(entry, "compact");
|
|
934
|
+
} catch (err) {
|
|
935
|
+
log.hook.warn("on-compact: daily log failed", { error: String(err) });
|
|
936
|
+
}
|
|
937
|
+
updateSessionLastActive(summary.id);
|
|
938
|
+
} catch (err) {
|
|
939
|
+
logHookError("on-compact", err);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function updateSessionLastActive(sessionId) {
|
|
943
|
+
try {
|
|
944
|
+
if (!sessionId) return;
|
|
945
|
+
if (!fs6.existsSync(SESSION_DB_PATH)) return;
|
|
946
|
+
const Database = __require("better-sqlite3");
|
|
947
|
+
const db = new Database(SESSION_DB_PATH);
|
|
948
|
+
try {
|
|
949
|
+
db.pragma("journal_mode = WAL");
|
|
950
|
+
db.pragma("busy_timeout = 5000");
|
|
951
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
952
|
+
const stmt = db.prepare(
|
|
953
|
+
`UPDATE sessions SET last_active_at = ? WHERE claude_session_id = ?`
|
|
954
|
+
);
|
|
955
|
+
db.transaction(() => {
|
|
956
|
+
stmt.run(now, sessionId);
|
|
957
|
+
})();
|
|
958
|
+
} finally {
|
|
959
|
+
try {
|
|
960
|
+
db.close();
|
|
961
|
+
} catch {
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
} catch (err) {
|
|
965
|
+
log.hook.warn("on-compact: session update failed", { error: String(err) });
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
process.stdin.setEncoding("utf-8");
|
|
969
|
+
process.stdin.on("data", () => {
|
|
970
|
+
});
|
|
971
|
+
process.stdin.on("end", () => {
|
|
972
|
+
main();
|
|
973
|
+
process.exit(0);
|
|
974
|
+
});
|
|
975
|
+
process.stdin.resume();
|
|
976
|
+
//# sourceMappingURL=on-compact.js.map
|