@cuongtran001/kanna 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/{arc-CAysfmX5.js → arc-BHj370W7.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BKqRYsm4.js → architectureDiagram-3BPJPVTR-1G88q1PY.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-Ce7Eorec.js → blockDiagram-GPEHLZMM-D0L3Co5v.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CJ77zauH.js → c4Diagram-AAUBKEIU-CQtERl3H.js} +1 -1
- package/dist/client/assets/channel-BP9IMEvd.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Rk_3zLxJ.js → chunk-2J33WTMH-DxOwt09f.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-DPIz6Nvf.js → chunk-4BX2VUAB-Wry66y9R.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-D-TkbGh2.js → chunk-55IACEB6-D6p-Hi4x.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-B4NOSIZV.js → chunk-727SXJPM-CRQZt23N.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-Cm_TGLYm.js → chunk-AQP2D5EJ-BzgTKOM9.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Bu45BUVh.js → chunk-FMBD7UC4-CKfD-91C.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-C1Bw8_gg.js → chunk-ND2GUHAM-a71B27wc.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DueG15o0.js → chunk-QZHKN3VN-vv9gJwx0.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK--W6Wt7FF.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2--W6Wt7FF.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-YgpMurXf.js → cose-bilkent-S5V4N54A-Cu-dCCaI.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-Dop7Oq67.js → dagre-BM42HDAG-D8coOWLK.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-unMfBEJH.js → diagram-2AECGRRQ-NIwZ65jE.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-D9EJ9qMI.js → diagram-5GNKFQAL-d46aE_le.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-C5IykOIR.js → diagram-KO2AKTUF-xdm2UxWc.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DJAuPngz.js → diagram-LMA3HP47-hDbLwC5K.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-B3wHFtS-.js → diagram-OG6HWLK6-MHUGG7ki.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-Dm7kt9o5.js → erDiagram-TEJ5UH35-Ro6n9X70.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-s9Lds3ny.js → flowDiagram-I6XJVG4X-DOZqvRMu.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CRQNYdAf.js → ganttDiagram-6RSMTGT7-Bn2NXYIy.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DhYjK4Jb.js → gitGraphDiagram-PVQCEYII-DK44bfbC.js} +1 -1
- package/dist/client/assets/{index-C0U2kwuB.js → index-CWJpHyQy.js} +115 -115
- package/dist/client/assets/{index-CLsOWXff.css → index-Cyzd4zZx.css} +1 -1
- package/dist/client/assets/{index-CbSY9HfS.js → index-DU56d4oC.js} +1 -1
- package/dist/client/assets/{infoDiagram-5YYISTIA-CRKJ0PSH.js → infoDiagram-5YYISTIA-C2tW_Dwj.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CEAkB0IY.js → ishikawaDiagram-YF4QCWOH-C__4Z1wz.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-CyOK5_DG.js → journeyDiagram-JHISSGLW-Bbn5EawR.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-BfKjiJSo.js → kanban-definition-UN3LZRKU-RgS5IRWl.js} +1 -1
- package/dist/client/assets/{linear-DLR6miy3.js → linear-DfI0qBO_.js} +1 -1
- package/dist/client/assets/{mermaid.core-D7AdC_lE.js → mermaid.core-BiN10VHc.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-zYyJtDSa.js → mindmap-definition-RKZ34NQL-BQhibhh3.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-BgP8PFlo.js → pieDiagram-4H26LBE5-BEfLRn7a.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Di8h7J96.js → quadrantDiagram-W4KKPZXB-ba641Y2x.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DuTzFl3R.js → requirementDiagram-4Y6WPE33-BGtl-tVO.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DBRoMm_T.js → sankeyDiagram-5OEKKPKP-DyziaSFr.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Bmb-1HUN.js → sequenceDiagram-3UESZ5HK-BfYBAYga.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-fnv5ayn-.js → stateDiagram-AJRCARHV-BfUfmLYA.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CdGcgYEq.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-ByWRdpQX.js → timeline-definition-PNZ67QCA-T6z7dEFl.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN--2WGpVXW.js → vennDiagram-CIIHVFJN-BAwQbFFd.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-24zlnzxK.js → wardley-L42UT6IY-DF1cRKsP.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Cllvqb3K.js → wardleyDiagram-YWT4CUSO-BRtAZ8za.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-ZcdC6nPE.js → xychartDiagram-2RQKCTM6-Brb-q76h.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.test.ts +22 -6
- package/src/server/agent.ts +35 -4987
- package/src/server/chat-op-log.test.ts +63 -0
- package/src/server/chat-op-log.ts +71 -0
- package/src/server/chat-ops-diff.test.ts +120 -0
- package/src/server/chat-ops-diff.ts +97 -0
- package/src/server/chat-ops-parity.test.ts +140 -0
- 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 +532 -0
- package/src/server/event-store-messages.adapter.ts +455 -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 +291 -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 +75 -0
- package/src/server/event-store.ts +257 -2285
- package/src/server/events.ts +1 -1
- package/src/server/harness-types.ts +15 -1
- package/src/server/oauth-errors.ts +19 -0
- package/src/server/storage/backend.ts +7 -0
- package/src/server/storage/fs-storage.adapter.ts +21 -1
- package/src/server/storage/in-memory-storage.ts +12 -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 +560 -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 +360 -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 +238 -0
- package/src/server/ws-router.test.ts +90 -0
- package/src/server/ws-router.ts +340 -2254
- package/src/shared/app-settings-types.ts +365 -0
- package/src/shared/chat-ops.test.ts +138 -0
- package/src/shared/chat-ops.ts +71 -0
- package/src/shared/core-types.ts +27 -0
- package/src/shared/git-diff-types.ts +165 -0
- package/src/shared/mcp-types.ts +105 -0
- package/src/shared/protocol.ts +3 -2
- 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 +31 -1711
- package/dist/client/assets/channel-KQhRwDzJ.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-ChxT1GNq.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-ChxT1GNq.js +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-D4X-ZnLV.js +0 -1
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message and transcript read operations extracted from event-store.ts.
|
|
3
|
+
*
|
|
4
|
+
* `loadTranscriptFromDisk` performs synchronous disk IO, so this module is an
|
|
5
|
+
* adapter (`.adapter.ts`). All other functions in this file are pure in-memory
|
|
6
|
+
* reads — they are co-located here for cohesion because they share the same
|
|
7
|
+
* deps interface and depend on `loadTranscriptFromDisk`.
|
|
8
|
+
*
|
|
9
|
+
* This module must NOT import from event-store.ts (no circular deps).
|
|
10
|
+
*/
|
|
11
|
+
import path from "node:path"
|
|
12
|
+
import type { ChatHistoryPage, QueuedChatMessage, TranscriptEntry } from "../shared/types"
|
|
13
|
+
import type { StorageBackend } from "./storage/backend"
|
|
14
|
+
import type { ToolRequest } from "../shared/permission-policy"
|
|
15
|
+
import type { ChatRecord, StoreState } from "./events"
|
|
16
|
+
import { cloneTranscriptEntries } from "./events"
|
|
17
|
+
import {
|
|
18
|
+
coalesceContextWindowUpdates,
|
|
19
|
+
decodeCursor,
|
|
20
|
+
getHistorySnapshot,
|
|
21
|
+
getMessagesPageFromEntries,
|
|
22
|
+
} from "./event-store-helpers"
|
|
23
|
+
|
|
24
|
+
// ─── Transcript LRU cache ──────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Small LRU of fully loaded transcripts (Map insertion order = recency).
|
|
28
|
+
* Replaces the former single-chat `cachedTranscriptRef` so switching between
|
|
29
|
+
* a handful of chats does not re-read MB-scale JSONL files from disk.
|
|
30
|
+
*/
|
|
31
|
+
export class TranscriptCache {
|
|
32
|
+
private readonly byChat = new Map<string, TranscriptEntry[]>()
|
|
33
|
+
|
|
34
|
+
constructor(private readonly maxChats: number = 4) {}
|
|
35
|
+
|
|
36
|
+
/** Returns the cached entries (touching LRU recency), or undefined. */
|
|
37
|
+
get(chatId: string): TranscriptEntry[] | undefined {
|
|
38
|
+
const entries = this.byChat.get(chatId)
|
|
39
|
+
if (!entries) return undefined
|
|
40
|
+
this.byChat.delete(chatId)
|
|
41
|
+
this.byChat.set(chatId, entries)
|
|
42
|
+
return entries
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
set(chatId: string, entries: TranscriptEntry[]): void {
|
|
46
|
+
this.byChat.delete(chatId)
|
|
47
|
+
this.byChat.set(chatId, entries)
|
|
48
|
+
while (this.byChat.size > this.maxChats) {
|
|
49
|
+
const oldest = this.byChat.keys().next().value
|
|
50
|
+
if (oldest === undefined) break
|
|
51
|
+
this.byChat.delete(oldest)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Appends to a cached transcript; no-op when the chat is not cached. */
|
|
56
|
+
appendTo(chatId: string, entry: TranscriptEntry): void {
|
|
57
|
+
this.byChat.get(chatId)?.push(entry)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
has(chatId: string): boolean {
|
|
61
|
+
return this.byChat.has(chatId)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
invalidate(chatId: string): void {
|
|
65
|
+
this.byChat.delete(chatId)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
invalidateAll(): void {
|
|
69
|
+
this.byChat.clear()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ─── Deps interface ────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
export interface MessageReadDeps {
|
|
76
|
+
readonly storage: StorageBackend
|
|
77
|
+
readonly transcriptsDir: string
|
|
78
|
+
readonly transcriptCache: TranscriptCache
|
|
79
|
+
readonly legacyMessagesByChatId: Map<string, TranscriptEntry[]>
|
|
80
|
+
readonly seenMessageIdsByChatId: Map<string, Set<string>>
|
|
81
|
+
readonly queuedMessagesByChatId: StoreState["queuedMessagesByChatId"]
|
|
82
|
+
readonly chatsById: Map<string, ChatRecord>
|
|
83
|
+
listPendingToolRequests: (chatId: string) => ToolRequest[]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── Private helpers ───────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
function transcriptPath(deps: MessageReadDeps, chatId: string): string {
|
|
89
|
+
return path.join(deps.transcriptsDir, `${chatId}.jsonl`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─── Tail-read fast path ───────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
const TAIL_CHUNK_BYTES = 256 * 1024
|
|
95
|
+
const NEWLINE = 0x0a
|
|
96
|
+
const utf8Decoder = new TextDecoder()
|
|
97
|
+
|
|
98
|
+
export interface TranscriptTailResult {
|
|
99
|
+
entries: TranscriptEntry[]
|
|
100
|
+
/** Absolute byte offset of each entry's raw JSONL line, parallel to `entries`. */
|
|
101
|
+
lineOffsets: number[]
|
|
102
|
+
/** True when the slice covered the start of the file (entries are complete up to the end offset). */
|
|
103
|
+
reachedStart: boolean
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseJsonlSlice(
|
|
107
|
+
buf: Uint8Array,
|
|
108
|
+
sliceStart: number,
|
|
109
|
+
atStart: boolean,
|
|
110
|
+
): { entries: TranscriptEntry[]; lineOffsets: number[] } {
|
|
111
|
+
const entries: TranscriptEntry[] = []
|
|
112
|
+
const lineOffsets: number[] = []
|
|
113
|
+
let lineStart = 0
|
|
114
|
+
let skippedPartialFirstLine = atStart
|
|
115
|
+
for (let i = 0; i <= buf.length; i += 1) {
|
|
116
|
+
const atEnd = i === buf.length
|
|
117
|
+
if (!atEnd && buf[i] !== NEWLINE) continue
|
|
118
|
+
if (!skippedPartialFirstLine) {
|
|
119
|
+
// The slice may begin mid-line; the first segment is untrustworthy.
|
|
120
|
+
skippedPartialFirstLine = true
|
|
121
|
+
lineStart = i + 1
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
if (i > lineStart) {
|
|
125
|
+
const text = utf8Decoder.decode(buf.subarray(lineStart, i)).trim()
|
|
126
|
+
if (text) {
|
|
127
|
+
try {
|
|
128
|
+
const entry: TranscriptEntry = JSON.parse(text)
|
|
129
|
+
entries.push(entry)
|
|
130
|
+
lineOffsets.push(sliceStart + lineStart)
|
|
131
|
+
} catch {
|
|
132
|
+
// torn/partial final line — skip
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
lineStart = i + 1
|
|
137
|
+
}
|
|
138
|
+
return { entries, lineOffsets }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Reads only the tail of the transcript JSONL (growing backwards until more
|
|
143
|
+
* than `minEntries` lines or BOF). Returns null when the storage backend has
|
|
144
|
+
* no byte-slice APIs — callers must fall back to the full-parse path.
|
|
145
|
+
*/
|
|
146
|
+
export function readTranscriptTail(
|
|
147
|
+
deps: MessageReadDeps,
|
|
148
|
+
chatId: string,
|
|
149
|
+
minEntries: number,
|
|
150
|
+
endOffset?: number,
|
|
151
|
+
chunkBytes: number = TAIL_CHUNK_BYTES,
|
|
152
|
+
): TranscriptTailResult | null {
|
|
153
|
+
const { storage } = deps
|
|
154
|
+
if (typeof storage.readSliceSync !== "function" || typeof storage.sizeSync !== "function") {
|
|
155
|
+
return null
|
|
156
|
+
}
|
|
157
|
+
const tPath = transcriptPath(deps, chatId)
|
|
158
|
+
if (!storage.existsSync(tPath)) {
|
|
159
|
+
return { entries: [], lineOffsets: [], reachedStart: true }
|
|
160
|
+
}
|
|
161
|
+
const fileSize = storage.sizeSync(tPath)
|
|
162
|
+
const end = Math.min(endOffset ?? fileSize, fileSize)
|
|
163
|
+
if (end <= 0) {
|
|
164
|
+
return { entries: [], lineOffsets: [], reachedStart: true }
|
|
165
|
+
}
|
|
166
|
+
let chunk = Math.max(chunkBytes, 64)
|
|
167
|
+
for (;;) {
|
|
168
|
+
const start = Math.max(0, end - chunk)
|
|
169
|
+
const buf = storage.readSliceSync(tPath, start, end)
|
|
170
|
+
const parsed = parseJsonlSlice(buf, start, start === 0)
|
|
171
|
+
if (start === 0 || parsed.entries.length > minEntries) {
|
|
172
|
+
return { ...parsed, reachedStart: start === 0 }
|
|
173
|
+
}
|
|
174
|
+
chunk *= 2
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function decodeByteCursor(cursor: string): number | null {
|
|
179
|
+
if (!cursor.startsWith("byte:")) return null
|
|
180
|
+
const value = Number.parseInt(cursor.slice("byte:".length), 10)
|
|
181
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
182
|
+
throw new Error("Invalid history cursor")
|
|
183
|
+
}
|
|
184
|
+
return value
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Caches a COMPLETE transcript and seeds the messageId dedup set. */
|
|
188
|
+
function seedFullTranscript(deps: MessageReadDeps, chatId: string, entries: TranscriptEntry[]): void {
|
|
189
|
+
const seen = getSeenMessageIds(deps, chatId)
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
const mid = entry.messageId
|
|
192
|
+
if (typeof mid === "string" && mid.length > 0) {
|
|
193
|
+
seen.add(mid)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
deps.transcriptCache.set(chatId, entries)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Parses the single JSONL line starting at `offset` (the first entry of the
|
|
201
|
+
* already-served newer page). Used as a coalesce sentinel so a cwu run that
|
|
202
|
+
* straddles the page boundary collapses exactly like the full-array path.
|
|
203
|
+
*/
|
|
204
|
+
function readEntryAtOffset(deps: MessageReadDeps, chatId: string, offset: number): TranscriptEntry | null {
|
|
205
|
+
const { storage } = deps
|
|
206
|
+
if (typeof storage.readSliceSync !== "function" || typeof storage.sizeSync !== "function") return null
|
|
207
|
+
const tPath = transcriptPath(deps, chatId)
|
|
208
|
+
const fileSize = storage.sizeSync(tPath)
|
|
209
|
+
const end = Math.min(offset + 1024 * 1024, fileSize)
|
|
210
|
+
const buf = storage.readSliceSync(tPath, offset, end)
|
|
211
|
+
const newlineIdx = buf.indexOf(NEWLINE)
|
|
212
|
+
if (newlineIdx < 0 && end < fileSize) return null
|
|
213
|
+
const lineEnd = newlineIdx < 0 ? buf.length : newlineIdx
|
|
214
|
+
try {
|
|
215
|
+
const entry: TranscriptEntry = JSON.parse(utf8Decoder.decode(buf.subarray(0, lineEnd)))
|
|
216
|
+
return entry
|
|
217
|
+
} catch {
|
|
218
|
+
return null
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function pageFromTail(tail: TranscriptTailResult, limit: number, nextEntry?: TranscriptEntry | null): ChatHistoryPage {
|
|
223
|
+
// The sentinel participates in coalescing (so a trailing cwu run collapses
|
|
224
|
+
// against the newer page's leading cwu) and is then removed.
|
|
225
|
+
const coalesced = nextEntry
|
|
226
|
+
? coalesceContextWindowUpdates([...tail.entries, nextEntry]).slice(0, -1)
|
|
227
|
+
: coalesceContextWindowUpdates(tail.entries)
|
|
228
|
+
const startIdx = Math.max(0, coalesced.length - limit)
|
|
229
|
+
const pageEntries = coalesced.slice(startIdx)
|
|
230
|
+
const hasOlder = !tail.reachedStart || startIdx > 0
|
|
231
|
+
let olderCursor: string | null = null
|
|
232
|
+
const first = pageEntries[0]
|
|
233
|
+
if (hasOlder && first) {
|
|
234
|
+
const rawIdx = tail.entries.indexOf(first)
|
|
235
|
+
const offset = tail.lineOffsets[rawIdx]
|
|
236
|
+
olderCursor = offset === undefined ? null : `byte:${offset}`
|
|
237
|
+
} else if (hasOlder && tail.lineOffsets.length > 0) {
|
|
238
|
+
// Page fully absorbed by coalescing (pure cwu run) — continue paging
|
|
239
|
+
// from the start of this slice so pagination cannot stall.
|
|
240
|
+
olderCursor = `byte:${tail.lineOffsets[0]}`
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
messages: cloneTranscriptEntries(pageEntries),
|
|
244
|
+
hasOlder,
|
|
245
|
+
olderCursor,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Serves the most recent page via tail-read, avoiding a full-file parse on
|
|
251
|
+
* cold open. When the tail turns out to be the whole file, the transcript is
|
|
252
|
+
* promoted into the cache (with seen-messageId seeding). Returns null when
|
|
253
|
+
* the backend lacks slice APIs.
|
|
254
|
+
*/
|
|
255
|
+
export function getRecentMessagesPageTail(
|
|
256
|
+
deps: MessageReadDeps,
|
|
257
|
+
chatId: string,
|
|
258
|
+
limit: number,
|
|
259
|
+
chunkBytes?: number,
|
|
260
|
+
): ChatHistoryPage | null {
|
|
261
|
+
const tail = readTranscriptTail(deps, chatId, limit, undefined, chunkBytes)
|
|
262
|
+
if (!tail) return null
|
|
263
|
+
if (tail.reachedStart) {
|
|
264
|
+
seedFullTranscript(deps, chatId, tail.entries)
|
|
265
|
+
}
|
|
266
|
+
return pageFromTail(tail, limit)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ─── Exported functions ────────────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
/** Returns (or lazily creates) the seen-messageId dedup set for a chat. */
|
|
272
|
+
export function getSeenMessageIds(deps: MessageReadDeps, chatId: string): Set<string> {
|
|
273
|
+
let set = deps.seenMessageIdsByChatId.get(chatId)
|
|
274
|
+
if (!set) {
|
|
275
|
+
set = new Set<string>()
|
|
276
|
+
deps.seenMessageIdsByChatId.set(chatId, set)
|
|
277
|
+
}
|
|
278
|
+
return set
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Reads transcript entries from disk synchronously.
|
|
283
|
+
* Populates the seenMessageIds set as a side-effect.
|
|
284
|
+
*/
|
|
285
|
+
export function loadTranscriptFromDisk(
|
|
286
|
+
deps: MessageReadDeps,
|
|
287
|
+
chatId: string,
|
|
288
|
+
): TranscriptEntry[] {
|
|
289
|
+
const tPath = transcriptPath(deps, chatId)
|
|
290
|
+
if (!deps.storage.existsSync(tPath)) {
|
|
291
|
+
return []
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const text = deps.storage.readTextSync(tPath)
|
|
295
|
+
if (!text.trim()) return []
|
|
296
|
+
|
|
297
|
+
const entries: TranscriptEntry[] = []
|
|
298
|
+
const seen = getSeenMessageIds(deps, chatId)
|
|
299
|
+
for (const rawLine of text.split("\n")) {
|
|
300
|
+
const line = rawLine.trim()
|
|
301
|
+
if (!line) continue
|
|
302
|
+
const entry: TranscriptEntry & { messageId?: string } = JSON.parse(line)
|
|
303
|
+
entries.push(entry)
|
|
304
|
+
const mid = entry.messageId
|
|
305
|
+
if (typeof mid === "string" && mid.length > 0) {
|
|
306
|
+
seen.add(mid)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return entries
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Returns the cached transcript WITHOUT cloning (loads it on miss).
|
|
314
|
+
* Do-not-mutate contract: callers must treat the array and its entries as
|
|
315
|
+
* read-only; anything returned to mutation-prone callers must be cloned.
|
|
316
|
+
*/
|
|
317
|
+
export function getMessagesView(deps: MessageReadDeps, chatId: string): readonly TranscriptEntry[] {
|
|
318
|
+
const cached = deps.transcriptCache.get(chatId)
|
|
319
|
+
if (cached) return cached
|
|
320
|
+
|
|
321
|
+
const legacyEntries = deps.legacyMessagesByChatId.get(chatId)
|
|
322
|
+
if (legacyEntries) {
|
|
323
|
+
const copy = cloneTranscriptEntries(legacyEntries)
|
|
324
|
+
deps.transcriptCache.set(chatId, copy)
|
|
325
|
+
return copy
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const entries = loadTranscriptFromDisk(deps, chatId)
|
|
329
|
+
deps.transcriptCache.set(chatId, entries)
|
|
330
|
+
return entries
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Returns cloned transcript entries for `chatId`, using the in-memory cache
|
|
335
|
+
* or loading from disk as needed.
|
|
336
|
+
*/
|
|
337
|
+
export function getMessages(deps: MessageReadDeps, chatId: string): TranscriptEntry[] {
|
|
338
|
+
return cloneTranscriptEntries(getMessagesView(deps, chatId))
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Returns queued messages for a chat, with attachment arrays cloned. */
|
|
342
|
+
export function getQueuedMessages(
|
|
343
|
+
deps: MessageReadDeps,
|
|
344
|
+
chatId: string,
|
|
345
|
+
): QueuedChatMessage[] {
|
|
346
|
+
const entries = deps.queuedMessagesByChatId.get(chatId) ?? []
|
|
347
|
+
return entries.map((entry) => ({
|
|
348
|
+
...entry,
|
|
349
|
+
attachments: [...entry.attachments],
|
|
350
|
+
}))
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Returns a single queued message by id, or null. */
|
|
354
|
+
export function getQueuedMessage(
|
|
355
|
+
deps: MessageReadDeps,
|
|
356
|
+
chatId: string,
|
|
357
|
+
queuedMessageId: string,
|
|
358
|
+
): QueuedChatMessage | null {
|
|
359
|
+
return getQueuedMessages(deps, chatId).find((entry) => entry.id === queuedMessageId) ?? null
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Returns the most recent page of transcript messages. */
|
|
363
|
+
export function getRecentMessagesPage(
|
|
364
|
+
deps: MessageReadDeps,
|
|
365
|
+
chatId: string,
|
|
366
|
+
limit: number,
|
|
367
|
+
): ChatHistoryPage {
|
|
368
|
+
if (limit <= 0) {
|
|
369
|
+
return { messages: [], hasOlder: false, olderCursor: null }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!deps.transcriptCache.has(chatId) && !deps.legacyMessagesByChatId.has(chatId)) {
|
|
373
|
+
const tailPage = getRecentMessagesPageTail(deps, chatId, limit)
|
|
374
|
+
if (tailPage) return tailPage
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const entries = coalesceContextWindowUpdates(getMessagesView(deps, chatId))
|
|
378
|
+
const page = getMessagesPageFromEntries(entries, limit)
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
messages: page.entries,
|
|
382
|
+
hasOlder: page.hasOlder,
|
|
383
|
+
olderCursor: page.olderCursor,
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Returns a page of transcript messages before the given cursor. */
|
|
388
|
+
export function getMessagesPageBefore(
|
|
389
|
+
deps: MessageReadDeps,
|
|
390
|
+
chatId: string,
|
|
391
|
+
beforeCursor: string,
|
|
392
|
+
limit: number,
|
|
393
|
+
): ChatHistoryPage {
|
|
394
|
+
if (limit <= 0) {
|
|
395
|
+
return { messages: [], hasOlder: false, olderCursor: null }
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Byte cursors are only ever issued by the tail-read path, whose storage
|
|
399
|
+
// has slice APIs — so readTranscriptTail cannot return null here.
|
|
400
|
+
const byteOffset = decodeByteCursor(beforeCursor)
|
|
401
|
+
if (byteOffset !== null) {
|
|
402
|
+
const tail = readTranscriptTail(deps, chatId, limit, byteOffset)
|
|
403
|
+
if (!tail) throw new Error("Invalid history cursor")
|
|
404
|
+
return pageFromTail(tail, limit, readEntryAtOffset(deps, chatId, byteOffset))
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Coalesce identically to getRecentMessagesPage so cursors (which index the
|
|
408
|
+
// coalesced array) stay consistent across recent + load-older paging.
|
|
409
|
+
const beforeIndex = decodeCursor(beforeCursor)
|
|
410
|
+
const entries = coalesceContextWindowUpdates(getMessagesView(deps, chatId))
|
|
411
|
+
const page = getMessagesPageFromEntries(entries, limit, beforeIndex)
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
messages: page.entries,
|
|
415
|
+
hasOlder: page.hasOlder,
|
|
416
|
+
olderCursor: page.olderCursor,
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** Returns merged transcript + pending tool request entries, plus a history snapshot. */
|
|
421
|
+
export function getRecentChatHistory(
|
|
422
|
+
deps: MessageReadDeps,
|
|
423
|
+
chatId: string,
|
|
424
|
+
recentLimit: number,
|
|
425
|
+
) {
|
|
426
|
+
const page = getRecentMessagesPage(deps, chatId, recentLimit)
|
|
427
|
+
const pending = deps.listPendingToolRequests(chatId)
|
|
428
|
+
const pendingEntries: TranscriptEntry[] = pending.map((req) => ({
|
|
429
|
+
_id: `pending-tool-request-${req.id}`,
|
|
430
|
+
createdAt: req.createdAt,
|
|
431
|
+
kind: "pending_tool_request",
|
|
432
|
+
toolRequestId: req.id,
|
|
433
|
+
toolName: req.toolName,
|
|
434
|
+
arguments: req.arguments,
|
|
435
|
+
}))
|
|
436
|
+
const merged = [...page.messages, ...pendingEntries]
|
|
437
|
+
return {
|
|
438
|
+
messages: merged,
|
|
439
|
+
history: getHistorySnapshot(
|
|
440
|
+
{
|
|
441
|
+
entries: merged,
|
|
442
|
+
hasOlder: page.hasOlder,
|
|
443
|
+
olderCursor: page.olderCursor,
|
|
444
|
+
},
|
|
445
|
+
recentLimit,
|
|
446
|
+
),
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Returns the count of active (non-deleted, non-archived) chats for a project. */
|
|
451
|
+
export function getChatCount(deps: MessageReadDeps, projectId: string): number {
|
|
452
|
+
return [...deps.chatsById.values()].filter(
|
|
453
|
+
(chat) => chat.projectId === projectId && !chat.deletedAt && !chat.archivedAt,
|
|
454
|
+
).length
|
|
455
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestration event write-path + subscription management extracted from
|
|
3
|
+
* event-store.ts.
|
|
4
|
+
*
|
|
5
|
+
* Wraps the pure read functions from event-store-orch.ts and adds:
|
|
6
|
+
* - `appendOrchestrationEvent` — sync apply + async disk enqueue
|
|
7
|
+
* - `subscribeOrchRuns` / `notifyOrchRunsChanged` — observer pattern
|
|
8
|
+
*
|
|
9
|
+
* All functions use injected deps so they carry no direct IO. EventStore
|
|
10
|
+
* is the sole owner of the underlying maps and write-chain.
|
|
11
|
+
*
|
|
12
|
+
* Must NOT import from event-store.ts (no circular deps).
|
|
13
|
+
*/
|
|
14
|
+
import { LOG_PREFIX } from "../shared/branding"
|
|
15
|
+
import { log } from "../shared/log"
|
|
16
|
+
import type { OrchRunSnapshot } from "../shared/orchestration-types"
|
|
17
|
+
import type { OrchestrationEvent, OrchRunRecord } from "./events"
|
|
18
|
+
import {
|
|
19
|
+
gatedOrchTasks as gatedOrchTasksFn,
|
|
20
|
+
getAllOrchRunSnapshots,
|
|
21
|
+
getOrchLastPhaseOutput as getOrchLastPhaseOutputFn,
|
|
22
|
+
getOrchRunEvents as getOrchRunEventsFn,
|
|
23
|
+
getOrchRunSnapshot,
|
|
24
|
+
getOrchTaskSpec as getOrchTaskSpecFn,
|
|
25
|
+
nonTerminalOrchTasks as nonTerminalOrchTasksFn,
|
|
26
|
+
} from "./event-store-orch"
|
|
27
|
+
|
|
28
|
+
// ─── Deps interface ────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
export interface OrchSubscriptionDeps {
|
|
31
|
+
readonly orchRunsById: Map<string, OrchRunRecord>
|
|
32
|
+
readonly orchLogPath: string
|
|
33
|
+
readonly orchRunsSubscribers: Set<() => void>
|
|
34
|
+
applyEvent: (event: OrchestrationEvent) => void
|
|
35
|
+
enqueueDiskAppend: (filePath: string, payload: string) => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Subscription state ───────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/** Create the mutable subscription state held by EventStore. */
|
|
41
|
+
export function createOrchSubscriptionState(): { orchRunsSubscribers: Set<() => void> } {
|
|
42
|
+
return { orchRunsSubscribers: new Set() }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Write path ────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Apply synchronously, then enqueue the disk append — the sync apply is what
|
|
49
|
+
* makes an orchestration claim atomic within one event-loop turn (same
|
|
50
|
+
* pattern as appendSubagentEvent).
|
|
51
|
+
*/
|
|
52
|
+
export function appendOrchestrationEvent(
|
|
53
|
+
deps: OrchSubscriptionDeps,
|
|
54
|
+
event: OrchestrationEvent,
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
deps.applyEvent(event)
|
|
57
|
+
deps.enqueueDiskAppend(deps.orchLogPath, `${JSON.stringify(event)}\n`)
|
|
58
|
+
notifyOrchRunsChanged(deps)
|
|
59
|
+
return Promise.resolve()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Observe orchestration read-model changes. The callback fires after each
|
|
64
|
+
* live orch event is applied (not during boot replay — no subscribers yet).
|
|
65
|
+
* Returns an unsubscribe fn.
|
|
66
|
+
*/
|
|
67
|
+
export function subscribeOrchRuns(deps: OrchSubscriptionDeps, cb: () => void): () => void {
|
|
68
|
+
deps.orchRunsSubscribers.add(cb)
|
|
69
|
+
return () => { deps.orchRunsSubscribers.delete(cb) }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function notifyOrchRunsChanged(deps: OrchSubscriptionDeps): void {
|
|
73
|
+
for (const cb of deps.orchRunsSubscribers) {
|
|
74
|
+
try { cb() } catch (err) { log.warn(`${LOG_PREFIX} orch-runs subscriber threw`, { err }) }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Read-model thin wrappers ──────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
export function getOrchRun(
|
|
81
|
+
orchRunsById: Map<string, OrchRunRecord>,
|
|
82
|
+
runId: string,
|
|
83
|
+
): OrchRunSnapshot | null {
|
|
84
|
+
return getOrchRunSnapshot(orchRunsById, runId)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getOrchRuns(orchRunsById: Map<string, OrchRunRecord>): OrchRunSnapshot[] {
|
|
88
|
+
return getAllOrchRunSnapshots(orchRunsById)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function nonTerminalOrchTasks(
|
|
92
|
+
orchRunsById: Map<string, OrchRunRecord>,
|
|
93
|
+
): Iterable<{ runId: string; taskId: string; state: "claimed" | "running" }> {
|
|
94
|
+
return nonTerminalOrchTasksFn(orchRunsById)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function gatedOrchTasks(
|
|
98
|
+
orchRunsById: Map<string, OrchRunRecord>,
|
|
99
|
+
): Iterable<{ runId: string; taskId: string; phaseIndex: number }> {
|
|
100
|
+
return gatedOrchTasksFn(orchRunsById)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function getOrchTaskSpec(
|
|
104
|
+
orchRunsById: Map<string, OrchRunRecord>,
|
|
105
|
+
runId: string,
|
|
106
|
+
taskId: string,
|
|
107
|
+
): { prompt: string; scopePaths: string[] } | null {
|
|
108
|
+
return getOrchTaskSpecFn(orchRunsById, runId, taskId)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getOrchLastPhaseOutput(
|
|
112
|
+
orchRunsById: Map<string, OrchRunRecord>,
|
|
113
|
+
runId: string,
|
|
114
|
+
taskId: string,
|
|
115
|
+
): string | null {
|
|
116
|
+
return getOrchLastPhaseOutputFn(orchRunsById, runId, taskId)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getOrchRunEvents(
|
|
120
|
+
orchRunsById: Map<string, OrchRunRecord>,
|
|
121
|
+
runId: string,
|
|
122
|
+
): OrchestrationEvent[] {
|
|
123
|
+
return getOrchRunEventsFn(orchRunsById, runId)
|
|
124
|
+
}
|