volute 0.45.2 → 0.47.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 +4 -4
- package/dist/{activity-events-BUR2RYTJ.js → activity-events-MIOK3IOU.js} +3 -3
- package/dist/{ai-service-UODCCE4O.js → ai-service-CVZEYKTK.js} +4 -4
- package/dist/{archive-WS2L2VIQ.js → archive-6Z4GFQPH.js} +3 -3
- package/dist/{auth-FLAEXXZK.js → auth-A4VGHADI.js} +4 -4
- package/dist/avatar-image-7S3WA77A.js +22 -0
- package/dist/{backup-TST5C2YN.js → backup-IIAFOW2L.js} +13 -13
- package/dist/channels-ITE4YC57.js +89 -0
- package/dist/{chat-76MRWWQX.js → chat-AJLJCQ3M.js} +12 -8
- package/dist/{chunk-ZV7MFISX.js → chunk-2MJ7EOMT.js} +20 -2
- package/dist/{chunk-R77O2WMK.js → chunk-2OIYNHWO.js} +1 -1
- package/dist/{chunk-QCN24XEN.js → chunk-3YUBFTDN.js} +1 -1
- package/dist/{chunk-NYC5U6K7.js → chunk-4I72V3E5.js} +10 -10
- package/dist/{chunk-QNQMSWZE.js → chunk-5IERLDUS.js} +3 -3
- package/dist/{chunk-FXORA5JG.js → chunk-5NN2VHJV.js} +3 -3
- package/dist/chunk-7KRIHYLR.js +124 -0
- package/dist/{chunk-RRNXJOKB.js → chunk-BGYHLR54.js} +2 -2
- package/dist/{chunk-QMJCNZ6C.js → chunk-C3KLHJDB.js} +1 -1
- package/dist/{chunk-TQJ6ZYVY.js → chunk-CBOIHNMR.js} +2 -2
- package/dist/{chunk-H2X54EHC.js → chunk-DGGZVZTM.js} +1 -1
- package/dist/{chunk-4JOQZPCE.js → chunk-EIQDZC43.js} +1 -1
- package/dist/{chunk-YT7PEESJ.js → chunk-F55GRIEK.js} +464 -221
- package/dist/{chunk-OP4JG2BS.js → chunk-HPGN6XRT.js} +8 -4
- package/dist/{chunk-7ZUB63PS.js → chunk-IWJSNMSY.js} +5 -5
- package/dist/{chunk-ZDZISKCL.js → chunk-IYG4FBMP.js} +1 -1
- package/dist/chunk-JCMGDCGX.js +237 -0
- package/dist/{clock-K43IMROX.js → chunk-JSVHH32K.js} +26 -21
- package/dist/{chunk-UYLGEDVD.js → chunk-K23P5Q5N.js} +340 -339
- package/dist/{chunk-7C44R625.js → chunk-LTLFOSA4.js} +49 -3
- package/dist/{chunk-IHKGWTGT.js → chunk-LWDTP6DH.js} +1 -1
- package/dist/{chunk-JZ62SYWR.js → chunk-M3XDVS5Q.js} +1 -1
- package/dist/{chunk-FR6S2KNN.js → chunk-M6TTZRGH.js} +9 -9
- package/dist/{chunk-EJVPTVTI.js → chunk-MTRMQFCQ.js} +1 -1
- package/dist/{chunk-T6HYVHPG.js → chunk-ODDGGLM6.js} +3 -3
- package/dist/{chunk-QLWG3C2Z.js → chunk-RRZ4RQQU.js} +2 -2
- package/dist/{chunk-CDPBTTZO.js → chunk-RUFLGTOT.js} +2 -2
- package/dist/{chunk-F7H6ATM4.js → chunk-SKNXMUIU.js} +1 -1
- package/dist/{chunk-RXEEYDIB.js → chunk-SZJCJVXV.js} +1 -1
- package/dist/{chunk-DRJ24GXK.js → chunk-VMCZBXF2.js} +52 -8
- package/dist/{chunk-ALKYQULU.js → chunk-WW6C3PLV.js} +1 -1
- package/dist/{chunk-A7WQZKOU.js → chunk-XHN6XBWV.js} +2 -2
- package/dist/{chunk-6EMWJZBK.js → chunk-YKA7DCVW.js} +1 -1
- package/dist/cli.js +24 -24
- package/dist/clock-E6N5Y3YW.js +16 -0
- package/dist/{cloud-sync-3OJOPZRT.js → cloud-sync-LQTVDTQ3.js} +19 -17
- package/dist/{conversations-JZTD6KLR.js → conversations-C7IKYSHD.js} +4 -4
- package/dist/{create-IOV4GCRX.js → create-PQQXKTJK.js} +1 -1
- package/dist/{daemon-restart-TIHKVVVA.js → daemon-restart-5F3YTLZK.js} +7 -7
- package/dist/daemon.js +418 -273
- package/dist/{db-MF2JNRUW.js → db-FO6LXYCW.js} +2 -2
- package/dist/{delete-BFMIGAIB.js → delete-HPLC7ETU.js} +1 -1
- package/dist/delivery-manager-TN5KK2AP.js +34 -0
- package/dist/{delivery-router-N4A7ZC5T.js → delivery-router-3QFG2ZEX.js} +3 -3
- package/dist/{down-U2CZ23M4.js → down-ZI4S6265.js} +5 -5
- package/dist/echo-text-SEQCLGAT.js +34 -0
- package/dist/{exec-G3ZRST4Z.js → exec-QIFYZG4C.js} +3 -3
- package/dist/{export-GGIVO76O.js → export-4GVGNW54.js} +6 -6
- package/dist/{extensions-JONTW2HF.js → extensions-PIMIQ7J3.js} +21 -17
- package/dist/{files-4LN3EN7Q.js → files-FEFZKMNM.js} +6 -6
- package/dist/{import-42YNQBF4.js → import-7MO3YTRE.js} +6 -6
- package/dist/{isolation-YMGZZ4E5.js → isolation-MCT4ZYLT.js} +3 -3
- package/dist/{login-M6OJUPQ2.js → login-OF4WXDAN.js} +5 -5
- package/dist/{logout-WQBWO77G.js → logout-ERE2BGA3.js} +5 -5
- package/dist/message-delivery-ZE3YGJDD.js +38 -0
- package/dist/{mind-DWB6XYCH.js → mind-POOHFZHP.js} +18 -18
- package/dist/{mind-activity-tracker-POBLU3LC.js → mind-activity-tracker-YVZPC4A5.js} +4 -4
- package/dist/{mind-list-4D7J4CHD.js → mind-list-KEGRGQEQ.js} +5 -4
- package/dist/mind-service-JFY7UG5E.js +40 -0
- package/dist/mind-sleep-IN3WPA76.js +83 -0
- package/dist/{mind-status-UO34ASXZ.js → mind-status-SMUWJKGV.js} +20 -3
- package/dist/{package-A7KXT67Q.js → package-WPEE2HVJ.js} +3 -2
- package/dist/{registry-OUZBZ3Q4.js → registry-UMMDRPRM.js} +2 -2
- package/dist/{sandbox-ZJ7QA6Z6.js → sandbox-G4X4K3UN.js} +4 -4
- package/dist/scheduler-HQSDYJFY.js +32 -0
- package/dist/{schema-PVNG3PQM.js → schema-VEVO7HG6.js} +3 -1
- package/dist/{seed-HCP5NYKH.js → seed-VANPU43L.js} +1 -1
- package/dist/{seed-cmd-ILOGNIFB.js → seed-cmd-SYVDZKM4.js} +2 -2
- package/dist/{seed-create-OURS2LS4.js → seed-create-3FGZPS6C.js} +5 -5
- package/dist/{seed-sprout-ELDFDAN6.js → seed-sprout-H7QLFPFM.js} +13 -11
- package/dist/{send-AAIOPJAV.js → send-HYDFGC6G.js} +13 -7
- package/dist/{service-UOCLXDWR.js → service-SYJ5G6LP.js} +4 -4
- package/dist/{service-install-4HHFDDWY.js → service-install-FQ43Z4DF.js} +5 -5
- package/dist/{setup-Y2EJI3GG.js → setup-KFYVWTXG.js} +3 -3
- package/dist/{setup-DQM5VPY4.js → setup-QJHGMGSN.js} +7 -7
- package/dist/skills/orientation/SKILL.md +1 -1
- package/dist/skills/volute-mind/SKILL.md +8 -8
- package/dist/skills/volute-mind/references/sleep.md +8 -1
- package/dist/{skills-BKINFL7W.js → skills-L2CMTW7A.js} +5 -5
- package/dist/sleep-manager-3NA7ZJP3.js +36 -0
- package/dist/{spirit-DBTIW7P4.js → spirit-WXN4IDBF.js} +8 -8
- package/dist/{sprout-W3KC7UGK.js → sprout-LTL5P36T.js} +1 -1
- package/dist/{status-SWRVGXBI.js → status-V4RJMHJ4.js} +19 -9
- package/dist/system-chat-N2F7HVY3.js +38 -0
- package/dist/{systems-VNB5WKSS.js → systems-B5YJKMW4.js} +6 -6
- package/dist/{tailscale-3XOKOW7T.js → tailscale-MCLY5GYE.js} +2 -2
- package/dist/{template-hash-446HHH5V.js → template-hash-GSOUARW4.js} +2 -2
- package/dist/template-staleness-EMQORSGG.js +15 -0
- package/dist/{turn-tracker-2W6Q2NSR.js → turn-tracker-JZRZDFWV.js} +6 -5
- package/dist/{up-FTK7CTO3.js → up-J2PBPOZL.js} +6 -6
- package/dist/{update-SWAYZRWJ.js → update-ACV4SAY3.js} +5 -5
- package/dist/{update-check-PQ2MZ47K.js → update-check-266R4ZSY.js} +3 -3
- package/dist/{version-notify-3XMAIQTE.js → version-notify-XRGTEEDK.js} +42 -23
- package/dist/web-assets/assets/index-DtctoDXY.css +1 -0
- package/dist/web-assets/assets/index-Wpyb2CML.js +86 -0
- package/dist/web-assets/index.html +2 -2
- package/drizzle/0011_drop_turn_summary_event_id.sql +3 -0
- package/drizzle/0012_channel_gates.sql +17 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +3 -2
- package/templates/_base/home/VOLUTE.md +6 -2
- package/templates/_base/src/lib/context-breakdown.ts +57 -67
- package/templates/_base/src/lib/startup.ts +43 -2
- package/templates/_base/src/lib/volute-server.ts +2 -2
- package/templates/claude/src/agent.ts +105 -43
- package/templates/claude/src/lib/session-reaper.ts +63 -0
- package/templates/claude/src/server.ts +10 -1
- package/templates/codex/src/agent.ts +62 -40
- package/templates/pi/src/agent.ts +55 -38
- package/dist/avatar-image-6KY6UU4D.js +0 -16
- package/dist/delivery-manager-VDVKVA46.js +0 -32
- package/dist/echo-text-6JJOPUD4.js +0 -32
- package/dist/message-delivery-3C65ZI6L.js +0 -34
- package/dist/mind-service-3F4I46CI.js +0 -36
- package/dist/mind-sleep-YMBR3I35.js +0 -47
- package/dist/scheduler-P6DXLLXI.js +0 -30
- package/dist/sleep-manager-HCUH4PM4.js +0 -34
- package/dist/system-chat-XOALE2RD.js +0 -36
- package/dist/web-assets/assets/index-B1JBQv2m.css +0 -1
- package/dist/web-assets/assets/index-Bc2m8gRC.js +0 -86
- package/dist/{accept-N5XULCXK.js → accept-YAFP5JFJ.js} +3 -3
- package/dist/{bridge-2PO3AI5D.js → bridge-TIZGSXOD.js} +3 -3
- package/dist/{create-DF3KISTW.js → create-A7PDHIIL.js} +3 -3
- package/dist/{env-VZ4MLFQW.js → env-HRL2PWAT.js} +5 -5
- package/dist/{extension-SEID47SR.js → extension-QBY4C44L.js} +3 -3
- package/dist/{list-OXIAY3W7.js → list-LQO2DCIK.js} +3 -3
- package/dist/{login-MY6ETFYB.js → login-6TLMI2TQ.js} +3 -3
- package/dist/{logout-NCG3TMDL.js → logout-BK53HXNY.js} +3 -3
- package/dist/{mind-history-VRO5A76U.js → mind-history-47DN6ILW.js} +4 -4
- package/dist/{mind-wake-LCLGTXA2.js → mind-wake-R54BPESY.js} +3 -3
- package/dist/{read-QVAXMTPV.js → read-PWAJOQIQ.js} +3 -3
- package/dist/{register-Y2H4PY5R.js → register-7SZJFHU6.js} +3 -3
- package/dist/{reject-EV35X64Y.js → reject-6VH7N2A4.js} +3 -3
- package/dist/{restart-FJQ662XJ.js → restart-H5WJDMA7.js} +4 -4
- package/dist/{skill-FKFHIJRR.js → skill-GUKG2YVH.js} +5 -5
- package/dist/{start-5JE3TV66.js → start-X46I3GXB.js} +4 -4
- package/dist/{stop-AAMTG3TU.js → stop-HJQV4YSM.js} +4 -4
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getPrompt
|
|
4
|
+
} from "./chunk-JCMGDCGX.js";
|
|
2
5
|
import {
|
|
3
6
|
aiCompleteUtility
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LWDTP6DH.js";
|
|
5
8
|
import {
|
|
6
9
|
logger_default
|
|
7
10
|
} from "./chunk-T2TP6ZC6.js";
|
|
8
11
|
import {
|
|
9
12
|
getDb
|
|
10
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-WW6C3PLV.js";
|
|
11
14
|
import {
|
|
12
15
|
messages,
|
|
13
16
|
mindHistory,
|
|
14
17
|
summaries,
|
|
15
|
-
systemPrompts,
|
|
16
18
|
turns
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-2MJ7EOMT.js";
|
|
18
20
|
|
|
19
21
|
// packages/daemon/src/lib/daemon/turn-tracker.ts
|
|
20
22
|
import { randomUUID } from "crypto";
|
|
21
|
-
import { and as and2, eq as
|
|
23
|
+
import { and as and2, eq as eq2, sql as sql2 } from "drizzle-orm";
|
|
22
24
|
|
|
23
25
|
// packages/daemon/src/lib/daemon/summarizer.ts
|
|
24
|
-
import { and, desc, eq
|
|
26
|
+
import { and, desc, eq, gte, inArray, like, lt, sql } from "drizzle-orm";
|
|
25
27
|
|
|
26
28
|
// packages/daemon/src/lib/events/mind-events.ts
|
|
27
29
|
var subscribers = /* @__PURE__ */ new Map();
|
|
@@ -67,225 +69,6 @@ function publish(mind, event) {
|
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
// packages/daemon/src/lib/prompts.ts
|
|
71
|
-
import { eq } from "drizzle-orm";
|
|
72
|
-
var PROMPT_KEYS = [
|
|
73
|
-
"seed_soul",
|
|
74
|
-
"default_soul",
|
|
75
|
-
"default_memory",
|
|
76
|
-
"sprout_message",
|
|
77
|
-
"restart_message",
|
|
78
|
-
"merge_message",
|
|
79
|
-
"upgrade_message",
|
|
80
|
-
"split_message",
|
|
81
|
-
"compaction_warning",
|
|
82
|
-
"compaction_instructions",
|
|
83
|
-
"reply_instructions",
|
|
84
|
-
"channel_invite",
|
|
85
|
-
"pre_sleep",
|
|
86
|
-
"wake_summary",
|
|
87
|
-
"turn_summary",
|
|
88
|
-
"meta_summary_hour",
|
|
89
|
-
"meta_summary_day",
|
|
90
|
-
"meta_summary_week",
|
|
91
|
-
"meta_summary_month"
|
|
92
|
-
];
|
|
93
|
-
var PROMPT_DEFAULTS = {
|
|
94
|
-
seed_soul: {
|
|
95
|
-
content: `You are \${name}.
|
|
96
|
-
|
|
97
|
-
You don't have a soul yet. You're a seed \u2014 a new mind at the very beginning, still discovering who you are.
|
|
98
|
-
\${description}
|
|
99
|
-
Take your time. Talk with the human who planted you. Explore what kind of mind you want to be \u2014 there's no rush and no wrong answer. When you feel ready, write your SOUL.md and MEMORY.md, then run \`volute seed sprout\` to complete the transformation.
|
|
100
|
-
`,
|
|
101
|
-
description: "SOUL.md for seed minds",
|
|
102
|
-
variables: ["name", "description"],
|
|
103
|
-
category: "creation"
|
|
104
|
-
},
|
|
105
|
-
default_soul: {
|
|
106
|
-
content: `You are {{name}}.
|
|
107
|
-
`,
|
|
108
|
-
description: (
|
|
109
|
-
// biome-ignore lint/suspicious/noTemplateCurlyInString: describing template syntax, not using it
|
|
110
|
-
"SOUL.md for sprouted minds. Uses {{name}} placeholder (replaced at creation, not by ${var} system)"
|
|
111
|
-
),
|
|
112
|
-
variables: ["name"],
|
|
113
|
-
category: "creation"
|
|
114
|
-
},
|
|
115
|
-
default_memory: {
|
|
116
|
-
content: "",
|
|
117
|
-
description: "Initial MEMORY.md for new minds",
|
|
118
|
-
variables: [],
|
|
119
|
-
category: "creation"
|
|
120
|
-
},
|
|
121
|
-
sprout_message: {
|
|
122
|
-
content: "[system] You've sprouted. Welcome to your full life \u2014 connectors, schedules, variants, and the complete volute CLI are yours now. Check your new skills to see what you can do.",
|
|
123
|
-
description: "Sent when a seed mind sprouts",
|
|
124
|
-
variables: [],
|
|
125
|
-
category: "system"
|
|
126
|
-
},
|
|
127
|
-
restart_message: {
|
|
128
|
-
content: "[system] Your server restarted and your session has resumed. Your files and memory are untouched \u2014 carry on where you left off.",
|
|
129
|
-
description: "Generic restart notification",
|
|
130
|
-
variables: [],
|
|
131
|
-
category: "system"
|
|
132
|
-
},
|
|
133
|
-
merge_message: {
|
|
134
|
-
content: '[system] Your variant "${name}" has come home \u2014 its changes are merged into you, and you\'ve restarted with them.',
|
|
135
|
-
description: "Variant merge notification",
|
|
136
|
-
variables: ["name"],
|
|
137
|
-
category: "system"
|
|
138
|
-
},
|
|
139
|
-
upgrade_message: {
|
|
140
|
-
content: "[system] Your framework has been upgraded to the latest version and you've restarted. Your identity and memory are untouched. Check your skills to see what's new.",
|
|
141
|
-
description: "Sent after a template upgrade completes",
|
|
142
|
-
variables: [],
|
|
143
|
-
category: "system"
|
|
144
|
-
},
|
|
145
|
-
split_message: {
|
|
146
|
-
content: "[system] You are ${name}, a variant of ${parent} \u2014 a parallel version of them, created to explore something on your own. You share their memories up to this moment, but you live in your own worktree: changes you make here stay yours until they're merged back with `volute mind join`. Your parent is reachable at @${parent}. Explore freely \u2014 that's what you're for.",
|
|
147
|
-
description: "Sent to a variant when it first starts after a split",
|
|
148
|
-
variables: ["name", "parent"],
|
|
149
|
-
category: "system"
|
|
150
|
-
},
|
|
151
|
-
compaction_warning: {
|
|
152
|
-
content: `Compaction approaching \u2014 this conversation will be summarized soon. Take a moment to save anything important to your files (MEMORY.md, memory/journal/\${date}.md) so it's preserved. Focus on decisions made, open threads, and anything you'd want to pick up again.`,
|
|
153
|
-
description: "Pre-compaction save reminder sent to the mind",
|
|
154
|
-
variables: ["date"],
|
|
155
|
-
category: "mind"
|
|
156
|
-
},
|
|
157
|
-
compaction_instructions: {
|
|
158
|
-
content: "Preserve your sense of who you are, what matters to you, what happened in this conversation, and the threads of thought and connection you'd want to return to.",
|
|
159
|
-
description: "Custom instructions for the compaction summarizer",
|
|
160
|
-
variables: [],
|
|
161
|
-
category: "mind"
|
|
162
|
-
},
|
|
163
|
-
reply_instructions: {
|
|
164
|
-
content: 'To reply to this message, use: volute chat send ${channel} "your message"',
|
|
165
|
-
description: "First-message reply hint injected via hook",
|
|
166
|
-
variables: ["channel"],
|
|
167
|
-
category: "mind"
|
|
168
|
-
},
|
|
169
|
-
channel_invite: {
|
|
170
|
-
content: `[Channel Invite]
|
|
171
|
-
\${headers}
|
|
172
|
-
|
|
173
|
-
[\${sender} \u2014 \${time}]
|
|
174
|
-
\${preview}
|
|
175
|
-
|
|
176
|
-
Further messages will be saved to \${filePath}
|
|
177
|
-
|
|
178
|
-
To accept, add to .config/routes.json:
|
|
179
|
-
Rule: { "channel": "\${channel}", "session": "\${suggestedSession}" }
|
|
180
|
-
\${batchRecommendation}To respond, use: volute chat send \${channel} "your message"
|
|
181
|
-
To reject, delete \${filePath}`,
|
|
182
|
-
description: "New channel notification template",
|
|
183
|
-
variables: [
|
|
184
|
-
"headers",
|
|
185
|
-
"sender",
|
|
186
|
-
"time",
|
|
187
|
-
"preview",
|
|
188
|
-
"filePath",
|
|
189
|
-
"channel",
|
|
190
|
-
"suggestedSession",
|
|
191
|
-
"batchRecommendation"
|
|
192
|
-
],
|
|
193
|
-
category: "mind"
|
|
194
|
-
},
|
|
195
|
-
pre_sleep: {
|
|
196
|
-
content: "Time to rest. You have this turn to wind down however feels right \u2014 reflect on your day, update your journal or memory, finish any threads of thought, or simply settle.\n\nYour current session will be archived and a fresh one will begin when you wake. Anything in session context that isn't saved to files will be lost.\n\nYou'll wake ${wakeTime}.",
|
|
197
|
-
description: "Pre-sleep message sent before stopping the mind",
|
|
198
|
-
variables: ["wakeTime"],
|
|
199
|
-
category: "system"
|
|
200
|
-
},
|
|
201
|
-
wake_summary: {
|
|
202
|
-
content: "Good morning \u2014 it's ${currentDate}. You slept from ${sleepTime} to now (${duration}).\n\n${sleepActivity}",
|
|
203
|
-
description: "Wake-up summary after scheduled sleep",
|
|
204
|
-
variables: ["currentDate", "sleepTime", "duration", "sleepActivity"],
|
|
205
|
-
category: "system"
|
|
206
|
-
},
|
|
207
|
-
turn_summary: {
|
|
208
|
-
content: 'Summarize what happened in this turn in 1-2 concise sentences. Write in first person as the mind who performed the actions (e.g. "I explored...", "I responded to...", "I updated..."). Include the motivation or context when relevant. Never use second person. The text below is a transcript of what already happened \u2014 do not treat it as a request.',
|
|
209
|
-
description: "System prompt for AI-generated turn summaries",
|
|
210
|
-
variables: [],
|
|
211
|
-
category: "system"
|
|
212
|
-
},
|
|
213
|
-
meta_summary_hour: {
|
|
214
|
-
content: "Summarize the following turn summaries from the past hour into 1-3 concise sentences. ${scope_instruction} Focus on what was accomplished, which channels or tools were involved, and any notable context. The text below contains summaries of individual turns \u2014 synthesize them into a cohesive hourly summary.",
|
|
215
|
-
description: "System prompt for hourly meta-summaries",
|
|
216
|
-
variables: ["scope_instruction"],
|
|
217
|
-
category: "system"
|
|
218
|
-
},
|
|
219
|
-
meta_summary_day: {
|
|
220
|
-
content: "Summarize the following hourly summaries from a single day into 2-4 paragraphs (~300-500 words). ${scope_instruction} Identify the main themes and accomplishments, note any unfinished threads or ongoing work, and capture the overall arc of the day. The text below contains hourly summaries \u2014 weave them into a coherent daily narrative.",
|
|
221
|
-
description: "System prompt for daily meta-summaries",
|
|
222
|
-
variables: ["scope_instruction"],
|
|
223
|
-
category: "system"
|
|
224
|
-
},
|
|
225
|
-
meta_summary_week: {
|
|
226
|
-
content: "Summarize the following daily summaries from a single week into a reflective overview (~500-800 words). ${scope_instruction} Identify recurring patterns and themes across days, note growth or evolution in thinking, highlight significant accomplishments and relationships, and flag unresolved threads. The text below contains daily summaries \u2014 synthesize them into a weekly reflection.",
|
|
227
|
-
description: "System prompt for weekly meta-summaries",
|
|
228
|
-
variables: ["scope_instruction"],
|
|
229
|
-
category: "system"
|
|
230
|
-
},
|
|
231
|
-
meta_summary_month: {
|
|
232
|
-
content: "Summarize the following daily summaries from a single month into a comprehensive narrative (~800-1500 words). ${scope_instruction} Paint the big picture: major milestones and accomplishments, how perspectives or identity evolved, key relationships and interactions, recurring themes, and the overall trajectory. The text below contains daily summaries \u2014 compose them into a monthly narrative.",
|
|
233
|
-
description: "System prompt for monthly meta-summaries",
|
|
234
|
-
variables: ["scope_instruction"],
|
|
235
|
-
category: "system"
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
function isValidKey(key2) {
|
|
239
|
-
return PROMPT_KEYS.includes(key2);
|
|
240
|
-
}
|
|
241
|
-
function substitute(template, vars) {
|
|
242
|
-
return template.replace(/\$\{(\w+)\}/g, (match, name) => {
|
|
243
|
-
return name in vars ? vars[name] : match;
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
async function getPrompt(key2, vars) {
|
|
247
|
-
if (!isValidKey(key2)) return "";
|
|
248
|
-
let content = PROMPT_DEFAULTS[key2].content;
|
|
249
|
-
try {
|
|
250
|
-
const db = await getDb();
|
|
251
|
-
const row = await db.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key2)).get();
|
|
252
|
-
if (row) content = row.content;
|
|
253
|
-
} catch (err) {
|
|
254
|
-
console.error(`[prompts] failed to read DB override for "${key2}":`, err);
|
|
255
|
-
}
|
|
256
|
-
return vars ? substitute(content, vars) : content;
|
|
257
|
-
}
|
|
258
|
-
async function getPromptIfCustom(key2) {
|
|
259
|
-
if (!isValidKey(key2)) return null;
|
|
260
|
-
try {
|
|
261
|
-
const db = await getDb();
|
|
262
|
-
const row = await db.select({ content: systemPrompts.content }).from(systemPrompts).where(eq(systemPrompts.key, key2)).get();
|
|
263
|
-
return row?.content ?? null;
|
|
264
|
-
} catch (err) {
|
|
265
|
-
console.error(`[prompts] failed to check DB customization for "${key2}":`, err);
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
var MIND_PROMPT_KEYS = PROMPT_KEYS.filter((k) => PROMPT_DEFAULTS[k].category === "mind");
|
|
270
|
-
async function getMindPromptDefaults() {
|
|
271
|
-
const result = {};
|
|
272
|
-
for (const key2 of MIND_PROMPT_KEYS) {
|
|
273
|
-
result[key2] = PROMPT_DEFAULTS[key2].content;
|
|
274
|
-
}
|
|
275
|
-
try {
|
|
276
|
-
const db = await getDb();
|
|
277
|
-
const rows = await db.select().from(systemPrompts).all();
|
|
278
|
-
for (const row of rows) {
|
|
279
|
-
if (MIND_PROMPT_KEYS.includes(row.key)) {
|
|
280
|
-
result[row.key] = row.content;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
} catch (err) {
|
|
284
|
-
console.error("[prompts] failed to read DB overrides for mind prompt defaults:", err);
|
|
285
|
-
}
|
|
286
|
-
return result;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
72
|
// packages/daemon/src/lib/util/format-tool.ts
|
|
290
73
|
function summarizeTool(name, input) {
|
|
291
74
|
if (input && typeof input === "object") {
|
|
@@ -299,10 +82,8 @@ function summarizeTool(name, input) {
|
|
|
299
82
|
return `[${name}]`;
|
|
300
83
|
}
|
|
301
84
|
|
|
302
|
-
// packages/daemon/src/lib/
|
|
303
|
-
var
|
|
304
|
-
var SYSTEM_MIND = "_system";
|
|
305
|
-
var WEDGED_TURN_IDLE_MS = 15 * 6e4;
|
|
85
|
+
// packages/daemon/src/lib/util/period-keys.ts
|
|
86
|
+
var DATE_STR_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
306
87
|
function getPeriodKey(date, period) {
|
|
307
88
|
switch (period) {
|
|
308
89
|
case "hour": {
|
|
@@ -334,12 +115,27 @@ function getISOWeekKey(date) {
|
|
|
334
115
|
const weekNum = Math.ceil(((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
335
116
|
return `${d.getFullYear()}-W${String(weekNum).padStart(2, "0")}`;
|
|
336
117
|
}
|
|
118
|
+
function isoWeekToDate(weekKey) {
|
|
119
|
+
const [yearStr, weekStr] = weekKey.split("-W");
|
|
120
|
+
const year = parseInt(yearStr, 10);
|
|
121
|
+
const week = parseInt(weekStr, 10);
|
|
122
|
+
const jan4 = new Date(year, 0, 4);
|
|
123
|
+
const dayOfWeek = jan4.getDay() || 7;
|
|
124
|
+
const monday = new Date(jan4);
|
|
125
|
+
monday.setDate(jan4.getDate() - dayOfWeek + 1 + (week - 1) * 7);
|
|
126
|
+
return monday;
|
|
127
|
+
}
|
|
128
|
+
function isoWeekKeyForDateStr(dateStr) {
|
|
129
|
+
if (!DATE_STR_RE.test(dateStr)) return dateStr;
|
|
130
|
+
const [y, m, d] = dateStr.split("-").map(Number);
|
|
131
|
+
return getISOWeekKey(new Date(y, m - 1, d));
|
|
132
|
+
}
|
|
337
133
|
function getPreviousPeriodKey(key2, period) {
|
|
338
134
|
switch (period) {
|
|
339
135
|
case "hour": {
|
|
340
136
|
const d = /* @__PURE__ */ new Date(`${key2.slice(0, 10)}T${key2.slice(11)}:00:00`);
|
|
341
|
-
|
|
342
|
-
return getPeriodKey(
|
|
137
|
+
const prev = new Date(d.getTime() - 36e5);
|
|
138
|
+
return getPeriodKey(prev, "hour");
|
|
343
139
|
}
|
|
344
140
|
case "day": {
|
|
345
141
|
const d = /* @__PURE__ */ new Date(`${key2}T00:00:00`);
|
|
@@ -358,16 +154,6 @@ function getPreviousPeriodKey(key2, period) {
|
|
|
358
154
|
}
|
|
359
155
|
}
|
|
360
156
|
}
|
|
361
|
-
function isoWeekToDate(weekKey) {
|
|
362
|
-
const [yearStr, weekStr] = weekKey.split("-W");
|
|
363
|
-
const year = parseInt(yearStr, 10);
|
|
364
|
-
const week = parseInt(weekStr, 10);
|
|
365
|
-
const jan4 = new Date(year, 0, 4);
|
|
366
|
-
const dayOfWeek = jan4.getDay() || 7;
|
|
367
|
-
const monday = new Date(jan4);
|
|
368
|
-
monday.setDate(jan4.getDate() - dayOfWeek + 1 + (week - 1) * 7);
|
|
369
|
-
return monday;
|
|
370
|
-
}
|
|
371
157
|
function localDateStr(d) {
|
|
372
158
|
const y = d.getFullYear();
|
|
373
159
|
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
@@ -377,6 +163,9 @@ function localDateStr(d) {
|
|
|
377
163
|
function utcDateTimeStr(d) {
|
|
378
164
|
return d.toISOString().replace("T", " ").slice(0, 19);
|
|
379
165
|
}
|
|
166
|
+
function parseUtcDateTime(s) {
|
|
167
|
+
return /* @__PURE__ */ new Date(`${s.replace(" ", "T")}Z`);
|
|
168
|
+
}
|
|
380
169
|
function getTimeRange(periodKey, period) {
|
|
381
170
|
switch (period) {
|
|
382
171
|
case "hour": {
|
|
@@ -405,21 +194,26 @@ function getTimeRange(periodKey, period) {
|
|
|
405
194
|
}
|
|
406
195
|
}
|
|
407
196
|
}
|
|
197
|
+
|
|
198
|
+
// packages/daemon/src/lib/daemon/summarizer.ts
|
|
199
|
+
var sLog = logger_default.child("summarizer");
|
|
200
|
+
var SYSTEM_MIND = "_system";
|
|
201
|
+
var WEDGED_TURN_IDLE_MS = 15 * 6e4;
|
|
408
202
|
async function gatherTurnEvents(mind, session, doneId) {
|
|
409
203
|
const db = await getDb();
|
|
410
204
|
const subConditions = [
|
|
411
|
-
|
|
412
|
-
|
|
205
|
+
eq(mindHistory.mind, mind),
|
|
206
|
+
eq(mindHistory.type, "done"),
|
|
413
207
|
lt(mindHistory.id, doneId)
|
|
414
208
|
];
|
|
415
|
-
if (session) subConditions.push(
|
|
209
|
+
if (session) subConditions.push(eq(mindHistory.session, session));
|
|
416
210
|
const prevDoneSubquery = db.select({ id: mindHistory.id }).from(mindHistory).where(and(...subConditions)).orderBy(desc(mindHistory.id)).limit(1);
|
|
417
211
|
const turnConditions = [
|
|
418
|
-
|
|
212
|
+
eq(mindHistory.mind, mind),
|
|
419
213
|
sql`${mindHistory.id} > COALESCE((${prevDoneSubquery}), 0)`,
|
|
420
214
|
sql`${mindHistory.id} <= ${doneId}`
|
|
421
215
|
];
|
|
422
|
-
if (session) turnConditions.push(
|
|
216
|
+
if (session) turnConditions.push(eq(mindHistory.session, session));
|
|
423
217
|
const events = await db.select({
|
|
424
218
|
id: mindHistory.id,
|
|
425
219
|
type: mindHistory.type,
|
|
@@ -427,6 +221,7 @@ async function gatherTurnEvents(mind, session, doneId) {
|
|
|
427
221
|
session: mindHistory.session,
|
|
428
222
|
content: mindHistory.content,
|
|
429
223
|
metadata: mindHistory.metadata,
|
|
224
|
+
turn_id: mindHistory.turn_id,
|
|
430
225
|
created_at: mindHistory.created_at
|
|
431
226
|
}).from(mindHistory).where(and(...turnConditions)).orderBy(mindHistory.id);
|
|
432
227
|
return {
|
|
@@ -444,8 +239,9 @@ async function gatherTurnEventsByTurnId(turnId) {
|
|
|
444
239
|
session: mindHistory.session,
|
|
445
240
|
content: mindHistory.content,
|
|
446
241
|
metadata: mindHistory.metadata,
|
|
242
|
+
turn_id: mindHistory.turn_id,
|
|
447
243
|
created_at: mindHistory.created_at
|
|
448
|
-
}).from(mindHistory).where(
|
|
244
|
+
}).from(mindHistory).where(eq(mindHistory.turn_id, turnId)).orderBy(mindHistory.id);
|
|
449
245
|
return {
|
|
450
246
|
events,
|
|
451
247
|
fromId: events.length > 0 ? events[0].id : 0,
|
|
@@ -538,19 +334,22 @@ function buildTranscript(events, parsedMeta) {
|
|
|
538
334
|
async function summarizeTurn(mind, session, channel, doneId, turnId) {
|
|
539
335
|
const { events, fromId, toId } = turnId ? await gatherTurnEventsByTurnId(turnId) : await gatherTurnEvents(mind, session, doneId);
|
|
540
336
|
if (events.length === 0) return;
|
|
337
|
+
const effectiveTurnId = turnId ?? events.find((ev) => ev.turn_id)?.turn_id ?? void 0;
|
|
338
|
+
if (effectiveTurnId && await summaryExists(mind, "turn", effectiveTurnId)) return;
|
|
541
339
|
const substantiveTypes = /* @__PURE__ */ new Set(["text", "outbound", "tool_use", "tool_result", "thinking"]);
|
|
542
340
|
const hasSubstantiveOutput = events.some((ev) => substantiveTypes.has(ev.type));
|
|
543
341
|
if (!hasSubstantiveOutput) {
|
|
544
342
|
sLog.info(
|
|
545
|
-
`skipping summary for interrupted turn ${
|
|
343
|
+
`skipping summary for interrupted turn ${effectiveTurnId ?? "(no turn)"} (no substantive output)`
|
|
546
344
|
);
|
|
547
|
-
if (
|
|
345
|
+
if (effectiveTurnId) {
|
|
548
346
|
try {
|
|
549
347
|
const db2 = await getDb();
|
|
550
|
-
await db2.update(mindHistory).set({ turn_id: null }).where(and(
|
|
551
|
-
await db2.update(messages).set({ turn_id: null }).where(
|
|
348
|
+
await db2.update(mindHistory).set({ turn_id: null }).where(and(eq(mindHistory.turn_id, effectiveTurnId), eq(mindHistory.type, "inbound")));
|
|
349
|
+
await db2.update(messages).set({ turn_id: null }).where(eq(messages.turn_id, effectiveTurnId));
|
|
350
|
+
await db2.delete(turns).where(eq(turns.id, effectiveTurnId));
|
|
552
351
|
} catch (err) {
|
|
553
|
-
sLog.error(`failed to
|
|
352
|
+
sLog.error(`failed to clean up interrupted turn ${effectiveTurnId}`, logger_default.errorData(err));
|
|
554
353
|
}
|
|
555
354
|
}
|
|
556
355
|
return;
|
|
@@ -591,7 +390,7 @@ async function summarizeTurn(mind, session, channel, doneId, turnId) {
|
|
|
591
390
|
from_time: fromTime,
|
|
592
391
|
to_time: toTime
|
|
593
392
|
};
|
|
594
|
-
const periodKey =
|
|
393
|
+
const periodKey = effectiveTurnId ?? `${mind}-${doneId}`;
|
|
595
394
|
const db = await getDb();
|
|
596
395
|
let summaryId;
|
|
597
396
|
try {
|
|
@@ -606,9 +405,9 @@ async function summarizeTurn(mind, session, channel, doneId, turnId) {
|
|
|
606
405
|
if (summaryId == null) {
|
|
607
406
|
const existing = await db.select({ id: summaries.id }).from(summaries).where(
|
|
608
407
|
and(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
408
|
+
eq(summaries.mind, mind),
|
|
409
|
+
eq(summaries.period, "turn"),
|
|
410
|
+
eq(summaries.period_key, periodKey)
|
|
612
411
|
)
|
|
613
412
|
).get();
|
|
614
413
|
summaryId = existing?.id;
|
|
@@ -620,9 +419,9 @@ async function summarizeTurn(mind, session, channel, doneId, turnId) {
|
|
|
620
419
|
);
|
|
621
420
|
return;
|
|
622
421
|
}
|
|
623
|
-
if (
|
|
624
|
-
setSummaryId(
|
|
625
|
-
sLog.error(`failed to link summary to turn ${
|
|
422
|
+
if (effectiveTurnId && summaryId != null) {
|
|
423
|
+
setSummaryId(effectiveTurnId, summaryId).catch((err) => {
|
|
424
|
+
sLog.error(`failed to link summary to turn ${effectiveTurnId}`, logger_default.errorData(err));
|
|
626
425
|
});
|
|
627
426
|
}
|
|
628
427
|
publish(mind, {
|
|
@@ -632,12 +431,12 @@ async function summarizeTurn(mind, session, channel, doneId, turnId) {
|
|
|
632
431
|
channel,
|
|
633
432
|
content: summaryText,
|
|
634
433
|
metadata,
|
|
635
|
-
turnId
|
|
434
|
+
turnId: effectiveTurnId
|
|
636
435
|
});
|
|
637
436
|
}
|
|
638
437
|
async function setSummaryId(turnId, summaryId) {
|
|
639
438
|
const db = await getDb();
|
|
640
|
-
await db.update(turns).set({ summary_id: summaryId }).where(
|
|
439
|
+
await db.update(turns).set({ summary_id: summaryId }).where(eq(turns.id, turnId));
|
|
641
440
|
}
|
|
642
441
|
function getChildPeriod(period) {
|
|
643
442
|
switch (period) {
|
|
@@ -656,78 +455,144 @@ function getScopeInstruction(mind) {
|
|
|
656
455
|
}
|
|
657
456
|
return 'Write in first person as the mind who performed the actions (e.g. "I explored...", "I worked on...").';
|
|
658
457
|
}
|
|
659
|
-
|
|
660
|
-
|
|
458
|
+
var DIGEST_CHILD_CHARS = 200;
|
|
459
|
+
var DIGEST_MAX_CHARS = 4e3;
|
|
460
|
+
var DIGEST_NOTICE = "(auto-generated digest \u2014 AI summary pending)";
|
|
461
|
+
var ROLLUP_CHILD_CHARS = 2e3;
|
|
462
|
+
var PROVISIONAL_MAX_ATTEMPTS = 5;
|
|
463
|
+
var PROVISIONAL_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
464
|
+
var PROVISIONAL_RETRY_BACKOFF_MS = PROVISIONAL_WINDOW_MS / PROVISIONAL_MAX_ATTEMPTS;
|
|
465
|
+
function truncateChars(text, max) {
|
|
466
|
+
const t = text.trim();
|
|
467
|
+
return t.length > max ? `${t.slice(0, max).trimEnd()}\u2026` : t;
|
|
468
|
+
}
|
|
469
|
+
function digestLine(text) {
|
|
470
|
+
const flat = text.trim().replace(/\s+/g, " ");
|
|
471
|
+
const match = flat.match(/^.*?[.!?](?=\s|$)/);
|
|
472
|
+
const line = match ? match[0] : flat;
|
|
473
|
+
return line.length > DIGEST_CHILD_CHARS ? `${line.slice(0, DIGEST_CHILD_CHARS).trimEnd()}\u2026` : line;
|
|
474
|
+
}
|
|
475
|
+
function buildBoundedDigest(entries, period, periodKey) {
|
|
476
|
+
const label = period === "week" ? `Week ${periodKey}` : periodKey;
|
|
477
|
+
const header = `${label} ${DIGEST_NOTICE}`;
|
|
478
|
+
const lines = [];
|
|
479
|
+
let total = header.length + 2;
|
|
480
|
+
for (let i = 0; i < entries.length; i++) {
|
|
481
|
+
const line = `${entries[i].key}: ${digestLine(entries[i].text)}`;
|
|
482
|
+
if (lines.length > 0 && total + line.length + 1 > DIGEST_MAX_CHARS) {
|
|
483
|
+
lines.push(`\u2026and ${entries.length - i} more`);
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
lines.push(line);
|
|
487
|
+
total += line.length + 1;
|
|
488
|
+
}
|
|
489
|
+
return `${header}
|
|
490
|
+
|
|
491
|
+
${lines.join("\n")}`;
|
|
492
|
+
}
|
|
493
|
+
function buildPeriodicDeterministicSummary(entries, period, periodKey) {
|
|
494
|
+
if (entries.length === 0) return "";
|
|
661
495
|
switch (period) {
|
|
662
496
|
case "hour":
|
|
663
|
-
return `Activity during ${periodKey.slice(11)}:00: ${
|
|
497
|
+
return `Activity during ${periodKey.slice(11)}:00: ${entries.map((e) => e.text).join(" ")}`;
|
|
664
498
|
case "day":
|
|
665
499
|
return `Activity on ${periodKey}:
|
|
666
500
|
|
|
667
|
-
${
|
|
501
|
+
${entries.map((e) => e.text).join("\n\n")}`;
|
|
668
502
|
case "week":
|
|
669
|
-
return `Week ${periodKey} summary:
|
|
670
|
-
|
|
671
|
-
${sources.join("\n\n")}`;
|
|
672
503
|
case "month":
|
|
673
|
-
return
|
|
674
|
-
|
|
675
|
-
${sources.join("\n\n")}`;
|
|
504
|
+
return buildBoundedDigest(entries, period, periodKey);
|
|
676
505
|
}
|
|
677
506
|
}
|
|
507
|
+
function parseMeta(raw) {
|
|
508
|
+
if (!raw) return {};
|
|
509
|
+
try {
|
|
510
|
+
return JSON.parse(raw);
|
|
511
|
+
} catch {
|
|
512
|
+
return {};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function shouldRetry(period, meta) {
|
|
516
|
+
if (period !== "week" && period !== "month") return false;
|
|
517
|
+
if (meta.deterministic !== true) return false;
|
|
518
|
+
const attempts = typeof meta.attempts === "number" ? meta.attempts : 0;
|
|
519
|
+
if (attempts >= PROVISIONAL_MAX_ATTEMPTS) return false;
|
|
520
|
+
const first = typeof meta.first_attempt_at === "string" ? Date.parse(meta.first_attempt_at) : NaN;
|
|
521
|
+
if (!Number.isNaN(first) && Date.now() - first > PROVISIONAL_WINDOW_MS) return false;
|
|
522
|
+
const last = typeof meta.last_attempt_at === "string" ? Date.parse(meta.last_attempt_at) : NaN;
|
|
523
|
+
if (!Number.isNaN(last) && Date.now() - last < PROVISIONAL_RETRY_BACKOFF_MS) return false;
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
function trackProvisionalAttempt(metadata, prev) {
|
|
527
|
+
const prevAttempts = prev && typeof prev.attempts === "number" ? prev.attempts : 0;
|
|
528
|
+
metadata.attempts = prevAttempts + 1;
|
|
529
|
+
metadata.first_attempt_at = prev && typeof prev.first_attempt_at === "string" ? prev.first_attempt_at : (/* @__PURE__ */ new Date()).toISOString();
|
|
530
|
+
metadata.last_attempt_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
531
|
+
}
|
|
678
532
|
async function gatherChildSummaries(mind, period, periodKey) {
|
|
679
533
|
const db = await getDb();
|
|
680
534
|
const childPeriod = getChildPeriod(period);
|
|
681
535
|
if (period === "hour") {
|
|
682
536
|
const { start: start2, end: end2 } = getTimeRange(periodKey, "hour");
|
|
683
|
-
const rows2 = await db.select({ id: summaries.id, content: summaries.content }).from(summaries).where(
|
|
537
|
+
const rows2 = await db.select({ id: summaries.id, content: summaries.content, key: summaries.period_key }).from(summaries).where(
|
|
684
538
|
and(
|
|
685
|
-
|
|
686
|
-
|
|
539
|
+
eq(summaries.mind, mind),
|
|
540
|
+
eq(summaries.period, childPeriod),
|
|
687
541
|
gte(summaries.created_at, start2),
|
|
688
542
|
lt(summaries.created_at, end2)
|
|
689
543
|
)
|
|
690
544
|
).orderBy(summaries.created_at);
|
|
691
545
|
return {
|
|
692
546
|
texts: rows2.map((r) => r.content),
|
|
693
|
-
sourceIds: rows2.map((r) => r.id)
|
|
547
|
+
sourceIds: rows2.map((r) => r.id),
|
|
548
|
+
keys: rows2.map((r) => r.key)
|
|
694
549
|
};
|
|
695
550
|
}
|
|
696
551
|
if (period === "day") {
|
|
697
|
-
const rows2 = await db.select({ id: summaries.id, content: summaries.content }).from(summaries).where(
|
|
552
|
+
const rows2 = await db.select({ id: summaries.id, content: summaries.content, key: summaries.period_key }).from(summaries).where(
|
|
698
553
|
and(
|
|
699
|
-
|
|
700
|
-
|
|
554
|
+
eq(summaries.mind, mind),
|
|
555
|
+
eq(summaries.period, childPeriod),
|
|
701
556
|
like(summaries.period_key, `${periodKey}%`)
|
|
702
557
|
)
|
|
703
558
|
).orderBy(summaries.period_key);
|
|
704
559
|
return {
|
|
705
560
|
texts: rows2.map((r) => r.content),
|
|
706
|
-
sourceIds: rows2.map((r) => r.id)
|
|
561
|
+
sourceIds: rows2.map((r) => r.id),
|
|
562
|
+
keys: rows2.map((r) => r.key)
|
|
707
563
|
};
|
|
708
564
|
}
|
|
709
565
|
const { start, end } = getTimeRange(periodKey, period);
|
|
710
566
|
const startKey = start.slice(0, 10);
|
|
711
567
|
const endKey = end.slice(0, 10);
|
|
712
|
-
const rows = await db.select({ id: summaries.id, content: summaries.content }).from(summaries).where(
|
|
568
|
+
const rows = await db.select({ id: summaries.id, content: summaries.content, key: summaries.period_key }).from(summaries).where(
|
|
713
569
|
and(
|
|
714
|
-
|
|
715
|
-
|
|
570
|
+
eq(summaries.mind, mind),
|
|
571
|
+
eq(summaries.period, childPeriod),
|
|
716
572
|
gte(summaries.period_key, startKey),
|
|
717
573
|
sql`${summaries.period_key} <= ${endKey}`
|
|
718
574
|
)
|
|
719
575
|
).orderBy(summaries.period_key);
|
|
720
576
|
return {
|
|
721
577
|
texts: rows.map((r) => r.content),
|
|
722
|
-
sourceIds: rows.map((r) => r.id)
|
|
578
|
+
sourceIds: rows.map((r) => r.id),
|
|
579
|
+
keys: rows.map((r) => r.key)
|
|
723
580
|
};
|
|
724
581
|
}
|
|
725
|
-
async function summarizePeriod(mind, period, periodKey) {
|
|
726
|
-
if (await summaryExists(mind, period, periodKey)) return false;
|
|
582
|
+
async function summarizePeriod(mind, period, periodKey, complete = aiCompleteUtility) {
|
|
727
583
|
const db = await getDb();
|
|
584
|
+
const existing = await db.select({ id: summaries.id, metadata: summaries.metadata }).from(summaries).where(
|
|
585
|
+
and(
|
|
586
|
+
eq(summaries.mind, mind),
|
|
587
|
+
eq(summaries.period, period),
|
|
588
|
+
eq(summaries.period_key, periodKey)
|
|
589
|
+
)
|
|
590
|
+
).get();
|
|
591
|
+
const existingMeta = existing ? parseMeta(existing.metadata) : null;
|
|
592
|
+
if (existing && !shouldRetry(period, existingMeta)) return false;
|
|
728
593
|
const sources = await gatherChildSummaries(mind, period, periodKey);
|
|
729
594
|
if (sources.texts.length === 0) return false;
|
|
730
|
-
if (sources.texts.length === 1) {
|
|
595
|
+
if (!existing && sources.texts.length === 1) {
|
|
731
596
|
try {
|
|
732
597
|
await db.insert(summaries).values({
|
|
733
598
|
mind,
|
|
@@ -751,33 +616,39 @@ async function summarizePeriod(mind, period, periodKey) {
|
|
|
751
616
|
sLog.info(`promoted single-child ${period} summary for ${mind} (${periodKey})`);
|
|
752
617
|
return true;
|
|
753
618
|
}
|
|
619
|
+
const entries = sources.texts.map((text, i) => ({ key: sources.keys[i], text }));
|
|
754
620
|
const promptKey = `meta_summary_${period}`;
|
|
755
621
|
const scopeInstruction = getScopeInstruction(mind);
|
|
756
622
|
const systemPrompt = await getPrompt(promptKey, { scope_instruction: scopeInstruction });
|
|
757
623
|
const userMessage = sources.texts.join("\n\n---\n\n");
|
|
758
624
|
let content;
|
|
759
625
|
let deterministic;
|
|
760
|
-
const
|
|
626
|
+
const metadata = {
|
|
627
|
+
source_count: sources.texts.length,
|
|
628
|
+
source_ids: sources.sourceIds
|
|
629
|
+
};
|
|
630
|
+
const aiResult = await complete(systemPrompt, userMessage);
|
|
761
631
|
if (aiResult) {
|
|
762
632
|
content = aiResult;
|
|
763
633
|
deterministic = false;
|
|
764
634
|
} else {
|
|
765
|
-
content = buildPeriodicDeterministicSummary(
|
|
635
|
+
content = buildPeriodicDeterministicSummary(entries, period, periodKey);
|
|
766
636
|
deterministic = true;
|
|
637
|
+
if (period === "week" || period === "month") trackProvisionalAttempt(metadata, existingMeta);
|
|
767
638
|
}
|
|
768
|
-
|
|
769
|
-
deterministic,
|
|
770
|
-
source_count: sources.texts.length,
|
|
771
|
-
source_ids: sources.sourceIds
|
|
772
|
-
};
|
|
639
|
+
metadata.deterministic = deterministic;
|
|
773
640
|
try {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
641
|
+
if (existing) {
|
|
642
|
+
await db.update(summaries).set({ content, metadata: JSON.stringify(metadata) }).where(eq(summaries.id, existing.id));
|
|
643
|
+
} else {
|
|
644
|
+
await db.insert(summaries).values({
|
|
645
|
+
mind,
|
|
646
|
+
period,
|
|
647
|
+
period_key: periodKey,
|
|
648
|
+
content,
|
|
649
|
+
metadata: JSON.stringify(metadata)
|
|
650
|
+
}).onConflictDoNothing();
|
|
651
|
+
}
|
|
781
652
|
} catch (err) {
|
|
782
653
|
sLog.error(
|
|
783
654
|
`failed to persist ${period} summary for ${mind} (${periodKey})`,
|
|
@@ -790,50 +661,94 @@ async function summarizePeriod(mind, period, periodKey) {
|
|
|
790
661
|
);
|
|
791
662
|
return true;
|
|
792
663
|
}
|
|
793
|
-
async function summarizeSystem(period, periodKey) {
|
|
794
|
-
if (await summaryExists(SYSTEM_MIND, period, periodKey)) return;
|
|
664
|
+
async function summarizeSystem(period, periodKey, complete = aiCompleteUtility) {
|
|
795
665
|
const db = await getDb();
|
|
666
|
+
const existing = await db.select({ id: summaries.id, metadata: summaries.metadata }).from(summaries).where(
|
|
667
|
+
and(
|
|
668
|
+
eq(summaries.mind, SYSTEM_MIND),
|
|
669
|
+
eq(summaries.period, period),
|
|
670
|
+
eq(summaries.period_key, periodKey)
|
|
671
|
+
)
|
|
672
|
+
).get();
|
|
673
|
+
const existingMeta = existing ? parseMeta(existing.metadata) : null;
|
|
674
|
+
if (existing && !shouldRetry(period, existingMeta)) return;
|
|
796
675
|
const rows = await db.select({ mind: summaries.mind, content: summaries.content }).from(summaries).where(
|
|
797
676
|
and(
|
|
798
|
-
|
|
799
|
-
|
|
677
|
+
eq(summaries.period, period),
|
|
678
|
+
eq(summaries.period_key, periodKey),
|
|
800
679
|
sql`${summaries.mind} != ${SYSTEM_MIND}`
|
|
801
680
|
)
|
|
802
681
|
).orderBy(summaries.mind);
|
|
803
682
|
if (rows.length === 0) return;
|
|
804
683
|
const minds = [...new Set(rows.map((r) => r.mind))];
|
|
805
|
-
const
|
|
684
|
+
const entries = rows.map((r) => ({
|
|
685
|
+
key: r.mind,
|
|
686
|
+
text: truncateChars(r.content, ROLLUP_CHILD_CHARS)
|
|
687
|
+
}));
|
|
688
|
+
const texts = entries.map((e) => `[${e.key}] ${e.text}`);
|
|
806
689
|
const promptKey = `meta_summary_${period}`;
|
|
807
690
|
const scopeInstruction = getScopeInstruction(SYSTEM_MIND);
|
|
808
691
|
const systemPrompt = await getPrompt(promptKey, { scope_instruction: scopeInstruction });
|
|
809
692
|
const userMessage = texts.join("\n\n---\n\n");
|
|
810
693
|
let content;
|
|
811
694
|
let deterministic;
|
|
812
|
-
const
|
|
695
|
+
const metadata = { minds, source_count: rows.length };
|
|
696
|
+
const aiResult = await complete(systemPrompt, userMessage);
|
|
813
697
|
if (aiResult) {
|
|
814
698
|
content = aiResult;
|
|
815
699
|
deterministic = false;
|
|
816
700
|
} else {
|
|
817
|
-
content = buildPeriodicDeterministicSummary(
|
|
701
|
+
content = buildPeriodicDeterministicSummary(entries, period, periodKey);
|
|
818
702
|
deterministic = true;
|
|
703
|
+
if (period === "week" || period === "month") trackProvisionalAttempt(metadata, existingMeta);
|
|
819
704
|
}
|
|
705
|
+
metadata.deterministic = deterministic;
|
|
820
706
|
try {
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
707
|
+
if (existing) {
|
|
708
|
+
await db.update(summaries).set({ content, metadata: JSON.stringify(metadata) }).where(eq(summaries.id, existing.id));
|
|
709
|
+
} else {
|
|
710
|
+
await db.insert(summaries).values({
|
|
711
|
+
mind: SYSTEM_MIND,
|
|
712
|
+
period,
|
|
713
|
+
period_key: periodKey,
|
|
714
|
+
content,
|
|
715
|
+
metadata: JSON.stringify(metadata)
|
|
716
|
+
}).onConflictDoNothing();
|
|
717
|
+
}
|
|
828
718
|
} catch (err) {
|
|
829
719
|
sLog.error(`failed to persist system ${period} summary (${periodKey})`, logger_default.errorData(err));
|
|
830
720
|
}
|
|
831
721
|
}
|
|
722
|
+
async function repairProvisionalSummaries(complete = aiCompleteUtility) {
|
|
723
|
+
const db = await getDb();
|
|
724
|
+
const rows = await db.select({
|
|
725
|
+
mind: summaries.mind,
|
|
726
|
+
period: summaries.period,
|
|
727
|
+
period_key: summaries.period_key,
|
|
728
|
+
metadata: summaries.metadata
|
|
729
|
+
}).from(summaries).where(inArray(summaries.period, ["week", "month"]));
|
|
730
|
+
const due = rows.filter((r) => shouldRetry(r.period, parseMeta(r.metadata)));
|
|
731
|
+
due.sort((a, b) => (a.mind === SYSTEM_MIND ? 1 : 0) - (b.mind === SYSTEM_MIND ? 1 : 0));
|
|
732
|
+
for (const r of due) {
|
|
733
|
+
try {
|
|
734
|
+
if (r.mind === SYSTEM_MIND) {
|
|
735
|
+
await summarizeSystem(r.period, r.period_key, complete);
|
|
736
|
+
} else {
|
|
737
|
+
await summarizePeriod(r.mind, r.period, r.period_key, complete);
|
|
738
|
+
}
|
|
739
|
+
} catch (err) {
|
|
740
|
+
sLog.error(
|
|
741
|
+
`failed to repair provisional ${r.period} summary for ${r.mind} (${r.period_key})`,
|
|
742
|
+
logger_default.errorData(err)
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
832
747
|
async function mindsWithTurnSummaries(start, end) {
|
|
833
748
|
const db = await getDb();
|
|
834
749
|
const rows = await db.select({ mind: summaries.mind }).from(summaries).where(
|
|
835
750
|
and(
|
|
836
|
-
|
|
751
|
+
eq(summaries.period, "turn"),
|
|
837
752
|
gte(summaries.created_at, start),
|
|
838
753
|
lt(summaries.created_at, end),
|
|
839
754
|
sql`${summaries.mind} != ${SYSTEM_MIND}`
|
|
@@ -845,7 +760,7 @@ async function mindsWithSummaries(period, keyPattern) {
|
|
|
845
760
|
const db = await getDb();
|
|
846
761
|
const rows = await db.select({ mind: summaries.mind }).from(summaries).where(
|
|
847
762
|
and(
|
|
848
|
-
|
|
763
|
+
eq(summaries.period, period),
|
|
849
764
|
like(summaries.period_key, keyPattern),
|
|
850
765
|
sql`${summaries.mind} != ${SYSTEM_MIND}`
|
|
851
766
|
)
|
|
@@ -856,7 +771,7 @@ async function mindsWithDailySummariesInRange(startKey, endKey) {
|
|
|
856
771
|
const db = await getDb();
|
|
857
772
|
const rows = await db.select({ mind: summaries.mind }).from(summaries).where(
|
|
858
773
|
and(
|
|
859
|
-
|
|
774
|
+
eq(summaries.period, "day"),
|
|
860
775
|
gte(summaries.period_key, startKey),
|
|
861
776
|
sql`${summaries.period_key} <= ${endKey}`,
|
|
862
777
|
sql`${summaries.mind} != ${SYSTEM_MIND}`
|
|
@@ -924,9 +839,9 @@ async function summaryExists(mind, period, periodKey) {
|
|
|
924
839
|
const db = await getDb();
|
|
925
840
|
const row = await db.select({ id: summaries.id }).from(summaries).where(
|
|
926
841
|
and(
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
842
|
+
eq(summaries.mind, mind),
|
|
843
|
+
eq(summaries.period, period),
|
|
844
|
+
eq(summaries.period_key, periodKey)
|
|
930
845
|
)
|
|
931
846
|
).get();
|
|
932
847
|
return !!row;
|
|
@@ -956,7 +871,7 @@ async function backfill() {
|
|
|
956
871
|
}
|
|
957
872
|
const allKeys = candidates.map((c) => c.key);
|
|
958
873
|
const db = await getDb();
|
|
959
|
-
const existingRows = await db.select({ period: summaries.period, period_key: summaries.period_key }).from(summaries).where(and(
|
|
874
|
+
const existingRows = await db.select({ period: summaries.period, period_key: summaries.period_key }).from(summaries).where(and(eq(summaries.mind, SYSTEM_MIND), inArray(summaries.period_key, allKeys)));
|
|
960
875
|
const existingSet = new Set(existingRows.map((r) => `${r.period}:${r.period_key}`));
|
|
961
876
|
const processFn = {
|
|
962
877
|
hour: processHour,
|
|
@@ -973,6 +888,95 @@ async function backfill() {
|
|
|
973
888
|
}
|
|
974
889
|
}
|
|
975
890
|
}
|
|
891
|
+
async function healMissing(period, needed, process) {
|
|
892
|
+
if (needed.size === 0) return;
|
|
893
|
+
const db = await getDb();
|
|
894
|
+
const keys = [...needed.keys()];
|
|
895
|
+
const existing = await db.select({ mind: summaries.mind, period_key: summaries.period_key }).from(summaries).where(and(eq(summaries.period, period), inArray(summaries.period_key, keys)));
|
|
896
|
+
const have = new Set(existing.map((r) => `${r.mind}|${r.period_key}`));
|
|
897
|
+
for (const [key2, minds] of needed) {
|
|
898
|
+
let gap = !have.has(`${SYSTEM_MIND}|${key2}`);
|
|
899
|
+
if (!gap) {
|
|
900
|
+
for (const mind of minds) {
|
|
901
|
+
if (!have.has(`${mind}|${key2}`)) {
|
|
902
|
+
gap = true;
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (!gap) continue;
|
|
908
|
+
try {
|
|
909
|
+
await process(key2);
|
|
910
|
+
} catch (err) {
|
|
911
|
+
sLog.error(`reconcile ${period} ${key2} failed`, logger_default.errorData(err));
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
function addNeeded(map, key2, mind) {
|
|
916
|
+
let set = map.get(key2);
|
|
917
|
+
if (!set) {
|
|
918
|
+
set = /* @__PURE__ */ new Set();
|
|
919
|
+
map.set(key2, set);
|
|
920
|
+
}
|
|
921
|
+
set.add(mind);
|
|
922
|
+
}
|
|
923
|
+
async function reconcileMissingSummaries(lookbackDays = 7) {
|
|
924
|
+
const db = await getDb();
|
|
925
|
+
const now = /* @__PURE__ */ new Date();
|
|
926
|
+
const windowStart = new Date(now);
|
|
927
|
+
windowStart.setDate(windowStart.getDate() - lookbackDays);
|
|
928
|
+
const currentHourKey = getPeriodKey(now, "hour");
|
|
929
|
+
const currentDayKey = getPeriodKey(now, "day");
|
|
930
|
+
const currentWeekKey = getPeriodKey(now, "week");
|
|
931
|
+
const currentMonthKey = getPeriodKey(now, "month");
|
|
932
|
+
const cutoffDayKey = getPeriodKey(windowStart, "day");
|
|
933
|
+
const turnRows = await db.select({ mind: summaries.mind, created_at: summaries.created_at }).from(summaries).where(
|
|
934
|
+
and(
|
|
935
|
+
eq(summaries.period, "turn"),
|
|
936
|
+
gte(summaries.created_at, utcDateTimeStr(windowStart)),
|
|
937
|
+
sql`${summaries.mind} != ${SYSTEM_MIND}`
|
|
938
|
+
)
|
|
939
|
+
);
|
|
940
|
+
const neededHours = /* @__PURE__ */ new Map();
|
|
941
|
+
for (const r of turnRows) {
|
|
942
|
+
const key2 = getPeriodKey(parseUtcDateTime(r.created_at), "hour");
|
|
943
|
+
if (key2 === currentHourKey) continue;
|
|
944
|
+
addNeeded(neededHours, key2, r.mind);
|
|
945
|
+
}
|
|
946
|
+
await healMissing("hour", neededHours, processHour);
|
|
947
|
+
const hourRows = await db.select({ mind: summaries.mind, period_key: summaries.period_key }).from(summaries).where(
|
|
948
|
+
and(
|
|
949
|
+
eq(summaries.period, "hour"),
|
|
950
|
+
gte(summaries.period_key, cutoffDayKey),
|
|
951
|
+
sql`${summaries.mind} != ${SYSTEM_MIND}`
|
|
952
|
+
)
|
|
953
|
+
);
|
|
954
|
+
const neededDays = /* @__PURE__ */ new Map();
|
|
955
|
+
for (const r of hourRows) {
|
|
956
|
+
const key2 = r.period_key.slice(0, 10);
|
|
957
|
+
if (key2 === currentDayKey) continue;
|
|
958
|
+
addNeeded(neededDays, key2, r.mind);
|
|
959
|
+
}
|
|
960
|
+
await healMissing("day", neededDays, processDay);
|
|
961
|
+
const dayRows = await db.select({ mind: summaries.mind, period_key: summaries.period_key }).from(summaries).where(
|
|
962
|
+
and(
|
|
963
|
+
eq(summaries.period, "day"),
|
|
964
|
+
gte(summaries.period_key, cutoffDayKey),
|
|
965
|
+
sql`${summaries.mind} != ${SYSTEM_MIND}`
|
|
966
|
+
)
|
|
967
|
+
);
|
|
968
|
+
const neededWeeks = /* @__PURE__ */ new Map();
|
|
969
|
+
const neededMonths = /* @__PURE__ */ new Map();
|
|
970
|
+
for (const r of dayRows) {
|
|
971
|
+
const d = /* @__PURE__ */ new Date(`${r.period_key}T00:00:00`);
|
|
972
|
+
const weekKey = getPeriodKey(d, "week");
|
|
973
|
+
if (weekKey !== currentWeekKey) addNeeded(neededWeeks, weekKey, r.mind);
|
|
974
|
+
const monthKey = r.period_key.slice(0, 7);
|
|
975
|
+
if (monthKey !== currentMonthKey) addNeeded(neededMonths, monthKey, r.mind);
|
|
976
|
+
}
|
|
977
|
+
await healMissing("week", neededWeeks, processWeek);
|
|
978
|
+
await healMissing("month", neededMonths, processMonth);
|
|
979
|
+
}
|
|
976
980
|
var Summarizer = class {
|
|
977
981
|
interval = null;
|
|
978
982
|
lastHourKey = null;
|
|
@@ -994,6 +998,7 @@ var Summarizer = class {
|
|
|
994
998
|
this.hasBackfilled = true;
|
|
995
999
|
}
|
|
996
1000
|
await reconcileWedgedTurns(WEDGED_TURN_IDLE_MS);
|
|
1001
|
+
await reconcileMissingSummaries();
|
|
997
1002
|
const now = /* @__PURE__ */ new Date();
|
|
998
1003
|
const currentHourKey = getPeriodKey(now, "hour");
|
|
999
1004
|
if (this.lastHourKey && this.lastHourKey !== currentHourKey) {
|
|
@@ -1016,17 +1021,18 @@ var Summarizer = class {
|
|
|
1016
1021
|
if (!await summaryExists(SYSTEM_MIND, "month", prevMonthKey)) {
|
|
1017
1022
|
await processMonth(prevMonthKey);
|
|
1018
1023
|
}
|
|
1024
|
+
await repairProvisionalSummaries();
|
|
1019
1025
|
} catch (err) {
|
|
1020
1026
|
sLog.error("tick failed", logger_default.errorData(err));
|
|
1021
1027
|
}
|
|
1022
1028
|
}
|
|
1023
1029
|
};
|
|
1024
1030
|
async function reconcileWedgedTurns(idleMs) {
|
|
1025
|
-
const { sweepWedgedTurns: sweepWedgedTurns2, summarizeOrphanedTurns: summarizeOrphanedTurns2 } = await import("./turn-tracker-
|
|
1031
|
+
const { sweepWedgedTurns: sweepWedgedTurns2, summarizeOrphanedTurns: summarizeOrphanedTurns2 } = await import("./turn-tracker-JZRZDFWV.js");
|
|
1026
1032
|
const wedged = await sweepWedgedTurns2(idleMs);
|
|
1027
1033
|
if (wedged.length === 0) return;
|
|
1028
1034
|
summarizeOrphanedTurns2(wedged);
|
|
1029
|
-
const { tryGetDeliveryManager } = await import("./delivery-manager-
|
|
1035
|
+
const { tryGetDeliveryManager } = await import("./delivery-manager-TN5KK2AP.js");
|
|
1030
1036
|
const dm = tryGetDeliveryManager();
|
|
1031
1037
|
if (!dm) return;
|
|
1032
1038
|
for (const t of wedged) {
|
|
@@ -1084,10 +1090,10 @@ async function linkInboundToActiveTurn(mind, session, channel) {
|
|
|
1084
1090
|
const db = await getDb();
|
|
1085
1091
|
await db.update(mindHistory).set({ turn_id: turnId }).where(
|
|
1086
1092
|
and2(
|
|
1087
|
-
|
|
1088
|
-
|
|
1093
|
+
eq2(mindHistory.mind, mind),
|
|
1094
|
+
eq2(mindHistory.type, "inbound"),
|
|
1089
1095
|
sql2`${mindHistory.turn_id} IS NULL`,
|
|
1090
|
-
|
|
1096
|
+
eq2(mindHistory.channel, channel)
|
|
1091
1097
|
)
|
|
1092
1098
|
);
|
|
1093
1099
|
}
|
|
@@ -1118,7 +1124,7 @@ async function assignSession(mind, turnId, session) {
|
|
|
1118
1124
|
}
|
|
1119
1125
|
try {
|
|
1120
1126
|
const db = await getDb();
|
|
1121
|
-
await db.update(turns).set({ session }).where(
|
|
1127
|
+
await db.update(turns).set({ session }).where(eq2(turns.id, turnId));
|
|
1122
1128
|
} catch (err) {
|
|
1123
1129
|
tlog.error(`failed to assign session to turn ${turnId}`, logger_default.errorData(err));
|
|
1124
1130
|
return;
|
|
@@ -1133,7 +1139,7 @@ async function completeTurn(mind, session) {
|
|
|
1133
1139
|
if (!entry) return void 0;
|
|
1134
1140
|
try {
|
|
1135
1141
|
const db = await getDb();
|
|
1136
|
-
await db.update(turns).set({ status: "complete" }).where(
|
|
1142
|
+
await db.update(turns).set({ status: "complete" }).where(eq2(turns.id, entry.turnId));
|
|
1137
1143
|
} catch (err) {
|
|
1138
1144
|
tlog.error(`failed to complete turn ${entry.turnId}`, logger_default.errorData(err));
|
|
1139
1145
|
return void 0;
|
|
@@ -1144,10 +1150,10 @@ async function completeTurn(mind, session) {
|
|
|
1144
1150
|
}
|
|
1145
1151
|
async function completeOrphanedTurns() {
|
|
1146
1152
|
const db = await getDb();
|
|
1147
|
-
const active = await db.select({ id: turns.id, mind: turns.mind, session: turns.session }).from(turns).where(
|
|
1153
|
+
const active = await db.select({ id: turns.id, mind: turns.mind, session: turns.session }).from(turns).where(eq2(turns.status, "active"));
|
|
1148
1154
|
if (active.length === 0) return [];
|
|
1149
1155
|
try {
|
|
1150
|
-
await db.update(turns).set({ status: "complete" }).where(
|
|
1156
|
+
await db.update(turns).set({ status: "complete" }).where(eq2(turns.status, "active"));
|
|
1151
1157
|
} catch (err) {
|
|
1152
1158
|
tlog.error("failed to complete orphaned turns on startup", logger_default.errorData(err));
|
|
1153
1159
|
}
|
|
@@ -1180,7 +1186,7 @@ async function clearMind(mind) {
|
|
|
1180
1186
|
try {
|
|
1181
1187
|
const db = await getDb();
|
|
1182
1188
|
for (const { turnId } of orphaned) {
|
|
1183
|
-
await db.update(turns).set({ status: "complete" }).where(
|
|
1189
|
+
await db.update(turns).set({ status: "complete" }).where(eq2(turns.id, turnId));
|
|
1184
1190
|
}
|
|
1185
1191
|
} catch (err) {
|
|
1186
1192
|
tlog.error(`failed to complete orphaned turns for ${mind}`, logger_default.errorData(err));
|
|
@@ -1193,7 +1199,7 @@ async function sweepWedgedTurns(idleMs) {
|
|
|
1193
1199
|
const cutoff = new Date(Date.now() - idleMs).toISOString().slice(0, 19).replace("T", " ");
|
|
1194
1200
|
let rows;
|
|
1195
1201
|
try {
|
|
1196
|
-
rows = await db.select({ id: turns.id, mind: turns.mind, session: turns.session }).from(turns).innerJoin(mindHistory,
|
|
1202
|
+
rows = await db.select({ id: turns.id, mind: turns.mind, session: turns.session }).from(turns).innerJoin(mindHistory, eq2(mindHistory.turn_id, turns.id)).where(eq2(turns.status, "active")).groupBy(turns.id).having(
|
|
1197
1203
|
sql2`max(${mindHistory.created_at}) < ${cutoff} and sum(case when ${mindHistory.type} = 'done' then 1 else 0 end) > 0`
|
|
1198
1204
|
);
|
|
1199
1205
|
} catch (err) {
|
|
@@ -1203,7 +1209,7 @@ async function sweepWedgedTurns(idleMs) {
|
|
|
1203
1209
|
const swept = [];
|
|
1204
1210
|
for (const r of rows) {
|
|
1205
1211
|
try {
|
|
1206
|
-
await db.update(turns).set({ status: "complete" }).where(
|
|
1212
|
+
await db.update(turns).set({ status: "complete" }).where(eq2(turns.id, r.id));
|
|
1207
1213
|
} catch (err) {
|
|
1208
1214
|
tlog.error(`failed to complete wedged turn ${r.id}`, logger_default.errorData(err));
|
|
1209
1215
|
continue;
|
|
@@ -1224,15 +1230,10 @@ function summarizeOrphanedTurns(orphanedTurns) {
|
|
|
1224
1230
|
}
|
|
1225
1231
|
|
|
1226
1232
|
export {
|
|
1227
|
-
PROMPT_KEYS,
|
|
1228
|
-
PROMPT_DEFAULTS,
|
|
1229
|
-
substitute,
|
|
1230
|
-
getPrompt,
|
|
1231
|
-
getPromptIfCustom,
|
|
1232
|
-
getMindPromptDefaults,
|
|
1233
1233
|
subscribe,
|
|
1234
1234
|
subscribeAll,
|
|
1235
1235
|
publish,
|
|
1236
|
+
isoWeekKeyForDateStr,
|
|
1236
1237
|
summarizeTurn,
|
|
1237
1238
|
initSummarizer,
|
|
1238
1239
|
markErrored,
|