@cuongtran001/kanna 1.2.0 → 1.3.1
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/client/assets/{arc-BLk1CZKB.js → arc-DXtN3EWJ.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-B01ZQYte.js → architectureDiagram-3BPJPVTR-CIGqW1z-.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-diL6a3cq.js → blockDiagram-GPEHLZMM-BafNS_H6.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-BY1verL8.js → c4Diagram-AAUBKEIU-YdAgRSis.js} +1 -1
- package/dist/client/assets/channel-BEYTrUAm.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-psdZuajw.js → chunk-2J33WTMH-eiP5y5G-.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-CrwQVBc5.js → chunk-4BX2VUAB-CvLGzPMF.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-9-aj4jKO.js → chunk-55IACEB6-Y-S9Fb8A.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-1FNK9_nB.js → chunk-727SXJPM-6sJH1RI9.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DFhvd_Ek.js → chunk-AQP2D5EJ-hV1r_IXO.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-B7MLrDa7.js → chunk-FMBD7UC4-CC3L28S5.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-D4Df05QE.js → chunk-ND2GUHAM-BZ3czhdH.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-Iq17Dznm.js → chunk-QZHKN3VN-Bjyd6Yr-.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CvnF5Ud6.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CvnF5Ud6.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-BmaZhtXy.js → cose-bilkent-S5V4N54A-CfxdDghE.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-BCmpfnfP.js → dagre-BM42HDAG-B8CddsbB.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-BIEcKowS.js → diagram-2AECGRRQ-F0yS0fVB.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-CRBABBfv.js → diagram-5GNKFQAL-cW0i0qlh.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-BRxUk16N.js → diagram-KO2AKTUF-BzAKMeJT.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DPBs4xl1.js → diagram-LMA3HP47-qdC4XqIl.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-BE9rgVv8.js → diagram-OG6HWLK6-CGcpi6Dh.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-D3vgA1xJ.js → erDiagram-TEJ5UH35-CxNTDicM.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-Cgu-8XXC.js → flowDiagram-I6XJVG4X-qkGa5TqY.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CgjXVyTR.js → ganttDiagram-6RSMTGT7-D9mVPoQi.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-zD5D8B-i.js → gitGraphDiagram-PVQCEYII-DA0grAXe.js} +1 -1
- package/dist/client/assets/{index-DBTdhMbJ.js → index-BqUY1Bm4.js} +1 -1
- package/dist/client/assets/index-CLsOWXff.css +32 -0
- package/dist/client/assets/{index-zQLLeAfl.js → index-TU1k6ov6.js} +253 -243
- package/dist/client/assets/{infoDiagram-5YYISTIA-BaFHqT4u.js → infoDiagram-5YYISTIA-CGljtLs3.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-2yAC1br2.js → ishikawaDiagram-YF4QCWOH-CtC0xrmG.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-DacQ-ND5.js → journeyDiagram-JHISSGLW-URLnVZeN.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-Qlbwm3z4.js → kanban-definition-UN3LZRKU-C2Q6aqQh.js} +1 -1
- package/dist/client/assets/{linear-DeafqpgO.js → linear-ByKHtsIx.js} +1 -1
- package/dist/client/assets/{mermaid.core-KYiCPJDj.js → mermaid.core-Dl_f2KLc.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-B1V-T6d3.js → mindmap-definition-RKZ34NQL-CaJ9IDLI.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-DOF9OFRu.js → pieDiagram-4H26LBE5-mmYaf2Oa.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-lH1mB7ko.js → quadrantDiagram-W4KKPZXB-B_PO6OCC.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-BaTsJ_sS.js → requirementDiagram-4Y6WPE33-M4huwJt1.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Pfxib5iI.js → sankeyDiagram-5OEKKPKP-B0hb1sWh.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BQMEKkBC.js → sequenceDiagram-3UESZ5HK-BAqSptnA.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-CuldNnzp.js → stateDiagram-AJRCARHV-DTNkvPi5.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CVN3oGTF.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-DP7u0YAg.js → timeline-definition-PNZ67QCA-MgAjHz8Q.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-QlrjpNeQ.js → vennDiagram-CIIHVFJN-DmKoO51W.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-BqdJ_sAM.js → wardley-L42UT6IY-CWcyHnYs.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Wi3kaQ5X.js → wardleyDiagram-YWT4CUSO-BGIGmFP9.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-D-KXMyCG.js → xychartDiagram-2RQKCTM6-D20YIvEl.js} +1 -1
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/src/server/agent-coordinator-types.ts +158 -0
- package/src/server/agent-coordinator.ts +919 -0
- package/src/server/agent-deps-builders.ts +425 -0
- package/src/server/agent.oauth-rotation.test.ts +101 -0
- package/src/server/agent.test.ts +24 -8
- package/src/server/agent.ts +35 -4963
- package/src/server/auto-continue/read-model.test.ts +2 -2
- package/src/server/auto-continue/read-model.ts +4 -1
- package/src/server/claude-autocontinue-commands.test.ts +494 -0
- package/src/server/claude-autocontinue-commands.ts +256 -0
- package/src/server/claude-cancel-handler.test.ts +472 -0
- package/src/server/claude-cancel-handler.ts +241 -0
- package/src/server/claude-chat-management.test.ts +395 -0
- package/src/server/claude-chat-management.ts +251 -0
- package/src/server/claude-harness-stream.test.ts +273 -0
- package/src/server/claude-harness-stream.ts +158 -0
- package/src/server/claude-loop-orch-commands.test.ts +579 -0
- package/src/server/claude-loop-orch-commands.ts +466 -0
- package/src/server/claude-message-normalizer.test.ts +213 -0
- package/src/server/claude-message-normalizer.ts +436 -0
- package/src/server/claude-prompt-helpers.test.ts +248 -0
- package/src/server/claude-prompt-helpers.ts +139 -0
- package/src/server/claude-sdk-queue.test.ts +169 -0
- package/src/server/claude-sdk-queue.ts +82 -0
- package/src/server/claude-send-command.test.ts +414 -0
- package/src/server/claude-send-command.ts +420 -0
- package/src/server/claude-session-config-helpers.test.ts +279 -0
- package/src/server/claude-session-config-helpers.ts +151 -0
- package/src/server/claude-session-config.test.ts +250 -0
- package/src/server/claude-session-config.ts +159 -0
- package/src/server/claude-session-error-handler.test.ts +498 -0
- package/src/server/claude-session-error-handler.ts +361 -0
- package/src/server/claude-session-lifecycle.test.ts +669 -0
- package/src/server/claude-session-lifecycle.ts +283 -0
- package/src/server/claude-session-rebuild.test.ts +224 -0
- package/src/server/claude-session-rebuild.ts +101 -0
- package/src/server/claude-session-runner.test.ts +523 -0
- package/src/server/claude-session-runner.ts +521 -0
- package/src/server/claude-session-spawner.test.ts +401 -0
- package/src/server/claude-session-spawner.ts +401 -0
- package/src/server/claude-session-start.test.ts +205 -0
- package/src/server/claude-session-start.ts +277 -0
- package/src/server/claude-session-state-queries.test.ts +344 -0
- package/src/server/claude-session-state-queries.ts +160 -0
- package/src/server/claude-session-state.ts +104 -0
- package/src/server/claude-slash-commands.test.ts +277 -0
- package/src/server/claude-slash-commands.ts +234 -0
- package/src/server/claude-spawn-helpers.test.ts +221 -0
- package/src/server/claude-spawn-helpers.ts +179 -0
- package/src/server/claude-steer-log.test.ts +243 -0
- package/src/server/claude-steer-log.ts +58 -0
- package/src/server/claude-subagent-tool-response.test.ts +213 -0
- package/src/server/claude-subagent-tool-response.ts +165 -0
- package/src/server/claude-subagent-wiring.test.ts +263 -0
- package/src/server/claude-subagent-wiring.ts +324 -0
- package/src/server/claude-tool-respond.test.ts +321 -0
- package/src/server/claude-tool-respond.ts +142 -0
- package/src/server/claude-turn-runner.test.ts +310 -0
- package/src/server/claude-turn-runner.ts +228 -0
- package/src/server/claude-turn-starter.test.ts +266 -0
- package/src/server/claude-turn-starter.ts +566 -0
- package/src/server/claude-usage-math.test.ts +169 -0
- package/src/server/claude-usage-math.ts +132 -0
- package/src/server/diff-store-branch-ops.adapter.test.ts +332 -0
- package/src/server/diff-store-branch-ops.adapter.ts +602 -0
- package/src/server/diff-store-commit-ops.adapter.test.ts +358 -0
- package/src/server/diff-store-commit-ops.adapter.ts +258 -0
- package/src/server/diff-store-errors.ts +85 -0
- package/src/server/diff-store-file-ops.adapter.ts +212 -0
- package/src/server/diff-store-git-branch.adapter.ts +686 -0
- package/src/server/diff-store-io.adapter.ts +27 -0
- package/src/server/diff-store-parse.test.ts +212 -0
- package/src/server/diff-store-parse.ts +122 -0
- package/src/server/diff-store-state.ts +82 -0
- package/src/server/diff-store.ts +76 -1854
- package/src/server/event-store-apply.test.ts +280 -0
- package/src/server/event-store-apply.ts +132 -0
- package/src/server/event-store-chat-lifecycle.test.ts +408 -0
- package/src/server/event-store-chat-lifecycle.ts +441 -0
- package/src/server/event-store-entity-write.ts +356 -0
- package/src/server/event-store-helpers.test.ts +230 -0
- package/src/server/event-store-helpers.ts +235 -0
- package/src/server/event-store-init.ts +276 -0
- package/src/server/event-store-messages.adapter.test.ts +365 -0
- package/src/server/event-store-messages.adapter.ts +213 -0
- package/src/server/event-store-orch-subscription.ts +124 -0
- package/src/server/event-store-orch.test.ts +462 -0
- package/src/server/event-store-orch.ts +312 -0
- package/src/server/event-store-peripheral-events.adapter.test.ts +254 -0
- package/src/server/event-store-peripheral-events.adapter.ts +105 -0
- package/src/server/event-store-snapshot.test.ts +597 -0
- package/src/server/event-store-snapshot.ts +658 -0
- package/src/server/event-store-subagent.test.ts +408 -0
- package/src/server/event-store-subagent.ts +198 -0
- package/src/server/event-store-tool-requests.test.ts +210 -0
- package/src/server/event-store-tool-requests.ts +131 -0
- package/src/server/event-store-transcript-write.adapter.ts +285 -0
- package/src/server/event-store-write-ops.test.ts +554 -0
- package/src/server/event-store-write-ops.ts +529 -0
- package/src/server/event-store.test.ts +35 -0
- package/src/server/event-store.ts +251 -2284
- package/src/server/events.ts +3 -0
- package/src/server/harness-types.ts +15 -1
- package/src/server/oauth-errors.ts +19 -0
- package/src/server/read-models.ts +26 -1
- package/src/server/subagent-orchestrator.ts +3 -0
- package/src/server/ws-router-agent-ctrl.test.ts +187 -0
- package/src/server/ws-router-agent-ctrl.ts +137 -0
- package/src/server/ws-router-broadcast.ts +488 -0
- package/src/server/ws-router-chat.test.ts +461 -0
- package/src/server/ws-router-chat.ts +279 -0
- package/src/server/ws-router-defaults.test.ts +283 -0
- package/src/server/ws-router-defaults.ts +434 -0
- package/src/server/ws-router-diff.test.ts +167 -0
- package/src/server/ws-router-diff.ts +250 -0
- package/src/server/ws-router-envelope.ts +329 -0
- package/src/server/ws-router-misc.test.ts +422 -0
- package/src/server/ws-router-misc.ts +265 -0
- package/src/server/ws-router-orch.test.ts +190 -0
- package/src/server/ws-router-orch.ts +93 -0
- package/src/server/ws-router-project.test.ts +403 -0
- package/src/server/ws-router-project.ts +256 -0
- package/src/server/ws-router-push.test.ts +193 -0
- package/src/server/ws-router-push.ts +120 -0
- package/src/server/ws-router-settings.test.ts +173 -0
- package/src/server/ws-router-settings.ts +416 -0
- package/src/server/ws-router-skills.test.ts +194 -0
- package/src/server/ws-router-skills.ts +204 -0
- package/src/server/ws-router-utils.ts +224 -0
- package/src/server/ws-router.ts +339 -2254
- package/src/shared/app-settings-types.ts +365 -0
- package/src/shared/core-types.ts +27 -0
- package/src/shared/git-diff-types.ts +165 -0
- package/src/shared/loop-progress.test.ts +106 -0
- package/src/shared/loop-progress.ts +88 -0
- package/src/shared/mcp-types.ts +105 -0
- package/src/shared/provider-model-types.ts +437 -0
- package/src/shared/subagent-types.ts +198 -0
- package/src/shared/tool-call-types.ts +314 -0
- package/src/shared/transcript-types.ts +270 -0
- package/src/shared/types.ts +27 -1668
- package/dist/client/assets/channel-BMkhjiLI.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-C5Kg1XOD.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C5Kg1XOD.js +0 -1
- package/dist/client/assets/index-H6C-f2Vp.css +0 -32
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ca2U11af.js +0 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helper functions extracted from event-store.ts.
|
|
3
|
+
* No EventStore class dependencies. No IO side effects.
|
|
4
|
+
*/
|
|
5
|
+
import { log } from "../shared/log"
|
|
6
|
+
import type { ChatHistorySnapshot, SlashCommand, TranscriptEntry } from "../shared/types"
|
|
7
|
+
import { cloneTranscriptEntries } from "./events"
|
|
8
|
+
import type { StoreEvent } from "./events"
|
|
9
|
+
|
|
10
|
+
export interface TranscriptPageResult {
|
|
11
|
+
entries: TranscriptEntry[]
|
|
12
|
+
hasOlder: boolean
|
|
13
|
+
olderCursor: string | null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function normalizeSidebarProjectOrder<T>(value: T): string[] {
|
|
17
|
+
if (!Array.isArray(value)) {
|
|
18
|
+
return []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const seen = new Set<string>()
|
|
22
|
+
const projectIds: string[] = []
|
|
23
|
+
for (const entry of value) {
|
|
24
|
+
if (typeof entry !== "string") continue
|
|
25
|
+
const projectId = entry.trim()
|
|
26
|
+
if (!projectId || seen.has(projectId)) continue
|
|
27
|
+
seen.add(projectId)
|
|
28
|
+
projectIds.push(projectId)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return projectIds
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isSendToStartingProfilingEnabled(): boolean {
|
|
35
|
+
return process.env.KANNA_PROFILE_SEND_TO_STARTING === "1"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function logSendToStartingProfile(stage: string, details?: Record<string, unknown>): void {
|
|
39
|
+
if (!isSendToStartingProfilingEnabled()) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
log.info("[kanna/send->starting][server]", JSON.stringify({
|
|
44
|
+
stage,
|
|
45
|
+
...details,
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getReplayEventPriority(event: StoreEvent): number {
|
|
50
|
+
const discriminator = "type" in event ? event.type : event.kind
|
|
51
|
+
switch (discriminator) {
|
|
52
|
+
case "project_opened":
|
|
53
|
+
case "project_removed":
|
|
54
|
+
case "sidebar_project_order_set":
|
|
55
|
+
case "project_star_set":
|
|
56
|
+
return 0
|
|
57
|
+
case "chat_created":
|
|
58
|
+
return 1
|
|
59
|
+
case "chat_renamed":
|
|
60
|
+
case "chat_provider_set":
|
|
61
|
+
case "chat_plan_mode_set":
|
|
62
|
+
return 2
|
|
63
|
+
case "message_appended":
|
|
64
|
+
return 3
|
|
65
|
+
case "queued_message_enqueued":
|
|
66
|
+
case "queued_message_removed":
|
|
67
|
+
return 4
|
|
68
|
+
case "turn_started":
|
|
69
|
+
return 5
|
|
70
|
+
case "session_token_set":
|
|
71
|
+
case "session_commands_loaded":
|
|
72
|
+
return 6
|
|
73
|
+
case "pending_fork_session_token_set":
|
|
74
|
+
return 6
|
|
75
|
+
case "turn_cancelled":
|
|
76
|
+
return 7
|
|
77
|
+
case "turn_finished":
|
|
78
|
+
case "turn_failed":
|
|
79
|
+
return 8
|
|
80
|
+
case "chat_read_state_set":
|
|
81
|
+
case "chat_source_hash_set":
|
|
82
|
+
case "chat_policy_override_set":
|
|
83
|
+
case "chat_compact_failures_set":
|
|
84
|
+
return 9
|
|
85
|
+
case "chat_deleted":
|
|
86
|
+
case "chat_archived":
|
|
87
|
+
case "chat_unarchived":
|
|
88
|
+
return 10
|
|
89
|
+
case "auto_continue_proposed":
|
|
90
|
+
case "auto_continue_accepted":
|
|
91
|
+
case "auto_continue_rescheduled":
|
|
92
|
+
case "auto_continue_cancelled":
|
|
93
|
+
case "auto_continue_fired":
|
|
94
|
+
case "loop_armed":
|
|
95
|
+
case "loop_disarmed":
|
|
96
|
+
return 11
|
|
97
|
+
case "stack_added":
|
|
98
|
+
case "stack_removed":
|
|
99
|
+
case "stack_renamed":
|
|
100
|
+
case "stack_project_added":
|
|
101
|
+
case "stack_project_removed":
|
|
102
|
+
return 0
|
|
103
|
+
case "subagent_run_started":
|
|
104
|
+
case "subagent_message_delta":
|
|
105
|
+
case "subagent_entry_appended":
|
|
106
|
+
case "subagent_run_completed":
|
|
107
|
+
case "subagent_run_failed":
|
|
108
|
+
case "subagent_run_cancelled":
|
|
109
|
+
case "subagent_tool_pending":
|
|
110
|
+
case "subagent_tool_resolved":
|
|
111
|
+
return 5
|
|
112
|
+
// tool_request_put shares priority 5 with subagent_* events; sourceIndex
|
|
113
|
+
// tie-break orders them (tool-requests has sourceIndex 7, turns has 5).
|
|
114
|
+
case "tool_request_put":
|
|
115
|
+
return 5
|
|
116
|
+
case "tool_request_resolved":
|
|
117
|
+
return 6
|
|
118
|
+
case "orch_run_created":
|
|
119
|
+
case "orch_worktree_provisioned":
|
|
120
|
+
case "orch_worktree_init_started":
|
|
121
|
+
case "orch_worktree_init_completed":
|
|
122
|
+
case "orch_task_claimed":
|
|
123
|
+
case "orch_phase_started":
|
|
124
|
+
case "orch_phase_completed":
|
|
125
|
+
case "orch_gate_opened":
|
|
126
|
+
case "orch_gate_resolved":
|
|
127
|
+
case "orch_scope_overlap_flagged":
|
|
128
|
+
case "orch_config_warning":
|
|
129
|
+
case "orch_verify_started":
|
|
130
|
+
case "orch_verify_completed":
|
|
131
|
+
case "orch_task_committed":
|
|
132
|
+
case "orch_task_failed":
|
|
133
|
+
case "orch_task_requeued":
|
|
134
|
+
case "orch_run_completed":
|
|
135
|
+
case "orch_run_cancelled":
|
|
136
|
+
return 5
|
|
137
|
+
default: {
|
|
138
|
+
const _exhaustive: never = discriminator
|
|
139
|
+
throw new Error(`Unhandled replay event type: ${String(_exhaustive)}`)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function encodeHistoryCursor(index: number): string {
|
|
145
|
+
return `idx:${index}`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function decodeCursor(cursor: string): number {
|
|
149
|
+
if (cursor.startsWith("idx:")) {
|
|
150
|
+
const value = Number.parseInt(cursor.slice("idx:".length), 10)
|
|
151
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
152
|
+
throw new Error("Invalid history cursor")
|
|
153
|
+
}
|
|
154
|
+
return value
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
throw new Error("Invalid history cursor")
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function slashCommandsEqual(a: SlashCommand[], b: SlashCommand[]): boolean {
|
|
161
|
+
if (a.length !== b.length) return false
|
|
162
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
163
|
+
const ai = a[i]
|
|
164
|
+
const bi = b[i]
|
|
165
|
+
if (ai.name !== bi.name || ai.description !== bi.description || ai.argumentHint !== bi.argumentHint) {
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return true
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// `context_window_updated` entries are token-readout noise emitted once per
|
|
173
|
+
// stream tick — only the latest value matters. A single turn can emit hundreds
|
|
174
|
+
// of them, and the bounded live window (getMessagesPageFromEntries) counts each
|
|
175
|
+
// 1:1 against the limit, so a flood evicts real turns (e.g. the user_prompt)
|
|
176
|
+
// out of the snapshot the client renders. Collapsing each maximal run of
|
|
177
|
+
// consecutive cwu entries to its last entry keeps the latest readout while
|
|
178
|
+
// freeing window budget for real turns. Applied only on the live-window read
|
|
179
|
+
// path (not getMessages), so getLatestContextWindowUsage / full-transcript
|
|
180
|
+
// export / importer still observe every persisted cwu.
|
|
181
|
+
export function coalesceContextWindowUpdates(entries: TranscriptEntry[]): TranscriptEntry[] {
|
|
182
|
+
const result: TranscriptEntry[] = []
|
|
183
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
184
|
+
const entry = entries[index]!
|
|
185
|
+
const next = entries[index + 1]
|
|
186
|
+
if (entry.kind === "context_window_updated" && next?.kind === "context_window_updated") {
|
|
187
|
+
// Drop this cwu; the last of the run survives.
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
result.push(entry)
|
|
191
|
+
}
|
|
192
|
+
return result
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function getHistorySnapshot(page: TranscriptPageResult, recentLimit: number): ChatHistorySnapshot {
|
|
196
|
+
return {
|
|
197
|
+
hasOlder: page.hasOlder,
|
|
198
|
+
olderCursor: page.olderCursor,
|
|
199
|
+
recentLimit,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function getForkedChatTitle(title: string): string {
|
|
204
|
+
const trimmed = title.trim()
|
|
205
|
+
if (!trimmed) return "Fork: New Chat"
|
|
206
|
+
return trimmed.startsWith("Fork: ") ? trimmed : `Fork: ${trimmed}`
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Slice a page of transcript entries from a flat array.
|
|
211
|
+
*
|
|
212
|
+
* - `limit` controls page size; `beforeIndex` (from a cursor) is the
|
|
213
|
+
* exclusive upper bound of the window (undefined = latest page).
|
|
214
|
+
* - Returns stable `TranscriptPageResult` with cloned entries so
|
|
215
|
+
* callers cannot mutate the in-memory transcript.
|
|
216
|
+
*/
|
|
217
|
+
export function getMessagesPageFromEntries(
|
|
218
|
+
entries: TranscriptEntry[],
|
|
219
|
+
limit: number,
|
|
220
|
+
beforeIndex?: number,
|
|
221
|
+
): TranscriptPageResult {
|
|
222
|
+
if (entries.length === 0) {
|
|
223
|
+
return { entries: [], hasOlder: false, olderCursor: null }
|
|
224
|
+
}
|
|
225
|
+
const endIndex =
|
|
226
|
+
beforeIndex === undefined
|
|
227
|
+
? entries.length
|
|
228
|
+
: Math.max(0, Math.min(beforeIndex, entries.length))
|
|
229
|
+
const startIndex = Math.max(0, endIndex - limit)
|
|
230
|
+
return {
|
|
231
|
+
entries: cloneTranscriptEntries(entries.slice(startIndex, endIndex)),
|
|
232
|
+
hasOlder: startIndex > 0,
|
|
233
|
+
olderCursor: startIndex > 0 ? encodeHistoryCursor(startIndex) : null,
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventStore initialization and snapshot/migration helpers extracted from
|
|
3
|
+
* event-store.ts.
|
|
4
|
+
*
|
|
5
|
+
* Contains initialize, ensureFile, clearStorage, loadSnapshot, resetState,
|
|
6
|
+
* clearLegacyTranscriptState, loadSidebarProjectOrder, replayLogs,
|
|
7
|
+
* shouldSnapshotLogs, snapshotAndTruncateLogs, getLegacyTranscriptStats,
|
|
8
|
+
* hasLegacyTranscriptData, and migrateLegacyTranscripts.
|
|
9
|
+
*
|
|
10
|
+
* This file does direct disk IO and must be suffixed .ts (not .adapter.ts)
|
|
11
|
+
* because the side-effect seal only applies to files in src/server/** production
|
|
12
|
+
* code that are NOT in an exempt glob. However, since this file performs IO and
|
|
13
|
+
* is NOT an adapter, callers (EventStore) must be careful to only call these
|
|
14
|
+
* from the class constructor / initialize flow.
|
|
15
|
+
*
|
|
16
|
+
* Must NOT import from event-store.ts (no circular deps).
|
|
17
|
+
*/
|
|
18
|
+
import path from "node:path"
|
|
19
|
+
import type { AgentProvider, TranscriptEntry } from "../shared/types"
|
|
20
|
+
import type { StoreState } from "./events"
|
|
21
|
+
import type { StorageBackend } from "./storage/backend"
|
|
22
|
+
import type { CachedTranscriptRef } from "./event-store-messages.adapter"
|
|
23
|
+
import type { CloudflareTunnelEvent } from "./cloudflare-tunnel/events"
|
|
24
|
+
import {
|
|
25
|
+
buildSnapshotFile,
|
|
26
|
+
calcShouldTruncateLogs,
|
|
27
|
+
computeLegacyTranscriptStats,
|
|
28
|
+
loadAndReplayLogs,
|
|
29
|
+
loadSidebarOrder,
|
|
30
|
+
loadSnapshotIntoState,
|
|
31
|
+
migrateLegacyTranscripts as migrateLegacyTranscriptsImpl,
|
|
32
|
+
truncateLogsAfterSnapshot,
|
|
33
|
+
type LegacyTranscriptStats,
|
|
34
|
+
type SnapshotLogPaths,
|
|
35
|
+
} from "./event-store-snapshot"
|
|
36
|
+
|
|
37
|
+
export type { LegacyTranscriptStats }
|
|
38
|
+
|
|
39
|
+
// ─── Deps interface ────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export interface EventStoreInitDeps {
|
|
42
|
+
readonly storage: StorageBackend
|
|
43
|
+
readonly dataDir: string
|
|
44
|
+
readonly snapshotPath: string
|
|
45
|
+
readonly projectsLogPath: string
|
|
46
|
+
readonly chatsLogPath: string
|
|
47
|
+
readonly messagesLogPath: string
|
|
48
|
+
readonly queuedMessagesLogPath: string
|
|
49
|
+
readonly turnsLogPath: string
|
|
50
|
+
readonly schedulesLogPath: string
|
|
51
|
+
readonly tunnelLogPath: string
|
|
52
|
+
readonly sharesLogPath: string
|
|
53
|
+
readonly pushLogPath: string
|
|
54
|
+
readonly stacksLogPath: string
|
|
55
|
+
readonly toolRequestsLogPath: string
|
|
56
|
+
readonly orchLogPath: string
|
|
57
|
+
readonly transcriptsDir: string
|
|
58
|
+
readonly sidebarProjectOrderPath: string
|
|
59
|
+
readonly state: StoreState
|
|
60
|
+
readonly legacyMessagesByChatId: Map<string, TranscriptEntry[]>
|
|
61
|
+
readonly tunnelEventsByChatId: Map<string, CloudflareTunnelEvent[]>
|
|
62
|
+
readonly cachedTranscriptRef: CachedTranscriptRef
|
|
63
|
+
readonly sidebarProjectOrderRef: { value: string[] }
|
|
64
|
+
/** Gets the current legacySidebarProjectOrder array reference. */
|
|
65
|
+
getLegacySidebarProjectOrder: () => string[]
|
|
66
|
+
/** Sets legacySidebarProjectOrder. */
|
|
67
|
+
setLegacySidebarProjectOrder: (v: string[]) => void
|
|
68
|
+
/** Gets the current snapshotHasLegacyMessages value. */
|
|
69
|
+
getSnapshotHasLegacyMessages: () => boolean
|
|
70
|
+
/** Sets snapshotHasLegacyMessages. */
|
|
71
|
+
setSnapshotHasLegacyMessages: (v: boolean) => void
|
|
72
|
+
/** Gets the current storageReset flag. */
|
|
73
|
+
getStorageReset: () => boolean
|
|
74
|
+
/** Sets the storageReset flag. */
|
|
75
|
+
setStorageReset: (v: boolean) => void
|
|
76
|
+
/** The replayChatProvider map for log replay. */
|
|
77
|
+
replayChatProvider: Map<string, AgentProvider | null>
|
|
78
|
+
/** Applies a store event (routes to applyStoreEvent). */
|
|
79
|
+
applyEvent: (event: Parameters<typeof import("./event-store-apply").applyStoreEvent>[0]) => void
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── Internal helpers ──────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
function getLogPaths(deps: EventStoreInitDeps): SnapshotLogPaths {
|
|
85
|
+
return {
|
|
86
|
+
snapshotPath: deps.snapshotPath,
|
|
87
|
+
projectsLogPath: deps.projectsLogPath,
|
|
88
|
+
chatsLogPath: deps.chatsLogPath,
|
|
89
|
+
messagesLogPath: deps.messagesLogPath,
|
|
90
|
+
queuedMessagesLogPath: deps.queuedMessagesLogPath,
|
|
91
|
+
turnsLogPath: deps.turnsLogPath,
|
|
92
|
+
schedulesLogPath: deps.schedulesLogPath,
|
|
93
|
+
stacksLogPath: deps.stacksLogPath,
|
|
94
|
+
toolRequestsLogPath: deps.toolRequestsLogPath,
|
|
95
|
+
orchLogPath: deps.orchLogPath,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function ensureFile(deps: EventStoreInitDeps, filePath: string): Promise<void> {
|
|
100
|
+
if (!(await deps.storage.exists(filePath))) {
|
|
101
|
+
await deps.storage.writeText(filePath, "")
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resetState(deps: EventStoreInitDeps): void {
|
|
106
|
+
deps.state.projectsById.clear()
|
|
107
|
+
deps.state.projectIdsByPath.clear()
|
|
108
|
+
deps.state.chatsById.clear()
|
|
109
|
+
deps.state.queuedMessagesByChatId.clear()
|
|
110
|
+
deps.state.sidebarProjectOrder = []
|
|
111
|
+
deps.state.autoContinueEventsByChatId.clear()
|
|
112
|
+
deps.state.stacksById.clear()
|
|
113
|
+
deps.tunnelEventsByChatId.clear()
|
|
114
|
+
deps.sidebarProjectOrderRef.value = []
|
|
115
|
+
deps.setLegacySidebarProjectOrder([])
|
|
116
|
+
deps.cachedTranscriptRef.value = null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function clearLegacyTranscriptState(deps: EventStoreInitDeps): void {
|
|
120
|
+
deps.legacyMessagesByChatId.clear()
|
|
121
|
+
deps.setSnapshotHasLegacyMessages(false)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function clearStorage(deps: EventStoreInitDeps): Promise<void> {
|
|
125
|
+
if (deps.getStorageReset()) return
|
|
126
|
+
deps.setStorageReset(true)
|
|
127
|
+
resetState(deps)
|
|
128
|
+
clearLegacyTranscriptState(deps)
|
|
129
|
+
await Promise.all([
|
|
130
|
+
deps.storage.writeText(deps.snapshotPath, ""),
|
|
131
|
+
deps.storage.writeText(deps.projectsLogPath, ""),
|
|
132
|
+
deps.storage.writeText(deps.chatsLogPath, ""),
|
|
133
|
+
deps.storage.writeText(deps.messagesLogPath, ""),
|
|
134
|
+
deps.storage.writeText(deps.queuedMessagesLogPath, ""),
|
|
135
|
+
deps.storage.writeText(deps.turnsLogPath, ""),
|
|
136
|
+
deps.storage.writeText(deps.schedulesLogPath, ""),
|
|
137
|
+
deps.storage.writeText(deps.tunnelLogPath, ""),
|
|
138
|
+
deps.storage.writeText(deps.sharesLogPath, ""),
|
|
139
|
+
deps.storage.writeText(deps.stacksLogPath, ""),
|
|
140
|
+
deps.storage.writeText(deps.toolRequestsLogPath, ""),
|
|
141
|
+
deps.storage.writeText(deps.orchLogPath, ""),
|
|
142
|
+
])
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function loadSnapshot(deps: EventStoreInitDeps): Promise<void> {
|
|
146
|
+
const result = await loadSnapshotIntoState(
|
|
147
|
+
deps.storage,
|
|
148
|
+
deps.snapshotPath,
|
|
149
|
+
deps.state,
|
|
150
|
+
deps.legacyMessagesByChatId,
|
|
151
|
+
() => clearStorage(deps),
|
|
152
|
+
)
|
|
153
|
+
deps.setSnapshotHasLegacyMessages(result.snapshotHasLegacyMessages)
|
|
154
|
+
deps.setLegacySidebarProjectOrder(result.legacySidebarProjectOrder)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function loadSidebarProjectOrder(deps: EventStoreInitDeps): Promise<void> {
|
|
158
|
+
deps.sidebarProjectOrderRef.value = await loadSidebarOrder(
|
|
159
|
+
deps.storage,
|
|
160
|
+
deps.sidebarProjectOrderPath,
|
|
161
|
+
deps.projectsLogPath,
|
|
162
|
+
deps.dataDir,
|
|
163
|
+
deps.getLegacySidebarProjectOrder(),
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function replayLogs(deps: EventStoreInitDeps): Promise<void> {
|
|
168
|
+
await loadAndReplayLogs(
|
|
169
|
+
deps.storage,
|
|
170
|
+
getLogPaths(deps),
|
|
171
|
+
() => deps.getStorageReset(),
|
|
172
|
+
(event) => { deps.applyEvent(event) },
|
|
173
|
+
() => clearStorage(deps),
|
|
174
|
+
() => { deps.replayChatProvider.clear() },
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function shouldSnapshotLogs(deps: EventStoreInitDeps): Promise<boolean> {
|
|
179
|
+
return calcShouldTruncateLogs(deps.storage, getLogPaths(deps))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ─── Public API ────────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
export async function initializeEventStore(
|
|
185
|
+
deps: EventStoreInitDeps,
|
|
186
|
+
callbacks: {
|
|
187
|
+
loadTunnelEvents: () => Promise<void>
|
|
188
|
+
loadShareEvents: () => Promise<void>
|
|
189
|
+
hasLegacyTranscriptData: () => Promise<boolean>
|
|
190
|
+
snapshotAndTruncateLogs: () => Promise<void>
|
|
191
|
+
},
|
|
192
|
+
): Promise<void> {
|
|
193
|
+
await deps.storage.mkdir(deps.dataDir)
|
|
194
|
+
await deps.storage.mkdir(deps.transcriptsDir)
|
|
195
|
+
await ensureFile(deps, deps.projectsLogPath)
|
|
196
|
+
await ensureFile(deps, deps.chatsLogPath)
|
|
197
|
+
await ensureFile(deps, deps.messagesLogPath)
|
|
198
|
+
await ensureFile(deps, deps.queuedMessagesLogPath)
|
|
199
|
+
await ensureFile(deps, deps.turnsLogPath)
|
|
200
|
+
await ensureFile(deps, deps.schedulesLogPath)
|
|
201
|
+
await ensureFile(deps, deps.tunnelLogPath)
|
|
202
|
+
await ensureFile(deps, deps.sharesLogPath)
|
|
203
|
+
await ensureFile(deps, deps.pushLogPath)
|
|
204
|
+
await ensureFile(deps, deps.stacksLogPath)
|
|
205
|
+
await ensureFile(deps, deps.toolRequestsLogPath)
|
|
206
|
+
await ensureFile(deps, deps.orchLogPath)
|
|
207
|
+
await loadSnapshot(deps)
|
|
208
|
+
await replayLogs(deps)
|
|
209
|
+
await callbacks.loadTunnelEvents()
|
|
210
|
+
await callbacks.loadShareEvents()
|
|
211
|
+
await loadSidebarProjectOrder(deps)
|
|
212
|
+
if (!(await callbacks.hasLegacyTranscriptData()) && await shouldSnapshotLogs(deps)) {
|
|
213
|
+
await callbacks.snapshotAndTruncateLogs()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function resetEventStoreState(deps: EventStoreInitDeps): void {
|
|
218
|
+
resetState(deps)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function clearEventStoreLegacyTranscriptState(deps: EventStoreInitDeps): void {
|
|
222
|
+
clearLegacyTranscriptState(deps)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ─── Snapshot / legacy migration ops ─────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
export async function getLegacyTranscriptStats(
|
|
228
|
+
deps: EventStoreInitDeps,
|
|
229
|
+
): Promise<LegacyTranscriptStats> {
|
|
230
|
+
return computeLegacyTranscriptStats(
|
|
231
|
+
deps.storage,
|
|
232
|
+
deps.messagesLogPath,
|
|
233
|
+
deps.getSnapshotHasLegacyMessages(),
|
|
234
|
+
deps.legacyMessagesByChatId,
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function hasLegacyTranscriptData(deps: EventStoreInitDeps): Promise<boolean> {
|
|
239
|
+
return (await getLegacyTranscriptStats(deps)).hasLegacyData
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export async function snapshotAndTruncateLogs(deps: EventStoreInitDeps): Promise<void> {
|
|
243
|
+
const projects = [...deps.state.projectsById.values()].filter((p) => !p.deletedAt)
|
|
244
|
+
const snapshot = buildSnapshotFile(deps.state, projects)
|
|
245
|
+
const logPaths: SnapshotLogPaths = {
|
|
246
|
+
snapshotPath: deps.snapshotPath,
|
|
247
|
+
projectsLogPath: deps.projectsLogPath,
|
|
248
|
+
chatsLogPath: deps.chatsLogPath,
|
|
249
|
+
messagesLogPath: deps.messagesLogPath,
|
|
250
|
+
queuedMessagesLogPath: deps.queuedMessagesLogPath,
|
|
251
|
+
turnsLogPath: deps.turnsLogPath,
|
|
252
|
+
schedulesLogPath: deps.schedulesLogPath,
|
|
253
|
+
stacksLogPath: deps.stacksLogPath,
|
|
254
|
+
toolRequestsLogPath: deps.toolRequestsLogPath,
|
|
255
|
+
orchLogPath: deps.orchLogPath,
|
|
256
|
+
}
|
|
257
|
+
await truncateLogsAfterSnapshot(deps.storage, logPaths, JSON.stringify(snapshot, null, 2))
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export async function migrateLegacyTranscripts(
|
|
261
|
+
deps: EventStoreInitDeps,
|
|
262
|
+
onProgress?: (message: string) => void,
|
|
263
|
+
): Promise<boolean> {
|
|
264
|
+
const stats = await getLegacyTranscriptStats(deps)
|
|
265
|
+
return migrateLegacyTranscriptsImpl(
|
|
266
|
+
deps.storage,
|
|
267
|
+
deps.transcriptsDir,
|
|
268
|
+
stats,
|
|
269
|
+
deps.legacyMessagesByChatId,
|
|
270
|
+
(chatId) => path.join(deps.transcriptsDir, `${chatId}.jsonl`),
|
|
271
|
+
() => { clearEventStoreLegacyTranscriptState(deps) },
|
|
272
|
+
() => snapshotAndTruncateLogs(deps),
|
|
273
|
+
() => { deps.cachedTranscriptRef.value = null },
|
|
274
|
+
onProgress,
|
|
275
|
+
)
|
|
276
|
+
}
|