@elyracode/agent-core 0.1.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 +488 -0
- package/dist/agent-loop.d.ts +24 -0
- package/dist/agent-loop.d.ts.map +1 -0
- package/dist/agent-loop.js +479 -0
- package/dist/agent-loop.js.map +1 -0
- package/dist/agent.d.ts +118 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +402 -0
- package/dist/agent.js.map +1 -0
- package/dist/harness/agent-harness.d.ts +78 -0
- package/dist/harness/agent-harness.d.ts.map +1 -0
- package/dist/harness/agent-harness.js +602 -0
- package/dist/harness/agent-harness.js.map +1 -0
- package/dist/harness/compaction/branch-summarization.d.ts +88 -0
- package/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/harness/compaction/branch-summarization.js +243 -0
- package/dist/harness/compaction/branch-summarization.js.map +1 -0
- package/dist/harness/compaction/compaction.d.ts +122 -0
- package/dist/harness/compaction/compaction.d.ts.map +1 -0
- package/dist/harness/compaction/compaction.js +616 -0
- package/dist/harness/compaction/compaction.js.map +1 -0
- package/dist/harness/compaction/utils.d.ts +38 -0
- package/dist/harness/compaction/utils.d.ts.map +1 -0
- package/dist/harness/compaction/utils.js +153 -0
- package/dist/harness/compaction/utils.js.map +1 -0
- package/dist/harness/env/nodejs.d.ts +44 -0
- package/dist/harness/env/nodejs.d.ts.map +1 -0
- package/dist/harness/env/nodejs.js +348 -0
- package/dist/harness/env/nodejs.js.map +1 -0
- package/dist/harness/execution-env.d.ts +4 -0
- package/dist/harness/execution-env.d.ts.map +1 -0
- package/dist/harness/execution-env.js +3 -0
- package/dist/harness/execution-env.js.map +1 -0
- package/dist/harness/messages.d.ts +51 -0
- package/dist/harness/messages.d.ts.map +1 -0
- package/dist/harness/messages.js +102 -0
- package/dist/harness/messages.js.map +1 -0
- package/dist/harness/prompt-templates.d.ts +45 -0
- package/dist/harness/prompt-templates.d.ts.map +1 -0
- package/dist/harness/prompt-templates.js +200 -0
- package/dist/harness/prompt-templates.js.map +1 -0
- package/dist/harness/session/repo/jsonl.d.ts +20 -0
- package/dist/harness/session/repo/jsonl.d.ts.map +1 -0
- package/dist/harness/session/repo/jsonl.js +92 -0
- package/dist/harness/session/repo/jsonl.js.map +1 -0
- package/dist/harness/session/repo/memory.d.ts +18 -0
- package/dist/harness/session/repo/memory.d.ts.map +1 -0
- package/dist/harness/session/repo/memory.js +42 -0
- package/dist/harness/session/repo/memory.js.map +1 -0
- package/dist/harness/session/repo/shared.d.ts +10 -0
- package/dist/harness/session/repo/shared.d.ts.map +1 -0
- package/dist/harness/session/repo/shared.js +31 -0
- package/dist/harness/session/repo/shared.js.map +1 -0
- package/dist/harness/session/session.d.ts +32 -0
- package/dist/harness/session/session.d.ts.map +1 -0
- package/dist/harness/session/session.js +196 -0
- package/dist/harness/session/session.js.map +1 -0
- package/dist/harness/session/storage/jsonl.d.ts +30 -0
- package/dist/harness/session/storage/jsonl.d.ts.map +1 -0
- package/dist/harness/session/storage/jsonl.js +170 -0
- package/dist/harness/session/storage/jsonl.js.map +1 -0
- package/dist/harness/session/storage/memory.d.ts +26 -0
- package/dist/harness/session/storage/memory.d.ts.map +1 -0
- package/dist/harness/session/storage/memory.js +90 -0
- package/dist/harness/session/storage/memory.js.map +1 -0
- package/dist/harness/skills.d.ts +41 -0
- package/dist/harness/skills.d.ts.map +1 -0
- package/dist/harness/skills.js +259 -0
- package/dist/harness/skills.js.map +1 -0
- package/dist/harness/system-prompt.d.ts +3 -0
- package/dist/harness/system-prompt.d.ts.map +1 -0
- package/dist/harness/system-prompt.js +30 -0
- package/dist/harness/system-prompt.js.map +1 -0
- package/dist/harness/types.d.ts +497 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +16 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/utils/shell-output.d.ts +14 -0
- package/dist/harness/utils/shell-output.d.ts.map +1 -0
- package/dist/harness/utils/shell-output.js +97 -0
- package/dist/harness/utils/shell-output.js.map +1 -0
- package/dist/harness/utils/truncate.d.ts +70 -0
- package/dist/harness/utils/truncate.d.ts.map +1 -0
- package/dist/harness/utils/truncate.js +205 -0
- package/dist/harness/utils/truncate.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy.d.ts +69 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +278 -0
- package/dist/proxy.js.map +1 -0
- package/dist/types.d.ts +386 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage } from "../messages.js";
|
|
2
|
+
export function buildSessionContext(pathEntries) {
|
|
3
|
+
let thinkingLevel = "off";
|
|
4
|
+
let model = null;
|
|
5
|
+
let compaction = null;
|
|
6
|
+
for (const entry of pathEntries) {
|
|
7
|
+
if (entry.type === "thinking_level_change") {
|
|
8
|
+
thinkingLevel = entry.thinkingLevel;
|
|
9
|
+
}
|
|
10
|
+
else if (entry.type === "model_change") {
|
|
11
|
+
model = { provider: entry.provider, modelId: entry.modelId };
|
|
12
|
+
}
|
|
13
|
+
else if (entry.type === "message" && entry.message.role === "assistant") {
|
|
14
|
+
model = { provider: entry.message.provider, modelId: entry.message.model };
|
|
15
|
+
}
|
|
16
|
+
else if (entry.type === "compaction") {
|
|
17
|
+
compaction = entry;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const messages = [];
|
|
21
|
+
const appendMessage = (entry) => {
|
|
22
|
+
if (entry.type === "message") {
|
|
23
|
+
messages.push(entry.message);
|
|
24
|
+
}
|
|
25
|
+
else if (entry.type === "custom_message") {
|
|
26
|
+
messages.push(createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp));
|
|
27
|
+
}
|
|
28
|
+
else if (entry.type === "branch_summary" && entry.summary) {
|
|
29
|
+
messages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp));
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
if (compaction) {
|
|
33
|
+
messages.push(createCompactionSummaryMessage(compaction.summary, compaction.tokensBefore, compaction.timestamp));
|
|
34
|
+
const compactionIdx = pathEntries.findIndex((e) => e.type === "compaction" && e.id === compaction.id);
|
|
35
|
+
let foundFirstKept = false;
|
|
36
|
+
for (let i = 0; i < compactionIdx; i++) {
|
|
37
|
+
const entry = pathEntries[i];
|
|
38
|
+
if (entry.id === compaction.firstKeptEntryId)
|
|
39
|
+
foundFirstKept = true;
|
|
40
|
+
if (foundFirstKept)
|
|
41
|
+
appendMessage(entry);
|
|
42
|
+
}
|
|
43
|
+
for (let i = compactionIdx + 1; i < pathEntries.length; i++) {
|
|
44
|
+
appendMessage(pathEntries[i]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
for (const entry of pathEntries) {
|
|
49
|
+
appendMessage(entry);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { messages, thinkingLevel, model };
|
|
53
|
+
}
|
|
54
|
+
export class Session {
|
|
55
|
+
storage;
|
|
56
|
+
constructor(storage) {
|
|
57
|
+
this.storage = storage;
|
|
58
|
+
}
|
|
59
|
+
getMetadata() {
|
|
60
|
+
return this.storage.getMetadata();
|
|
61
|
+
}
|
|
62
|
+
getStorage() {
|
|
63
|
+
return this.storage;
|
|
64
|
+
}
|
|
65
|
+
getLeafId() {
|
|
66
|
+
return this.storage.getLeafId();
|
|
67
|
+
}
|
|
68
|
+
getEntry(id) {
|
|
69
|
+
return this.storage.getEntry(id);
|
|
70
|
+
}
|
|
71
|
+
getEntries() {
|
|
72
|
+
return this.storage.getEntries();
|
|
73
|
+
}
|
|
74
|
+
async getBranch(fromId) {
|
|
75
|
+
const leafId = fromId ?? (await this.storage.getLeafId());
|
|
76
|
+
return this.storage.getPathToRoot(leafId);
|
|
77
|
+
}
|
|
78
|
+
async buildContext() {
|
|
79
|
+
return buildSessionContext(await this.getBranch());
|
|
80
|
+
}
|
|
81
|
+
getLabel(id) {
|
|
82
|
+
return this.storage.getLabel(id);
|
|
83
|
+
}
|
|
84
|
+
async getSessionName() {
|
|
85
|
+
const entries = await this.storage.findEntries("session_info");
|
|
86
|
+
return entries[entries.length - 1]?.name?.trim() || undefined;
|
|
87
|
+
}
|
|
88
|
+
async appendTypedEntry(entry) {
|
|
89
|
+
await this.storage.appendEntry(entry);
|
|
90
|
+
return entry.id;
|
|
91
|
+
}
|
|
92
|
+
async appendMessage(message) {
|
|
93
|
+
return this.appendTypedEntry({
|
|
94
|
+
type: "message",
|
|
95
|
+
id: await this.storage.createEntryId(),
|
|
96
|
+
parentId: await this.storage.getLeafId(),
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
message,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async appendThinkingLevelChange(thinkingLevel) {
|
|
102
|
+
return this.appendTypedEntry({
|
|
103
|
+
type: "thinking_level_change",
|
|
104
|
+
id: await this.storage.createEntryId(),
|
|
105
|
+
parentId: await this.storage.getLeafId(),
|
|
106
|
+
timestamp: new Date().toISOString(),
|
|
107
|
+
thinkingLevel,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async appendModelChange(provider, modelId) {
|
|
111
|
+
return this.appendTypedEntry({
|
|
112
|
+
type: "model_change",
|
|
113
|
+
id: await this.storage.createEntryId(),
|
|
114
|
+
parentId: await this.storage.getLeafId(),
|
|
115
|
+
timestamp: new Date().toISOString(),
|
|
116
|
+
provider,
|
|
117
|
+
modelId,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromHook) {
|
|
121
|
+
return this.appendTypedEntry({
|
|
122
|
+
type: "compaction",
|
|
123
|
+
id: await this.storage.createEntryId(),
|
|
124
|
+
parentId: await this.storage.getLeafId(),
|
|
125
|
+
timestamp: new Date().toISOString(),
|
|
126
|
+
summary,
|
|
127
|
+
firstKeptEntryId,
|
|
128
|
+
tokensBefore,
|
|
129
|
+
details,
|
|
130
|
+
fromHook,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async appendCustomEntry(customType, data) {
|
|
134
|
+
return this.appendTypedEntry({
|
|
135
|
+
type: "custom",
|
|
136
|
+
id: await this.storage.createEntryId(),
|
|
137
|
+
parentId: await this.storage.getLeafId(),
|
|
138
|
+
timestamp: new Date().toISOString(),
|
|
139
|
+
customType,
|
|
140
|
+
data,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async appendCustomMessageEntry(customType, content, display, details) {
|
|
144
|
+
return this.appendTypedEntry({
|
|
145
|
+
type: "custom_message",
|
|
146
|
+
id: await this.storage.createEntryId(),
|
|
147
|
+
parentId: await this.storage.getLeafId(),
|
|
148
|
+
timestamp: new Date().toISOString(),
|
|
149
|
+
customType,
|
|
150
|
+
content,
|
|
151
|
+
display,
|
|
152
|
+
details,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async appendLabel(targetId, label) {
|
|
156
|
+
if (!(await this.storage.getEntry(targetId))) {
|
|
157
|
+
throw new Error(`Entry ${targetId} not found`);
|
|
158
|
+
}
|
|
159
|
+
return this.appendTypedEntry({
|
|
160
|
+
type: "label",
|
|
161
|
+
id: await this.storage.createEntryId(),
|
|
162
|
+
parentId: await this.storage.getLeafId(),
|
|
163
|
+
timestamp: new Date().toISOString(),
|
|
164
|
+
targetId,
|
|
165
|
+
label,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
async appendSessionName(name) {
|
|
169
|
+
return this.appendTypedEntry({
|
|
170
|
+
type: "session_info",
|
|
171
|
+
id: await this.storage.createEntryId(),
|
|
172
|
+
parentId: await this.storage.getLeafId(),
|
|
173
|
+
timestamp: new Date().toISOString(),
|
|
174
|
+
name: name.trim(),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async moveTo(entryId, summary) {
|
|
178
|
+
if (entryId !== null && !(await this.storage.getEntry(entryId))) {
|
|
179
|
+
throw new Error(`Entry ${entryId} not found`);
|
|
180
|
+
}
|
|
181
|
+
await this.storage.setLeafId(entryId);
|
|
182
|
+
if (!summary)
|
|
183
|
+
return undefined;
|
|
184
|
+
return this.appendTypedEntry({
|
|
185
|
+
type: "branch_summary",
|
|
186
|
+
id: await this.storage.createEntryId(),
|
|
187
|
+
parentId: entryId,
|
|
188
|
+
timestamp: new Date().toISOString(),
|
|
189
|
+
fromId: entryId ?? "root",
|
|
190
|
+
summary: summary.summary,
|
|
191
|
+
details: summary.details,
|
|
192
|
+
fromHook: summary.fromHook,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/harness/session/session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ImageContent, TextContent } from \"@elyracode/ai\";\nimport type { AgentMessage } from \"../../types.js\";\nimport { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage } from \"../messages.js\";\nimport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tCustomEntry,\n\tCustomMessageEntry,\n\tLabelEntry,\n\tMessageEntry,\n\tModelChangeEntry,\n\tSessionContext,\n\tSessionInfoEntry,\n\tSessionMetadata,\n\tSessionStorage,\n\tSessionTreeEntry,\n\tThinkingLevelChangeEntry,\n} from \"../types.js\";\n\nexport function buildSessionContext(pathEntries: SessionTreeEntry[]): SessionContext {\n\tlet thinkingLevel = \"off\";\n\tlet model: { provider: string; modelId: string } | null = null;\n\tlet compaction: CompactionEntry | null = null;\n\n\tfor (const entry of pathEntries) {\n\t\tif (entry.type === \"thinking_level_change\") {\n\t\t\tthinkingLevel = entry.thinkingLevel;\n\t\t} else if (entry.type === \"model_change\") {\n\t\t\tmodel = { provider: entry.provider, modelId: entry.modelId };\n\t\t} else if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\tmodel = { provider: entry.message.provider, modelId: entry.message.model };\n\t\t} else if (entry.type === \"compaction\") {\n\t\t\tcompaction = entry;\n\t\t}\n\t}\n\n\tconst messages: AgentMessage[] = [];\n\tconst appendMessage = (entry: SessionTreeEntry) => {\n\t\tif (entry.type === \"message\") {\n\t\t\tmessages.push(entry.message as AgentMessage);\n\t\t} else if (entry.type === \"custom_message\") {\n\t\t\tmessages.push(\n\t\t\t\tcreateCustomMessage(\n\t\t\t\t\tentry.customType,\n\t\t\t\t\tentry.content as string | (TextContent | ImageContent)[],\n\t\t\t\t\tentry.display,\n\t\t\t\t\tentry.details,\n\t\t\t\t\tentry.timestamp,\n\t\t\t\t),\n\t\t\t);\n\t\t} else if (entry.type === \"branch_summary\" && entry.summary) {\n\t\t\tmessages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp));\n\t\t}\n\t};\n\n\tif (compaction) {\n\t\tmessages.push(createCompactionSummaryMessage(compaction.summary, compaction.tokensBefore, compaction.timestamp));\n\t\tconst compactionIdx = pathEntries.findIndex((e) => e.type === \"compaction\" && e.id === compaction.id);\n\t\tlet foundFirstKept = false;\n\t\tfor (let i = 0; i < compactionIdx; i++) {\n\t\t\tconst entry = pathEntries[i]!;\n\t\t\tif (entry.id === compaction.firstKeptEntryId) foundFirstKept = true;\n\t\t\tif (foundFirstKept) appendMessage(entry);\n\t\t}\n\t\tfor (let i = compactionIdx + 1; i < pathEntries.length; i++) {\n\t\t\tappendMessage(pathEntries[i]!);\n\t\t}\n\t} else {\n\t\tfor (const entry of pathEntries) {\n\t\t\tappendMessage(entry);\n\t\t}\n\t}\n\n\treturn { messages, thinkingLevel, model };\n}\n\nexport class Session<TMetadata extends SessionMetadata = SessionMetadata> {\n\tprivate storage: SessionStorage<TMetadata>;\n\n\tconstructor(storage: SessionStorage<TMetadata>) {\n\t\tthis.storage = storage;\n\t}\n\n\tgetMetadata(): Promise<TMetadata> {\n\t\treturn this.storage.getMetadata();\n\t}\n\n\tgetStorage(): SessionStorage<TMetadata> {\n\t\treturn this.storage;\n\t}\n\n\tgetLeafId(): Promise<string | null> {\n\t\treturn this.storage.getLeafId();\n\t}\n\n\tgetEntry(id: string): Promise<SessionTreeEntry | undefined> {\n\t\treturn this.storage.getEntry(id);\n\t}\n\n\tgetEntries(): Promise<SessionTreeEntry[]> {\n\t\treturn this.storage.getEntries();\n\t}\n\n\tasync getBranch(fromId?: string): Promise<SessionTreeEntry[]> {\n\t\tconst leafId = fromId ?? (await this.storage.getLeafId());\n\t\treturn this.storage.getPathToRoot(leafId);\n\t}\n\n\tasync buildContext(): Promise<SessionContext> {\n\t\treturn buildSessionContext(await this.getBranch());\n\t}\n\n\tgetLabel(id: string): Promise<string | undefined> {\n\t\treturn this.storage.getLabel(id);\n\t}\n\n\tasync getSessionName(): Promise<string | undefined> {\n\t\tconst entries = await this.storage.findEntries(\"session_info\");\n\t\treturn entries[entries.length - 1]?.name?.trim() || undefined;\n\t}\n\n\tprivate async appendTypedEntry<TEntry extends SessionTreeEntry>(entry: TEntry): Promise<string> {\n\t\tawait this.storage.appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\tasync appendMessage(message: AgentMessage): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"message\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t} satisfies MessageEntry);\n\t}\n\n\tasync appendThinkingLevelChange(thinkingLevel: string): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"thinking_level_change\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t} satisfies ThinkingLevelChangeEntry);\n\t}\n\n\tasync appendModelChange(provider: string, modelId: string): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"model_change\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tprovider,\n\t\t\tmodelId,\n\t\t} satisfies ModelChangeEntry);\n\t}\n\n\tasync appendCompaction<T = unknown>(\n\t\tsummary: string,\n\t\tfirstKeptEntryId: string,\n\t\ttokensBefore: number,\n\t\tdetails?: T,\n\t\tfromHook?: boolean,\n\t): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"compaction\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tsummary,\n\t\t\tfirstKeptEntryId,\n\t\t\ttokensBefore,\n\t\t\tdetails,\n\t\t\tfromHook,\n\t\t} satisfies CompactionEntry<T>);\n\t}\n\n\tasync appendCustomEntry(customType: string, data?: unknown): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"custom\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcustomType,\n\t\t\tdata,\n\t\t} satisfies CustomEntry);\n\t}\n\n\tasync appendCustomMessageEntry<T = unknown>(\n\t\tcustomType: string,\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\tdisplay: boolean,\n\t\tdetails?: T,\n\t): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"custom_message\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcustomType,\n\t\t\tcontent,\n\t\t\tdisplay,\n\t\t\tdetails,\n\t\t} satisfies CustomMessageEntry<T>);\n\t}\n\n\tasync appendLabel(targetId: string, label: string | undefined): Promise<string> {\n\t\tif (!(await this.storage.getEntry(targetId))) {\n\t\t\tthrow new Error(`Entry ${targetId} not found`);\n\t\t}\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"label\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttargetId,\n\t\t\tlabel,\n\t\t} satisfies LabelEntry);\n\t}\n\n\tasync appendSessionName(name: string): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"session_info\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tname: name.trim(),\n\t\t} satisfies SessionInfoEntry);\n\t}\n\n\tasync moveTo(\n\t\tentryId: string | null,\n\t\tsummary?: { summary: string; details?: unknown; fromHook?: boolean },\n\t): Promise<string | undefined> {\n\t\tif (entryId !== null && !(await this.storage.getEntry(entryId))) {\n\t\t\tthrow new Error(`Entry ${entryId} not found`);\n\t\t}\n\t\tawait this.storage.setLeafId(entryId);\n\t\tif (!summary) return undefined;\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"branch_summary\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: entryId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tfromId: entryId ?? \"root\",\n\t\t\tsummary: summary.summary,\n\t\t\tdetails: summary.details,\n\t\t\tfromHook: summary.fromHook,\n\t\t} satisfies BranchSummaryEntry);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { JsonlSessionMetadata, SessionStorage, SessionTreeEntry } from "../../types.js";
|
|
2
|
+
export declare function loadJsonlSessionMetadata(filePath: string): Promise<JsonlSessionMetadata>;
|
|
3
|
+
export declare class JsonlSessionStorage implements SessionStorage<JsonlSessionMetadata> {
|
|
4
|
+
private readonly filePath;
|
|
5
|
+
private readonly metadata;
|
|
6
|
+
private entries;
|
|
7
|
+
private byId;
|
|
8
|
+
private labelsById;
|
|
9
|
+
private currentLeafId;
|
|
10
|
+
private constructor();
|
|
11
|
+
static open(filePath: string): Promise<JsonlSessionStorage>;
|
|
12
|
+
static create(filePath: string, options: {
|
|
13
|
+
cwd: string;
|
|
14
|
+
sessionId: string;
|
|
15
|
+
parentSessionPath?: string;
|
|
16
|
+
}): Promise<JsonlSessionStorage>;
|
|
17
|
+
getMetadata(): Promise<JsonlSessionMetadata>;
|
|
18
|
+
getLeafId(): Promise<string | null>;
|
|
19
|
+
setLeafId(leafId: string | null): Promise<void>;
|
|
20
|
+
createEntryId(): Promise<string>;
|
|
21
|
+
appendEntry(entry: SessionTreeEntry): Promise<void>;
|
|
22
|
+
getEntry(id: string): Promise<SessionTreeEntry | undefined>;
|
|
23
|
+
findEntries<TType extends SessionTreeEntry["type"]>(type: TType): Promise<Array<Extract<SessionTreeEntry, {
|
|
24
|
+
type: TType;
|
|
25
|
+
}>>>;
|
|
26
|
+
getLabel(id: string): Promise<string | undefined>;
|
|
27
|
+
getPathToRoot(leafId: string | null): Promise<SessionTreeEntry[]>;
|
|
28
|
+
getEntries(): Promise<SessionTreeEntry[]>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=jsonl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.d.ts","sourceRoot":"","sources":["../../../../src/harness/session/storage/jsonl.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA+C7F,wBAAsB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAkB9F;AAkCD,qBAAa,mBAAoB,YAAW,cAAc,CAAC,oBAAoB,CAAC;IAC/E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;IAChD,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,IAAI,CAAgC;IAC5C,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,aAAa,CAAgB;IAErC,OAAO,eAON;IAED,OAAa,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAIhE;IAED,OAAa,MAAM,CAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;QACR,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC3B,GACC,OAAO,CAAC,mBAAmB,CAAC,CAa9B;IAEK,WAAW,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAEjD;IAEK,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAExC;IAEK,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAKpD;IAEK,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAErC;IAEK,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMxD;IAEK,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAEhE;IAEK,WAAW,CAAC,KAAK,SAAS,gBAAgB,CAAC,MAAM,CAAC,EACvD,IAAI,EAAE,KAAK,GACT,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC,CAAC,CAE5D;IAEK,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAEtD;IAEK,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAStE;IAEK,UAAU,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAE9C;CACD","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { createReadStream } from \"node:fs\";\nimport { appendFile, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport type { JsonlSessionMetadata, SessionStorage, SessionTreeEntry } from \"../../types.js\";\n\ninterface SessionHeader {\n\ttype: \"session\";\n\tversion: 3;\n\tid: string;\n\ttimestamp: string;\n\tcwd: string;\n\tparentSession?: string;\n}\n\nfunction updateLabelCache(labelsById: Map<string, string>, entry: SessionTreeEntry): void {\n\tif (entry.type !== \"label\") return;\n\tconst label = entry.label?.trim();\n\tif (label) {\n\t\tlabelsById.set(entry.targetId, label);\n\t} else {\n\t\tlabelsById.delete(entry.targetId);\n\t}\n}\n\nfunction buildLabelsById(entries: SessionTreeEntry[]): Map<string, string> {\n\tconst labelsById = new Map<string, string>();\n\tfor (const entry of entries) {\n\t\tupdateLabelCache(labelsById, entry);\n\t}\n\treturn labelsById;\n}\n\nfunction generateEntryId(byId: { has(id: string): boolean }): string {\n\tfor (let i = 0; i < 100; i++) {\n\t\tconst id = randomUUID().slice(0, 8);\n\t\tif (!byId.has(id)) return id;\n\t}\n\treturn randomUUID();\n}\n\nfunction headerToSessionMetadata(header: SessionHeader, path: string): JsonlSessionMetadata {\n\treturn {\n\t\tid: header.id,\n\t\tcreatedAt: header.timestamp,\n\t\tcwd: header.cwd,\n\t\tpath,\n\t\tparentSessionPath: header.parentSession,\n\t};\n}\n\nexport async function loadJsonlSessionMetadata(filePath: string): Promise<JsonlSessionMetadata> {\n\tconst stream = createReadStream(filePath, { encoding: \"utf8\" });\n\tconst lines = createInterface({ input: stream, crlfDelay: Infinity });\n\ttry {\n\t\tfor await (const line of lines) {\n\t\t\tif (!line.trim()) break;\n\t\t\ttry {\n\t\t\t\tconst header = JSON.parse(line) as SessionHeader;\n\t\t\t\treturn headerToSessionMetadata(header, resolve(filePath));\n\t\t\t} catch {\n\t\t\t\tthrow new Error(`Invalid JSONL session file ${filePath}: first line is not a valid session header`);\n\t\t\t}\n\t\t}\n\t\tthrow new Error(`Invalid JSONL session file ${filePath}: missing session header`);\n\t} finally {\n\t\tlines.close();\n\t\tstream.destroy();\n\t}\n}\n\nasync function loadJsonlStorage(filePath: string): Promise<{\n\theader: SessionHeader;\n\tentries: SessionTreeEntry[];\n\tleafId: string | null;\n}> {\n\tconst content = await readFile(filePath, \"utf8\");\n\tconst lines = content.split(\"\\n\").filter((line) => line.trim());\n\tif (lines.length === 0) {\n\t\tthrow new Error(`Invalid JSONL session file ${filePath}: missing session header`);\n\t}\n\n\tlet header: SessionHeader;\n\ttry {\n\t\theader = JSON.parse(lines[0]!) as SessionHeader;\n\t} catch {\n\t\tthrow new Error(`Invalid JSONL session file ${filePath}: first line is not a valid session header`);\n\t}\n\n\tconst entries: SessionTreeEntry[] = [];\n\tlet leafId: string | null = null;\n\tfor (const line of lines.slice(1)) {\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line) as SessionTreeEntry;\n\t\t\tentries.push(entry);\n\t\t\tleafId = entry.id;\n\t\t} catch {\n\t\t\t// ignore malformed entry lines\n\t\t}\n\t}\n\treturn { header, entries, leafId };\n}\n\nexport class JsonlSessionStorage implements SessionStorage<JsonlSessionMetadata> {\n\tprivate readonly filePath: string;\n\tprivate readonly metadata: JsonlSessionMetadata;\n\tprivate entries: SessionTreeEntry[];\n\tprivate byId: Map<string, SessionTreeEntry>;\n\tprivate labelsById: Map<string, string>;\n\tprivate currentLeafId: string | null;\n\n\tprivate constructor(filePath: string, header: SessionHeader, entries: SessionTreeEntry[], leafId: string | null) {\n\t\tthis.filePath = resolve(filePath);\n\t\tthis.metadata = headerToSessionMetadata(header, this.filePath);\n\t\tthis.entries = entries;\n\t\tthis.byId = new Map(entries.map((entry) => [entry.id, entry]));\n\t\tthis.labelsById = buildLabelsById(entries);\n\t\tthis.currentLeafId = leafId;\n\t}\n\n\tstatic async open(filePath: string): Promise<JsonlSessionStorage> {\n\t\tconst resolvedPath = resolve(filePath);\n\t\tconst loaded = await loadJsonlStorage(resolvedPath);\n\t\treturn new JsonlSessionStorage(resolvedPath, loaded.header, loaded.entries, loaded.leafId);\n\t}\n\n\tstatic async create(\n\t\tfilePath: string,\n\t\toptions: {\n\t\t\tcwd: string;\n\t\t\tsessionId: string;\n\t\t\tparentSessionPath?: string;\n\t\t},\n\t): Promise<JsonlSessionStorage> {\n\t\tconst resolvedPath = resolve(filePath);\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: 3,\n\t\t\tid: options.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: options.cwd,\n\t\t\tparentSession: options.parentSessionPath,\n\t\t};\n\t\tawait mkdir(dirname(resolvedPath), { recursive: true });\n\t\tawait writeFile(resolvedPath, `${JSON.stringify(header)}\\n`);\n\t\treturn new JsonlSessionStorage(resolvedPath, header, [], null);\n\t}\n\n\tasync getMetadata(): Promise<JsonlSessionMetadata> {\n\t\treturn this.metadata;\n\t}\n\n\tasync getLeafId(): Promise<string | null> {\n\t\treturn this.currentLeafId;\n\t}\n\n\tasync setLeafId(leafId: string | null): Promise<void> {\n\t\tif (leafId !== null && !this.byId.has(leafId)) {\n\t\t\tthrow new Error(`Entry ${leafId} not found`);\n\t\t}\n\t\tthis.currentLeafId = leafId;\n\t}\n\n\tasync createEntryId(): Promise<string> {\n\t\treturn generateEntryId(this.byId);\n\t}\n\n\tasync appendEntry(entry: SessionTreeEntry): Promise<void> {\n\t\tawait appendFile(this.filePath, `${JSON.stringify(entry)}\\n`);\n\t\tthis.entries.push(entry);\n\t\tthis.byId.set(entry.id, entry);\n\t\tupdateLabelCache(this.labelsById, entry);\n\t\tthis.currentLeafId = entry.id;\n\t}\n\n\tasync getEntry(id: string): Promise<SessionTreeEntry | undefined> {\n\t\treturn this.byId.get(id);\n\t}\n\n\tasync findEntries<TType extends SessionTreeEntry[\"type\"]>(\n\t\ttype: TType,\n\t): Promise<Array<Extract<SessionTreeEntry, { type: TType }>>> {\n\t\treturn this.entries.filter((entry): entry is Extract<SessionTreeEntry, { type: TType }> => entry.type === type);\n\t}\n\n\tasync getLabel(id: string): Promise<string | undefined> {\n\t\treturn this.labelsById.get(id);\n\t}\n\n\tasync getPathToRoot(leafId: string | null): Promise<SessionTreeEntry[]> {\n\t\tif (leafId === null) return [];\n\t\tconst path: SessionTreeEntry[] = [];\n\t\tlet current = this.byId.get(leafId);\n\t\twhile (current) {\n\t\t\tpath.unshift(current);\n\t\t\tcurrent = current.parentId ? this.byId.get(current.parentId) : undefined;\n\t\t}\n\t\treturn path;\n\t}\n\n\tasync getEntries(): Promise<SessionTreeEntry[]> {\n\t\treturn [...this.entries];\n\t}\n}\n"]}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
function updateLabelCache(labelsById, entry) {
|
|
7
|
+
if (entry.type !== "label")
|
|
8
|
+
return;
|
|
9
|
+
const label = entry.label?.trim();
|
|
10
|
+
if (label) {
|
|
11
|
+
labelsById.set(entry.targetId, label);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
labelsById.delete(entry.targetId);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function buildLabelsById(entries) {
|
|
18
|
+
const labelsById = new Map();
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
updateLabelCache(labelsById, entry);
|
|
21
|
+
}
|
|
22
|
+
return labelsById;
|
|
23
|
+
}
|
|
24
|
+
function generateEntryId(byId) {
|
|
25
|
+
for (let i = 0; i < 100; i++) {
|
|
26
|
+
const id = randomUUID().slice(0, 8);
|
|
27
|
+
if (!byId.has(id))
|
|
28
|
+
return id;
|
|
29
|
+
}
|
|
30
|
+
return randomUUID();
|
|
31
|
+
}
|
|
32
|
+
function headerToSessionMetadata(header, path) {
|
|
33
|
+
return {
|
|
34
|
+
id: header.id,
|
|
35
|
+
createdAt: header.timestamp,
|
|
36
|
+
cwd: header.cwd,
|
|
37
|
+
path,
|
|
38
|
+
parentSessionPath: header.parentSession,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export async function loadJsonlSessionMetadata(filePath) {
|
|
42
|
+
const stream = createReadStream(filePath, { encoding: "utf8" });
|
|
43
|
+
const lines = createInterface({ input: stream, crlfDelay: Infinity });
|
|
44
|
+
try {
|
|
45
|
+
for await (const line of lines) {
|
|
46
|
+
if (!line.trim())
|
|
47
|
+
break;
|
|
48
|
+
try {
|
|
49
|
+
const header = JSON.parse(line);
|
|
50
|
+
return headerToSessionMetadata(header, resolve(filePath));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new Error(`Invalid JSONL session file ${filePath}: first line is not a valid session header`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Invalid JSONL session file ${filePath}: missing session header`);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
lines.close();
|
|
60
|
+
stream.destroy();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function loadJsonlStorage(filePath) {
|
|
64
|
+
const content = await readFile(filePath, "utf8");
|
|
65
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
66
|
+
if (lines.length === 0) {
|
|
67
|
+
throw new Error(`Invalid JSONL session file ${filePath}: missing session header`);
|
|
68
|
+
}
|
|
69
|
+
let header;
|
|
70
|
+
try {
|
|
71
|
+
header = JSON.parse(lines[0]);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
throw new Error(`Invalid JSONL session file ${filePath}: first line is not a valid session header`);
|
|
75
|
+
}
|
|
76
|
+
const entries = [];
|
|
77
|
+
let leafId = null;
|
|
78
|
+
for (const line of lines.slice(1)) {
|
|
79
|
+
try {
|
|
80
|
+
const entry = JSON.parse(line);
|
|
81
|
+
entries.push(entry);
|
|
82
|
+
leafId = entry.id;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// ignore malformed entry lines
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { header, entries, leafId };
|
|
89
|
+
}
|
|
90
|
+
export class JsonlSessionStorage {
|
|
91
|
+
filePath;
|
|
92
|
+
metadata;
|
|
93
|
+
entries;
|
|
94
|
+
byId;
|
|
95
|
+
labelsById;
|
|
96
|
+
currentLeafId;
|
|
97
|
+
constructor(filePath, header, entries, leafId) {
|
|
98
|
+
this.filePath = resolve(filePath);
|
|
99
|
+
this.metadata = headerToSessionMetadata(header, this.filePath);
|
|
100
|
+
this.entries = entries;
|
|
101
|
+
this.byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
102
|
+
this.labelsById = buildLabelsById(entries);
|
|
103
|
+
this.currentLeafId = leafId;
|
|
104
|
+
}
|
|
105
|
+
static async open(filePath) {
|
|
106
|
+
const resolvedPath = resolve(filePath);
|
|
107
|
+
const loaded = await loadJsonlStorage(resolvedPath);
|
|
108
|
+
return new JsonlSessionStorage(resolvedPath, loaded.header, loaded.entries, loaded.leafId);
|
|
109
|
+
}
|
|
110
|
+
static async create(filePath, options) {
|
|
111
|
+
const resolvedPath = resolve(filePath);
|
|
112
|
+
const header = {
|
|
113
|
+
type: "session",
|
|
114
|
+
version: 3,
|
|
115
|
+
id: options.sessionId,
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
cwd: options.cwd,
|
|
118
|
+
parentSession: options.parentSessionPath,
|
|
119
|
+
};
|
|
120
|
+
await mkdir(dirname(resolvedPath), { recursive: true });
|
|
121
|
+
await writeFile(resolvedPath, `${JSON.stringify(header)}\n`);
|
|
122
|
+
return new JsonlSessionStorage(resolvedPath, header, [], null);
|
|
123
|
+
}
|
|
124
|
+
async getMetadata() {
|
|
125
|
+
return this.metadata;
|
|
126
|
+
}
|
|
127
|
+
async getLeafId() {
|
|
128
|
+
return this.currentLeafId;
|
|
129
|
+
}
|
|
130
|
+
async setLeafId(leafId) {
|
|
131
|
+
if (leafId !== null && !this.byId.has(leafId)) {
|
|
132
|
+
throw new Error(`Entry ${leafId} not found`);
|
|
133
|
+
}
|
|
134
|
+
this.currentLeafId = leafId;
|
|
135
|
+
}
|
|
136
|
+
async createEntryId() {
|
|
137
|
+
return generateEntryId(this.byId);
|
|
138
|
+
}
|
|
139
|
+
async appendEntry(entry) {
|
|
140
|
+
await appendFile(this.filePath, `${JSON.stringify(entry)}\n`);
|
|
141
|
+
this.entries.push(entry);
|
|
142
|
+
this.byId.set(entry.id, entry);
|
|
143
|
+
updateLabelCache(this.labelsById, entry);
|
|
144
|
+
this.currentLeafId = entry.id;
|
|
145
|
+
}
|
|
146
|
+
async getEntry(id) {
|
|
147
|
+
return this.byId.get(id);
|
|
148
|
+
}
|
|
149
|
+
async findEntries(type) {
|
|
150
|
+
return this.entries.filter((entry) => entry.type === type);
|
|
151
|
+
}
|
|
152
|
+
async getLabel(id) {
|
|
153
|
+
return this.labelsById.get(id);
|
|
154
|
+
}
|
|
155
|
+
async getPathToRoot(leafId) {
|
|
156
|
+
if (leafId === null)
|
|
157
|
+
return [];
|
|
158
|
+
const path = [];
|
|
159
|
+
let current = this.byId.get(leafId);
|
|
160
|
+
while (current) {
|
|
161
|
+
path.unshift(current);
|
|
162
|
+
current = current.parentId ? this.byId.get(current.parentId) : undefined;
|
|
163
|
+
}
|
|
164
|
+
return path;
|
|
165
|
+
}
|
|
166
|
+
async getEntries() {
|
|
167
|
+
return [...this.entries];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=jsonl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.js","sourceRoot":"","sources":["../../../../src/harness/session/storage/jsonl.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { createReadStream } from \"node:fs\";\nimport { appendFile, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport type { JsonlSessionMetadata, SessionStorage, SessionTreeEntry } from \"../../types.js\";\n\ninterface SessionHeader {\n\ttype: \"session\";\n\tversion: 3;\n\tid: string;\n\ttimestamp: string;\n\tcwd: string;\n\tparentSession?: string;\n}\n\nfunction updateLabelCache(labelsById: Map<string, string>, entry: SessionTreeEntry): void {\n\tif (entry.type !== \"label\") return;\n\tconst label = entry.label?.trim();\n\tif (label) {\n\t\tlabelsById.set(entry.targetId, label);\n\t} else {\n\t\tlabelsById.delete(entry.targetId);\n\t}\n}\n\nfunction buildLabelsById(entries: SessionTreeEntry[]): Map<string, string> {\n\tconst labelsById = new Map<string, string>();\n\tfor (const entry of entries) {\n\t\tupdateLabelCache(labelsById, entry);\n\t}\n\treturn labelsById;\n}\n\nfunction generateEntryId(byId: { has(id: string): boolean }): string {\n\tfor (let i = 0; i < 100; i++) {\n\t\tconst id = randomUUID().slice(0, 8);\n\t\tif (!byId.has(id)) return id;\n\t}\n\treturn randomUUID();\n}\n\nfunction headerToSessionMetadata(header: SessionHeader, path: string): JsonlSessionMetadata {\n\treturn {\n\t\tid: header.id,\n\t\tcreatedAt: header.timestamp,\n\t\tcwd: header.cwd,\n\t\tpath,\n\t\tparentSessionPath: header.parentSession,\n\t};\n}\n\nexport async function loadJsonlSessionMetadata(filePath: string): Promise<JsonlSessionMetadata> {\n\tconst stream = createReadStream(filePath, { encoding: \"utf8\" });\n\tconst lines = createInterface({ input: stream, crlfDelay: Infinity });\n\ttry {\n\t\tfor await (const line of lines) {\n\t\t\tif (!line.trim()) break;\n\t\t\ttry {\n\t\t\t\tconst header = JSON.parse(line) as SessionHeader;\n\t\t\t\treturn headerToSessionMetadata(header, resolve(filePath));\n\t\t\t} catch {\n\t\t\t\tthrow new Error(`Invalid JSONL session file ${filePath}: first line is not a valid session header`);\n\t\t\t}\n\t\t}\n\t\tthrow new Error(`Invalid JSONL session file ${filePath}: missing session header`);\n\t} finally {\n\t\tlines.close();\n\t\tstream.destroy();\n\t}\n}\n\nasync function loadJsonlStorage(filePath: string): Promise<{\n\theader: SessionHeader;\n\tentries: SessionTreeEntry[];\n\tleafId: string | null;\n}> {\n\tconst content = await readFile(filePath, \"utf8\");\n\tconst lines = content.split(\"\\n\").filter((line) => line.trim());\n\tif (lines.length === 0) {\n\t\tthrow new Error(`Invalid JSONL session file ${filePath}: missing session header`);\n\t}\n\n\tlet header: SessionHeader;\n\ttry {\n\t\theader = JSON.parse(lines[0]!) as SessionHeader;\n\t} catch {\n\t\tthrow new Error(`Invalid JSONL session file ${filePath}: first line is not a valid session header`);\n\t}\n\n\tconst entries: SessionTreeEntry[] = [];\n\tlet leafId: string | null = null;\n\tfor (const line of lines.slice(1)) {\n\t\ttry {\n\t\t\tconst entry = JSON.parse(line) as SessionTreeEntry;\n\t\t\tentries.push(entry);\n\t\t\tleafId = entry.id;\n\t\t} catch {\n\t\t\t// ignore malformed entry lines\n\t\t}\n\t}\n\treturn { header, entries, leafId };\n}\n\nexport class JsonlSessionStorage implements SessionStorage<JsonlSessionMetadata> {\n\tprivate readonly filePath: string;\n\tprivate readonly metadata: JsonlSessionMetadata;\n\tprivate entries: SessionTreeEntry[];\n\tprivate byId: Map<string, SessionTreeEntry>;\n\tprivate labelsById: Map<string, string>;\n\tprivate currentLeafId: string | null;\n\n\tprivate constructor(filePath: string, header: SessionHeader, entries: SessionTreeEntry[], leafId: string | null) {\n\t\tthis.filePath = resolve(filePath);\n\t\tthis.metadata = headerToSessionMetadata(header, this.filePath);\n\t\tthis.entries = entries;\n\t\tthis.byId = new Map(entries.map((entry) => [entry.id, entry]));\n\t\tthis.labelsById = buildLabelsById(entries);\n\t\tthis.currentLeafId = leafId;\n\t}\n\n\tstatic async open(filePath: string): Promise<JsonlSessionStorage> {\n\t\tconst resolvedPath = resolve(filePath);\n\t\tconst loaded = await loadJsonlStorage(resolvedPath);\n\t\treturn new JsonlSessionStorage(resolvedPath, loaded.header, loaded.entries, loaded.leafId);\n\t}\n\n\tstatic async create(\n\t\tfilePath: string,\n\t\toptions: {\n\t\t\tcwd: string;\n\t\t\tsessionId: string;\n\t\t\tparentSessionPath?: string;\n\t\t},\n\t): Promise<JsonlSessionStorage> {\n\t\tconst resolvedPath = resolve(filePath);\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: 3,\n\t\t\tid: options.sessionId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: options.cwd,\n\t\t\tparentSession: options.parentSessionPath,\n\t\t};\n\t\tawait mkdir(dirname(resolvedPath), { recursive: true });\n\t\tawait writeFile(resolvedPath, `${JSON.stringify(header)}\\n`);\n\t\treturn new JsonlSessionStorage(resolvedPath, header, [], null);\n\t}\n\n\tasync getMetadata(): Promise<JsonlSessionMetadata> {\n\t\treturn this.metadata;\n\t}\n\n\tasync getLeafId(): Promise<string | null> {\n\t\treturn this.currentLeafId;\n\t}\n\n\tasync setLeafId(leafId: string | null): Promise<void> {\n\t\tif (leafId !== null && !this.byId.has(leafId)) {\n\t\t\tthrow new Error(`Entry ${leafId} not found`);\n\t\t}\n\t\tthis.currentLeafId = leafId;\n\t}\n\n\tasync createEntryId(): Promise<string> {\n\t\treturn generateEntryId(this.byId);\n\t}\n\n\tasync appendEntry(entry: SessionTreeEntry): Promise<void> {\n\t\tawait appendFile(this.filePath, `${JSON.stringify(entry)}\\n`);\n\t\tthis.entries.push(entry);\n\t\tthis.byId.set(entry.id, entry);\n\t\tupdateLabelCache(this.labelsById, entry);\n\t\tthis.currentLeafId = entry.id;\n\t}\n\n\tasync getEntry(id: string): Promise<SessionTreeEntry | undefined> {\n\t\treturn this.byId.get(id);\n\t}\n\n\tasync findEntries<TType extends SessionTreeEntry[\"type\"]>(\n\t\ttype: TType,\n\t): Promise<Array<Extract<SessionTreeEntry, { type: TType }>>> {\n\t\treturn this.entries.filter((entry): entry is Extract<SessionTreeEntry, { type: TType }> => entry.type === type);\n\t}\n\n\tasync getLabel(id: string): Promise<string | undefined> {\n\t\treturn this.labelsById.get(id);\n\t}\n\n\tasync getPathToRoot(leafId: string | null): Promise<SessionTreeEntry[]> {\n\t\tif (leafId === null) return [];\n\t\tconst path: SessionTreeEntry[] = [];\n\t\tlet current = this.byId.get(leafId);\n\t\twhile (current) {\n\t\t\tpath.unshift(current);\n\t\t\tcurrent = current.parentId ? this.byId.get(current.parentId) : undefined;\n\t\t}\n\t\treturn path;\n\t}\n\n\tasync getEntries(): Promise<SessionTreeEntry[]> {\n\t\treturn [...this.entries];\n\t}\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SessionMetadata, SessionStorage, SessionTreeEntry } from "../../types.js";
|
|
2
|
+
export declare class InMemorySessionStorage implements SessionStorage {
|
|
3
|
+
private readonly metadata;
|
|
4
|
+
private entries;
|
|
5
|
+
private byId;
|
|
6
|
+
private labelsById;
|
|
7
|
+
private leafId;
|
|
8
|
+
constructor(options?: {
|
|
9
|
+
entries?: SessionTreeEntry[];
|
|
10
|
+
leafId?: string | null;
|
|
11
|
+
metadata?: SessionMetadata;
|
|
12
|
+
});
|
|
13
|
+
getMetadata(): Promise<SessionMetadata>;
|
|
14
|
+
getLeafId(): Promise<string | null>;
|
|
15
|
+
setLeafId(leafId: string | null): Promise<void>;
|
|
16
|
+
createEntryId(): Promise<string>;
|
|
17
|
+
appendEntry(entry: SessionTreeEntry): Promise<void>;
|
|
18
|
+
getEntry(id: string): Promise<SessionTreeEntry | undefined>;
|
|
19
|
+
findEntries<TType extends SessionTreeEntry["type"]>(type: TType): Promise<Array<Extract<SessionTreeEntry, {
|
|
20
|
+
type: TType;
|
|
21
|
+
}>>>;
|
|
22
|
+
getLabel(id: string): Promise<string | undefined>;
|
|
23
|
+
getPathToRoot(leafId: string | null): Promise<SessionTreeEntry[]>;
|
|
24
|
+
getEntries(): Promise<SessionTreeEntry[]>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../../src/harness/session/storage/memory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA4BxF,qBAAa,sBAAuB,YAAW,cAAc;IAC5D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,IAAI,CAAgC;IAC5C,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,MAAM,CAAgB;IAE9B,YAAY,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;KAAE,EASzG;IAEK,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC,CAE5C;IAEK,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAExC;IAEK,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAKpD;IAEK,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAErC;IAEK,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxD;IAEK,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAEhE;IAEK,WAAW,CAAC,KAAK,SAAS,gBAAgB,CAAC,MAAM,CAAC,EACvD,IAAI,EAAE,KAAK,GACT,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC,CAAC,CAE5D;IAEK,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAEtD;IAEK,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAStE;IAEK,UAAU,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAE9C;CACD","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { v7 as uuidv7 } from \"uuid\";\nimport type { SessionMetadata, SessionStorage, SessionTreeEntry } from \"../../types.js\";\n\nfunction updateLabelCache(labelsById: Map<string, string>, entry: SessionTreeEntry): void {\n\tif (entry.type !== \"label\") return;\n\tconst label = entry.label?.trim();\n\tif (label) {\n\t\tlabelsById.set(entry.targetId, label);\n\t} else {\n\t\tlabelsById.delete(entry.targetId);\n\t}\n}\n\nfunction buildLabelsById(entries: SessionTreeEntry[]): Map<string, string> {\n\tconst labelsById = new Map<string, string>();\n\tfor (const entry of entries) {\n\t\tupdateLabelCache(labelsById, entry);\n\t}\n\treturn labelsById;\n}\n\nfunction generateEntryId(byId: { has(id: string): boolean }): string {\n\tfor (let i = 0; i < 100; i++) {\n\t\tconst id = randomUUID().slice(0, 8);\n\t\tif (!byId.has(id)) return id;\n\t}\n\treturn randomUUID();\n}\n\nexport class InMemorySessionStorage implements SessionStorage {\n\tprivate readonly metadata: SessionMetadata;\n\tprivate entries: SessionTreeEntry[];\n\tprivate byId: Map<string, SessionTreeEntry>;\n\tprivate labelsById: Map<string, string>;\n\tprivate leafId: string | null;\n\n\tconstructor(options?: { entries?: SessionTreeEntry[]; leafId?: string | null; metadata?: SessionMetadata }) {\n\t\tthis.entries = options?.entries ? [...options.entries] : [];\n\t\tthis.byId = new Map(this.entries.map((entry) => [entry.id, entry]));\n\t\tthis.labelsById = buildLabelsById(this.entries);\n\t\tthis.leafId = options?.leafId ?? this.entries[this.entries.length - 1]?.id ?? null;\n\t\tif (this.leafId !== null && !this.byId.has(this.leafId)) {\n\t\t\tthrow new Error(`Entry ${this.leafId} not found`);\n\t\t}\n\t\tthis.metadata = options?.metadata ?? { id: uuidv7(), createdAt: new Date().toISOString() };\n\t}\n\n\tasync getMetadata(): Promise<SessionMetadata> {\n\t\treturn this.metadata;\n\t}\n\n\tasync getLeafId(): Promise<string | null> {\n\t\treturn this.leafId;\n\t}\n\n\tasync setLeafId(leafId: string | null): Promise<void> {\n\t\tif (leafId !== null && !this.byId.has(leafId)) {\n\t\t\tthrow new Error(`Entry ${leafId} not found`);\n\t\t}\n\t\tthis.leafId = leafId;\n\t}\n\n\tasync createEntryId(): Promise<string> {\n\t\treturn generateEntryId(this.byId);\n\t}\n\n\tasync appendEntry(entry: SessionTreeEntry): Promise<void> {\n\t\tthis.entries.push(entry);\n\t\tthis.byId.set(entry.id, entry);\n\t\tupdateLabelCache(this.labelsById, entry);\n\t\tthis.leafId = entry.id;\n\t}\n\n\tasync getEntry(id: string): Promise<SessionTreeEntry | undefined> {\n\t\treturn this.byId.get(id);\n\t}\n\n\tasync findEntries<TType extends SessionTreeEntry[\"type\"]>(\n\t\ttype: TType,\n\t): Promise<Array<Extract<SessionTreeEntry, { type: TType }>>> {\n\t\treturn this.entries.filter((entry): entry is Extract<SessionTreeEntry, { type: TType }> => entry.type === type);\n\t}\n\n\tasync getLabel(id: string): Promise<string | undefined> {\n\t\treturn this.labelsById.get(id);\n\t}\n\n\tasync getPathToRoot(leafId: string | null): Promise<SessionTreeEntry[]> {\n\t\tif (leafId === null) return [];\n\t\tconst path: SessionTreeEntry[] = [];\n\t\tlet current = this.byId.get(leafId);\n\t\twhile (current) {\n\t\t\tpath.unshift(current);\n\t\t\tcurrent = current.parentId ? this.byId.get(current.parentId) : undefined;\n\t\t}\n\t\treturn path;\n\t}\n\n\tasync getEntries(): Promise<SessionTreeEntry[]> {\n\t\treturn [...this.entries];\n\t}\n}\n"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { v7 as uuidv7 } from "uuid";
|
|
3
|
+
function updateLabelCache(labelsById, entry) {
|
|
4
|
+
if (entry.type !== "label")
|
|
5
|
+
return;
|
|
6
|
+
const label = entry.label?.trim();
|
|
7
|
+
if (label) {
|
|
8
|
+
labelsById.set(entry.targetId, label);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
labelsById.delete(entry.targetId);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function buildLabelsById(entries) {
|
|
15
|
+
const labelsById = new Map();
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
updateLabelCache(labelsById, entry);
|
|
18
|
+
}
|
|
19
|
+
return labelsById;
|
|
20
|
+
}
|
|
21
|
+
function generateEntryId(byId) {
|
|
22
|
+
for (let i = 0; i < 100; i++) {
|
|
23
|
+
const id = randomUUID().slice(0, 8);
|
|
24
|
+
if (!byId.has(id))
|
|
25
|
+
return id;
|
|
26
|
+
}
|
|
27
|
+
return randomUUID();
|
|
28
|
+
}
|
|
29
|
+
export class InMemorySessionStorage {
|
|
30
|
+
metadata;
|
|
31
|
+
entries;
|
|
32
|
+
byId;
|
|
33
|
+
labelsById;
|
|
34
|
+
leafId;
|
|
35
|
+
constructor(options) {
|
|
36
|
+
this.entries = options?.entries ? [...options.entries] : [];
|
|
37
|
+
this.byId = new Map(this.entries.map((entry) => [entry.id, entry]));
|
|
38
|
+
this.labelsById = buildLabelsById(this.entries);
|
|
39
|
+
this.leafId = options?.leafId ?? this.entries[this.entries.length - 1]?.id ?? null;
|
|
40
|
+
if (this.leafId !== null && !this.byId.has(this.leafId)) {
|
|
41
|
+
throw new Error(`Entry ${this.leafId} not found`);
|
|
42
|
+
}
|
|
43
|
+
this.metadata = options?.metadata ?? { id: uuidv7(), createdAt: new Date().toISOString() };
|
|
44
|
+
}
|
|
45
|
+
async getMetadata() {
|
|
46
|
+
return this.metadata;
|
|
47
|
+
}
|
|
48
|
+
async getLeafId() {
|
|
49
|
+
return this.leafId;
|
|
50
|
+
}
|
|
51
|
+
async setLeafId(leafId) {
|
|
52
|
+
if (leafId !== null && !this.byId.has(leafId)) {
|
|
53
|
+
throw new Error(`Entry ${leafId} not found`);
|
|
54
|
+
}
|
|
55
|
+
this.leafId = leafId;
|
|
56
|
+
}
|
|
57
|
+
async createEntryId() {
|
|
58
|
+
return generateEntryId(this.byId);
|
|
59
|
+
}
|
|
60
|
+
async appendEntry(entry) {
|
|
61
|
+
this.entries.push(entry);
|
|
62
|
+
this.byId.set(entry.id, entry);
|
|
63
|
+
updateLabelCache(this.labelsById, entry);
|
|
64
|
+
this.leafId = entry.id;
|
|
65
|
+
}
|
|
66
|
+
async getEntry(id) {
|
|
67
|
+
return this.byId.get(id);
|
|
68
|
+
}
|
|
69
|
+
async findEntries(type) {
|
|
70
|
+
return this.entries.filter((entry) => entry.type === type);
|
|
71
|
+
}
|
|
72
|
+
async getLabel(id) {
|
|
73
|
+
return this.labelsById.get(id);
|
|
74
|
+
}
|
|
75
|
+
async getPathToRoot(leafId) {
|
|
76
|
+
if (leafId === null)
|
|
77
|
+
return [];
|
|
78
|
+
const path = [];
|
|
79
|
+
let current = this.byId.get(leafId);
|
|
80
|
+
while (current) {
|
|
81
|
+
path.unshift(current);
|
|
82
|
+
current = current.parentId ? this.byId.get(current.parentId) : undefined;
|
|
83
|
+
}
|
|
84
|
+
return path;
|
|
85
|
+
}
|
|
86
|
+
async getEntries() {
|
|
87
|
+
return [...this.entries];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../../src/harness/session/storage/memory.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { v7 as uuidv7 } from \"uuid\";\nimport type { SessionMetadata, SessionStorage, SessionTreeEntry } from \"../../types.js\";\n\nfunction updateLabelCache(labelsById: Map<string, string>, entry: SessionTreeEntry): void {\n\tif (entry.type !== \"label\") return;\n\tconst label = entry.label?.trim();\n\tif (label) {\n\t\tlabelsById.set(entry.targetId, label);\n\t} else {\n\t\tlabelsById.delete(entry.targetId);\n\t}\n}\n\nfunction buildLabelsById(entries: SessionTreeEntry[]): Map<string, string> {\n\tconst labelsById = new Map<string, string>();\n\tfor (const entry of entries) {\n\t\tupdateLabelCache(labelsById, entry);\n\t}\n\treturn labelsById;\n}\n\nfunction generateEntryId(byId: { has(id: string): boolean }): string {\n\tfor (let i = 0; i < 100; i++) {\n\t\tconst id = randomUUID().slice(0, 8);\n\t\tif (!byId.has(id)) return id;\n\t}\n\treturn randomUUID();\n}\n\nexport class InMemorySessionStorage implements SessionStorage {\n\tprivate readonly metadata: SessionMetadata;\n\tprivate entries: SessionTreeEntry[];\n\tprivate byId: Map<string, SessionTreeEntry>;\n\tprivate labelsById: Map<string, string>;\n\tprivate leafId: string | null;\n\n\tconstructor(options?: { entries?: SessionTreeEntry[]; leafId?: string | null; metadata?: SessionMetadata }) {\n\t\tthis.entries = options?.entries ? [...options.entries] : [];\n\t\tthis.byId = new Map(this.entries.map((entry) => [entry.id, entry]));\n\t\tthis.labelsById = buildLabelsById(this.entries);\n\t\tthis.leafId = options?.leafId ?? this.entries[this.entries.length - 1]?.id ?? null;\n\t\tif (this.leafId !== null && !this.byId.has(this.leafId)) {\n\t\t\tthrow new Error(`Entry ${this.leafId} not found`);\n\t\t}\n\t\tthis.metadata = options?.metadata ?? { id: uuidv7(), createdAt: new Date().toISOString() };\n\t}\n\n\tasync getMetadata(): Promise<SessionMetadata> {\n\t\treturn this.metadata;\n\t}\n\n\tasync getLeafId(): Promise<string | null> {\n\t\treturn this.leafId;\n\t}\n\n\tasync setLeafId(leafId: string | null): Promise<void> {\n\t\tif (leafId !== null && !this.byId.has(leafId)) {\n\t\t\tthrow new Error(`Entry ${leafId} not found`);\n\t\t}\n\t\tthis.leafId = leafId;\n\t}\n\n\tasync createEntryId(): Promise<string> {\n\t\treturn generateEntryId(this.byId);\n\t}\n\n\tasync appendEntry(entry: SessionTreeEntry): Promise<void> {\n\t\tthis.entries.push(entry);\n\t\tthis.byId.set(entry.id, entry);\n\t\tupdateLabelCache(this.labelsById, entry);\n\t\tthis.leafId = entry.id;\n\t}\n\n\tasync getEntry(id: string): Promise<SessionTreeEntry | undefined> {\n\t\treturn this.byId.get(id);\n\t}\n\n\tasync findEntries<TType extends SessionTreeEntry[\"type\"]>(\n\t\ttype: TType,\n\t): Promise<Array<Extract<SessionTreeEntry, { type: TType }>>> {\n\t\treturn this.entries.filter((entry): entry is Extract<SessionTreeEntry, { type: TType }> => entry.type === type);\n\t}\n\n\tasync getLabel(id: string): Promise<string | undefined> {\n\t\treturn this.labelsById.get(id);\n\t}\n\n\tasync getPathToRoot(leafId: string | null): Promise<SessionTreeEntry[]> {\n\t\tif (leafId === null) return [];\n\t\tconst path: SessionTreeEntry[] = [];\n\t\tlet current = this.byId.get(leafId);\n\t\twhile (current) {\n\t\t\tpath.unshift(current);\n\t\t\tcurrent = current.parentId ? this.byId.get(current.parentId) : undefined;\n\t\t}\n\t\treturn path;\n\t}\n\n\tasync getEntries(): Promise<SessionTreeEntry[]> {\n\t\treturn [...this.entries];\n\t}\n}\n"]}
|