volute 0.32.0 → 0.34.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 +16 -0
- package/dist/{accept-74M7I4RZ.js → accept-TW6V4WI4.js} +4 -4
- package/dist/{activity-events-HETAODOK.js → activity-events-BN7V6KCC.js} +4 -4
- package/dist/{ai-service-ZIPCV3MX.js → ai-service-PSILB5WD.js} +5 -5
- package/dist/{api-client-YPKOZP2O.js → api-client-XUXOB7LI.js} +1 -1
- package/dist/api.d.ts +1198 -957
- package/dist/{archive-INXYFVCW.js → archive-C2VEMQOR.js} +4 -4
- package/dist/{auth-6DMGES3I.js → auth-ZFZXJZDQ.js} +5 -5
- package/dist/{bridge-BVCBTGPF.js → bridge-O753D5F4.js} +4 -4
- package/dist/{chat-XT4OBJBU.js → chat-BHYX7DJ4.js} +9 -9
- package/dist/{chunk-M7UL5S3Q.js → chunk-2IOP6PHB.js} +1 -1
- package/dist/{chunk-JJ7W6WSB.js → chunk-47XDEWWV.js} +5 -5
- package/dist/{chunk-RSX4OPZY.js → chunk-47ZPNLF4.js} +7 -7
- package/dist/{chunk-RPZZSXV3.js → chunk-4JSR7YO7.js} +20 -1
- package/dist/chunk-6LXAAQ43.js +22 -0
- package/dist/{chunk-TSXLLQZW.js → chunk-6OWJXUAR.js} +10 -1
- package/dist/{chunk-I5KY25PQ.js → chunk-6WAWMWR5.js} +1 -1
- package/dist/{chunk-LSGWR54X.js → chunk-7F2SW2KD.js} +2 -2
- package/dist/chunk-7KJOFUNN.js +22 -0
- package/dist/{spirit-N4W4UQRH.js → chunk-B2BVAIZ4.js} +21 -12
- package/dist/{chunk-LGB6JBHI.js → chunk-BDK73LK6.js} +5 -55
- package/dist/{chunk-IYDIE3HG.js → chunk-BFWHBQK4.js} +1 -1
- package/dist/{chunk-TDRYEPH4.js → chunk-BM474GX6.js} +4 -4
- package/dist/{chunk-R7E6CRVQ.js → chunk-BTWAGDV5.js} +1 -1
- package/dist/{chunk-WKF5FEFK.js → chunk-CVL5IGIR.js} +629 -174
- package/dist/{chunk-S6NFERDC.js → chunk-E5C7OWZ2.js} +20 -22
- package/dist/chunk-FYCALD4Q.js +23 -0
- package/dist/{chunk-SKLSMHXO.js → chunk-IS7WJ56Q.js} +1 -1
- package/dist/{chunk-2NGTS5UU.js → chunk-M3K5AARV.js} +1 -1
- package/dist/{chunk-ALEF47VT.js → chunk-MLOQKQNB.js} +1 -1
- package/dist/{chunk-D5G5YOPL.js → chunk-N3DNFPVA.js} +41 -5
- package/dist/{chunk-LRCG2JLP.js → chunk-N7BLAHNE.js} +5 -1
- package/dist/chunk-OYAKCAVY.js +29 -0
- package/dist/{chunk-UKVWJRKN.js → chunk-PLDWHR4D.js} +1 -1
- package/dist/{chunk-QBQ424EM.js → chunk-TAHX36HZ.js} +545 -246
- package/dist/chunk-U5BTYSAL.js +59 -0
- package/dist/{chunk-SX5TKJBZ.js → chunk-V45JXOWY.js} +2 -2
- package/dist/{chunk-2FLJ63GU.js → chunk-V6ZCNULL.js} +2 -2
- package/dist/{chunk-QZANELPX.js → chunk-XWXBJQBE.js} +3 -2
- package/dist/cli.js +32 -24
- package/dist/{clock-2UOZ6JPU.js → clock-3X4DSC2N.js} +38 -23
- package/dist/{cloud-sync-JN3NWKEM.js → cloud-sync-TG3TIX5H.js} +21 -17
- package/dist/{config-H2H4UIF7.js → config-OROA5DUA.js} +4 -4
- package/dist/connectors/discord-bridge.js +1 -1
- package/dist/connectors/slack-bridge.js +1 -1
- package/dist/connectors/telegram-bridge.js +1 -1
- package/dist/{conversations-3O5O6AS3.js → conversations-HL2JP5GI.js} +5 -5
- package/dist/{create-RNLNCORE.js → create-3SEKKI6P.js} +5 -5
- package/dist/{create-WBBYI6V7.js → create-UOSOQ2HN.js} +4 -4
- package/dist/daemon-client-WOAQXXBM.js +12 -0
- package/dist/{daemon-restart-NGFHFAUF.js → daemon-restart-5ABHNXJZ.js} +9 -8
- package/dist/daemon.js +2730 -1520
- package/dist/{db-RA45JBFG.js → db-PLEDCBHZ.js} +1 -1
- package/dist/db-RYX3SS2W.js +9 -0
- package/dist/{delete-QTGWEDBI.js → delete-KYOVWR23.js} +3 -3
- package/dist/delivery-manager-2BR5NZKF.js +32 -0
- package/dist/{delivery-router-FL45JL7N.js → delivery-router-D5ELDMS2.js} +4 -4
- package/dist/down-QVFN4UPK.js +15 -0
- package/dist/{env-RLYQBOOP.js → env-R34DT7XL.js} +10 -6
- package/dist/exec-DVLXKRIO.js +17 -0
- package/dist/{export-SUYRLI5Q.js → export-6ZXAXATG.js} +6 -6
- package/dist/extension-PM42QCID.js +97 -0
- package/dist/extensions-BBGVL5JC.js +38 -0
- package/dist/{files-EAMPO2SJ.js → files-VQV2VZQO.js} +5 -5
- package/dist/{import-DDUFE7AY.js → import-MK2I2T6F.js} +5 -5
- package/dist/isolation-62MKDZN3.js +22 -0
- package/dist/{join-I5QEE3LG.js → join-DGYHTJUH.js} +3 -3
- package/dist/lib-DYEZMGW7.js +6588 -0
- package/dist/{list-DW2VRTOZ.js → list-C644WTHV.js} +16 -8
- package/dist/{login-7CHPW2PN.js → login-IIGEQPHL.js} +4 -4
- package/dist/{login-RIJF2F4G.js → login-KZQLMAWE.js} +4 -4
- package/dist/{logout-5MLHZALK.js → logout-AGTZVRGP.js} +4 -4
- package/dist/{logout-UZJRGY4Z.js → logout-KD6GXIJJ.js} +4 -4
- package/dist/message-delivery-V3R6NXJP.js +42 -0
- package/dist/{mind-2B6M7Y25.js → mind-BI4EPBVZ.js} +25 -19
- package/dist/{mind-activity-tracker-NZZT2NTT.js → mind-activity-tracker-2ACNHA7B.js} +5 -5
- package/dist/mind-history-WOYFLQAI.js +264 -0
- package/dist/{mind-list-WUPMQDYQ.js → mind-list-6VPM7GUQ.js} +4 -4
- package/dist/mind-manager-MWW3BTS4.js +32 -0
- package/dist/mind-profile-WPG42U5Y.js +47 -0
- package/dist/mind-service-VIKZJK2M.js +38 -0
- package/dist/{mind-sleep-B7BHJLH7.js → mind-sleep-XDISJY74.js} +4 -4
- package/dist/{mind-status-L3EFFRPR.js → mind-status-7FTZWPZF.js} +4 -4
- package/dist/{mind-wake-GY3RFX7Y.js → mind-wake-KIIKEI3A.js} +4 -4
- package/dist/{package-PK6JUFL3.js → package-V2WHWVG6.js} +9 -5
- package/dist/{read-5AMJRO3D.js → read-H5C26YO7.js} +18 -8
- package/dist/read-stdin-PIRM6A2Y.js +8 -0
- package/dist/{register-V2JZZKFK.js → register-J27WP33N.js} +4 -4
- package/dist/{registry-PJ4S5PHQ.js → registry-UYV5S6QT.js} +3 -3
- package/dist/{reject-33HEZMZ4.js → reject-OEANJYIA.js} +4 -4
- package/dist/{restart-3UCMRUVC.js → restart-V5EGYBJG.js} +4 -4
- package/dist/{sandbox-JANNTX6U.js → sandbox-SI5HMBP3.js} +5 -5
- package/dist/scheduler-AGG3L2FO.js +32 -0
- package/dist/{schema-PA3M5ZKH.js → schema-ETMABTW4.js} +4 -2
- package/dist/seed-WNGI6PNW.js +11 -0
- package/dist/seed-check-PXTH7YXS.js +32 -0
- package/dist/seed-cmd-VENFTGS3.js +36 -0
- package/dist/{seed-ALUQ55FF.js → seed-create-663ALOKH.js} +8 -8
- package/dist/{sprout-L2GFOVF7.js → seed-sprout-EH3AGKAI.js} +24 -11
- package/dist/{send-3MI36LEF.js → send-7FUUUZZH.js} +66 -51
- package/dist/{setup-SZIARWI6.js → setup-GGMKENLN.js} +6 -4
- package/dist/{setup-WENLVPVP.js → setup-Z3DEVWV7.js} +13 -11
- package/dist/{skill-TUVOTW4Z.js → skill-DKNYJS4P.js} +12 -8
- package/dist/skills/imagegen/SKILL.md +11 -7
- package/dist/skills/imagegen/scripts/imagegen.ts +146 -25
- package/dist/skills/orientation/SKILL.md +9 -2
- package/dist/skills/plan-coordinator/SKILL.md +60 -0
- package/dist/skills/seed-nurture/SKILL.md +42 -0
- package/dist/skills/volute-mind/SKILL.md +11 -221
- package/dist/skills/volute-mind/references/extensions.md +37 -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-XNZK6P4K.js → skills-Q6VZ2UGD.js} +11 -6
- package/dist/sleep-manager-BJK2ROPX.js +36 -0
- package/dist/spirit-4JP4TY4C.js +23 -0
- package/dist/{split-STOROBYJ.js → split-3YPMS2CL.js} +3 -3
- package/dist/sprout-E3HJIV2Z.js +11 -0
- package/dist/{start-K2NCUUCG.js → start-W3TPKX4D.js} +4 -4
- package/dist/{status-TCUMUO6M.js → status-4OVFXFEJ.js} +7 -6
- package/dist/{stop-H26JZDXF.js → stop-GTT6YWYO.js} +4 -4
- package/dist/system-channel-DXD2JBOU.js +36 -0
- package/dist/system-chat-TYLOL7SX.js +36 -0
- package/dist/{systems-DHBKVYEY.js → systems-AYLO727G.js} +7 -7
- package/dist/{tailscale-XHQBZROW.js → tailscale-ZEUK7GKZ.js} +3 -3
- package/dist/{template-hash-A6VVKOXJ.js → template-hash-EJRTKE36.js} +1 -1
- package/dist/up-PA7F2CXE.js +18 -0
- package/dist/{update-QVPRF6GR.js → update-HG4LCUSG.js} +7 -6
- package/dist/{update-check-ZD6OOIYQ.js → update-check-X3YG4WVP.js} +4 -4
- package/dist/{upgrade-O4Q7WJM3.js → upgrade-YGNIDICG.js} +3 -3
- package/dist/{variant-7TGZHOU3.js → variant-MZUMRTQO.js} +1 -1
- package/dist/{version-notify-TCKWBZZG.js → version-notify-YCH4UVQ2.js} +23 -20
- package/dist/volute-config-WBKYJGYQ.js +10 -0
- package/dist/web-assets/assets/index-DiiwC-CZ.css +1 -0
- package/dist/web-assets/assets/index-d6y5b9Ij.js +75 -0
- package/dist/web-assets/ext-theme.css +48 -9
- package/dist/web-assets/index.html +2 -2
- 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 +8 -4
- 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/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 +69 -8
- package/templates/claude/.init/CLAUDE.md +4 -10
- package/templates/claude/package.json.tmpl +1 -0
- package/templates/claude/src/agent.ts +100 -32
- package/templates/claude/src/lib/hooks/reply-instructions.ts +27 -7
- package/templates/claude/src/lib/stream-consumer.ts +40 -2
- package/templates/claude/src/server.ts +1 -0
- package/templates/codex/package.json.tmpl +1 -0
- package/templates/codex/src/agent.ts +81 -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/chunk-K3NQKI34.js +0 -10
- package/dist/daemon-client-6QXHZ7US.js +0 -12
- package/dist/db-F34YLV7D.js +0 -9
- package/dist/delivery-manager-SDVXFD4W.js +0 -28
- package/dist/down-TB3ESMNP.js +0 -14
- package/dist/extension-FQ5D3NCC.js +0 -174
- package/dist/extensions-GDYWQXC4.js +0 -29
- package/dist/history-FO5PHBQ5.js +0 -128
- package/dist/message-delivery-2FIM7QKO.js +0 -32
- package/dist/mind-manager-BNCMGYXW.js +0 -28
- package/dist/mind-service-AV273WT4.js +0 -34
- package/dist/sleep-manager-53DZOWW7.js +0 -32
- package/dist/system-chat-NPYFYZVI.js +0 -32
- package/dist/up-6I6BHRTO.js +0 -17
- package/dist/web-assets/assets/index-Bui7U9Uu.css +0 -1
- package/dist/web-assets/assets/index-e36DIo1b.js +0 -73
|
@@ -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,6 +450,9 @@ 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
|
|
|
@@ -455,9 +481,17 @@ export function createMind(options: {
|
|
|
455
481
|
};
|
|
456
482
|
session.listeners.add(filteredListener);
|
|
457
483
|
|
|
458
|
-
// Track channel for reply instructions
|
|
484
|
+
// Track channel/sender for reply instructions
|
|
459
485
|
if (meta.channel) {
|
|
460
|
-
session.messageChannels.set(meta.messageId,
|
|
486
|
+
session.messageChannels.set(meta.messageId, {
|
|
487
|
+
channel: meta.channel,
|
|
488
|
+
sender: meta.sender,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Update reply instructions mode from routing config
|
|
493
|
+
if (meta.replyInstructions) {
|
|
494
|
+
session.replyInstructionsMode = meta.replyInstructions;
|
|
461
495
|
}
|
|
462
496
|
|
|
463
497
|
// Interrupt if requested and session is mid-turn
|
|
@@ -497,5 +531,39 @@ export function createMind(options: {
|
|
|
497
531
|
return handler;
|
|
498
532
|
}
|
|
499
533
|
|
|
500
|
-
|
|
534
|
+
const systemPromptTokens = countSystemPromptTokens(options.systemPrompt);
|
|
535
|
+
const claudeMdTokens = countSdkInstructionTokens(options.cwd);
|
|
536
|
+
const skillDescTokens = countSkillDescriptionTokens([resolvePath(options.cwd, ".claude/skills")]);
|
|
537
|
+
|
|
538
|
+
function getContextInfo(): ContextInfo {
|
|
539
|
+
return {
|
|
540
|
+
sessions: Array.from(sessions.values()).map((s) => {
|
|
541
|
+
try {
|
|
542
|
+
const sessionId = sessionStore.load(s.name);
|
|
543
|
+
const jsonlPath = sessionId ? findClaudeSessionFile(options.cwd, sessionId) : null;
|
|
544
|
+
const parsed = jsonlPath
|
|
545
|
+
? parseClaudeSessionJSONL(
|
|
546
|
+
jsonlPath,
|
|
547
|
+
systemPromptTokens,
|
|
548
|
+
claudeMdTokens,
|
|
549
|
+
skillDescTokens,
|
|
550
|
+
)
|
|
551
|
+
: null;
|
|
552
|
+
|
|
553
|
+
return {
|
|
554
|
+
name: s.name,
|
|
555
|
+
contextTokens: parsed?.contextTokens ?? s.contextTokens,
|
|
556
|
+
contextWindow: maxContextTokens,
|
|
557
|
+
breakdown: parsed?.breakdown,
|
|
558
|
+
};
|
|
559
|
+
} catch (err) {
|
|
560
|
+
log("mind", `failed to get context breakdown for session "${s.name}":`, err);
|
|
561
|
+
return { name: s.name, contextTokens: s.contextTokens, contextWindow: maxContextTokens };
|
|
562
|
+
}
|
|
563
|
+
}),
|
|
564
|
+
systemPrompt: systemPromptTokens,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return { resolve, waitForCommits: autoCommit.waitForCommits, getContextInfo };
|
|
501
569
|
}
|
|
@@ -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,
|
|
@@ -74,6 +74,44 @@ export async function consumeStream(
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
+
if (msg.type === "user") {
|
|
78
|
+
// Tool result messages — the SDK sends these after tool execution.
|
|
79
|
+
// Extract tool_result content blocks and emit them so the daemon can
|
|
80
|
+
// link outbound records to the correct turn via correlation markers.
|
|
81
|
+
const content = (msg as { message?: { content?: unknown[] } }).message?.content;
|
|
82
|
+
if (Array.isArray(content)) {
|
|
83
|
+
for (const b of content) {
|
|
84
|
+
if (
|
|
85
|
+
b &&
|
|
86
|
+
typeof b === "object" &&
|
|
87
|
+
"type" in b &&
|
|
88
|
+
b.type === "tool_result" &&
|
|
89
|
+
"content" in b
|
|
90
|
+
) {
|
|
91
|
+
const resultContent = Array.isArray(b.content)
|
|
92
|
+
? b.content
|
|
93
|
+
.filter(
|
|
94
|
+
(c: unknown): c is { type: "text"; text: string } =>
|
|
95
|
+
!!c && typeof c === "object" && "type" in c && c.type === "text",
|
|
96
|
+
)
|
|
97
|
+
.map((c) => c.text)
|
|
98
|
+
.join("")
|
|
99
|
+
: typeof b.content === "string"
|
|
100
|
+
? b.content
|
|
101
|
+
: "";
|
|
102
|
+
if (resultContent) {
|
|
103
|
+
const toolUseId =
|
|
104
|
+
"tool_use_id" in b && typeof b.tool_use_id === "string" ? b.tool_use_id : "unknown";
|
|
105
|
+
emit(session, {
|
|
106
|
+
type: "tool_result",
|
|
107
|
+
content: resultContent,
|
|
108
|
+
metadata: { tool_use_id: toolUseId },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
77
115
|
if (msg.type === "result") {
|
|
78
116
|
if (session.currentMessageId) {
|
|
79
117
|
session.messageChannels.delete(session.currentMessageId);
|
|
@@ -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 {
|
|
@@ -134,6 +149,7 @@ export function createMind(options: {
|
|
|
134
149
|
function initSession(session: CodexSession) {
|
|
135
150
|
const isEphemeral = session.name.startsWith("new-");
|
|
136
151
|
log("mind", `session "${session.name}": ${isEphemeral ? "ephemeral" : "persistent"}`);
|
|
152
|
+
emit(session, { type: "session_start" });
|
|
137
153
|
|
|
138
154
|
if (!isEphemeral) {
|
|
139
155
|
const savedThreadId = sessionStore.load(session.name);
|
|
@@ -143,6 +159,7 @@ export function createMind(options: {
|
|
|
143
159
|
session.thread = codex.resumeThread(savedThreadId, {
|
|
144
160
|
workingDirectory: options.cwd,
|
|
145
161
|
model: options.model,
|
|
162
|
+
modelReasoningEffort: options.reasoningEffort,
|
|
146
163
|
skipGitRepoCheck: true,
|
|
147
164
|
sandboxMode: "danger-full-access",
|
|
148
165
|
});
|
|
@@ -157,6 +174,7 @@ export function createMind(options: {
|
|
|
157
174
|
session.thread = codex.startThread({
|
|
158
175
|
workingDirectory: options.cwd,
|
|
159
176
|
model: options.model,
|
|
177
|
+
modelReasoningEffort: options.reasoningEffort,
|
|
160
178
|
skipGitRepoCheck: true,
|
|
161
179
|
sandboxMode: "danger-full-access",
|
|
162
180
|
});
|
|
@@ -192,6 +210,20 @@ export function createMind(options: {
|
|
|
192
210
|
// Refresh system prompt before each turn (picks up MEMORY.md changes)
|
|
193
211
|
refreshSystemPrompt();
|
|
194
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
|
+
|
|
195
227
|
// Run pre-prompt hooks
|
|
196
228
|
try {
|
|
197
229
|
const hookResult = await runHooks(hooksDir, "pre-prompt", {
|
|
@@ -210,11 +242,14 @@ export function createMind(options: {
|
|
|
210
242
|
warn("mind", "pre-prompt hook failed:", err);
|
|
211
243
|
}
|
|
212
244
|
|
|
213
|
-
// Reply instructions on first message per channel
|
|
245
|
+
// Reply instructions on first message per channel (skip system messages)
|
|
214
246
|
const channel = meta.channel;
|
|
215
247
|
if (channel && !session.firstMessagePerChannel.has(channel)) {
|
|
216
248
|
session.firstMessagePerChannel.add(channel);
|
|
217
|
-
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);
|
|
218
253
|
emit(session, {
|
|
219
254
|
type: "context",
|
|
220
255
|
content: replyInstructions,
|
|
@@ -280,7 +315,9 @@ export function createMind(options: {
|
|
|
280
315
|
if (item.type === "agent_message" || item.type === "agentMessage") {
|
|
281
316
|
itemText.set(event.itemId ?? item.id, "");
|
|
282
317
|
} else if (item.type === "reasoning") {
|
|
283
|
-
|
|
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 });
|
|
284
321
|
} else if (item.type === "command_execution" || item.type === "commandExecution") {
|
|
285
322
|
const cmd = item.command ?? item.args?.join(" ") ?? "";
|
|
286
323
|
emit(session, {
|
|
@@ -355,7 +392,10 @@ export function createMind(options: {
|
|
|
355
392
|
if (!item) break;
|
|
356
393
|
const itemType = item.type;
|
|
357
394
|
|
|
358
|
-
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") {
|
|
359
399
|
// Emit any remaining delta
|
|
360
400
|
const id = event.itemId ?? item.id;
|
|
361
401
|
const prev = itemText.get(id) ?? "";
|
|
@@ -549,5 +589,38 @@ export function createMind(options: {
|
|
|
549
589
|
return handler;
|
|
550
590
|
}
|
|
551
591
|
|
|
552
|
-
|
|
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 };
|
|
553
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();
|