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
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { resolve as resolvePath } from "node:path";
|
|
3
|
-
import type { HookCallback } from "@anthropic-ai/claude-agent-sdk";
|
|
3
|
+
import type { HookCallback, SyncHookJSONOutput } from "@anthropic-ai/claude-agent-sdk";
|
|
4
4
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
5
5
|
import { toSDKContent } from "./lib/content.js";
|
|
6
|
+
import {
|
|
7
|
+
countSdkInstructionTokens,
|
|
8
|
+
countSkillDescriptionTokens,
|
|
9
|
+
countSystemPromptTokens,
|
|
10
|
+
findClaudeSessionFile,
|
|
11
|
+
parseClaudeSessionJSONL,
|
|
12
|
+
} from "./lib/context-breakdown.js";
|
|
6
13
|
import { daemonEmit } from "./lib/daemon-client.js";
|
|
7
14
|
import { runHooks } from "./lib/hook-loader.js";
|
|
8
15
|
import { createAutoCommitHook } from "./lib/hooks/auto-commit.js";
|
|
@@ -23,6 +30,7 @@ import type {
|
|
|
23
30
|
VoluteContentPart,
|
|
24
31
|
VoluteEvent,
|
|
25
32
|
} from "./lib/types.js";
|
|
33
|
+
import type { ContextInfo } from "./lib/volute-server.js";
|
|
26
34
|
|
|
27
35
|
type Session = {
|
|
28
36
|
name: string;
|
|
@@ -31,7 +39,10 @@ type Session = {
|
|
|
31
39
|
messageIds: (string | undefined)[];
|
|
32
40
|
currentMessageId?: string;
|
|
33
41
|
currentQuery?: ReturnType<typeof query>;
|
|
34
|
-
messageChannels: Map<string, string>;
|
|
42
|
+
messageChannels: Map<string, { channel: string; sender?: string }>;
|
|
43
|
+
replyInstructionsFired: boolean;
|
|
44
|
+
replyInstructionsMode: "once" | "always" | "never";
|
|
45
|
+
contextTokens: number;
|
|
35
46
|
};
|
|
36
47
|
|
|
37
48
|
export function createMind(options: {
|
|
@@ -45,7 +56,11 @@ export function createMind(options: {
|
|
|
45
56
|
maxContextTokens?: number;
|
|
46
57
|
subagents?: Record<string, SubagentConfig>;
|
|
47
58
|
onIdentityReload?: () => Promise<void>;
|
|
48
|
-
}): {
|
|
59
|
+
}): {
|
|
60
|
+
resolve: HandlerResolver;
|
|
61
|
+
waitForCommits: () => Promise<void>;
|
|
62
|
+
getContextInfo: () => ContextInfo;
|
|
63
|
+
} {
|
|
49
64
|
const autoCommit = createAutoCommitHook(options.cwd);
|
|
50
65
|
const identityReload = createIdentityReloadHook(options.cwd);
|
|
51
66
|
const sessionStore = createSessionStore(options.sessionsDir);
|
|
@@ -135,11 +150,16 @@ export function createMind(options: {
|
|
|
135
150
|
function wrapHookWithEmit(hook: HookCallback, source: string, session: Session): HookCallback {
|
|
136
151
|
return async (...args) => {
|
|
137
152
|
const result = await hook(...args);
|
|
138
|
-
const
|
|
139
|
-
const
|
|
153
|
+
const syncResult = result as SyncHookJSONOutput;
|
|
154
|
+
const hookOutput = syncResult?.hookSpecificOutput;
|
|
155
|
+
const additionalContext =
|
|
156
|
+
hookOutput && "additionalContext" in hookOutput
|
|
157
|
+
? (hookOutput.additionalContext as string | undefined)
|
|
158
|
+
: undefined;
|
|
159
|
+
const decision = syncResult?.decision;
|
|
140
160
|
if (additionalContext || decision) {
|
|
141
161
|
const channel = session.currentMessageId
|
|
142
|
-
? session.messageChannels.get(session.currentMessageId)
|
|
162
|
+
? session.messageChannels.get(session.currentMessageId)?.channel
|
|
143
163
|
: undefined;
|
|
144
164
|
try {
|
|
145
165
|
daemonEmit({
|
|
@@ -164,7 +184,7 @@ export function createMind(options: {
|
|
|
164
184
|
const result = await runHooks(hooksDir, event, input as Record<string, unknown>);
|
|
165
185
|
if (result.additionalContext || Object.keys(result.metadata).length > 0) {
|
|
166
186
|
const channel = session.currentMessageId
|
|
167
|
-
? session.messageChannels.get(session.currentMessageId)
|
|
187
|
+
? session.messageChannels.get(session.currentMessageId)?.channel
|
|
168
188
|
: undefined;
|
|
169
189
|
try {
|
|
170
190
|
daemonEmit({
|
|
@@ -202,7 +222,7 @@ export function createMind(options: {
|
|
|
202
222
|
preCompactHook: HookCallback,
|
|
203
223
|
resume?: string,
|
|
204
224
|
) {
|
|
205
|
-
const replyInstructions = createReplyInstructionsHook(session.messageChannels);
|
|
225
|
+
const replyInstructions = createReplyInstructionsHook(session.messageChannels, session);
|
|
206
226
|
|
|
207
227
|
return query({
|
|
208
228
|
prompt: session.channel.iterable,
|
|
@@ -279,27 +299,30 @@ export function createMind(options: {
|
|
|
279
299
|
options.onIdentityReload?.();
|
|
280
300
|
}
|
|
281
301
|
},
|
|
282
|
-
onContextTokens:
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
302
|
+
onContextTokens: (tokens: number) => {
|
|
303
|
+
session.contextTokens = tokens;
|
|
304
|
+
if (
|
|
305
|
+
maxContextTokens &&
|
|
306
|
+
tokens >= maxContextTokens &&
|
|
307
|
+
!compactionTriggered.get(session.name)
|
|
308
|
+
) {
|
|
309
|
+
compactionTriggered.set(session.name, true);
|
|
310
|
+
log(
|
|
311
|
+
"mind",
|
|
312
|
+
`session "${session.name}": ${tokens} tokens >= ${maxContextTokens} — triggering compaction`,
|
|
313
|
+
);
|
|
314
|
+
session.messageIds.push(undefined);
|
|
315
|
+
session.channel.push({
|
|
316
|
+
type: "user",
|
|
317
|
+
session_id: "",
|
|
318
|
+
message: {
|
|
319
|
+
role: "user",
|
|
320
|
+
content: [{ type: "text", text: compactionMessage }],
|
|
321
|
+
},
|
|
322
|
+
parent_tool_use_id: null,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
},
|
|
303
326
|
};
|
|
304
327
|
|
|
305
328
|
async function runCompact(sessionId: string) {
|
|
@@ -427,11 +450,21 @@ export function createMind(options: {
|
|
|
427
450
|
listeners: new Set(),
|
|
428
451
|
messageIds: [],
|
|
429
452
|
messageChannels: new Map(),
|
|
453
|
+
replyInstructionsFired: false,
|
|
454
|
+
replyInstructionsMode: "once",
|
|
455
|
+
contextTokens: 0,
|
|
430
456
|
};
|
|
431
457
|
sessions.set(name, session);
|
|
432
458
|
|
|
433
459
|
const isEphemeral = name.startsWith("new-");
|
|
434
|
-
|
|
460
|
+
let savedSessionId = isEphemeral ? undefined : sessionStore.load(name);
|
|
461
|
+
// Validate that the SDK session file still exists — orphaned references
|
|
462
|
+
// cause the SDK to throw and can crash the process with EPIPE.
|
|
463
|
+
if (savedSessionId && !findClaudeSessionFile(options.cwd, savedSessionId)) {
|
|
464
|
+
log("mind", `session "${name}": stored session ${savedSessionId} not found, starting fresh`);
|
|
465
|
+
sessionStore.delete(name);
|
|
466
|
+
savedSessionId = undefined;
|
|
467
|
+
}
|
|
435
468
|
if (savedSessionId) {
|
|
436
469
|
log("mind", `session "${name}": resuming ${savedSessionId}`);
|
|
437
470
|
} else {
|
|
@@ -455,9 +488,17 @@ export function createMind(options: {
|
|
|
455
488
|
};
|
|
456
489
|
session.listeners.add(filteredListener);
|
|
457
490
|
|
|
458
|
-
// Track channel for reply instructions
|
|
491
|
+
// Track channel/sender for reply instructions
|
|
459
492
|
if (meta.channel) {
|
|
460
|
-
session.messageChannels.set(meta.messageId,
|
|
493
|
+
session.messageChannels.set(meta.messageId, {
|
|
494
|
+
channel: meta.channel,
|
|
495
|
+
sender: meta.sender,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Update reply instructions mode from routing config
|
|
500
|
+
if (meta.replyInstructions) {
|
|
501
|
+
session.replyInstructionsMode = meta.replyInstructions;
|
|
461
502
|
}
|
|
462
503
|
|
|
463
504
|
// Interrupt if requested and session is mid-turn
|
|
@@ -497,5 +538,39 @@ export function createMind(options: {
|
|
|
497
538
|
return handler;
|
|
498
539
|
}
|
|
499
540
|
|
|
500
|
-
|
|
541
|
+
const systemPromptTokens = countSystemPromptTokens(options.systemPrompt);
|
|
542
|
+
const claudeMdTokens = countSdkInstructionTokens(options.cwd);
|
|
543
|
+
const skillDescTokens = countSkillDescriptionTokens([resolvePath(options.cwd, ".claude/skills")]);
|
|
544
|
+
|
|
545
|
+
function getContextInfo(): ContextInfo {
|
|
546
|
+
return {
|
|
547
|
+
sessions: Array.from(sessions.values()).map((s) => {
|
|
548
|
+
try {
|
|
549
|
+
const sessionId = sessionStore.load(s.name);
|
|
550
|
+
const jsonlPath = sessionId ? findClaudeSessionFile(options.cwd, sessionId) : null;
|
|
551
|
+
const parsed = jsonlPath
|
|
552
|
+
? parseClaudeSessionJSONL(
|
|
553
|
+
jsonlPath,
|
|
554
|
+
systemPromptTokens,
|
|
555
|
+
claudeMdTokens,
|
|
556
|
+
skillDescTokens,
|
|
557
|
+
)
|
|
558
|
+
: null;
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
name: s.name,
|
|
562
|
+
contextTokens: parsed?.contextTokens ?? s.contextTokens,
|
|
563
|
+
contextWindow: maxContextTokens,
|
|
564
|
+
breakdown: parsed?.breakdown,
|
|
565
|
+
};
|
|
566
|
+
} catch (err) {
|
|
567
|
+
log("mind", `failed to get context breakdown for session "${s.name}":`, err);
|
|
568
|
+
return { name: s.name, contextTokens: s.contextTokens, contextWindow: maxContextTokens };
|
|
569
|
+
}
|
|
570
|
+
}),
|
|
571
|
+
systemPrompt: systemPromptTokens,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return { resolve, waitForCommits: autoCommit.waitForCommits, getContextInfo };
|
|
501
576
|
}
|
|
@@ -1,22 +1,42 @@
|
|
|
1
1
|
import type { HookCallback } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import { loadPrompts } from "../startup.js";
|
|
3
3
|
|
|
4
|
-
export function createReplyInstructionsHook(
|
|
5
|
-
|
|
4
|
+
export function createReplyInstructionsHook(
|
|
5
|
+
messageChannels: Map<string, { channel: string; sender?: string }>,
|
|
6
|
+
sessionState: {
|
|
7
|
+
replyInstructionsFired: boolean;
|
|
8
|
+
replyInstructionsMode: "once" | "always" | "never";
|
|
9
|
+
},
|
|
10
|
+
) {
|
|
6
11
|
const prompts = loadPrompts();
|
|
7
12
|
|
|
8
13
|
const hook: HookCallback = async () => {
|
|
9
|
-
|
|
14
|
+
// "never" suppresses reply instructions entirely
|
|
15
|
+
if (sessionState.replyInstructionsMode === "never") return {};
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
if (
|
|
17
|
+
// "once" only fires on first message per session
|
|
18
|
+
if (sessionState.replyInstructionsMode === "once" && sessionState.replyInstructionsFired)
|
|
19
|
+
return {};
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
const entry = messageChannels.values().next().value;
|
|
22
|
+
if (!entry) return {};
|
|
23
|
+
|
|
24
|
+
sessionState.replyInstructionsFired = true;
|
|
25
|
+
|
|
26
|
+
// System messages don't need reply instructions
|
|
27
|
+
if (entry.sender === "volute") {
|
|
28
|
+
return {
|
|
29
|
+
hookSpecificOutput: {
|
|
30
|
+
hookEventName: "UserPromptSubmit" as const,
|
|
31
|
+
additionalContext: "This is a system message — no reply is needed.",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
15
35
|
|
|
16
36
|
return {
|
|
17
37
|
hookSpecificOutput: {
|
|
18
38
|
hookEventName: "UserPromptSubmit" as const,
|
|
19
|
-
additionalContext: prompts.reply_instructions.replace(/\$\{channel\}/g, channel),
|
|
39
|
+
additionalContext: prompts.reply_instructions.replace(/\$\{channel\}/g, entry.channel),
|
|
20
40
|
},
|
|
21
41
|
};
|
|
22
42
|
};
|
|
@@ -8,7 +8,7 @@ export type StreamSession = {
|
|
|
8
8
|
name: string;
|
|
9
9
|
messageIds: (string | undefined)[];
|
|
10
10
|
currentMessageId?: string;
|
|
11
|
-
messageChannels: Map<string, string>;
|
|
11
|
+
messageChannels: Map<string, { channel: string; sender?: string }>;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export type StreamCallbacks = {
|
|
@@ -26,7 +26,7 @@ function emit(
|
|
|
26
26
|
event: { type: EventType; content?: string; metadata?: Record<string, unknown> },
|
|
27
27
|
) {
|
|
28
28
|
const channel = session.currentMessageId
|
|
29
|
-
? session.messageChannels.get(session.currentMessageId)
|
|
29
|
+
? session.messageChannels.get(session.currentMessageId)?.channel
|
|
30
30
|
: undefined;
|
|
31
31
|
const filtered = filterEvent(preset, {
|
|
32
32
|
...event,
|
|
@@ -3,11 +3,18 @@ import { resolve as resolvePath } from "node:path";
|
|
|
3
3
|
import { Codex } from "@openai/codex-sdk";
|
|
4
4
|
import { flushFileChanges, trackFileChange } from "./lib/auto-commit.js";
|
|
5
5
|
import { extractText } from "./lib/content.js";
|
|
6
|
+
import {
|
|
7
|
+
countSdkInstructionTokens,
|
|
8
|
+
countSkillDescriptionTokens,
|
|
9
|
+
countSystemPromptTokens,
|
|
10
|
+
findCodexSessionFile,
|
|
11
|
+
parseCodexSessionJSONL,
|
|
12
|
+
} from "./lib/context-breakdown.js";
|
|
6
13
|
import { daemonEmit, daemonRestart, type EventType } from "./lib/daemon-client.js";
|
|
7
14
|
import { runHooks } from "./lib/hook-loader.js";
|
|
8
15
|
import { log, warn } from "./lib/logger.js";
|
|
9
16
|
import { createSessionStore } from "./lib/session-store.js";
|
|
10
|
-
import { loadPrompts, loadSystemPrompt } from "./lib/startup.js";
|
|
17
|
+
import { getStartupContext, loadPrompts, loadSystemPrompt } from "./lib/startup.js";
|
|
11
18
|
import { filterEvent, loadTransparencyPreset } from "./lib/transparency.js";
|
|
12
19
|
import type {
|
|
13
20
|
HandlerMeta,
|
|
@@ -17,6 +24,7 @@ import type {
|
|
|
17
24
|
VoluteContentPart,
|
|
18
25
|
VoluteEvent,
|
|
19
26
|
} from "./lib/types.js";
|
|
27
|
+
import type { ContextInfo } from "./lib/volute-server.js";
|
|
20
28
|
|
|
21
29
|
/** Minimal interface for a Codex SDK thread — typed to the methods we actually use */
|
|
22
30
|
type CodexThread = {
|
|
@@ -63,9 +71,9 @@ export function createMind(options: {
|
|
|
63
71
|
cwd: string;
|
|
64
72
|
mindDir: string;
|
|
65
73
|
model?: string;
|
|
66
|
-
reasoningEffort?:
|
|
74
|
+
reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
67
75
|
maxContextTokens?: number;
|
|
68
|
-
}): { resolve: HandlerResolver } {
|
|
76
|
+
}): { resolve: HandlerResolver; getContextInfo: () => ContextInfo } {
|
|
69
77
|
const sessions = new Map<string, CodexSession>();
|
|
70
78
|
const prompts = loadPrompts();
|
|
71
79
|
const maxContextTokens = options.maxContextTokens;
|
|
@@ -76,6 +84,7 @@ export function createMind(options: {
|
|
|
76
84
|
|
|
77
85
|
const sessionStore = createSessionStore(resolvePath(options.mindDir, ".mind/codex-sessions"));
|
|
78
86
|
const hooksDir = resolvePath(options.cwd, ".local/hooks");
|
|
87
|
+
const startupContextPromise = getStartupContext().catch(() => null);
|
|
79
88
|
|
|
80
89
|
// Write system prompt to file for Codex model_instructions_file
|
|
81
90
|
const promptPath = resolvePath(options.mindDir, ".mind/system-prompt.md");
|
|
@@ -99,6 +108,9 @@ export function createMind(options: {
|
|
|
99
108
|
model_instructions_file: promptPath,
|
|
100
109
|
// Let the SDK handle compaction natively when a threshold is configured
|
|
101
110
|
model_auto_compact_token_limit: maxContextTokens ?? 999999999,
|
|
111
|
+
// Enable reasoning summaries so they appear as events
|
|
112
|
+
model_reasoning_summary: "auto",
|
|
113
|
+
model_supports_reasoning_summaries: true,
|
|
102
114
|
// The codex sandbox runs commands in /bin/zsh -lc which resets the environment.
|
|
103
115
|
// Set ZDOTDIR so the login shell sources our .zshenv with VOLUTE env vars and PATH.
|
|
104
116
|
shell_environment_policy: {
|
|
@@ -109,6 +121,9 @@ export function createMind(options: {
|
|
|
109
121
|
},
|
|
110
122
|
});
|
|
111
123
|
|
|
124
|
+
// Track which sessions have received startup context
|
|
125
|
+
const startupContextInjected = new Set<string>();
|
|
126
|
+
|
|
112
127
|
// --- Session lifecycle ---
|
|
113
128
|
|
|
114
129
|
function getOrCreateSession(name: string): CodexSession {
|
|
@@ -144,6 +159,7 @@ export function createMind(options: {
|
|
|
144
159
|
session.thread = codex.resumeThread(savedThreadId, {
|
|
145
160
|
workingDirectory: options.cwd,
|
|
146
161
|
model: options.model,
|
|
162
|
+
modelReasoningEffort: options.reasoningEffort,
|
|
147
163
|
skipGitRepoCheck: true,
|
|
148
164
|
sandboxMode: "danger-full-access",
|
|
149
165
|
});
|
|
@@ -158,6 +174,7 @@ export function createMind(options: {
|
|
|
158
174
|
session.thread = codex.startThread({
|
|
159
175
|
workingDirectory: options.cwd,
|
|
160
176
|
model: options.model,
|
|
177
|
+
modelReasoningEffort: options.reasoningEffort,
|
|
161
178
|
skipGitRepoCheck: true,
|
|
162
179
|
sandboxMode: "danger-full-access",
|
|
163
180
|
});
|
|
@@ -193,6 +210,20 @@ export function createMind(options: {
|
|
|
193
210
|
// Refresh system prompt before each turn (picks up MEMORY.md changes)
|
|
194
211
|
refreshSystemPrompt();
|
|
195
212
|
|
|
213
|
+
// Inject startup context on the first turn of each session
|
|
214
|
+
if (!startupContextInjected.has(session.name)) {
|
|
215
|
+
startupContextInjected.add(session.name);
|
|
216
|
+
const startupContext = await startupContextPromise;
|
|
217
|
+
if (startupContext) {
|
|
218
|
+
emit(session, {
|
|
219
|
+
type: "context",
|
|
220
|
+
content: startupContext,
|
|
221
|
+
metadata: { source: "startup-context" },
|
|
222
|
+
});
|
|
223
|
+
text = `${startupContext}\n\n${text}`;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
196
227
|
// Run pre-prompt hooks
|
|
197
228
|
try {
|
|
198
229
|
const hookResult = await runHooks(hooksDir, "pre-prompt", {
|
|
@@ -211,11 +242,14 @@ export function createMind(options: {
|
|
|
211
242
|
warn("mind", "pre-prompt hook failed:", err);
|
|
212
243
|
}
|
|
213
244
|
|
|
214
|
-
// Reply instructions on first message per channel
|
|
245
|
+
// Reply instructions on first message per channel (skip system messages)
|
|
215
246
|
const channel = meta.channel;
|
|
216
247
|
if (channel && !session.firstMessagePerChannel.has(channel)) {
|
|
217
248
|
session.firstMessagePerChannel.add(channel);
|
|
218
|
-
const
|
|
249
|
+
const isSystem = meta.sender === "volute";
|
|
250
|
+
const replyInstructions = isSystem
|
|
251
|
+
? "This is a system message — no reply is needed."
|
|
252
|
+
: prompts.reply_instructions.replace(/\$\{channel\}/g, channel);
|
|
219
253
|
emit(session, {
|
|
220
254
|
type: "context",
|
|
221
255
|
content: replyInstructions,
|
|
@@ -281,7 +315,9 @@ export function createMind(options: {
|
|
|
281
315
|
if (item.type === "agent_message" || item.type === "agentMessage") {
|
|
282
316
|
itemText.set(event.itemId ?? item.id, "");
|
|
283
317
|
} else if (item.type === "reasoning") {
|
|
284
|
-
|
|
318
|
+
// Reasoning text may arrive on started or completed
|
|
319
|
+
const text = item.text ?? item.content ?? "";
|
|
320
|
+
if (text) emit(session, { type: "thinking", content: text });
|
|
285
321
|
} else if (item.type === "command_execution" || item.type === "commandExecution") {
|
|
286
322
|
const cmd = item.command ?? item.args?.join(" ") ?? "";
|
|
287
323
|
emit(session, {
|
|
@@ -356,7 +392,10 @@ export function createMind(options: {
|
|
|
356
392
|
if (!item) break;
|
|
357
393
|
const itemType = item.type;
|
|
358
394
|
|
|
359
|
-
if (itemType === "
|
|
395
|
+
if (itemType === "reasoning") {
|
|
396
|
+
const text = item.text ?? item.content ?? "";
|
|
397
|
+
if (text) emit(session, { type: "thinking", content: text });
|
|
398
|
+
} else if (itemType === "agent_message" || itemType === "agentMessage") {
|
|
360
399
|
// Emit any remaining delta
|
|
361
400
|
const id = event.itemId ?? item.id;
|
|
362
401
|
const prev = itemText.get(id) ?? "";
|
|
@@ -550,5 +589,38 @@ export function createMind(options: {
|
|
|
550
589
|
return handler;
|
|
551
590
|
}
|
|
552
591
|
|
|
553
|
-
|
|
592
|
+
const systemPromptTokens = countSystemPromptTokens(options.systemPrompt);
|
|
593
|
+
const claudeMdTokens = countSdkInstructionTokens(options.cwd);
|
|
594
|
+
const skillDescTokens = countSkillDescriptionTokens([resolvePath(options.cwd, ".agents/skills")]);
|
|
595
|
+
|
|
596
|
+
function getContextInfo(): ContextInfo {
|
|
597
|
+
return {
|
|
598
|
+
sessions: Array.from(sessions.values()).map((s) => {
|
|
599
|
+
try {
|
|
600
|
+
const threadId = sessionStore.load(s.name);
|
|
601
|
+
const jsonlPath = threadId ? findCodexSessionFile(threadId) : null;
|
|
602
|
+
const parsed = jsonlPath
|
|
603
|
+
? parseCodexSessionJSONL(jsonlPath, systemPromptTokens, claudeMdTokens, skillDescTokens)
|
|
604
|
+
: null;
|
|
605
|
+
|
|
606
|
+
return {
|
|
607
|
+
name: s.name,
|
|
608
|
+
contextTokens: parsed?.contextTokens ?? s.cumulativeInputTokens,
|
|
609
|
+
contextWindow: maxContextTokens,
|
|
610
|
+
breakdown: parsed?.breakdown,
|
|
611
|
+
};
|
|
612
|
+
} catch (err) {
|
|
613
|
+
log("mind", `failed to get context breakdown for session "${s.name}":`, err);
|
|
614
|
+
return {
|
|
615
|
+
name: s.name,
|
|
616
|
+
contextTokens: s.cumulativeInputTokens,
|
|
617
|
+
contextWindow: maxContextTokens,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
}),
|
|
621
|
+
systemPrompt: systemPromptTokens,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return { resolve, getContextInfo };
|
|
554
626
|
}
|
|
@@ -4,7 +4,6 @@ import { createFileHandlerResolver } from "./lib/file-handler.js";
|
|
|
4
4
|
import { log, setLevel } from "./lib/logger.js";
|
|
5
5
|
import { createRouter } from "./lib/router.js";
|
|
6
6
|
import {
|
|
7
|
-
handleStartupContext,
|
|
8
7
|
loadConfig,
|
|
9
8
|
loadPackageInfo,
|
|
10
9
|
loadSystemPrompt,
|
|
@@ -45,15 +44,13 @@ const server = createVoluteServer({
|
|
|
45
44
|
port,
|
|
46
45
|
name: pkg.name,
|
|
47
46
|
version: pkg.version,
|
|
47
|
+
getContextInfo: mind.getContextInfo,
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
server.listen(port, async () => {
|
|
51
51
|
const addr = server.address();
|
|
52
52
|
const actualPort = typeof addr === "object" && addr ? addr.port : port;
|
|
53
53
|
log("server", `listening on :${actualPort}`);
|
|
54
|
-
await handleStartupContext((content) =>
|
|
55
|
-
router.route([{ type: "text", text: content }], { channel: "system" }),
|
|
56
|
-
);
|
|
57
54
|
});
|
|
58
55
|
|
|
59
56
|
setupShutdown();
|