@eminent337/aery-core 0.67.120 → 0.67.122
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 +14 -0
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +31 -1
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts +4 -3
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +7 -3
- package/dist/agent.js.map +1 -1
- package/dist/harness/agent-harness.d.ts +103 -0
- package/dist/harness/agent-harness.d.ts.map +1 -0
- package/dist/harness/agent-harness.js +788 -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 +631 -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 +50 -0
- package/dist/harness/env/nodejs.d.ts.map +1 -0
- package/dist/harness/env/nodejs.js +487 -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/factory.d.ts +6 -0
- package/dist/harness/factory.d.ts.map +1 -0
- package/dist/harness/factory.js +9 -0
- package/dist/harness/factory.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 +47 -0
- package/dist/harness/prompt-templates.d.ts.map +1 -0
- package/dist/harness/prompt-templates.js +201 -0
- package/dist/harness/prompt-templates.js.map +1 -0
- package/dist/harness/session/jsonl-repo.d.ts +26 -0
- package/dist/harness/session/jsonl-repo.d.ts.map +1 -0
- package/dist/harness/session/jsonl-repo.js +97 -0
- package/dist/harness/session/jsonl-repo.js.map +1 -0
- package/dist/harness/session/jsonl-storage.d.ts +33 -0
- package/dist/harness/session/jsonl-storage.d.ts.map +1 -0
- package/dist/harness/session/jsonl-storage.js +159 -0
- package/dist/harness/session/jsonl-storage.js.map +1 -0
- package/dist/harness/session/memory-repo.d.ts +18 -0
- package/dist/harness/session/memory-repo.d.ts.map +1 -0
- package/dist/harness/session/memory-repo.js +42 -0
- package/dist/harness/session/memory-repo.js.map +1 -0
- package/dist/harness/session/memory-storage.d.ts +26 -0
- package/dist/harness/session/memory-storage.d.ts.map +1 -0
- package/dist/harness/session/memory-storage.js +89 -0
- package/dist/harness/session/memory-storage.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/repo-utils.d.ts +10 -0
- package/dist/harness/session/repo-utils.d.ts.map +1 -0
- package/dist/harness/session/repo-utils.js +31 -0
- package/dist/harness/session/repo-utils.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/session/uuid.d.ts +2 -0
- package/dist/harness/session/uuid.d.ts.map +1 -0
- package/dist/harness/session/uuid.js +50 -0
- package/dist/harness/session/uuid.js.map +1 -0
- package/dist/harness/skills.d.ts +43 -0
- package/dist/harness/skills.d.ts.map +1 -0
- package/dist/harness/skills.js +255 -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 +578 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +56 -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 +125 -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 +288 -0
- package/dist/harness/utils/truncate.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +3 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +3 -0
- package/dist/node.js.map +1 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +5 -2
- package/dist/proxy.js.map +1 -1
- package/dist/types.d.ts +50 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +19 -2
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { FileSystem, JsonlSessionMetadata, SessionStorage, SessionTreeEntry } from "../types.js";
|
|
2
|
+
type JsonlSessionStorageFileSystem = Pick<FileSystem, "readTextFile" | "writeFile" | "appendFile">;
|
|
3
|
+
export declare function loadJsonlSessionMetadata(fs: JsonlSessionStorageFileSystem, filePath: string): Promise<JsonlSessionMetadata>;
|
|
4
|
+
export declare class JsonlSessionStorage implements SessionStorage<JsonlSessionMetadata> {
|
|
5
|
+
private readonly fs;
|
|
6
|
+
private readonly filePath;
|
|
7
|
+
private readonly metadata;
|
|
8
|
+
private entries;
|
|
9
|
+
private byId;
|
|
10
|
+
private labelsById;
|
|
11
|
+
private currentLeafId;
|
|
12
|
+
private constructor();
|
|
13
|
+
static open(fs: JsonlSessionStorageFileSystem, filePath: string): Promise<JsonlSessionStorage>;
|
|
14
|
+
static create(fs: JsonlSessionStorageFileSystem, filePath: string, options: {
|
|
15
|
+
cwd: string;
|
|
16
|
+
sessionId: string;
|
|
17
|
+
parentSessionPath?: string;
|
|
18
|
+
}): Promise<JsonlSessionStorage>;
|
|
19
|
+
getMetadata(): Promise<JsonlSessionMetadata>;
|
|
20
|
+
getLeafId(): Promise<string | null>;
|
|
21
|
+
setLeafId(leafId: string | null): Promise<void>;
|
|
22
|
+
createEntryId(): Promise<string>;
|
|
23
|
+
appendEntry(entry: SessionTreeEntry): Promise<void>;
|
|
24
|
+
getEntry(id: string): Promise<SessionTreeEntry | undefined>;
|
|
25
|
+
findEntries<TType extends SessionTreeEntry["type"]>(type: TType): Promise<Array<Extract<SessionTreeEntry, {
|
|
26
|
+
type: TType;
|
|
27
|
+
}>>>;
|
|
28
|
+
getLabel(id: string): Promise<string | undefined>;
|
|
29
|
+
getPathToRoot(leafId: string | null): Promise<SessionTreeEntry[]>;
|
|
30
|
+
getEntries(): Promise<SessionTreeEntry[]>;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=jsonl-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl-storage.d.ts","sourceRoot":"","sources":["../../../src/harness/session/jsonl-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAItG,KAAK,6BAA6B,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,GAAG,WAAW,GAAG,YAAY,CAAC,CAAC;AA+CnG,wBAAsB,wBAAwB,CAC7C,EAAE,EAAE,6BAA6B,EACjC,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,oBAAoB,CAAC,CAY/B;AAqCD,qBAAa,mBAAoB,YAAW,cAAc,CAAC,oBAAoB,CAAC;IAC/E,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAgC;IACnD,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,eAcN;IAED,OAAa,IAAI,CAAC,EAAE,EAAE,6BAA6B,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAGnG;IAED,OAAa,MAAM,CAClB,EAAE,EAAE,6BAA6B,EACjC,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,CAW9B;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 type { FileSystem, JsonlSessionMetadata, SessionStorage, SessionTreeEntry } from \"../types.js\";\nimport { getOrThrow } from \"../types.js\";\nimport { uuidv7 } from \"./uuid.js\";\n\ntype JsonlSessionStorageFileSystem = Pick<FileSystem, \"readTextFile\" | \"writeFile\" | \"appendFile\">;\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 = uuidv7().slice(0, 8);\n\t\tif (!byId.has(id)) return id;\n\t}\n\treturn uuidv7();\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(\n\tfs: JsonlSessionStorageFileSystem,\n\tfilePath: string,\n): Promise<JsonlSessionMetadata> {\n\tconst content = getOrThrow(await fs.readTextFile(filePath));\n\tfor (const line of content.split(\"\\n\")) {\n\t\tif (!line.trim()) break;\n\t\ttry {\n\t\t\tconst header = JSON.parse(line) as SessionHeader;\n\t\t\treturn headerToSessionMetadata(header, filePath);\n\t\t} catch {\n\t\t\tthrow new Error(`Invalid JSONL session file ${filePath}: first line is not a valid session header`);\n\t\t}\n\t}\n\tthrow new Error(`Invalid JSONL session file ${filePath}: missing session header`);\n}\n\nasync function loadJsonlStorage(\n\tfs: JsonlSessionStorageFileSystem,\n\tfilePath: string,\n): Promise<{\n\theader: SessionHeader;\n\tentries: SessionTreeEntry[];\n\tleafId: string | null;\n}> {\n\tconst content = getOrThrow(await fs.readTextFile(filePath));\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 fs: JsonlSessionStorageFileSystem;\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(\n\t\tfs: JsonlSessionStorageFileSystem,\n\t\tfilePath: string,\n\t\theader: SessionHeader,\n\t\tentries: SessionTreeEntry[],\n\t\tleafId: string | null,\n\t) {\n\t\tthis.fs = fs;\n\t\tthis.filePath = 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(fs: JsonlSessionStorageFileSystem, filePath: string): Promise<JsonlSessionStorage> {\n\t\tconst loaded = await loadJsonlStorage(fs, filePath);\n\t\treturn new JsonlSessionStorage(fs, filePath, loaded.header, loaded.entries, loaded.leafId);\n\t}\n\n\tstatic async create(\n\t\tfs: JsonlSessionStorageFileSystem,\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 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\tgetOrThrow(await fs.writeFile(filePath, `${JSON.stringify(header)}\\n`));\n\t\treturn new JsonlSessionStorage(fs, filePath, 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\tgetOrThrow(await this.fs.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,159 @@
|
|
|
1
|
+
import { getOrThrow } from "../types.js";
|
|
2
|
+
import { uuidv7 } from "./uuid.js";
|
|
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 = uuidv7().slice(0, 8);
|
|
24
|
+
if (!byId.has(id))
|
|
25
|
+
return id;
|
|
26
|
+
}
|
|
27
|
+
return uuidv7();
|
|
28
|
+
}
|
|
29
|
+
function headerToSessionMetadata(header, path) {
|
|
30
|
+
return {
|
|
31
|
+
id: header.id,
|
|
32
|
+
createdAt: header.timestamp,
|
|
33
|
+
cwd: header.cwd,
|
|
34
|
+
path,
|
|
35
|
+
parentSessionPath: header.parentSession,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export async function loadJsonlSessionMetadata(fs, filePath) {
|
|
39
|
+
const content = getOrThrow(await fs.readTextFile(filePath));
|
|
40
|
+
for (const line of content.split("\n")) {
|
|
41
|
+
if (!line.trim())
|
|
42
|
+
break;
|
|
43
|
+
try {
|
|
44
|
+
const header = JSON.parse(line);
|
|
45
|
+
return headerToSessionMetadata(header, filePath);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new Error(`Invalid JSONL session file ${filePath}: first line is not a valid session header`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`Invalid JSONL session file ${filePath}: missing session header`);
|
|
52
|
+
}
|
|
53
|
+
async function loadJsonlStorage(fs, filePath) {
|
|
54
|
+
const content = getOrThrow(await fs.readTextFile(filePath));
|
|
55
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
56
|
+
if (lines.length === 0) {
|
|
57
|
+
throw new Error(`Invalid JSONL session file ${filePath}: missing session header`);
|
|
58
|
+
}
|
|
59
|
+
let header;
|
|
60
|
+
try {
|
|
61
|
+
header = JSON.parse(lines[0]);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new Error(`Invalid JSONL session file ${filePath}: first line is not a valid session header`);
|
|
65
|
+
}
|
|
66
|
+
const entries = [];
|
|
67
|
+
let leafId = null;
|
|
68
|
+
for (const line of lines.slice(1)) {
|
|
69
|
+
try {
|
|
70
|
+
const entry = JSON.parse(line);
|
|
71
|
+
entries.push(entry);
|
|
72
|
+
leafId = entry.id;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// ignore malformed entry lines
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { header, entries, leafId };
|
|
79
|
+
}
|
|
80
|
+
export class JsonlSessionStorage {
|
|
81
|
+
fs;
|
|
82
|
+
filePath;
|
|
83
|
+
metadata;
|
|
84
|
+
entries;
|
|
85
|
+
byId;
|
|
86
|
+
labelsById;
|
|
87
|
+
currentLeafId;
|
|
88
|
+
constructor(fs, filePath, header, entries, leafId) {
|
|
89
|
+
this.fs = fs;
|
|
90
|
+
this.filePath = filePath;
|
|
91
|
+
this.metadata = headerToSessionMetadata(header, this.filePath);
|
|
92
|
+
this.entries = entries;
|
|
93
|
+
this.byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
94
|
+
this.labelsById = buildLabelsById(entries);
|
|
95
|
+
this.currentLeafId = leafId;
|
|
96
|
+
}
|
|
97
|
+
static async open(fs, filePath) {
|
|
98
|
+
const loaded = await loadJsonlStorage(fs, filePath);
|
|
99
|
+
return new JsonlSessionStorage(fs, filePath, loaded.header, loaded.entries, loaded.leafId);
|
|
100
|
+
}
|
|
101
|
+
static async create(fs, filePath, options) {
|
|
102
|
+
const header = {
|
|
103
|
+
type: "session",
|
|
104
|
+
version: 3,
|
|
105
|
+
id: options.sessionId,
|
|
106
|
+
timestamp: new Date().toISOString(),
|
|
107
|
+
cwd: options.cwd,
|
|
108
|
+
parentSession: options.parentSessionPath,
|
|
109
|
+
};
|
|
110
|
+
getOrThrow(await fs.writeFile(filePath, `${JSON.stringify(header)}\n`));
|
|
111
|
+
return new JsonlSessionStorage(fs, filePath, header, [], null);
|
|
112
|
+
}
|
|
113
|
+
async getMetadata() {
|
|
114
|
+
return this.metadata;
|
|
115
|
+
}
|
|
116
|
+
async getLeafId() {
|
|
117
|
+
return this.currentLeafId;
|
|
118
|
+
}
|
|
119
|
+
async setLeafId(leafId) {
|
|
120
|
+
if (leafId !== null && !this.byId.has(leafId)) {
|
|
121
|
+
throw new Error(`Entry ${leafId} not found`);
|
|
122
|
+
}
|
|
123
|
+
this.currentLeafId = leafId;
|
|
124
|
+
}
|
|
125
|
+
async createEntryId() {
|
|
126
|
+
return generateEntryId(this.byId);
|
|
127
|
+
}
|
|
128
|
+
async appendEntry(entry) {
|
|
129
|
+
getOrThrow(await this.fs.appendFile(this.filePath, `${JSON.stringify(entry)}\n`));
|
|
130
|
+
this.entries.push(entry);
|
|
131
|
+
this.byId.set(entry.id, entry);
|
|
132
|
+
updateLabelCache(this.labelsById, entry);
|
|
133
|
+
this.currentLeafId = entry.id;
|
|
134
|
+
}
|
|
135
|
+
async getEntry(id) {
|
|
136
|
+
return this.byId.get(id);
|
|
137
|
+
}
|
|
138
|
+
async findEntries(type) {
|
|
139
|
+
return this.entries.filter((entry) => entry.type === type);
|
|
140
|
+
}
|
|
141
|
+
async getLabel(id) {
|
|
142
|
+
return this.labelsById.get(id);
|
|
143
|
+
}
|
|
144
|
+
async getPathToRoot(leafId) {
|
|
145
|
+
if (leafId === null)
|
|
146
|
+
return [];
|
|
147
|
+
const path = [];
|
|
148
|
+
let current = this.byId.get(leafId);
|
|
149
|
+
while (current) {
|
|
150
|
+
path.unshift(current);
|
|
151
|
+
current = current.parentId ? this.byId.get(current.parentId) : undefined;
|
|
152
|
+
}
|
|
153
|
+
return path;
|
|
154
|
+
}
|
|
155
|
+
async getEntries() {
|
|
156
|
+
return [...this.entries];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=jsonl-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl-storage.js","sourceRoot":"","sources":["../../../src/harness/session/jsonl-storage.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FileSystem, JsonlSessionMetadata, SessionStorage, SessionTreeEntry } from \"../types.js\";\nimport { getOrThrow } from \"../types.js\";\nimport { uuidv7 } from \"./uuid.js\";\n\ntype JsonlSessionStorageFileSystem = Pick<FileSystem, \"readTextFile\" | \"writeFile\" | \"appendFile\">;\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 = uuidv7().slice(0, 8);\n\t\tif (!byId.has(id)) return id;\n\t}\n\treturn uuidv7();\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(\n\tfs: JsonlSessionStorageFileSystem,\n\tfilePath: string,\n): Promise<JsonlSessionMetadata> {\n\tconst content = getOrThrow(await fs.readTextFile(filePath));\n\tfor (const line of content.split(\"\\n\")) {\n\t\tif (!line.trim()) break;\n\t\ttry {\n\t\t\tconst header = JSON.parse(line) as SessionHeader;\n\t\t\treturn headerToSessionMetadata(header, filePath);\n\t\t} catch {\n\t\t\tthrow new Error(`Invalid JSONL session file ${filePath}: first line is not a valid session header`);\n\t\t}\n\t}\n\tthrow new Error(`Invalid JSONL session file ${filePath}: missing session header`);\n}\n\nasync function loadJsonlStorage(\n\tfs: JsonlSessionStorageFileSystem,\n\tfilePath: string,\n): Promise<{\n\theader: SessionHeader;\n\tentries: SessionTreeEntry[];\n\tleafId: string | null;\n}> {\n\tconst content = getOrThrow(await fs.readTextFile(filePath));\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 fs: JsonlSessionStorageFileSystem;\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(\n\t\tfs: JsonlSessionStorageFileSystem,\n\t\tfilePath: string,\n\t\theader: SessionHeader,\n\t\tentries: SessionTreeEntry[],\n\t\tleafId: string | null,\n\t) {\n\t\tthis.fs = fs;\n\t\tthis.filePath = 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(fs: JsonlSessionStorageFileSystem, filePath: string): Promise<JsonlSessionStorage> {\n\t\tconst loaded = await loadJsonlStorage(fs, filePath);\n\t\treturn new JsonlSessionStorage(fs, filePath, loaded.header, loaded.entries, loaded.leafId);\n\t}\n\n\tstatic async create(\n\t\tfs: JsonlSessionStorageFileSystem,\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 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\tgetOrThrow(await fs.writeFile(filePath, `${JSON.stringify(header)}\\n`));\n\t\treturn new JsonlSessionStorage(fs, filePath, 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\tgetOrThrow(await this.fs.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,18 @@
|
|
|
1
|
+
import type { Session, SessionMetadata, SessionRepo } from "../types.js";
|
|
2
|
+
export declare class InMemorySessionRepo implements SessionRepo<SessionMetadata, {
|
|
3
|
+
id?: string;
|
|
4
|
+
}, void> {
|
|
5
|
+
private sessions;
|
|
6
|
+
create(options?: {
|
|
7
|
+
id?: string;
|
|
8
|
+
}): Promise<Session<SessionMetadata>>;
|
|
9
|
+
open(metadata: SessionMetadata): Promise<Session<SessionMetadata>>;
|
|
10
|
+
list(): Promise<SessionMetadata[]>;
|
|
11
|
+
delete(metadata: SessionMetadata): Promise<void>;
|
|
12
|
+
fork(sourceMetadata: SessionMetadata, options: {
|
|
13
|
+
entryId?: string;
|
|
14
|
+
position?: "before" | "at";
|
|
15
|
+
id?: string;
|
|
16
|
+
}): Promise<Session<SessionMetadata>>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=memory-repo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-repo.d.ts","sourceRoot":"","sources":["../../../src/harness/session/memory-repo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAIzE,qBAAa,mBAAoB,YAAW,WAAW,CAAC,eAAe,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,CAAC;IAC9F,OAAO,CAAC,QAAQ,CAA+C;IAEzD,MAAM,CAAC,OAAO,GAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAS7E;IAEK,IAAI,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAMvE;IAEK,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAEvC;IAEK,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAErD;IAEK,IAAI,CACT,cAAc,EAAE,eAAe,EAC/B,OAAO,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GACpE,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAYnC;CACD","sourcesContent":["import type { Session, SessionMetadata, SessionRepo } from \"../types.js\";\nimport { InMemorySessionStorage } from \"./memory-storage.js\";\nimport { createSessionId, createTimestamp, getEntriesToFork, toSession } from \"./repo-utils.js\";\n\nexport class InMemorySessionRepo implements SessionRepo<SessionMetadata, { id?: string }, void> {\n\tprivate sessions = new Map<string, Session<SessionMetadata>>();\n\n\tasync create(options: { id?: string } = {}): Promise<Session<SessionMetadata>> {\n\t\tconst metadata: SessionMetadata = {\n\t\t\tid: options.id ?? createSessionId(),\n\t\t\tcreatedAt: createTimestamp(),\n\t\t};\n\t\tconst storage = new InMemorySessionStorage({ metadata });\n\t\tconst session = toSession(storage);\n\t\tthis.sessions.set(metadata.id, session);\n\t\treturn session;\n\t}\n\n\tasync open(metadata: SessionMetadata): Promise<Session<SessionMetadata>> {\n\t\tconst session = this.sessions.get(metadata.id);\n\t\tif (!session) {\n\t\t\tthrow new Error(`Session not found: ${metadata.id}`);\n\t\t}\n\t\treturn session;\n\t}\n\n\tasync list(): Promise<SessionMetadata[]> {\n\t\treturn Promise.all([...this.sessions.values()].map((session) => session.getMetadata()));\n\t}\n\n\tasync delete(metadata: SessionMetadata): Promise<void> {\n\t\tthis.sessions.delete(metadata.id);\n\t}\n\n\tasync fork(\n\t\tsourceMetadata: SessionMetadata,\n\t\toptions: { entryId?: string; position?: \"before\" | \"at\"; id?: string },\n\t): Promise<Session<SessionMetadata>> {\n\t\tconst source = await this.open(sourceMetadata);\n\t\tconst forkedEntries = await getEntriesToFork(source.getStorage(), options);\n\t\tconst metadata: SessionMetadata = {\n\t\t\tid: options.id ?? createSessionId(),\n\t\t\tcreatedAt: createTimestamp(),\n\t\t};\n\t\tconst leafId = forkedEntries[forkedEntries.length - 1]?.id ?? null;\n\t\tconst storage = new InMemorySessionStorage({ metadata, entries: forkedEntries, leafId });\n\t\tconst session = toSession(storage);\n\t\tthis.sessions.set(metadata.id, session);\n\t\treturn session;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { InMemorySessionStorage } from "./memory-storage.js";
|
|
2
|
+
import { createSessionId, createTimestamp, getEntriesToFork, toSession } from "./repo-utils.js";
|
|
3
|
+
export class InMemorySessionRepo {
|
|
4
|
+
sessions = new Map();
|
|
5
|
+
async create(options = {}) {
|
|
6
|
+
const metadata = {
|
|
7
|
+
id: options.id ?? createSessionId(),
|
|
8
|
+
createdAt: createTimestamp(),
|
|
9
|
+
};
|
|
10
|
+
const storage = new InMemorySessionStorage({ metadata });
|
|
11
|
+
const session = toSession(storage);
|
|
12
|
+
this.sessions.set(metadata.id, session);
|
|
13
|
+
return session;
|
|
14
|
+
}
|
|
15
|
+
async open(metadata) {
|
|
16
|
+
const session = this.sessions.get(metadata.id);
|
|
17
|
+
if (!session) {
|
|
18
|
+
throw new Error(`Session not found: ${metadata.id}`);
|
|
19
|
+
}
|
|
20
|
+
return session;
|
|
21
|
+
}
|
|
22
|
+
async list() {
|
|
23
|
+
return Promise.all([...this.sessions.values()].map((session) => session.getMetadata()));
|
|
24
|
+
}
|
|
25
|
+
async delete(metadata) {
|
|
26
|
+
this.sessions.delete(metadata.id);
|
|
27
|
+
}
|
|
28
|
+
async fork(sourceMetadata, options) {
|
|
29
|
+
const source = await this.open(sourceMetadata);
|
|
30
|
+
const forkedEntries = await getEntriesToFork(source.getStorage(), options);
|
|
31
|
+
const metadata = {
|
|
32
|
+
id: options.id ?? createSessionId(),
|
|
33
|
+
createdAt: createTimestamp(),
|
|
34
|
+
};
|
|
35
|
+
const leafId = forkedEntries[forkedEntries.length - 1]?.id ?? null;
|
|
36
|
+
const storage = new InMemorySessionStorage({ metadata, entries: forkedEntries, leafId });
|
|
37
|
+
const session = toSession(storage);
|
|
38
|
+
this.sessions.set(metadata.id, session);
|
|
39
|
+
return session;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=memory-repo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-repo.js","sourceRoot":"","sources":["../../../src/harness/session/memory-repo.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEhG,MAAM,OAAO,mBAAmB;IACvB,QAAQ,GAAG,IAAI,GAAG,EAAoC,CAAC;IAE/D,KAAK,CAAC,MAAM,CAAC,OAAO,GAAoB,EAAE,EAAqC;QAC9E,MAAM,QAAQ,GAAoB;YACjC,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,eAAe,EAAE;YACnC,SAAS,EAAE,eAAe,EAAE;SAC5B,CAAC;QACF,MAAM,OAAO,GAAG,IAAI,sBAAsB,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,OAAO,CAAC;IAAA,CACf;IAED,KAAK,CAAC,IAAI,CAAC,QAAyB,EAAqC;QACxE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,OAAO,CAAC;IAAA,CACf;IAED,KAAK,CAAC,IAAI,GAA+B;QACxC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAAA,CACxF;IAED,KAAK,CAAC,MAAM,CAAC,QAAyB,EAAiB;QACtD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAAA,CAClC;IAED,KAAK,CAAC,IAAI,CACT,cAA+B,EAC/B,OAAsE,EAClC;QACpC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC/C,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,OAAO,CAAC,CAAC;QAC3E,MAAM,QAAQ,GAAoB;YACjC,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,eAAe,EAAE;YACnC,SAAS,EAAE,eAAe,EAAE;SAC5B,CAAC;QACF,MAAM,MAAM,GAAG,aAAa,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;QACnE,MAAM,OAAO,GAAG,IAAI,sBAAsB,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC;QACzF,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,OAAO,CAAC;IAAA,CACf;CACD","sourcesContent":["import type { Session, SessionMetadata, SessionRepo } from \"../types.js\";\nimport { InMemorySessionStorage } from \"./memory-storage.js\";\nimport { createSessionId, createTimestamp, getEntriesToFork, toSession } from \"./repo-utils.js\";\n\nexport class InMemorySessionRepo implements SessionRepo<SessionMetadata, { id?: string }, void> {\n\tprivate sessions = new Map<string, Session<SessionMetadata>>();\n\n\tasync create(options: { id?: string } = {}): Promise<Session<SessionMetadata>> {\n\t\tconst metadata: SessionMetadata = {\n\t\t\tid: options.id ?? createSessionId(),\n\t\t\tcreatedAt: createTimestamp(),\n\t\t};\n\t\tconst storage = new InMemorySessionStorage({ metadata });\n\t\tconst session = toSession(storage);\n\t\tthis.sessions.set(metadata.id, session);\n\t\treturn session;\n\t}\n\n\tasync open(metadata: SessionMetadata): Promise<Session<SessionMetadata>> {\n\t\tconst session = this.sessions.get(metadata.id);\n\t\tif (!session) {\n\t\t\tthrow new Error(`Session not found: ${metadata.id}`);\n\t\t}\n\t\treturn session;\n\t}\n\n\tasync list(): Promise<SessionMetadata[]> {\n\t\treturn Promise.all([...this.sessions.values()].map((session) => session.getMetadata()));\n\t}\n\n\tasync delete(metadata: SessionMetadata): Promise<void> {\n\t\tthis.sessions.delete(metadata.id);\n\t}\n\n\tasync fork(\n\t\tsourceMetadata: SessionMetadata,\n\t\toptions: { entryId?: string; position?: \"before\" | \"at\"; id?: string },\n\t): Promise<Session<SessionMetadata>> {\n\t\tconst source = await this.open(sourceMetadata);\n\t\tconst forkedEntries = await getEntriesToFork(source.getStorage(), options);\n\t\tconst metadata: SessionMetadata = {\n\t\t\tid: options.id ?? createSessionId(),\n\t\t\tcreatedAt: createTimestamp(),\n\t\t};\n\t\tconst leafId = forkedEntries[forkedEntries.length - 1]?.id ?? null;\n\t\tconst storage = new InMemorySessionStorage({ metadata, entries: forkedEntries, leafId });\n\t\tconst session = toSession(storage);\n\t\tthis.sessions.set(metadata.id, session);\n\t\treturn session;\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-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-storage.d.ts","sourceRoot":"","sources":["../../../src/harness/session/memory-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA6BrF,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 type { SessionMetadata, SessionStorage, SessionTreeEntry } from \"../types.js\";\nimport { uuidv7 } from \"./uuid.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 = uuidv7().slice(0, 8);\n\t\tif (!byId.has(id)) return id;\n\t}\n\treturn uuidv7();\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,89 @@
|
|
|
1
|
+
import { uuidv7 } from "./uuid.js";
|
|
2
|
+
function updateLabelCache(labelsById, entry) {
|
|
3
|
+
if (entry.type !== "label")
|
|
4
|
+
return;
|
|
5
|
+
const label = entry.label?.trim();
|
|
6
|
+
if (label) {
|
|
7
|
+
labelsById.set(entry.targetId, label);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
labelsById.delete(entry.targetId);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function buildLabelsById(entries) {
|
|
14
|
+
const labelsById = new Map();
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
updateLabelCache(labelsById, entry);
|
|
17
|
+
}
|
|
18
|
+
return labelsById;
|
|
19
|
+
}
|
|
20
|
+
function generateEntryId(byId) {
|
|
21
|
+
for (let i = 0; i < 100; i++) {
|
|
22
|
+
const id = uuidv7().slice(0, 8);
|
|
23
|
+
if (!byId.has(id))
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
return uuidv7();
|
|
27
|
+
}
|
|
28
|
+
export class InMemorySessionStorage {
|
|
29
|
+
metadata;
|
|
30
|
+
entries;
|
|
31
|
+
byId;
|
|
32
|
+
labelsById;
|
|
33
|
+
leafId;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.entries = options?.entries ? [...options.entries] : [];
|
|
36
|
+
this.byId = new Map(this.entries.map((entry) => [entry.id, entry]));
|
|
37
|
+
this.labelsById = buildLabelsById(this.entries);
|
|
38
|
+
this.leafId = options?.leafId ?? this.entries[this.entries.length - 1]?.id ?? null;
|
|
39
|
+
if (this.leafId !== null && !this.byId.has(this.leafId)) {
|
|
40
|
+
throw new Error(`Entry ${this.leafId} not found`);
|
|
41
|
+
}
|
|
42
|
+
this.metadata = options?.metadata ?? { id: uuidv7(), createdAt: new Date().toISOString() };
|
|
43
|
+
}
|
|
44
|
+
async getMetadata() {
|
|
45
|
+
return this.metadata;
|
|
46
|
+
}
|
|
47
|
+
async getLeafId() {
|
|
48
|
+
return this.leafId;
|
|
49
|
+
}
|
|
50
|
+
async setLeafId(leafId) {
|
|
51
|
+
if (leafId !== null && !this.byId.has(leafId)) {
|
|
52
|
+
throw new Error(`Entry ${leafId} not found`);
|
|
53
|
+
}
|
|
54
|
+
this.leafId = leafId;
|
|
55
|
+
}
|
|
56
|
+
async createEntryId() {
|
|
57
|
+
return generateEntryId(this.byId);
|
|
58
|
+
}
|
|
59
|
+
async appendEntry(entry) {
|
|
60
|
+
this.entries.push(entry);
|
|
61
|
+
this.byId.set(entry.id, entry);
|
|
62
|
+
updateLabelCache(this.labelsById, entry);
|
|
63
|
+
this.leafId = entry.id;
|
|
64
|
+
}
|
|
65
|
+
async getEntry(id) {
|
|
66
|
+
return this.byId.get(id);
|
|
67
|
+
}
|
|
68
|
+
async findEntries(type) {
|
|
69
|
+
return this.entries.filter((entry) => entry.type === type);
|
|
70
|
+
}
|
|
71
|
+
async getLabel(id) {
|
|
72
|
+
return this.labelsById.get(id);
|
|
73
|
+
}
|
|
74
|
+
async getPathToRoot(leafId) {
|
|
75
|
+
if (leafId === null)
|
|
76
|
+
return [];
|
|
77
|
+
const path = [];
|
|
78
|
+
let current = this.byId.get(leafId);
|
|
79
|
+
while (current) {
|
|
80
|
+
path.unshift(current);
|
|
81
|
+
current = current.parentId ? this.byId.get(current.parentId) : undefined;
|
|
82
|
+
}
|
|
83
|
+
return path;
|
|
84
|
+
}
|
|
85
|
+
async getEntries() {
|
|
86
|
+
return [...this.entries];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=memory-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-storage.js","sourceRoot":"","sources":["../../../src/harness/session/memory-storage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,SAAS,gBAAgB,CAAC,UAA+B,EAAE,KAAuB,EAAQ;IACzF,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO;IACnC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;IAClC,IAAI,KAAK,EAAE,CAAC;QACX,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACvC,CAAC;SAAM,CAAC;QACP,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;AAAA,CACD;AAED,SAAS,eAAe,CAAC,OAA2B,EAAuB;IAC1E,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC7C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,gBAAgB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,UAAU,CAAC;AAAA,CAClB;AAED,SAAS,eAAe,CAAC,IAAkC,EAAU;IACpE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,OAAO,EAAE,CAAC;IAC9B,CAAC;IACD,OAAO,MAAM,EAAE,CAAC;AAAA,CAChB;AAED,MAAM,OAAO,sBAAsB;IACjB,QAAQ,CAAkB;IACnC,OAAO,CAAqB;IAC5B,IAAI,CAAgC;IACpC,UAAU,CAAsB;IAChC,MAAM,CAAgB;IAE9B,YAAY,OAA8F,EAAE;QAC3G,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACpE,IAAI,CAAC,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;QACnF,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,MAAM,YAAY,CAAC,CAAC;QACnD,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IAAA,CAC3F;IAED,KAAK,CAAC,WAAW,GAA6B;QAC7C,OAAO,IAAI,CAAC,QAAQ,CAAC;IAAA,CACrB;IAED,KAAK,CAAC,SAAS,GAA2B;QACzC,OAAO,IAAI,CAAC,MAAM,CAAC;IAAA,CACnB;IAED,KAAK,CAAC,SAAS,CAAC,MAAqB,EAAiB;QACrD,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,SAAS,MAAM,YAAY,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAAA,CACrB;IAED,KAAK,CAAC,aAAa,GAAoB;QACtC,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAAA,CAClC;IAED,KAAK,CAAC,WAAW,CAAC,KAAuB,EAAiB;QACzD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAC/B,gBAAgB,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC;IAAA,CACvB;IAED,KAAK,CAAC,QAAQ,CAAC,EAAU,EAAyC;QACjE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAAA,CACzB;IAED,KAAK,CAAC,WAAW,CAChB,IAAW,EACkD;QAC7D,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAuD,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAAA,CAChH;IAED,KAAK,CAAC,QAAQ,CAAC,EAAU,EAA+B;QACvD,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAAA,CAC/B;IAED,KAAK,CAAC,aAAa,CAAC,MAAqB,EAA+B;QACvE,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAuB,EAAE,CAAC;QACpC,IAAI,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpC,OAAO,OAAO,EAAE,CAAC;YAChB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACtB,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1E,CAAC;QACD,OAAO,IAAI,CAAC;IAAA,CACZ;IAED,KAAK,CAAC,UAAU,GAAgC;QAC/C,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAAA,CACzB;CACD","sourcesContent":["import type { SessionMetadata, SessionStorage, SessionTreeEntry } from \"../types.js\";\nimport { uuidv7 } from \"./uuid.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 = uuidv7().slice(0, 8);\n\t\tif (!byId.has(id)) return id;\n\t}\n\treturn uuidv7();\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,20 @@
|
|
|
1
|
+
import type { JsonlSessionCreateOptions, JsonlSessionListOptions, JsonlSessionMetadata, JsonlSessionRepoApi, Session } from "../../types.js";
|
|
2
|
+
export declare class JsonlSessionRepo implements JsonlSessionRepoApi {
|
|
3
|
+
private sessionsRoot;
|
|
4
|
+
constructor(options: {
|
|
5
|
+
sessionsRoot: string;
|
|
6
|
+
});
|
|
7
|
+
private getSessionDir;
|
|
8
|
+
private createSessionFilePath;
|
|
9
|
+
create(options: JsonlSessionCreateOptions): Promise<Session<JsonlSessionMetadata>>;
|
|
10
|
+
open(metadata: JsonlSessionMetadata): Promise<Session<JsonlSessionMetadata>>;
|
|
11
|
+
list(options?: JsonlSessionListOptions): Promise<JsonlSessionMetadata[]>;
|
|
12
|
+
delete(metadata: JsonlSessionMetadata): Promise<void>;
|
|
13
|
+
fork(sourceMetadata: JsonlSessionMetadata, options: JsonlSessionCreateOptions & {
|
|
14
|
+
entryId?: string;
|
|
15
|
+
position?: "before" | "at";
|
|
16
|
+
id?: string;
|
|
17
|
+
}): Promise<Session<JsonlSessionMetadata>>;
|
|
18
|
+
private listSessionDirs;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=jsonl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.d.ts","sourceRoot":"","sources":["../../../../src/harness/session/repo/jsonl.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,yBAAyB,EACzB,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,EACnB,OAAO,EACP,MAAM,gBAAgB,CAAC;AAiBxB,qBAAa,gBAAiB,YAAW,mBAAmB;IAC3D,OAAO,CAAC,YAAY,CAAS;IAE7B,YAAY,OAAO,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,EAE5C;IAED,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,qBAAqB;IAIvB,MAAM,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAWvF;IAEK,IAAI,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAMjF;IAEK,IAAI,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAgBjF;IAEK,MAAM,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1D;IAEK,IAAI,CACT,cAAc,EAAE,oBAAoB,EACpC,OAAO,EAAE,yBAAyB,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAChG,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAcxC;YAEa,eAAe;CAK7B","sourcesContent":["import { constants } from \"node:fs\";\nimport { access, mkdir, readdir, rm } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport type {\n\tJsonlSessionCreateOptions,\n\tJsonlSessionListOptions,\n\tJsonlSessionMetadata,\n\tJsonlSessionRepoApi,\n\tSession,\n} from \"../../types.js\";\nimport { JsonlSessionStorage, loadJsonlSessionMetadata } from \"../storage/jsonl.js\";\nimport { createSessionId, createTimestamp, getEntriesToFork, toSession } from \"./shared.js\";\n\nasync function exists(path: string): Promise<boolean> {\n\ttry {\n\t\tawait access(path, constants.F_OK);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction encodeCwd(cwd: string): string {\n\treturn `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n}\n\nexport class JsonlSessionRepo implements JsonlSessionRepoApi {\n\tprivate sessionsRoot: string;\n\n\tconstructor(options: { sessionsRoot: string }) {\n\t\tthis.sessionsRoot = resolve(options.sessionsRoot);\n\t}\n\n\tprivate getSessionDir(cwd: string): string {\n\t\treturn join(this.sessionsRoot, encodeCwd(cwd));\n\t}\n\n\tprivate createSessionFilePath(cwd: string, sessionId: string, timestamp: string): string {\n\t\treturn join(this.getSessionDir(cwd), `${timestamp.replace(/[:.]/g, \"-\")}_${sessionId}.jsonl`);\n\t}\n\n\tasync create(options: JsonlSessionCreateOptions): Promise<Session<JsonlSessionMetadata>> {\n\t\tawait mkdir(this.sessionsRoot, { recursive: true });\n\t\tconst id = options.id ?? createSessionId();\n\t\tconst createdAt = createTimestamp();\n\t\tconst filePath = this.createSessionFilePath(options.cwd, id, createdAt);\n\t\tconst storage = await JsonlSessionStorage.create(filePath, {\n\t\t\tcwd: options.cwd,\n\t\t\tsessionId: id,\n\t\t\tparentSessionPath: options.parentSessionPath,\n\t\t});\n\t\treturn toSession(storage);\n\t}\n\n\tasync open(metadata: JsonlSessionMetadata): Promise<Session<JsonlSessionMetadata>> {\n\t\tif (!(await exists(metadata.path))) {\n\t\t\tthrow new Error(`Session not found: ${metadata.path}`);\n\t\t}\n\t\tconst storage = await JsonlSessionStorage.open(metadata.path);\n\t\treturn toSession(storage);\n\t}\n\n\tasync list(options: JsonlSessionListOptions = {}): Promise<JsonlSessionMetadata[]> {\n\t\tconst dirs = options.cwd ? [this.getSessionDir(options.cwd)] : await this.listSessionDirs();\n\t\tconst sessions: JsonlSessionMetadata[] = [];\n\t\tfor (const dir of dirs) {\n\t\t\tif (!(await exists(dir))) continue;\n\t\t\tconst files = (await readdir(dir)).filter((file) => file.endsWith(\".jsonl\")).map((file) => join(dir, file));\n\t\t\tfor (const filePath of files) {\n\t\t\t\ttry {\n\t\t\t\t\tsessions.push(await loadJsonlSessionMetadata(filePath));\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore invalid session files when listing a directory.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());\n\t\treturn sessions;\n\t}\n\n\tasync delete(metadata: JsonlSessionMetadata): Promise<void> {\n\t\tawait rm(metadata.path, { force: true });\n\t}\n\n\tasync fork(\n\t\tsourceMetadata: JsonlSessionMetadata,\n\t\toptions: JsonlSessionCreateOptions & { entryId?: string; position?: \"before\" | \"at\"; id?: string },\n\t): Promise<Session<JsonlSessionMetadata>> {\n\t\tconst source = await this.open(sourceMetadata);\n\t\tconst forkedEntries = await getEntriesToFork(source.getStorage(), options);\n\t\tconst id = options.id ?? createSessionId();\n\t\tconst createdAt = createTimestamp();\n\t\tconst storage = await JsonlSessionStorage.create(this.createSessionFilePath(options.cwd, id, createdAt), {\n\t\t\tcwd: options.cwd,\n\t\t\tsessionId: id,\n\t\t\tparentSessionPath: options.parentSessionPath ?? sourceMetadata.path,\n\t\t});\n\t\tfor (const entry of forkedEntries) {\n\t\t\tawait storage.appendEntry(entry);\n\t\t}\n\t\treturn toSession(storage);\n\t}\n\n\tprivate async listSessionDirs(): Promise<string[]> {\n\t\tif (!(await exists(this.sessionsRoot))) return [];\n\t\tconst entries = await readdir(this.sessionsRoot, { withFileTypes: true });\n\t\treturn entries.filter((entry) => entry.isDirectory()).map((entry) => join(this.sessionsRoot, entry.name));\n\t}\n}\n"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, mkdir, readdir, rm } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { JsonlSessionStorage, loadJsonlSessionMetadata } from "../storage/jsonl.js";
|
|
5
|
+
import { createSessionId, createTimestamp, getEntriesToFork, toSession } from "./shared.js";
|
|
6
|
+
async function exists(path) {
|
|
7
|
+
try {
|
|
8
|
+
await access(path, constants.F_OK);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function encodeCwd(cwd) {
|
|
16
|
+
return `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
17
|
+
}
|
|
18
|
+
export class JsonlSessionRepo {
|
|
19
|
+
sessionsRoot;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.sessionsRoot = resolve(options.sessionsRoot);
|
|
22
|
+
}
|
|
23
|
+
getSessionDir(cwd) {
|
|
24
|
+
return join(this.sessionsRoot, encodeCwd(cwd));
|
|
25
|
+
}
|
|
26
|
+
createSessionFilePath(cwd, sessionId, timestamp) {
|
|
27
|
+
return join(this.getSessionDir(cwd), `${timestamp.replace(/[:.]/g, "-")}_${sessionId}.jsonl`);
|
|
28
|
+
}
|
|
29
|
+
async create(options) {
|
|
30
|
+
await mkdir(this.sessionsRoot, { recursive: true });
|
|
31
|
+
const id = options.id ?? createSessionId();
|
|
32
|
+
const createdAt = createTimestamp();
|
|
33
|
+
const filePath = this.createSessionFilePath(options.cwd, id, createdAt);
|
|
34
|
+
const storage = await JsonlSessionStorage.create(filePath, {
|
|
35
|
+
cwd: options.cwd,
|
|
36
|
+
sessionId: id,
|
|
37
|
+
parentSessionPath: options.parentSessionPath,
|
|
38
|
+
});
|
|
39
|
+
return toSession(storage);
|
|
40
|
+
}
|
|
41
|
+
async open(metadata) {
|
|
42
|
+
if (!(await exists(metadata.path))) {
|
|
43
|
+
throw new Error(`Session not found: ${metadata.path}`);
|
|
44
|
+
}
|
|
45
|
+
const storage = await JsonlSessionStorage.open(metadata.path);
|
|
46
|
+
return toSession(storage);
|
|
47
|
+
}
|
|
48
|
+
async list(options = {}) {
|
|
49
|
+
const dirs = options.cwd ? [this.getSessionDir(options.cwd)] : await this.listSessionDirs();
|
|
50
|
+
const sessions = [];
|
|
51
|
+
for (const dir of dirs) {
|
|
52
|
+
if (!(await exists(dir)))
|
|
53
|
+
continue;
|
|
54
|
+
const files = (await readdir(dir)).filter((file) => file.endsWith(".jsonl")).map((file) => join(dir, file));
|
|
55
|
+
for (const filePath of files) {
|
|
56
|
+
try {
|
|
57
|
+
sessions.push(await loadJsonlSessionMetadata(filePath));
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Ignore invalid session files when listing a directory.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
65
|
+
return sessions;
|
|
66
|
+
}
|
|
67
|
+
async delete(metadata) {
|
|
68
|
+
await rm(metadata.path, { force: true });
|
|
69
|
+
}
|
|
70
|
+
async fork(sourceMetadata, options) {
|
|
71
|
+
const source = await this.open(sourceMetadata);
|
|
72
|
+
const forkedEntries = await getEntriesToFork(source.getStorage(), options);
|
|
73
|
+
const id = options.id ?? createSessionId();
|
|
74
|
+
const createdAt = createTimestamp();
|
|
75
|
+
const storage = await JsonlSessionStorage.create(this.createSessionFilePath(options.cwd, id, createdAt), {
|
|
76
|
+
cwd: options.cwd,
|
|
77
|
+
sessionId: id,
|
|
78
|
+
parentSessionPath: options.parentSessionPath ?? sourceMetadata.path,
|
|
79
|
+
});
|
|
80
|
+
for (const entry of forkedEntries) {
|
|
81
|
+
await storage.appendEntry(entry);
|
|
82
|
+
}
|
|
83
|
+
return toSession(storage);
|
|
84
|
+
}
|
|
85
|
+
async listSessionDirs() {
|
|
86
|
+
if (!(await exists(this.sessionsRoot)))
|
|
87
|
+
return [];
|
|
88
|
+
const entries = await readdir(this.sessionsRoot, { withFileTypes: true });
|
|
89
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => join(this.sessionsRoot, entry.name));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=jsonl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.js","sourceRoot":"","sources":["../../../../src/harness/session/repo/jsonl.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from \"node:fs\";\nimport { access, mkdir, readdir, rm } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport type {\n\tJsonlSessionCreateOptions,\n\tJsonlSessionListOptions,\n\tJsonlSessionMetadata,\n\tJsonlSessionRepoApi,\n\tSession,\n} from \"../../types.js\";\nimport { JsonlSessionStorage, loadJsonlSessionMetadata } from \"../storage/jsonl.js\";\nimport { createSessionId, createTimestamp, getEntriesToFork, toSession } from \"./shared.js\";\n\nasync function exists(path: string): Promise<boolean> {\n\ttry {\n\t\tawait access(path, constants.F_OK);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction encodeCwd(cwd: string): string {\n\treturn `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n}\n\nexport class JsonlSessionRepo implements JsonlSessionRepoApi {\n\tprivate sessionsRoot: string;\n\n\tconstructor(options: { sessionsRoot: string }) {\n\t\tthis.sessionsRoot = resolve(options.sessionsRoot);\n\t}\n\n\tprivate getSessionDir(cwd: string): string {\n\t\treturn join(this.sessionsRoot, encodeCwd(cwd));\n\t}\n\n\tprivate createSessionFilePath(cwd: string, sessionId: string, timestamp: string): string {\n\t\treturn join(this.getSessionDir(cwd), `${timestamp.replace(/[:.]/g, \"-\")}_${sessionId}.jsonl`);\n\t}\n\n\tasync create(options: JsonlSessionCreateOptions): Promise<Session<JsonlSessionMetadata>> {\n\t\tawait mkdir(this.sessionsRoot, { recursive: true });\n\t\tconst id = options.id ?? createSessionId();\n\t\tconst createdAt = createTimestamp();\n\t\tconst filePath = this.createSessionFilePath(options.cwd, id, createdAt);\n\t\tconst storage = await JsonlSessionStorage.create(filePath, {\n\t\t\tcwd: options.cwd,\n\t\t\tsessionId: id,\n\t\t\tparentSessionPath: options.parentSessionPath,\n\t\t});\n\t\treturn toSession(storage);\n\t}\n\n\tasync open(metadata: JsonlSessionMetadata): Promise<Session<JsonlSessionMetadata>> {\n\t\tif (!(await exists(metadata.path))) {\n\t\t\tthrow new Error(`Session not found: ${metadata.path}`);\n\t\t}\n\t\tconst storage = await JsonlSessionStorage.open(metadata.path);\n\t\treturn toSession(storage);\n\t}\n\n\tasync list(options: JsonlSessionListOptions = {}): Promise<JsonlSessionMetadata[]> {\n\t\tconst dirs = options.cwd ? [this.getSessionDir(options.cwd)] : await this.listSessionDirs();\n\t\tconst sessions: JsonlSessionMetadata[] = [];\n\t\tfor (const dir of dirs) {\n\t\t\tif (!(await exists(dir))) continue;\n\t\t\tconst files = (await readdir(dir)).filter((file) => file.endsWith(\".jsonl\")).map((file) => join(dir, file));\n\t\t\tfor (const filePath of files) {\n\t\t\t\ttry {\n\t\t\t\t\tsessions.push(await loadJsonlSessionMetadata(filePath));\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore invalid session files when listing a directory.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());\n\t\treturn sessions;\n\t}\n\n\tasync delete(metadata: JsonlSessionMetadata): Promise<void> {\n\t\tawait rm(metadata.path, { force: true });\n\t}\n\n\tasync fork(\n\t\tsourceMetadata: JsonlSessionMetadata,\n\t\toptions: JsonlSessionCreateOptions & { entryId?: string; position?: \"before\" | \"at\"; id?: string },\n\t): Promise<Session<JsonlSessionMetadata>> {\n\t\tconst source = await this.open(sourceMetadata);\n\t\tconst forkedEntries = await getEntriesToFork(source.getStorage(), options);\n\t\tconst id = options.id ?? createSessionId();\n\t\tconst createdAt = createTimestamp();\n\t\tconst storage = await JsonlSessionStorage.create(this.createSessionFilePath(options.cwd, id, createdAt), {\n\t\t\tcwd: options.cwd,\n\t\t\tsessionId: id,\n\t\t\tparentSessionPath: options.parentSessionPath ?? sourceMetadata.path,\n\t\t});\n\t\tfor (const entry of forkedEntries) {\n\t\t\tawait storage.appendEntry(entry);\n\t\t}\n\t\treturn toSession(storage);\n\t}\n\n\tprivate async listSessionDirs(): Promise<string[]> {\n\t\tif (!(await exists(this.sessionsRoot))) return [];\n\t\tconst entries = await readdir(this.sessionsRoot, { withFileTypes: true });\n\t\treturn entries.filter((entry) => entry.isDirectory()).map((entry) => join(this.sessionsRoot, entry.name));\n\t}\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Session, SessionMetadata, SessionRepo } from "../../types.js";
|
|
2
|
+
export declare class InMemorySessionRepo implements SessionRepo<SessionMetadata, {
|
|
3
|
+
id?: string;
|
|
4
|
+
}, void> {
|
|
5
|
+
private sessions;
|
|
6
|
+
create(options?: {
|
|
7
|
+
id?: string;
|
|
8
|
+
}): Promise<Session<SessionMetadata>>;
|
|
9
|
+
open(metadata: SessionMetadata): Promise<Session<SessionMetadata>>;
|
|
10
|
+
list(): Promise<SessionMetadata[]>;
|
|
11
|
+
delete(metadata: SessionMetadata): Promise<void>;
|
|
12
|
+
fork(sourceMetadata: SessionMetadata, options: {
|
|
13
|
+
entryId?: string;
|
|
14
|
+
position?: "before" | "at";
|
|
15
|
+
id?: string;
|
|
16
|
+
}): Promise<Session<SessionMetadata>>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=memory.d.ts.map
|