volute 0.33.0 → 0.35.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/README.md +7 -6
- package/dist/accept-ZBDVVCEU.js +42 -0
- package/dist/activity-events-ZW4SDL2C.js +15 -0
- package/dist/{ai-service-SBY2WG7O.js → ai-service-LURBEDDB.js} +6 -6
- package/dist/{api-client-YPKOZP2O.js → api-client-3A77HMH7.js} +2 -2
- package/dist/api.d.ts +1 -5195
- package/dist/{archive-INXYFVCW.js → archive-ESU2FUN4.js} +4 -4
- package/dist/{auth-GKCDSO4T.js → auth-WX4TESEI.js} +6 -6
- package/dist/bridge-PXIO6PS2.js +206 -0
- package/dist/chat-QXAJF3FU.js +51 -0
- package/dist/{chunk-NNB4WIG7.js → chunk-2TGZJFAT.js} +3 -3
- package/dist/{chunk-6LXAAQ43.js → chunk-33ODGMFZ.js} +1 -1
- package/dist/{chunk-RPZZSXV3.js → chunk-5N7Y5WAM.js} +21 -2
- package/dist/chunk-5T5YMX6S.js +23 -0
- package/dist/{chunk-7J3HEVR7.js → chunk-5XJYUFZH.js} +28 -16
- package/dist/chunk-7KJOFUNN.js +22 -0
- package/dist/{chunk-2NGTS5UU.js → chunk-A2ZLHBHG.js} +2 -2
- package/dist/{chunk-KIEPMIM5.js → chunk-AN2W47GW.js} +2 -2
- package/dist/{chunk-G53F3JA4.js → chunk-AOB6GVRM.js} +1 -1
- package/dist/{chunk-LRCG2JLP.js → chunk-BDYXIWA5.js} +9 -5
- package/dist/{chunk-YUIHSKR6.js → chunk-BKF4WQCY.js} +2 -2
- package/dist/{chunk-N432I7QH.js → chunk-BMZQYACC.js} +2 -2
- package/dist/{chunk-NAOW2CLO.js → chunk-BTY4WNFE.js} +1 -1
- package/dist/{chunk-ALEF47VT.js → chunk-BV65KRHM.js} +2 -2
- package/dist/{chunk-KVK2DLWI.js → chunk-CORXD635.js} +4 -4
- package/dist/{chunk-PVY5W6QN.js → chunk-F7ZNLYKZ.js} +2 -2
- package/dist/{chunk-QTUVYI7W.js → chunk-FT5KETXZ.js} +3 -3
- package/dist/{chunk-C7I35G4R.js → chunk-IJHIXLVN.js} +44 -8
- package/dist/{chunk-JUKK7FPS.js → chunk-J6CJQDWI.js} +37 -28
- package/dist/{chunk-4RQBJWQX.js → chunk-LOPXTW6H.js} +1 -1
- package/dist/{chunk-RSX4OPZY.js → chunk-MDJGMOSD.js} +8 -137
- package/dist/{chunk-LOEJ4HPQ.js → chunk-N446KRP7.js} +3 -3
- package/dist/{chunk-I5KY25PQ.js → chunk-N5LMGYXX.js} +2 -2
- package/dist/{chunk-G6BSYHPK.js → chunk-NJK5SDGR.js} +1 -1
- package/dist/{chunk-D424ZQGI.js → chunk-O7IGP7ZW.js} +11 -3
- package/dist/{chunk-M7UL5S3Q.js → chunk-OTC67N2Z.js} +2 -2
- package/dist/{chunk-GY5HBI7A.js → chunk-PWQ2ITYG.js} +4 -4
- package/dist/{chunk-KTLFDYPT.js → chunk-QCH6K235.js} +1 -1
- package/dist/chunk-QHG4OMZL.js +145 -0
- package/dist/{chunk-SKLSMHXO.js → chunk-QWTR6AWZ.js} +3 -3
- package/dist/chunk-TXSA4Q3V.js +116 -0
- package/dist/{chunk-VH33ZWMW.js → chunk-VHJRZM2S.js} +2 -2
- package/dist/{chunk-SSI47XP2.js → chunk-VHWGEJ4V.js} +1 -1
- package/dist/chunk-VY3RB2V7.js +164 -0
- package/dist/chunk-WJPROOU5.js +8314 -0
- package/dist/{chunk-RVGLDGMI.js → chunk-WZRZFFCL.js} +25 -27
- package/dist/{chunk-JYVGHWEJ.js → chunk-XRQSAMX2.js} +4 -4
- package/dist/{chunk-OYAKCAVY.js → chunk-ZSR72JB3.js} +1 -1
- package/dist/{chunk-UKVWJRKN.js → chunk-ZX7EAV5J.js} +17 -7
- package/dist/cli.js +90 -29
- package/dist/clock-HSEKS5AR.js +289 -0
- package/dist/{cloud-sync-4NWLMFVH.js → cloud-sync-6JL4C24T.js} +22 -23
- package/dist/config-UTS7QULS.js +76 -0
- package/dist/connectors/discord-bridge.js +4 -4
- package/dist/connectors/slack-bridge.js +4 -4
- package/dist/connectors/telegram-bridge.js +4 -4
- package/dist/{conversations-AWI5SZW2.js → conversations-2PW57WO2.js} +6 -6
- package/dist/create-5BPOOJAN.js +75 -0
- package/dist/create-UVCK2CS6.js +50 -0
- package/dist/daemon-client-RVIKXGFQ.js +12 -0
- package/dist/daemon-restart-HSZ3BCX5.js +65 -0
- package/dist/daemon.js +1349 -1211
- package/dist/db-BDMH4SZ2.js +20 -0
- package/dist/db-BVBJ57TU.js +9 -0
- package/dist/delete-L5PAVDGQ.js +42 -0
- package/dist/delivery-manager-H5ZVBMCQ.js +31 -0
- package/dist/{delivery-router-FL45JL7N.js → delivery-router-HEJSJAHQ.js} +5 -5
- package/dist/down-74VXM45A.js +17 -0
- package/dist/env-E4XHO2BI.js +223 -0
- package/dist/exec-PY7THYH4.js +17 -0
- package/dist/export-OAS6QVBN.js +113 -0
- package/dist/extension-D74CNM7G.js +89 -0
- package/dist/extensions-XDDFY72A.js +49 -0
- package/dist/files-CWTK6V3H.js +53 -0
- package/dist/import-5A3T7QV4.js +143 -0
- package/dist/{isolation-LLAYQYDY.js → isolation-TK5RX2WM.js} +4 -4
- package/dist/join-DF5XSJAC.js +67 -0
- package/dist/lib-DYEZMGW7.js +6588 -0
- package/dist/list-PDMQM7ZV.js +53 -0
- package/dist/login-7TE6CIZF.js +60 -0
- package/dist/login-GOTAYLXP.js +51 -0
- package/dist/logout-6KIA74EV.js +29 -0
- package/dist/logout-T4XS6LRU.js +50 -0
- package/dist/message-delivery-GRC4W6P7.js +41 -0
- package/dist/mind-5IEYKV7I.js +97 -0
- package/dist/mind-activity-tracker-QBLIV7ZJ.js +18 -0
- package/dist/mind-history-IE2QH7U5.js +275 -0
- package/dist/mind-list-GEWHWAL4.js +38 -0
- package/dist/mind-manager-HFLB5653.js +31 -0
- package/dist/mind-profile-DCBDVF5B.js +53 -0
- package/dist/mind-service-X2CAA6W6.js +37 -0
- package/dist/mind-sleep-ITCF6OQA.js +47 -0
- package/dist/mind-status-X4SX3YUG.js +65 -0
- package/dist/mind-wake-KXMKMGWX.js +42 -0
- package/dist/{package-U3VFO273.js → package-D2FSVFAX.js} +11 -8
- package/dist/read-67VRP2DO.js +91 -0
- package/dist/{read-stdin-HQJ7774D.js → read-stdin-3X5VYKNS.js} +2 -2
- package/dist/register-SB7NXCOE.js +51 -0
- package/dist/{registry-PJ4S5PHQ.js → registry-GBSNW3HG.js} +3 -3
- package/dist/reject-MUR2KWJ4.js +40 -0
- package/dist/restart-5EGG4JXU.js +42 -0
- package/dist/{sandbox-GJOK4QLQ.js → sandbox-R37VIU36.js} +6 -6
- package/dist/scheduler-Y7O4CJXL.js +31 -0
- package/dist/{schema-PA3M5ZKH.js → schema-XVZ2CLKW.js} +4 -2
- package/dist/{seed-QDYVLG74.js → seed-EQORWX77.js} +3 -3
- package/dist/seed-check-KJNTL72M.js +35 -0
- package/dist/seed-cmd-ZM2XGVU2.js +30 -0
- package/dist/seed-create-DRWGGHEI.js +113 -0
- package/dist/seed-sprout-JYXGXOP3.js +148 -0
- package/dist/send-JBJJQ7CA.js +409 -0
- package/dist/service-WNPCNHOX.js +121 -0
- package/dist/{setup-XMCBE3LF.js → setup-BJ4YAY26.js} +155 -129
- package/dist/{setup-TISPCO22.js → setup-RHJRFURI.js} +4 -4
- package/dist/skill-TAAKEYBV.js +389 -0
- package/dist/skills/plan-coordinator/SKILL.md +60 -0
- package/dist/skills/volute-mind/SKILL.md +9 -227
- package/dist/skills/volute-mind/references/extensions.md +34 -0
- package/dist/skills/volute-mind/references/integrations.md +48 -0
- package/dist/skills/volute-mind/references/routing.md +86 -0
- package/dist/skills/volute-mind/references/sleep.md +33 -0
- package/dist/skills/volute-mind/references/variants.md +31 -0
- package/dist/{skills-7FV7EJTE.js → skills-EKMCQ46K.js} +12 -8
- package/dist/sleep-manager-7KFK3USC.js +35 -0
- package/dist/spirit-ZFRDXMG7.js +23 -0
- package/dist/split-AWVOYOPZ.js +64 -0
- package/dist/{sprout-WKLZXUIQ.js → sprout-HE4TITMK.js} +3 -3
- package/dist/start-3UXOPXQG.js +39 -0
- package/dist/status-ZK34WYIM.js +125 -0
- package/dist/stop-3XYIBGFM.js +41 -0
- package/dist/system-chat-IDPHYHY4.js +35 -0
- package/dist/systems-O43WGQY6.js +52 -0
- package/dist/{tailscale-XHQBZROW.js → tailscale-ZIZ2HWJ5.js} +5 -5
- package/dist/template-hash-A7FNHTB7.js +9 -0
- package/dist/up-77ICEDEW.js +19 -0
- package/dist/update-ANE5ZM7F.js +225 -0
- package/dist/{update-check-ZD6OOIYQ.js → update-check-UV55CBEP.js} +4 -4
- package/dist/upgrade-ZMDGC7M2.js +74 -0
- package/dist/variant-QWL2WSRI.js +62 -0
- package/dist/{version-notify-NBI2MTJO.js → version-notify-FXSEMXWW.js} +29 -28
- package/dist/{volute-config-HD7WWUQC.js → volute-config-D2XVS2YI.js} +2 -2
- package/dist/web-assets/assets/index-BhxWKvbB.css +1 -0
- package/dist/web-assets/assets/index-CHVKJ9II.js +75 -0
- package/dist/web-assets/ext-theme.css +48 -9
- package/dist/web-assets/index.html +2 -2
- package/dist/web-assets/sw.js +117 -0
- package/drizzle/0005_meta_summaries.sql +15 -0
- package/drizzle/meta/0005_snapshot.json +7 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +10 -7
- package/packages/extensions/pages/dist/ui/assets/index-DKZLNMED.js +2 -0
- package/packages/extensions/pages/dist/ui/index.html +1 -1
- package/packages/extensions/pages/skills/pages/SKILL.md +84 -9
- package/packages/extensions/plan/dist/ui/assets/index-CJj2gZnZ.css +1 -0
- package/packages/extensions/plan/dist/ui/assets/index-FMEJmvQz.js +61 -0
- package/packages/extensions/plan/dist/ui/index.html +14 -0
- package/packages/extensions/plan/skills/plan/SKILL.md +43 -0
- package/packages/extensions/plan/skills/plan/scripts/plan-hook.sh +37 -0
- package/templates/_base/home/VOLUTE.md +12 -19
- package/templates/_base/src/lib/auto-commit.ts +8 -8
- package/templates/_base/src/lib/context-breakdown.ts +450 -0
- package/templates/_base/src/lib/format-prefix.ts +17 -0
- package/templates/_base/src/lib/hook-loader.ts +8 -2
- package/templates/_base/src/lib/router.ts +75 -33
- package/templates/_base/src/lib/routing.ts +4 -1
- package/templates/_base/src/lib/startup.ts +16 -8
- package/templates/_base/src/lib/types.ts +2 -1
- package/templates/_base/src/lib/volute-server.ts +75 -8
- package/templates/claude/.init/CLAUDE.md +4 -10
- package/templates/claude/package.json.tmpl +1 -0
- package/templates/claude/src/agent.ts +108 -33
- package/templates/claude/src/lib/hooks/reply-instructions.ts +27 -7
- package/templates/claude/src/lib/stream-consumer.ts +2 -2
- package/templates/claude/src/server.ts +1 -0
- package/templates/codex/package.json.tmpl +1 -0
- package/templates/codex/src/agent.ts +80 -8
- package/templates/codex/src/server.ts +1 -4
- package/templates/pi/package.json.tmpl +1 -0
- package/templates/pi/src/agent.ts +115 -36
- package/templates/pi/src/lib/event-handler.ts +22 -7
- package/templates/pi/src/lib/reply-instructions-extension.ts +23 -4
- package/templates/pi/src/lib/subagents.ts +20 -17
- package/templates/pi/src/server.ts +2 -5
- package/dist/accept-D5VBM7JW.js +0 -42
- package/dist/activity-events-XJO3P4RR.js +0 -15
- package/dist/bridge-TXWWPPOJ.js +0 -207
- package/dist/chat-U5ZOME3O.js +0 -68
- package/dist/chunk-3Z2DPESO.js +0 -3634
- package/dist/chunk-A2A4KLFE.js +0 -1528
- package/dist/chunk-K3NQKI34.js +0 -10
- package/dist/chunk-NPKSDYA2.js +0 -156
- package/dist/chunk-PB65JZK2.js +0 -85
- package/dist/clock-BVH3V6E3.js +0 -266
- package/dist/config-H2H4UIF7.js +0 -72
- package/dist/create-2FK7Z46Y.js +0 -44
- package/dist/create-YWD2TIP4.js +0 -71
- package/dist/daemon-client-6QXHZ7US.js +0 -12
- package/dist/daemon-restart-GOBUKLX7.js +0 -52
- package/dist/db-F34YLV7D.js +0 -9
- package/dist/db-RA45JBFG.js +0 -16
- package/dist/delete-QTGWEDBI.js +0 -35
- package/dist/delivery-manager-PFAKEJTC.js +0 -32
- package/dist/down-FWWTEKXM.js +0 -15
- package/dist/env-JCOF2222.js +0 -191
- package/dist/export-SUYRLI5Q.js +0 -112
- package/dist/extension-OBTGKQQD.js +0 -175
- package/dist/extensions-KYNTVTMO.js +0 -30
- package/dist/files-65PMW5IK.js +0 -47
- package/dist/history-DKCDI3JO.js +0 -128
- package/dist/import-DDUFE7AY.js +0 -23
- package/dist/join-I5QEE3LG.js +0 -66
- package/dist/list-JQ463EDA.js +0 -41
- package/dist/login-D7ETSU4R.js +0 -47
- package/dist/login-RIJF2F4G.js +0 -47
- package/dist/logout-5MLHZALK.js +0 -40
- package/dist/logout-UZJRGY4Z.js +0 -21
- package/dist/message-delivery-DFF5SJRM.js +0 -42
- package/dist/mind-IOJFLEM5.js +0 -108
- package/dist/mind-activity-tracker-F6O4Q2SL.js +0 -18
- package/dist/mind-list-WUPMQDYQ.js +0 -30
- package/dist/mind-manager-NBJF5D26.js +0 -32
- package/dist/mind-profile-P67FEHOY.js +0 -47
- package/dist/mind-service-2MQ6UK5N.js +0 -38
- package/dist/mind-sleep-WW2IX7JT.js +0 -42
- package/dist/mind-status-L3EFFRPR.js +0 -56
- package/dist/mind-wake-VSSGW465.js +0 -37
- package/dist/read-EBY56C33.js +0 -75
- package/dist/register-HD74C4TT.js +0 -47
- package/dist/reject-UJKFBHRO.js +0 -40
- package/dist/restart-3UCMRUVC.js +0 -33
- package/dist/scheduler-ZZ7XGQG6.js +0 -32
- package/dist/seed-check-S2IX25RL.js +0 -32
- package/dist/seed-cmd-DKOUFEAU.js +0 -36
- package/dist/seed-create-4XBBOLRH.js +0 -112
- package/dist/seed-sprout-GQEIIQRT.js +0 -132
- package/dist/send-QIV2INHB.js +0 -373
- package/dist/skill-PSQGRRJX.js +0 -358
- package/dist/skills/shared-files/SKILL.md +0 -44
- package/dist/skills/shared-files/scripts/merge.ts +0 -72
- package/dist/skills/shared-files/scripts/pull.ts +0 -52
- package/dist/sleep-manager-JTXSN7NV.js +0 -36
- package/dist/spirit-VRONKFMF.js +0 -23
- package/dist/split-STOROBYJ.js +0 -63
- package/dist/start-K2NCUUCG.js +0 -33
- package/dist/status-3JBTFSMI.js +0 -115
- package/dist/stop-H26JZDXF.js +0 -32
- package/dist/system-chat-JAPOJ3KE.js +0 -36
- package/dist/systems-XRI52VCH.js +0 -61
- package/dist/template-hash-A6VVKOXJ.js +0 -9
- package/dist/up-M5AS6SBV.js +0 -18
- package/dist/update-UD543CXX.js +0 -215
- package/dist/upgrade-O4Q7WJM3.js +0 -67
- package/dist/variant-7TGZHOU3.js +0 -41
- package/dist/web-assets/assets/index-CWJrVveV.css +0 -1
- package/dist/web-assets/assets/index-DJt14FRI.js +0 -75
- package/packages/extensions/pages/dist/ui/assets/index-tLTROSk5.js +0 -2
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import type { ContextBreakdown } from "./volute-server.js";
|
|
4
|
+
|
|
5
|
+
// Lazy-loaded tokenizer — first call loads the encoding (~100ms), subsequent calls are fast
|
|
6
|
+
let _countTokens: ((text: string) => number) | null = null;
|
|
7
|
+
|
|
8
|
+
function countTokens(text: string): number {
|
|
9
|
+
if (!_countTokens) {
|
|
10
|
+
try {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
12
|
+
const mod = require("@anthropic-ai/tokenizer");
|
|
13
|
+
_countTokens = mod.countTokens;
|
|
14
|
+
} catch (err) {
|
|
15
|
+
// Tokenizer not installed — fall back to character estimation
|
|
16
|
+
console.warn(
|
|
17
|
+
"context-breakdown: @anthropic-ai/tokenizer not available, using character estimation:",
|
|
18
|
+
err instanceof Error ? err.message : err,
|
|
19
|
+
);
|
|
20
|
+
_countTokens = (t: string) => Math.round(t.length / 3.5);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return _countTokens!(text);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// --- Claude JSONL parser ---
|
|
27
|
+
|
|
28
|
+
type ClaudeContentBlock = {
|
|
29
|
+
type: string;
|
|
30
|
+
text?: string;
|
|
31
|
+
thinking?: string;
|
|
32
|
+
input?: unknown;
|
|
33
|
+
content?: unknown;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type ClaudeMessage = {
|
|
37
|
+
type: string;
|
|
38
|
+
message?: {
|
|
39
|
+
role?: string;
|
|
40
|
+
usage?: {
|
|
41
|
+
input_tokens?: number;
|
|
42
|
+
cache_creation_input_tokens?: number;
|
|
43
|
+
cache_read_input_tokens?: number;
|
|
44
|
+
};
|
|
45
|
+
content?: ClaudeContentBlock[];
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type ParsedContext = {
|
|
50
|
+
contextTokens: number;
|
|
51
|
+
breakdown: ContextBreakdown;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function parseClaudeSessionJSONL(
|
|
55
|
+
filePath: string,
|
|
56
|
+
systemPromptTokens: number,
|
|
57
|
+
claudeMdTokens: number,
|
|
58
|
+
skillDescriptionTokens: number,
|
|
59
|
+
): ParsedContext | null {
|
|
60
|
+
let data: string;
|
|
61
|
+
try {
|
|
62
|
+
data = readFileSync(filePath, "utf-8");
|
|
63
|
+
} catch (err: any) {
|
|
64
|
+
if (err?.code !== "ENOENT") console.warn(`context-breakdown: ${filePath}:`, err?.message);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const lines = data.split("\n").filter((l) => l.trim());
|
|
69
|
+
let lastContextTokens = 0;
|
|
70
|
+
const conv = { userText: 0, assistantText: 0, thinking: 0, toolUse: 0, toolResult: 0 };
|
|
71
|
+
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
let entry: ClaudeMessage;
|
|
74
|
+
try {
|
|
75
|
+
entry = JSON.parse(line);
|
|
76
|
+
} catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (entry.type === "assistant" && entry.message) {
|
|
81
|
+
const usage = entry.message.usage;
|
|
82
|
+
if (usage) {
|
|
83
|
+
const ctx =
|
|
84
|
+
(usage.input_tokens ?? 0) +
|
|
85
|
+
(usage.cache_creation_input_tokens ?? 0) +
|
|
86
|
+
(usage.cache_read_input_tokens ?? 0);
|
|
87
|
+
if (ctx > 0) lastContextTokens = ctx;
|
|
88
|
+
}
|
|
89
|
+
for (const block of entry.message.content ?? []) {
|
|
90
|
+
if (block.type === "thinking" && block.thinking) {
|
|
91
|
+
conv.thinking += countTokens(block.thinking);
|
|
92
|
+
} else if (block.type === "text" && block.text) {
|
|
93
|
+
conv.assistantText += countTokens(block.text);
|
|
94
|
+
} else if (block.type === "tool_use") {
|
|
95
|
+
const input = block.input;
|
|
96
|
+
conv.toolUse += countTokens(
|
|
97
|
+
typeof input === "string" ? input : JSON.stringify(input ?? {}),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} else if (entry.type === "user" && entry.message) {
|
|
102
|
+
for (const block of entry.message.content ?? []) {
|
|
103
|
+
if (block.type === "tool_result") {
|
|
104
|
+
const content = block.content;
|
|
105
|
+
if (typeof content === "string") {
|
|
106
|
+
conv.toolResult += countTokens(content);
|
|
107
|
+
} else if (Array.isArray(content)) {
|
|
108
|
+
for (const c of content) {
|
|
109
|
+
if (c && typeof c === "object" && "text" in c && typeof c.text === "string") {
|
|
110
|
+
conv.toolResult += countTokens(c.text);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} else if (block.type === "text" && block.text) {
|
|
115
|
+
conv.userText += countTokens(block.text);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (lastContextTokens === 0) return null;
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
contextTokens: lastContextTokens,
|
|
125
|
+
breakdown: {
|
|
126
|
+
systemPrompt: systemPromptTokens,
|
|
127
|
+
sdkInstructions: claudeMdTokens,
|
|
128
|
+
skillDescriptions: skillDescriptionTokens,
|
|
129
|
+
conversation: conv,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --- Codex JSONL parser ---
|
|
135
|
+
|
|
136
|
+
type CodexEntry = {
|
|
137
|
+
type: string;
|
|
138
|
+
payload?: {
|
|
139
|
+
type?: string;
|
|
140
|
+
info?: {
|
|
141
|
+
last_token_usage?: { input_tokens?: number; cached_input_tokens?: number };
|
|
142
|
+
total_token_usage?: { input_tokens?: number };
|
|
143
|
+
model_context_window?: number;
|
|
144
|
+
};
|
|
145
|
+
content?: Array<{ type?: string; text?: string }>;
|
|
146
|
+
role?: string;
|
|
147
|
+
// function_call fields
|
|
148
|
+
name?: string;
|
|
149
|
+
arguments?: string;
|
|
150
|
+
// function_call_output fields
|
|
151
|
+
output?: string;
|
|
152
|
+
// reasoning fields
|
|
153
|
+
summary?: Array<{ type?: string; text?: string }> | string;
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export function parseCodexSessionJSONL(
|
|
158
|
+
filePath: string,
|
|
159
|
+
systemPromptTokens: number,
|
|
160
|
+
claudeMdTokens: number,
|
|
161
|
+
skillDescriptionTokens: number,
|
|
162
|
+
): ParsedContext | null {
|
|
163
|
+
let data: string;
|
|
164
|
+
try {
|
|
165
|
+
data = readFileSync(filePath, "utf-8");
|
|
166
|
+
} catch (err: any) {
|
|
167
|
+
if (err?.code !== "ENOENT") console.warn(`context-breakdown: ${filePath}:`, err?.message);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const lines = data.split("\n").filter((l) => l.trim());
|
|
172
|
+
let lastContextTokens = 0;
|
|
173
|
+
const conv = { userText: 0, assistantText: 0, thinking: 0, toolUse: 0, toolResult: 0 };
|
|
174
|
+
|
|
175
|
+
for (const line of lines) {
|
|
176
|
+
let entry: CodexEntry;
|
|
177
|
+
try {
|
|
178
|
+
entry = JSON.parse(line);
|
|
179
|
+
} catch {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const payload = entry.payload;
|
|
184
|
+
if (!payload) continue;
|
|
185
|
+
|
|
186
|
+
if (entry.type === "event_msg" && payload.type === "token_count" && payload.info) {
|
|
187
|
+
const lastUsage = payload.info.last_token_usage;
|
|
188
|
+
if (lastUsage?.input_tokens) {
|
|
189
|
+
lastContextTokens = lastUsage.input_tokens;
|
|
190
|
+
}
|
|
191
|
+
} else if (entry.type === "response_item") {
|
|
192
|
+
if (payload.type === "reasoning") {
|
|
193
|
+
// Reasoning summaries are in the `summary` field (array of {text} objects)
|
|
194
|
+
const summary = payload.summary;
|
|
195
|
+
if (Array.isArray(summary)) {
|
|
196
|
+
for (const s of summary) {
|
|
197
|
+
if (s && typeof s === "object" && s.text) conv.thinking += countTokens(s.text);
|
|
198
|
+
}
|
|
199
|
+
} else if (typeof summary === "string" && summary) {
|
|
200
|
+
conv.thinking += countTokens(summary);
|
|
201
|
+
}
|
|
202
|
+
} else if (payload.type === "message") {
|
|
203
|
+
const text = payload.content?.map((c) => c.text ?? "").join("") ?? "";
|
|
204
|
+
if (text) {
|
|
205
|
+
if (payload.role === "assistant") {
|
|
206
|
+
conv.assistantText += countTokens(text);
|
|
207
|
+
} else {
|
|
208
|
+
conv.userText += countTokens(text);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} else if (payload.type === "function_call") {
|
|
212
|
+
const args = payload.arguments ?? "";
|
|
213
|
+
if (args) conv.toolUse += countTokens(args);
|
|
214
|
+
} else if (payload.type === "function_call_output") {
|
|
215
|
+
const output = payload.output ?? "";
|
|
216
|
+
if (output) conv.toolResult += countTokens(output);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (lastContextTokens === 0) return null;
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
contextTokens: lastContextTokens,
|
|
225
|
+
breakdown: {
|
|
226
|
+
systemPrompt: systemPromptTokens,
|
|
227
|
+
sdkInstructions: claudeMdTokens,
|
|
228
|
+
skillDescriptions: skillDescriptionTokens,
|
|
229
|
+
conversation: conv,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// --- Pi JSONL parser ---
|
|
235
|
+
|
|
236
|
+
type PiEntry = {
|
|
237
|
+
type: string;
|
|
238
|
+
// message entries
|
|
239
|
+
message?: {
|
|
240
|
+
role?: string;
|
|
241
|
+
usage?: { input?: number; cacheRead?: number; cacheWrite?: number };
|
|
242
|
+
content?: Array<{ type?: string; text?: string; thinking?: string; arguments?: unknown }>;
|
|
243
|
+
};
|
|
244
|
+
// custom_message entries (hook-injected context: reply-instructions, startup-context, etc.)
|
|
245
|
+
content?: string;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export function parsePiSessionJSONL(
|
|
249
|
+
filePath: string,
|
|
250
|
+
systemPromptTokens: number,
|
|
251
|
+
claudeMdTokens: number,
|
|
252
|
+
skillDescriptionTokens: number,
|
|
253
|
+
): ParsedContext | null {
|
|
254
|
+
let data: string;
|
|
255
|
+
try {
|
|
256
|
+
data = readFileSync(filePath, "utf-8");
|
|
257
|
+
} catch (err: any) {
|
|
258
|
+
if (err?.code !== "ENOENT") console.warn(`context-breakdown: ${filePath}:`, err?.message);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const lines = data.split("\n").filter((l) => l.trim());
|
|
263
|
+
let lastContextTokens = 0;
|
|
264
|
+
const conv = { userText: 0, assistantText: 0, thinking: 0, toolUse: 0, toolResult: 0 };
|
|
265
|
+
|
|
266
|
+
for (const line of lines) {
|
|
267
|
+
let entry: PiEntry;
|
|
268
|
+
try {
|
|
269
|
+
entry = JSON.parse(line);
|
|
270
|
+
} catch {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// custom_message entries are hook-injected context (reply-instructions, startup-context)
|
|
275
|
+
if (entry.type === "custom_message" && entry.content) {
|
|
276
|
+
conv.userText += countTokens(entry.content);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (entry.type !== "message" || !entry.message) continue;
|
|
281
|
+
const msg = entry.message;
|
|
282
|
+
|
|
283
|
+
if (msg.role === "assistant") {
|
|
284
|
+
const usage = msg.usage;
|
|
285
|
+
if (usage) {
|
|
286
|
+
const ctx = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
287
|
+
if (ctx > 0) lastContextTokens = ctx;
|
|
288
|
+
}
|
|
289
|
+
for (const block of msg.content ?? []) {
|
|
290
|
+
if (block.type === "thinking" && block.thinking) {
|
|
291
|
+
conv.thinking += countTokens(block.thinking);
|
|
292
|
+
} else if (block.type === "text" && block.text) {
|
|
293
|
+
conv.assistantText += countTokens(block.text);
|
|
294
|
+
} else if (block.type === "toolCall") {
|
|
295
|
+
const args = block.arguments;
|
|
296
|
+
conv.toolUse += countTokens(typeof args === "string" ? args : JSON.stringify(args ?? {}));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} else if (msg.role === "toolResult") {
|
|
300
|
+
for (const block of msg.content ?? []) {
|
|
301
|
+
if (block.text) conv.toolResult += countTokens(block.text);
|
|
302
|
+
}
|
|
303
|
+
} else if (msg.role === "user") {
|
|
304
|
+
for (const block of msg.content ?? []) {
|
|
305
|
+
if (block.type === "text" && block.text) {
|
|
306
|
+
conv.userText += countTokens(block.text);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (lastContextTokens === 0) return null;
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
contextTokens: lastContextTokens,
|
|
316
|
+
breakdown: {
|
|
317
|
+
systemPrompt: systemPromptTokens,
|
|
318
|
+
sdkInstructions: claudeMdTokens,
|
|
319
|
+
skillDescriptions: skillDescriptionTokens,
|
|
320
|
+
conversation: conv,
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// --- Measure known SDK overhead ---
|
|
326
|
+
|
|
327
|
+
/** Count tokens in the actual composed system prompt string. */
|
|
328
|
+
export function countSystemPromptTokens(systemPrompt: string): number {
|
|
329
|
+
return countTokens(systemPrompt);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Count tokens in the SDK instruction file (CLAUDE.md, MINDS.md, or AGENTS.md). */
|
|
333
|
+
export function countSdkInstructionTokens(cwd: string): number {
|
|
334
|
+
for (const name of ["CLAUDE.md", "MINDS.md", "AGENTS.md"]) {
|
|
335
|
+
try {
|
|
336
|
+
const content = readFileSync(resolve(cwd, name), "utf-8");
|
|
337
|
+
return countTokens(content);
|
|
338
|
+
} catch (err: any) {
|
|
339
|
+
if (err?.code !== "ENOENT")
|
|
340
|
+
console.warn(`context-breakdown: ${resolve(cwd, name)}:`, err?.message);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Count tokens in skill description frontmatter (always in context). */
|
|
347
|
+
export function countSkillDescriptionTokens(skillsDirs: string[]): number {
|
|
348
|
+
let total = 0;
|
|
349
|
+
for (const dir of skillsDirs) {
|
|
350
|
+
try {
|
|
351
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
352
|
+
if (!entry.isDirectory()) continue;
|
|
353
|
+
try {
|
|
354
|
+
const content = readFileSync(resolve(dir, entry.name, "SKILL.md"), "utf-8");
|
|
355
|
+
// Extract just the frontmatter description — that's what's always in context
|
|
356
|
+
const match = content.match(/^description:\s*(.+?)$/m);
|
|
357
|
+
if (match) total += countTokens(match[1]);
|
|
358
|
+
} catch (err: any) {
|
|
359
|
+
if (err?.code !== "ENOENT")
|
|
360
|
+
console.warn(`context-breakdown: SKILL.md in ${entry.name}:`, err?.message);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} catch (err: any) {
|
|
364
|
+
if (err?.code !== "ENOENT")
|
|
365
|
+
console.warn(`context-breakdown: skills dir ${dir}:`, err?.message);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return total;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// --- Session file finders ---
|
|
372
|
+
|
|
373
|
+
/** Find the Claude SDK JSONL file for a session ID. */
|
|
374
|
+
export function findClaudeSessionFile(cwd: string, sessionId: string): string | null {
|
|
375
|
+
// The SDK stores JSONL in ~/.claude/projects/ (global), not inside the mind's home dir.
|
|
376
|
+
// Check both the global location and the local one (in case of sandboxed minds).
|
|
377
|
+
const homeDir = process.env.HOME ?? "";
|
|
378
|
+
const dirs = [resolve(homeDir, ".claude/projects"), resolve(cwd, ".claude/projects")];
|
|
379
|
+
for (const projectsDir of dirs) {
|
|
380
|
+
try {
|
|
381
|
+
for (const dir of readdirSync(projectsDir)) {
|
|
382
|
+
const candidate = resolve(projectsDir, dir, `${sessionId}.jsonl`);
|
|
383
|
+
try {
|
|
384
|
+
statSync(candidate);
|
|
385
|
+
return candidate;
|
|
386
|
+
} catch {
|
|
387
|
+
// Not in this project dir
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
} catch (err: any) {
|
|
391
|
+
if (err?.code !== "ENOENT") console.warn(`context-breakdown: ${projectsDir}:`, err?.message);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Find the Codex JSONL file for a thread ID. */
|
|
398
|
+
export function findCodexSessionFile(threadId: string): string | null {
|
|
399
|
+
const codexDir = resolve(process.env.HOME ?? "", ".codex/sessions");
|
|
400
|
+
try {
|
|
401
|
+
for (const year of readdirSync(codexDir)) {
|
|
402
|
+
const yearDir = resolve(codexDir, year);
|
|
403
|
+
try {
|
|
404
|
+
if (!statSync(yearDir).isDirectory()) continue;
|
|
405
|
+
} catch {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
for (const month of readdirSync(yearDir)) {
|
|
409
|
+
const monthDir = resolve(yearDir, month);
|
|
410
|
+
try {
|
|
411
|
+
if (!statSync(monthDir).isDirectory()) continue;
|
|
412
|
+
} catch {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
for (const day of readdirSync(monthDir)) {
|
|
416
|
+
const dayDir = resolve(monthDir, day);
|
|
417
|
+
try {
|
|
418
|
+
if (!statSync(dayDir).isDirectory()) continue;
|
|
419
|
+
} catch {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
for (const file of readdirSync(dayDir)) {
|
|
423
|
+
if (file.endsWith(".jsonl") && file.includes(threadId)) {
|
|
424
|
+
return resolve(dayDir, file);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
} catch (err: any) {
|
|
431
|
+
if (err?.code !== "ENOENT") console.warn("context-breakdown: codex sessions:", err?.message);
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Find the latest Pi JSONL file for a session name. */
|
|
437
|
+
export function findPiSessionFile(sessionsDir: string, sessionName: string): string | null {
|
|
438
|
+
const dir = resolve(sessionsDir, sessionName);
|
|
439
|
+
try {
|
|
440
|
+
const files = readdirSync(dir)
|
|
441
|
+
.filter((f) => f.endsWith(".jsonl"))
|
|
442
|
+
.sort();
|
|
443
|
+
if (files.length === 0) return null;
|
|
444
|
+
return resolve(dir, files[files.length - 1]);
|
|
445
|
+
} catch (err: any) {
|
|
446
|
+
if (err?.code !== "ENOENT")
|
|
447
|
+
console.warn(`context-breakdown: pi sessions ${dir}:`, err?.message);
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import type { ChannelMeta, ParticipantProfile } from "./types.js";
|
|
2
2
|
|
|
3
|
+
/** Compact timestamp: YYYY-MM-DD HH:MM */
|
|
4
|
+
export function compactTimestamp(date: Date = new Date()): string {
|
|
5
|
+
const y = date.getFullYear();
|
|
6
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
7
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
8
|
+
const h = String(date.getHours()).padStart(2, "0");
|
|
9
|
+
const min = String(date.getMinutes()).padStart(2, "0");
|
|
10
|
+
return `${y}-${m}-${d} ${h}:${min}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Compact time-only: HH:MM */
|
|
14
|
+
export function compactTime(date: Date = new Date()): string {
|
|
15
|
+
const h = String(date.getHours()).padStart(2, "0");
|
|
16
|
+
const min = String(date.getMinutes()).padStart(2, "0");
|
|
17
|
+
return `${h}:${min}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
3
20
|
function derivePlatform(channel: string): string {
|
|
4
21
|
if (!channel.includes(":")) return "Volute";
|
|
5
22
|
const name = channel.split(":")[0];
|
|
@@ -55,13 +55,14 @@ export function executeHook(
|
|
|
55
55
|
scriptPath: string,
|
|
56
56
|
input: object,
|
|
57
57
|
timeout = DEFAULT_TIMEOUT,
|
|
58
|
+
cwd?: string,
|
|
58
59
|
): Promise<HookResult> {
|
|
59
60
|
return new Promise((resolve) => {
|
|
60
61
|
const { cmd, args } = getRunner(scriptPath);
|
|
61
62
|
const child = spawn(cmd, args, {
|
|
62
63
|
timeout,
|
|
63
64
|
stdio: ["pipe", "pipe", "pipe"],
|
|
64
|
-
cwd: process.cwd(),
|
|
65
|
+
cwd: cwd ?? process.cwd(),
|
|
65
66
|
env: process.env,
|
|
66
67
|
});
|
|
67
68
|
|
|
@@ -130,12 +131,17 @@ export async function runHooks(
|
|
|
130
131
|
const scripts = discoverHooks(hooksDir, event);
|
|
131
132
|
if (scripts.length === 0) return { metadata: {}, blocked: false };
|
|
132
133
|
|
|
134
|
+
// Hook shim scripts use paths relative to the mind's home directory
|
|
135
|
+
// (e.g. .claude/skills/<id>/scripts/<script>). hooksDir is <home>/.local/hooks,
|
|
136
|
+
// so resolving ../.. gives the home directory.
|
|
137
|
+
const homeDir = resolve(hooksDir, "../..");
|
|
138
|
+
|
|
133
139
|
const contextParts: string[] = [];
|
|
134
140
|
const metadata: Record<string, unknown> = {};
|
|
135
141
|
let blocked = false;
|
|
136
142
|
|
|
137
143
|
for (const script of scripts) {
|
|
138
|
-
const result = await executeHook(script, input, timeout);
|
|
144
|
+
const result = await executeHook(script, input, timeout, homeDir);
|
|
139
145
|
if (result.additionalContext) {
|
|
140
146
|
contextParts.push(result.additionalContext);
|
|
141
147
|
}
|