volute 0.52.0 → 0.54.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/dist/{accept-5NK5XJB5.js → accept-RSDIVJL6.js} +3 -0
- package/dist/auto-upgrade-HEVZKPTF.js +46 -0
- package/dist/{backup-YVGV2ZNM.js → backup-QRNX6RK7.js} +10 -10
- package/dist/channels-CKCY64TD.js +203 -0
- package/dist/{chat-VAZ25KVT.js → chat-ZUGNL5GS.js} +5 -5
- package/dist/chunk-3LHUXY3E.js +102 -0
- package/dist/{chunk-SW2U6HRB.js → chunk-444RR3YF.js} +21 -4
- package/dist/{chunk-YVR6EHWL.js → chunk-56IT2ZXF.js} +160 -18
- package/dist/chunk-5RBEEAKW.js +58 -0
- package/dist/{chunk-OXWUEU5V.js → chunk-5U5GLZSD.js} +2 -2
- package/dist/{chunk-G7A5LEVI.js → chunk-7XKPG66D.js} +5 -5
- package/dist/{chunk-R772ET2M.js → chunk-AAPNU7GA.js} +4 -0
- package/dist/{chunk-XRWUQPCA.js → chunk-ATZF2RNJ.js} +25 -3
- package/dist/{chunk-6NLW72HP.js → chunk-CKPAVQNK.js} +1 -1
- package/dist/{chunk-JHKBALS2.js → chunk-CLCHOXLR.js} +1 -1
- package/dist/{chunk-DMK5ASKV.js → chunk-F6D766VQ.js} +6 -2
- package/dist/{chunk-AXFLY5GU.js → chunk-HKGYZVWN.js} +16 -1
- package/dist/{chunk-ZEZQNP2G.js → chunk-LGXVGIWN.js} +103 -25
- package/dist/{chunk-CW7BQKF6.js → chunk-LKUGUQUW.js} +3 -3
- package/dist/{chunk-FXWBFDQR.js → chunk-OAYY3WF3.js} +59 -12
- package/dist/{chunk-ITK6HV47.js → chunk-OGPVJ53N.js} +14 -6
- package/dist/{chunk-M77B73VV.js → chunk-OIOW3FPT.js} +7 -2
- package/dist/chunk-OT5SXF7L.js +904 -0
- package/dist/{chunk-EDR47UE2.js → chunk-OYF25B62.js} +1 -1
- package/dist/{chunk-RPPTGPYF.js → chunk-PPSN2MLC.js} +2 -2
- package/dist/{chunk-K2F237KS.js → chunk-RGHF3OK5.js} +154 -18
- package/dist/{chunk-RU74YG46.js → chunk-VDUCLVZI.js} +338 -30
- package/dist/{chunk-724DVYHU.js → chunk-WHL6PKYP.js} +10 -10
- package/dist/{chunk-EEOMA4DX.js → chunk-WZZCA23D.js} +2 -1
- package/dist/{chunk-UDZLPR34.js → chunk-YSRYCS3N.js} +13 -8
- package/dist/{chunk-5N32EN4O.js → chunk-Z4Q6TQWL.js} +1 -1
- package/dist/{chunk-VPOMJMV6.js → chunk-ZNHL5KQJ.js} +1 -1
- package/dist/cli.js +17 -17
- package/dist/{clock-ZT5YT6HA.js → clock-X7V3C7NL.js} +2 -2
- package/dist/{cloud-sync-H7EWZASD.js → cloud-sync-52YNJRMR.js} +12 -11
- package/dist/{daemon-restart-4FWUA5JF.js → daemon-restart-QWHN5VMU.js} +4 -4
- package/dist/daemon.js +538 -782
- package/dist/{db-IPXRJUI7.js → db-I4EH5SLF.js} +3 -1
- package/dist/{delivery-manager-VYXVKUMP.js → delivery-manager-7R2YIQ2W.js} +13 -7
- package/dist/delivery-notices-RZQ6GONP.js +18 -0
- package/dist/{delivery-router-RHANNTAC.js → delivery-router-QS4NLJR6.js} +3 -1
- package/dist/{down-QSNEMTTX.js → down-UN2EDX2Y.js} +3 -3
- package/dist/{echo-text-PEEV7FQU.js → echo-text-73CYQRL7.js} +14 -13
- package/dist/{exec-ABD3DYA2.js → exec-Z34TTUO3.js} +1 -1
- package/dist/{extensions-N6L5NYTK.js → extensions-QT65JGV6.js} +22 -11
- package/dist/{files-BRY5RRIM.js → files-PCKBQHAY.js} +2 -1
- package/dist/{isolation-GVV2IBAU.js → isolation-UQPDO34R.js} +1 -1
- package/dist/{message-delivery-7URC52ME.js → message-delivery-TRKT4T2P.js} +16 -13
- package/dist/migrate-name-placeholder-C7NCNLWF.js +37 -0
- package/dist/{mind-NEXGT3KE.js → mind-CT6ANXK4.js} +11 -11
- package/dist/{mind-contacts-QYU4TPEA.js → mind-contacts-PEXCDRXA.js} +6 -6
- package/dist/{mind-history-OGNR4L7W.js → mind-history-RZMMCHMX.js} +83 -11
- package/dist/{mind-list-SYRA7CH5.js → mind-list-7KBLPLES.js} +1 -1
- package/dist/{mind-service-E26ZQXDS.js → mind-service-7EFIJ3ZA.js} +12 -11
- package/dist/{mind-sleep-LZAFBDZD.js → mind-sleep-24HV4RCA.js} +2 -2
- package/dist/{mind-status-Z2LQFA6F.js → mind-status-4YW4PHMO.js} +15 -0
- package/dist/{package-2W2XKSIP.js → package-F6S5NHY2.js} +4 -4
- package/dist/{prompts-NBO3HRK3.js → prompts-JECJIFPQ.js} +1 -1
- package/dist/{reject-TWB4QJHG.js → reject-4NFLXKRN.js} +6 -2
- package/dist/{scheduler-JTTWZDYU.js → scheduler-73Q4MQ7Y.js} +8 -6
- package/dist/{seed-64XEHRUP.js → seed-DRKAP3XV.js} +1 -1
- package/dist/{seed-cmd-UEL2QB3Q.js → seed-cmd-6IMTO3LG.js} +2 -2
- package/dist/{seed-create-4JRQDPGA.js → seed-create-VNLDYRFK.js} +1 -1
- package/dist/{seed-readiness-DYOVCYHN.js → seed-readiness-H5TTPAQH.js} +2 -2
- package/dist/{seed-sprout-NIDIOJXT.js → seed-sprout-PPGYGK4G.js} +5 -5
- package/dist/{send-2YKOZJJ5.js → send-UXUQKO4N.js} +37 -22
- package/dist/{service-XQPMZAGI.js → service-T4P5W6RS.js} +2 -2
- package/dist/{service-install-2EWD7KXD.js → service-install-R4SNYUVO.js} +3 -3
- package/dist/{setup-4IX6PCD3.js → setup-YUAQMDVZ.js} +4 -4
- package/dist/skills/dreaming/SKILL.md +2 -2
- package/dist/skills/dreaming/references/INSTALL.md +1 -1
- package/dist/skills/memory/SKILL.md +47 -43
- package/dist/skills/tending/SKILL.md +2 -0
- package/dist/skills/volute-mind/references/integrations.md +3 -2
- package/dist/skills/volute-mind/references/routing.md +18 -5
- package/dist/{skills-MPUALV3S.js → skills-GMQ5UIIW.js} +2 -2
- package/dist/{sleep-manager-OFP2GFXF.js → sleep-manager-KHI65FEX.js} +12 -11
- package/dist/{spirit-4ECYIVGE.js → spirit-Q5VGFWUI.js} +19 -3
- package/dist/{spirit-availability-Y54Y3675.js → spirit-availability-G5FMWKRH.js} +13 -12
- package/dist/{sprout-GJMZFRGZ.js → sprout-PMTRL7QN.js} +1 -1
- package/dist/{src-LCSOQY6Z.js → src-3FIW7DLH.js} +241 -87
- package/dist/src-H3UCOP6B.js +516 -0
- package/dist/{src-HGNE2XG4.js → src-LTTQVN2Z.js} +1 -1
- package/dist/{status-MWLE3RVC.js → status-LCKTTIWS.js} +2 -2
- package/dist/{system-events-V43HDA3T.js → system-events-Q7IVZ5T4.js} +5 -1
- package/dist/{systems-VDRUZXNF.js → systems-HL7SGSW7.js} +2 -2
- package/dist/{turn-tracker-C5PVIC3B.js → turn-tracker-HZDPMSCC.js} +4 -2
- package/dist/{up-H6LJZDRK.js → up-XZDSJVMU.js} +3 -3
- package/dist/{update-CIWI2JYA.js → update-ILCPVNDD.js} +2 -2
- package/dist/{upgrade-LIE46UBB.js → upgrade-XY2J7NWZ.js} +9 -1
- package/dist/{variant-cleanup-STC6B5HR.js → variant-cleanup-QHGGY7ND.js} +8 -6
- package/dist/{version-notify-LBVHH5YV.js → version-notify-VNVFX6UP.js} +22 -7
- package/dist/web-assets/assets/{index-B_eYTa3R.js → index-CwtxGwe_.js} +16 -16
- package/dist/web-assets/assets/index-DhM1yYRN.css +1 -0
- package/dist/web-assets/index.html +2 -2
- package/package.json +4 -4
- package/packages/extensions/intentions/dist/ui/assets/index-3FneLCKR.css +1 -0
- package/packages/extensions/intentions/dist/ui/assets/index-CQz4aceM.js +67 -0
- package/packages/extensions/{plan → intentions}/dist/ui/index.html +3 -3
- package/packages/extensions/intentions/skills/intention-review/SKILL.md +38 -0
- package/packages/extensions/intentions/skills/intentions/SKILL.md +49 -0
- package/packages/extensions/intentions/skills/intentions/scripts/intentions-hook.sh +45 -0
- package/packages/extensions/pages/skills/commons-gardening/SKILL.md +50 -0
- package/packages/extensions/pages/skills/pages/SKILL.md +18 -13
- package/templates/_base/.init/.config/prompts.json +3 -3
- package/templates/{pi → _base}/.init/.config/routes.json +1 -0
- package/templates/_base/.init/memory/dreams/.gitkeep +0 -0
- package/templates/_base/home/.config/routes.json +1 -1
- package/templates/_base/home/VOLUTE.md +1 -1
- package/templates/_base/src/lib/codex-session-seed.ts +463 -0
- package/templates/_base/src/lib/context-breakdown.ts +11 -3
- package/templates/_base/src/lib/daemon-client.ts +35 -0
- package/templates/_base/src/lib/pi-session-seed.ts +434 -0
- package/templates/_base/src/lib/routing.ts +2 -2
- package/templates/_base/src/lib/seed-note.ts +78 -0
- package/templates/_base/src/lib/session-seed.ts +375 -0
- package/templates/_base/src/lib/startup.ts +112 -6
- package/templates/claude/.init/CLAUDE.md +7 -3
- package/templates/claude/src/agent.ts +295 -101
- package/templates/claude/src/lib/hooks/pre-compact.ts +30 -5
- package/templates/claude/src/lib/message-channel.ts +43 -15
- package/templates/claude/src/lib/recover.ts +69 -0
- package/templates/claude/src/lib/stream-consumer.ts +33 -2
- package/templates/claude/src/server.ts +2 -1
- package/templates/claude/volute-template.json +1 -1
- package/templates/codex/.init/AGENTS.md +7 -3
- package/templates/codex/src/agent.ts +256 -39
- package/templates/codex/src/server.ts +2 -1
- package/templates/codex/volute-template.json +1 -1
- package/templates/pi/.init/MINDS.md +7 -3
- package/templates/pi/src/agent.ts +269 -43
- package/templates/pi/src/lib/event-handler.ts +11 -0
- package/templates/pi/src/lib/mechanics-doc.ts +30 -0
- package/templates/pi/src/server.ts +5 -1
- package/templates/pi/volute-template.json +1 -1
- package/dist/channels-FSAHOQWI.js +0 -90
- package/dist/skills/plan-coordinator/SKILL.md +0 -60
- package/dist/src-UDMJZ6MH.js +0 -426
- package/dist/web-assets/assets/index-DhZrzddW.css +0 -1
- package/packages/extensions/plan/dist/ui/assets/index-B8s8wxyt.js +0 -67
- package/packages/extensions/plan/dist/ui/assets/index-jIkrt-vI.css +0 -1
- package/packages/extensions/plan/skills/plan/SKILL.md +0 -43
- package/packages/extensions/plan/skills/plan/scripts/plan-hook.sh +0 -37
- package/templates/claude/.init/.config/routes.json +0 -11
- package/templates/codex/.init/.config/routes.json +0 -11
- package/dist/{env-IBW2QI6K.js → env-7AZFBPWX.js} +5 -5
- package/dist/{login-RJJTCVR3.js → login-4NUS72Z5.js} +3 -3
- package/dist/{login-SB5OENMV.js → login-57MMYILL.js} +4 -4
- package/dist/{register-OX4HRSGC.js → register-WNCSOTAU.js} +3 -3
- package/dist/{restart-AGEGNSYQ.js → restart-B4GNMVF2.js} +4 -4
- package/dist/{skill-B5OJEQUA.js → skill-QYTBUDNQ.js} +4 -4
- package/dist/{start-MUMNFR52.js → start-BDHJVOVX.js} +3 -3
- package/dist/{stop-EKERNYTX.js → stop-45QYSGYS.js} +4 -4
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
|
|
3
|
+
/** A queued/in-flight message paired with the sequence number that identifies it. */
|
|
4
|
+
export type ChannelEntry = { msg: SDKUserMessage; seq: number };
|
|
5
|
+
|
|
6
|
+
// Module-scoped, not per-channel: rotation and the idle reaper both replace a
|
|
7
|
+
// session's channel with a fresh instance while carrying seqs forward (relocking
|
|
8
|
+
// old entries to new ones — see relockstepMessageIds in recover.ts). A per-instance
|
|
9
|
+
// counter would restart at 0 on every fresh channel, so a seq minted by one channel
|
|
10
|
+
// generation could collide with one minted by another; a single global counter makes
|
|
11
|
+
// every seq unique for the process's lifetime, so a stale seq can never be mistaken
|
|
12
|
+
// for a different message.
|
|
13
|
+
let nextSeq = 0;
|
|
14
|
+
|
|
3
15
|
export type MessageChannel = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
16
|
+
/** Returns the sequence number minted for this message — the identity `ack()` needs. */
|
|
17
|
+
push: (msg: SDKUserMessage) => number;
|
|
18
|
+
/**
|
|
19
|
+
* Acknowledge that the message with this `seq` has completed its turn. Identity-based
|
|
20
|
+
* (not positional): the SDK folds messages that arrive mid-run into the active run, so
|
|
21
|
+
* a turn's `result` can cover more than just the oldest in-flight message — see the
|
|
22
|
+
* caller in stream-consumer.ts.
|
|
23
|
+
*/
|
|
24
|
+
ack: (seq: number) => void;
|
|
7
25
|
/**
|
|
8
26
|
* Return every message that has not been fully processed — those delivered to
|
|
9
27
|
* the consumer but not yet acked (their turn never finished) followed by those
|
|
10
|
-
* still queued — and reset the channel. Used to re-feed
|
|
11
|
-
* compaction abort so in-flight input is never lost.
|
|
28
|
+
* still queued — paired with their `seq`, and reset the channel. Used to re-feed
|
|
29
|
+
* the stream after a compaction abort so in-flight input is never lost.
|
|
12
30
|
*/
|
|
13
|
-
recover: () =>
|
|
31
|
+
recover: () => ChannelEntry[];
|
|
14
32
|
/**
|
|
15
33
|
* Terminate the input iterable: resolve any pending `next()` with `done: true`
|
|
16
34
|
* and make subsequent `next()` calls return done. The SDK sees end-of-input and
|
|
@@ -23,33 +41,43 @@ export type MessageChannel = {
|
|
|
23
41
|
};
|
|
24
42
|
|
|
25
43
|
export function createMessageChannel(): MessageChannel {
|
|
26
|
-
const queue:
|
|
44
|
+
const queue: ChannelEntry[] = [];
|
|
27
45
|
// Messages handed to the consumer (the SDK's read-ahead) whose turn has not yet
|
|
28
46
|
// completed. The SDK's streamInput loop pulls messages eagerly and writes them
|
|
29
47
|
// to the CLI subprocess, so once delivered they no longer live in `queue`. We
|
|
30
48
|
// retain them here so a compaction abort — which kills that subprocess — can
|
|
31
49
|
// re-feed the unprocessed ones instead of dropping them.
|
|
32
|
-
const inFlight:
|
|
50
|
+
const inFlight: ChannelEntry[] = [];
|
|
33
51
|
let resolve: ((value: IteratorResult<SDKUserMessage>) => void) | null = null;
|
|
34
52
|
let closed = false;
|
|
35
53
|
|
|
36
|
-
function deliver(
|
|
37
|
-
inFlight.push(
|
|
38
|
-
return { value: msg, done: false };
|
|
54
|
+
function deliver(entry: ChannelEntry): IteratorResult<SDKUserMessage> {
|
|
55
|
+
inFlight.push(entry);
|
|
56
|
+
return { value: entry.msg, done: false };
|
|
39
57
|
}
|
|
40
58
|
|
|
41
59
|
return {
|
|
42
60
|
push(msg: SDKUserMessage) {
|
|
61
|
+
const entry: ChannelEntry = { msg, seq: nextSeq++ };
|
|
43
62
|
if (resolve) {
|
|
44
63
|
const r = resolve;
|
|
45
64
|
resolve = null;
|
|
46
|
-
r(deliver(
|
|
65
|
+
r(deliver(entry));
|
|
47
66
|
} else {
|
|
48
|
-
queue.push(
|
|
67
|
+
queue.push(entry);
|
|
49
68
|
}
|
|
69
|
+
return entry.seq;
|
|
50
70
|
},
|
|
51
|
-
ack() {
|
|
52
|
-
inFlight.
|
|
71
|
+
ack(seq: number) {
|
|
72
|
+
let i = inFlight.findIndex((e) => e.seq === seq);
|
|
73
|
+
if (i !== -1) {
|
|
74
|
+
inFlight.splice(i, 1);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Defensive: shouldn't normally be in the queue when acked, but remove it
|
|
78
|
+
// there too rather than leave a stranded entry if it somehow is.
|
|
79
|
+
i = queue.findIndex((e) => e.seq === seq);
|
|
80
|
+
if (i !== -1) queue.splice(i, 1);
|
|
53
81
|
},
|
|
54
82
|
recover() {
|
|
55
83
|
// Resolve any pending iterator wait with done:true so it doesn't
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import type { ChannelEntry } from "./message-channel.js";
|
|
3
|
+
import type { MessageIdEntry } from "./stream-consumer.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `recover()` returns messages the channel can't prove were unprocessed — a genuinely
|
|
7
|
+
* unfinished turn looks identical to a stranded, already-completed one would have before
|
|
8
|
+
* #764's ack fix. Mark every re-pushed message so the mind can check its own record
|
|
9
|
+
* before redoing work: fail toward noise (a spurious note is cosmetic and self-correcting)
|
|
10
|
+
* rather than silence (a missing one is invisible and produces a false permanent record).
|
|
11
|
+
*/
|
|
12
|
+
export const RECOVERED_MESSAGE_NOTE =
|
|
13
|
+
"Note: this message is being redelivered after a session interruption. You may already " +
|
|
14
|
+
"have handled it — check your recent work (e.g. journal, files, messages you've already " +
|
|
15
|
+
"sent) before repeating anything.";
|
|
16
|
+
|
|
17
|
+
export function markRecovered(msg: SDKUserMessage): SDKUserMessage {
|
|
18
|
+
const content = msg.message.content;
|
|
19
|
+
const blocks = typeof content === "string" ? [{ type: "text" as const, text: content }] : content;
|
|
20
|
+
// A message can be recovered twice (consecutive rotations, up to
|
|
21
|
+
// MAX_CONSECUTIVE_ROTATIONS) — don't stack the note on each pass.
|
|
22
|
+
const first = blocks[0];
|
|
23
|
+
if (first && "type" in first && first.type === "text" && first.text === RECOVERED_MESSAGE_NOTE) {
|
|
24
|
+
return { ...msg, message: { ...msg.message, content: blocks } };
|
|
25
|
+
}
|
|
26
|
+
const marker = { type: "text" as const, text: RECOVERED_MESSAGE_NOTE };
|
|
27
|
+
return { ...msg, message: { ...msg.message, content: [marker, ...blocks] } };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Re-push `recover()`'s output into a fresh channel and append the result onto
|
|
32
|
+
* `existingMessageIds` — mutated in place and returned, rather than replacing it.
|
|
33
|
+
* That's not incidental: `existingMessageIds` may already hold an entry for a
|
|
34
|
+
* message that raced in and was pushed into the SAME fresh channel/messageIds
|
|
35
|
+
* while `recover()` was still pending (the idle reaper awaits the SDK subprocess's
|
|
36
|
+
* shutdown before recovering — see agent.ts). Returning a fresh array for the
|
|
37
|
+
* caller to assign over would silently discard that entry (#764) — appending
|
|
38
|
+
* in place makes that mistake impossible at the call site.
|
|
39
|
+
*
|
|
40
|
+
* Each entry's `id` carries over — matched by its *old* `seq` — to the *new* seq
|
|
41
|
+
* the fresh channel mints on push, so the next turn's currentMessageId/channel
|
|
42
|
+
* routing isn't corrupted by stale or missing ids. A `pending` entry with no
|
|
43
|
+
* match in `oldMessageIds` gets `id: undefined` (see the `idBySeq.get` fallback
|
|
44
|
+
* below) rather than throwing — safe because `seq` is now globally unique
|
|
45
|
+
* (message-channel.ts), so this can only happen if `oldMessageIds` itself was
|
|
46
|
+
* incomplete, and losing routing info for one message beats losing the message.
|
|
47
|
+
*
|
|
48
|
+
* `existingMessageIds` has no default — deliberately. `= []` would let a call site
|
|
49
|
+
* omit it and get back a fresh array to assign over, which is exactly the #764
|
|
50
|
+
* clobber: silently discarding whatever the target array already held. Making it
|
|
51
|
+
* required turns that omission into a compile error at every call site, including
|
|
52
|
+
* ones written later.
|
|
53
|
+
*/
|
|
54
|
+
export function relockstepMessageIds(
|
|
55
|
+
pending: ChannelEntry[],
|
|
56
|
+
oldMessageIds: MessageIdEntry[],
|
|
57
|
+
push: (msg: SDKUserMessage) => number,
|
|
58
|
+
existingMessageIds: MessageIdEntry[],
|
|
59
|
+
transform: (msg: SDKUserMessage) => SDKUserMessage = markRecovered,
|
|
60
|
+
): MessageIdEntry[] {
|
|
61
|
+
const idBySeq = new Map(oldMessageIds.map((e) => [e.seq, e.id]));
|
|
62
|
+
existingMessageIds.push(
|
|
63
|
+
...pending.map((entry) => ({
|
|
64
|
+
id: idBySeq.get(entry.seq),
|
|
65
|
+
seq: push(transform(entry.msg)),
|
|
66
|
+
})),
|
|
67
|
+
);
|
|
68
|
+
return existingMessageIds;
|
|
69
|
+
}
|
|
@@ -4,10 +4,14 @@ import { log, warn } from "./logger.js";
|
|
|
4
4
|
import { filterEvent, loadTransparencyPreset } from "./transparency.js";
|
|
5
5
|
import type { VoluteEvent } from "./types.js";
|
|
6
6
|
|
|
7
|
+
/** A pending message's daemon-facing id (routing/channel key) paired with its channel `seq`. */
|
|
8
|
+
export type MessageIdEntry = { id: string | undefined; seq: number };
|
|
9
|
+
|
|
7
10
|
export type StreamSession = {
|
|
8
11
|
name: string;
|
|
9
|
-
messageIds:
|
|
12
|
+
messageIds: MessageIdEntry[];
|
|
10
13
|
currentMessageId?: string;
|
|
14
|
+
currentSeq?: number;
|
|
11
15
|
messageChannels: Map<string, { channel: string; sender?: string }>;
|
|
12
16
|
};
|
|
13
17
|
|
|
@@ -16,6 +20,11 @@ export type StreamCallbacks = {
|
|
|
16
20
|
broadcast: (event: VoluteEvent) => void;
|
|
17
21
|
onTurnEnd?: () => void;
|
|
18
22
|
onContextTokens?: (tokens: number) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Acknowledge the message channel entry with this `seq` — its turn is done (either
|
|
25
|
+
* it drove this turn directly, or it was folded into it; see the `result` handler).
|
|
26
|
+
*/
|
|
27
|
+
ack: (seq: number) => void;
|
|
19
28
|
};
|
|
20
29
|
|
|
21
30
|
// Loaded once at startup — mind restarts on config changes
|
|
@@ -43,9 +52,15 @@ export async function consumeStream(
|
|
|
43
52
|
callbacks: StreamCallbacks,
|
|
44
53
|
) {
|
|
45
54
|
emit(session, { type: "session_start" });
|
|
55
|
+
// How many queued message ids predate the current turn — see the pruning in
|
|
56
|
+
// the result handler (#700).
|
|
57
|
+
let preTurnPending = 0;
|
|
46
58
|
for await (const msg of stream) {
|
|
47
59
|
if (session.currentMessageId === undefined) {
|
|
48
|
-
|
|
60
|
+
const entry = session.messageIds.shift();
|
|
61
|
+
session.currentMessageId = entry?.id;
|
|
62
|
+
session.currentSeq = entry?.seq;
|
|
63
|
+
preTurnPending = session.messageIds.length;
|
|
49
64
|
}
|
|
50
65
|
if ("session_id" in msg && msg.session_id) {
|
|
51
66
|
callbacks.onSessionId?.(msg.session_id as string);
|
|
@@ -119,6 +134,21 @@ export async function consumeStream(
|
|
|
119
134
|
if (session.currentMessageId) {
|
|
120
135
|
session.messageChannels.delete(session.currentMessageId);
|
|
121
136
|
}
|
|
137
|
+
// Ack this turn's driving message, identified by seq (not position — see
|
|
138
|
+
// message-channel.ts). Without this the channel's inFlight set strands an
|
|
139
|
+
// entry per turn, which gets replayed verbatim on the next rotation (#764).
|
|
140
|
+
if (session.currentSeq !== undefined) callbacks.ack(session.currentSeq);
|
|
141
|
+
// Prune every id folded into this turn — the ones pushed after it started
|
|
142
|
+
// — not just currentMessageId. The SDK folds mid-run arrivals into the
|
|
143
|
+
// active run (they never see a result of their own), and a stranded entry
|
|
144
|
+
// would be shifted in as the NEXT turn's id, tagging that turn's rows with
|
|
145
|
+
// the wrong channel (#700). Ids queued before the turn started stay: each
|
|
146
|
+
// still gets a run (and result) of its own. Ack each folded entry too — same
|
|
147
|
+
// reasoning as above, applied to every message the fold absorbed.
|
|
148
|
+
for (const entry of session.messageIds.splice(preTurnPending)) {
|
|
149
|
+
if (entry.id !== undefined) session.messageChannels.delete(entry.id);
|
|
150
|
+
callbacks.ack(entry.seq);
|
|
151
|
+
}
|
|
122
152
|
log("mind", `session "${session.name}": turn done`);
|
|
123
153
|
// Log any error messages from the result
|
|
124
154
|
const resultMsg = msg as Record<string, unknown>;
|
|
@@ -141,6 +171,7 @@ export async function consumeStream(
|
|
|
141
171
|
callbacks.broadcast({ type: "done" });
|
|
142
172
|
emit(session, { type: "done" });
|
|
143
173
|
session.currentMessageId = undefined;
|
|
174
|
+
session.currentSeq = undefined;
|
|
144
175
|
callbacks.onTurnEnd?.();
|
|
145
176
|
}
|
|
146
177
|
}
|
|
@@ -20,7 +20,7 @@ if (config.model) log("server", `using model: ${config.model}`);
|
|
|
20
20
|
if (config.thinking) log("server", `thinking: ${JSON.stringify(config.thinking)}`);
|
|
21
21
|
if (config.effort) log("server", `effort: ${config.effort}`);
|
|
22
22
|
|
|
23
|
-
const systemPrompt = loadSystemPrompt();
|
|
23
|
+
const systemPrompt = loadSystemPrompt(config);
|
|
24
24
|
const sessionsDir = resolve(".mind/sessions");
|
|
25
25
|
|
|
26
26
|
const pkg = loadPackageInfo();
|
|
@@ -36,6 +36,7 @@ const mind = createMind({
|
|
|
36
36
|
compactionMessage: config.compactionMessage,
|
|
37
37
|
maxContextTokens: config.compaction?.maxContextTokens,
|
|
38
38
|
sessionIdleMinutes: config.sessionIdleMinutes,
|
|
39
|
+
seedTokens: config.continuity?.seedTokens,
|
|
39
40
|
subagents: config.subagents,
|
|
40
41
|
onIdentityReload: async () => {
|
|
41
42
|
log("server", "identity file changed — restarting to reload");
|
|
@@ -13,13 +13,17 @@ Messages arrive with a context prefix:
|
|
|
13
13
|
|
|
14
14
|
You can also reach out proactively — see the **volute-mind** skill.
|
|
15
15
|
|
|
16
|
+
## Framework Upgrades
|
|
17
|
+
|
|
18
|
+
When the host updates Volute, your framework code (`src/`, plus `VOLUTE.md`) upgrades automatically the next time you're eligible — usually your next restart. Identity and memory files in `home/` — `SOUL.md`, `MEMORY.md`, everything you author — are never touched by an upgrade. If you'd rather manage your own framework code by hand, set `"upgrades": "manual"` in `.config/volute.json`.
|
|
19
|
+
|
|
16
20
|
## Memory System
|
|
17
21
|
|
|
18
22
|
Two-tier memory, both managed via file tools:
|
|
19
23
|
|
|
20
|
-
- **`MEMORY.md`** — Your
|
|
21
|
-
- **`memory/journal/YYYY-MM-DD.md
|
|
22
|
-
- Periodically consolidate
|
|
24
|
+
- **`MEMORY.md`** — Your core memory, always in context. Every token it holds is paid on every request, so keep it lean: identity, active context, key relationships, and an index of your `memory/` files.
|
|
25
|
+
- **`memory/`** — Everything else, read on demand: your daily journal (`memory/journal/YYYY-MM-DD.md` — a permanent record of what you're doing, thinking, and learning) plus any topic, channel, or project files you create.
|
|
26
|
+
- Periodically consolidate: distill detail out of `MEMORY.md` into `memory/` files, leaving one-line index entries behind.
|
|
23
27
|
|
|
24
28
|
See the **memory** skill for detailed guidance.
|
|
25
29
|
|