@code-yeongyu/senpi-agent-core 2026.8.12 → 2026.8.13
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 +13 -7
- package/dist/agent-loop.js +5 -1
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts +3 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +8 -0
- package/dist/agent.js.map +1 -1
- package/dist/harness/agent-harness.d.ts +452 -96
- package/dist/harness/agent-harness.d.ts.map +1 -1
- package/dist/harness/agent-harness.js +162 -1005
- package/dist/harness/agent-harness.js.map +1 -1
- package/dist/harness/compaction/branch-summarization.d.ts +13 -6
- package/dist/harness/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/harness/compaction/branch-summarization.js +10 -14
- package/dist/harness/compaction/branch-summarization.js.map +1 -1
- package/dist/harness/compaction/compaction.d.ts +14 -17
- package/dist/harness/compaction/compaction.d.ts.map +1 -1
- package/dist/harness/compaction/compaction.js +23 -36
- package/dist/harness/compaction/compaction.js.map +1 -1
- package/dist/harness/env/nodejs.d.ts +1 -0
- package/dist/harness/env/nodejs.d.ts.map +1 -1
- package/dist/harness/env/nodejs.js +23 -7
- package/dist/harness/env/nodejs.js.map +1 -1
- package/dist/harness/messages.d.ts +3 -3
- package/dist/harness/messages.d.ts.map +1 -1
- package/dist/harness/messages.js +3 -3
- package/dist/harness/messages.js.map +1 -1
- package/dist/harness/prompt-templates.d.ts.map +1 -1
- package/dist/harness/prompt-templates.js +4 -9
- package/dist/harness/prompt-templates.js.map +1 -1
- package/dist/harness/reducer.d.ts +100 -0
- package/dist/harness/reducer.d.ts.map +1 -0
- package/dist/harness/reducer.js +414 -0
- package/dist/harness/reducer.js.map +1 -0
- package/dist/harness/result.d.ts +40 -0
- package/dist/harness/result.d.ts.map +1 -0
- package/dist/harness/result.js +41 -0
- package/dist/harness/result.js.map +1 -0
- package/dist/harness/session/context.d.ts +22 -0
- package/dist/harness/session/context.d.ts.map +1 -0
- package/dist/harness/session/context.js +67 -0
- package/dist/harness/session/context.js.map +1 -0
- package/dist/harness/session/index.d.ts +7 -0
- package/dist/harness/session/index.d.ts.map +1 -0
- package/dist/harness/session/index.js +6 -0
- package/dist/harness/session/index.js.map +1 -0
- package/dist/harness/session/jsonl/codec.d.ts +8 -0
- package/dist/harness/session/jsonl/codec.d.ts.map +1 -0
- package/dist/harness/session/jsonl/codec.js +190 -0
- package/dist/harness/session/jsonl/codec.js.map +1 -0
- package/dist/harness/session/jsonl/errors.d.ts +5 -0
- package/dist/harness/session/jsonl/errors.d.ts.map +1 -0
- package/dist/harness/session/jsonl/errors.js +11 -0
- package/dist/harness/session/jsonl/errors.js.map +1 -0
- package/dist/harness/session/jsonl/repo.d.ts +23 -0
- package/dist/harness/session/jsonl/repo.d.ts.map +1 -0
- package/dist/harness/session/jsonl/repo.js +129 -0
- package/dist/harness/session/jsonl/repo.js.map +1 -0
- package/dist/harness/session/jsonl/storage.d.ts +43 -0
- package/dist/harness/session/jsonl/storage.d.ts.map +1 -0
- package/dist/harness/session/jsonl/storage.js +206 -0
- package/dist/harness/session/jsonl/storage.js.map +1 -0
- package/dist/harness/session/jsonl/types.d.ts +38 -0
- package/dist/harness/session/jsonl/types.d.ts.map +1 -0
- package/dist/harness/session/jsonl/types.js +2 -0
- package/dist/harness/session/jsonl/types.js.map +1 -0
- package/dist/harness/session/jsonl.d.ts +3 -0
- package/dist/harness/session/jsonl.d.ts.map +1 -0
- package/dist/harness/session/jsonl.js +2 -0
- package/dist/harness/session/jsonl.js.map +1 -0
- package/dist/harness/session/memory.d.ts +44 -0
- package/dist/harness/session/memory.d.ts.map +1 -0
- package/dist/harness/session/memory.js +142 -0
- package/dist/harness/session/memory.js.map +1 -0
- package/dist/harness/session/search.d.ts +19 -0
- package/dist/harness/session/search.d.ts.map +1 -0
- package/dist/harness/session/search.js +41 -0
- package/dist/harness/session/search.js.map +1 -0
- package/dist/harness/session/session.d.ts +43 -38
- package/dist/harness/session/session.d.ts.map +1 -1
- package/dist/harness/session/session.js +186 -304
- package/dist/harness/session/session.js.map +1 -1
- package/dist/harness/session/state.d.ts +65 -0
- package/dist/harness/session/state.d.ts.map +1 -0
- package/dist/harness/session/state.js +320 -0
- package/dist/harness/session/state.js.map +1 -0
- package/dist/harness/session/testing/conformance.d.ts +4 -0
- package/dist/harness/session/testing/conformance.d.ts.map +1 -0
- package/dist/harness/session/testing/conformance.js +743 -0
- package/dist/harness/session/testing/conformance.js.map +1 -0
- package/dist/harness/session/testing/index.d.ts +3 -0
- package/dist/harness/session/testing/index.d.ts.map +1 -0
- package/dist/harness/session/testing/index.js +2 -0
- package/dist/harness/session/testing/index.js.map +1 -0
- package/dist/harness/session/testing/types.d.ts +14 -0
- package/dist/harness/session/testing/types.d.ts.map +1 -0
- package/dist/harness/session/testing/types.js +2 -0
- package/dist/harness/session/testing/types.js.map +1 -0
- package/dist/harness/session/types.d.ts +333 -0
- package/dist/harness/session/types.d.ts.map +1 -0
- package/dist/harness/session/types.js +9 -0
- package/dist/harness/session/types.js.map +1 -0
- package/dist/harness/skills.js +21 -19
- package/dist/harness/skills.js.map +1 -1
- package/dist/harness/telemetry.d.ts +1221 -0
- package/dist/harness/telemetry.d.ts.map +1 -0
- package/dist/harness/telemetry.js +508 -0
- package/dist/harness/telemetry.js.map +1 -0
- package/dist/harness/types.d.ts +8 -485
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js +4 -16
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/utils/truncate.js.map +1 -1
- package/dist/index.d.ts +10 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -8
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +1 -1
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +1 -0
- package/dist/proxy.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +9 -2
- package/dist/harness/session/array-session-reader.d.ts +0 -3
- package/dist/harness/session/array-session-reader.d.ts.map +0 -1
- package/dist/harness/session/array-session-reader.js +0 -70
- package/dist/harness/session/array-session-reader.js.map +0 -1
- package/dist/harness/session/fork.d.ts +0 -4
- package/dist/harness/session/fork.d.ts.map +0 -1
- package/dist/harness/session/fork.js +0 -22
- package/dist/harness/session/fork.js.map +0 -1
- package/dist/harness/session/jsonl-store.d.ts +0 -13
- package/dist/harness/session/jsonl-store.d.ts.map +0 -1
- package/dist/harness/session/jsonl-store.js +0 -304
- package/dist/harness/session/jsonl-store.js.map +0 -1
- package/dist/harness/session/keyed-operation-queue.d.ts +0 -17
- package/dist/harness/session/keyed-operation-queue.d.ts.map +0 -1
- package/dist/harness/session/keyed-operation-queue.js +0 -60
- package/dist/harness/session/keyed-operation-queue.js.map +0 -1
- package/dist/harness/session/memory-store.d.ts +0 -6
- package/dist/harness/session/memory-store.d.ts.map +0 -1
- package/dist/harness/session/memory-store.js +0 -107
- package/dist/harness/session/memory-store.js.map +0 -1
- package/dist/harness/session/repository.d.ts +0 -28
- package/dist/harness/session/repository.d.ts.map +0 -1
- package/dist/harness/session/repository.js +0 -61
- package/dist/harness/session/repository.js.map +0 -1
- package/dist/harness/session/search-backend.d.ts +0 -8
- package/dist/harness/session/search-backend.d.ts.map +0 -1
- package/dist/harness/session/search-backend.js +0 -22
- package/dist/harness/session/search-backend.js.map +0 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { SessionState } from "../state.js";
|
|
2
|
+
import { SessionError, } from "../types.js";
|
|
3
|
+
import { encodeHeader, encodeMutation, metadataFromHeader, parseHeader, parseMutation } from "./codec.js";
|
|
4
|
+
import { fileResult, invalidFile } from "./errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Build a complete sibling temporary file, then atomically rename it over the destination.
|
|
7
|
+
* The populate callback must create or overwrite `tempPath` with the complete file. The
|
|
8
|
+
* destination is untouched until the rename commits, so a process crash while populating
|
|
9
|
+
* can leave only the ignored `.tmp` file behind.
|
|
10
|
+
*
|
|
11
|
+
* Rejects when population or rename fails. On rejection, temporary-file removal is
|
|
12
|
+
* best-effort and the original error is preserved. Callers must serialize publications to
|
|
13
|
+
* the same destination because they share its deterministic `.tmp` path.
|
|
14
|
+
*/
|
|
15
|
+
async function publishFileAtomically(fs, destinationPath, populate) {
|
|
16
|
+
const tempPath = `${destinationPath}.tmp`;
|
|
17
|
+
try {
|
|
18
|
+
await populate(tempPath);
|
|
19
|
+
fileResult(await fs.renameFile(tempPath, destinationPath), `Failed to publish staged file ${destinationPath}`);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
await fs.remove(tempPath, { force: true });
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class JsonlSessionStorage {
|
|
27
|
+
constructor(fs, metadata) {
|
|
28
|
+
this.state = new SessionState();
|
|
29
|
+
this.tail = Promise.resolve();
|
|
30
|
+
this.fs = fs;
|
|
31
|
+
this.metadata = structuredClone(metadata);
|
|
32
|
+
}
|
|
33
|
+
static async create(fs, path, header) {
|
|
34
|
+
fileResult(await fs.writeFile(path, encodeHeader(header)), `Failed to initialize session ${path}`);
|
|
35
|
+
const fileInfo = fileResult(await fs.fileInfo(path), `Failed to read session metadata ${path}`);
|
|
36
|
+
return new JsonlSessionStorage(fs, metadataFromHeader(header, path, fileInfo.mtimeMs));
|
|
37
|
+
}
|
|
38
|
+
static async load(fs, path) {
|
|
39
|
+
const content = fileResult(await fs.readTextFile(path), `Failed to read session ${path}`);
|
|
40
|
+
const physicalLines = content.split("\n");
|
|
41
|
+
if (physicalLines.at(-1) === "")
|
|
42
|
+
physicalLines.pop();
|
|
43
|
+
if (physicalLines.length === 0 || !physicalLines[0])
|
|
44
|
+
throw invalidFile(path, 1, "is missing a header");
|
|
45
|
+
const header = parseHeader(physicalLines[0], path);
|
|
46
|
+
const fileInfo = fileResult(await fs.fileInfo(path), `Failed to read session metadata ${path}`);
|
|
47
|
+
const storage = new JsonlSessionStorage(fs, metadataFromHeader(header, path, fileInfo.mtimeMs));
|
|
48
|
+
for (let index = 1; index < physicalLines.length; index++) {
|
|
49
|
+
const line = physicalLines[index];
|
|
50
|
+
let mutation;
|
|
51
|
+
try {
|
|
52
|
+
mutation = parseMutation(line, path, index + 1);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (index !== physicalLines.length - 1 || !(error instanceof SessionError) || error.cause === undefined)
|
|
56
|
+
throw error;
|
|
57
|
+
const validPrefix = `${physicalLines.slice(0, index).join("\n")}\n`;
|
|
58
|
+
await publishFileAtomically(fs, path, async (tempPath) => {
|
|
59
|
+
fileResult(await fs.writeFile(tempPath, validPrefix), `Failed to stage torn-tail repair ${path}`);
|
|
60
|
+
});
|
|
61
|
+
return storage;
|
|
62
|
+
}
|
|
63
|
+
storage.applyMutation(mutation, path, index + 1);
|
|
64
|
+
}
|
|
65
|
+
if (!content.endsWith("\n")) {
|
|
66
|
+
fileResult(await fs.appendFile(path, "\n"), `Failed to repair unterminated session tail ${path}`);
|
|
67
|
+
}
|
|
68
|
+
return storage;
|
|
69
|
+
}
|
|
70
|
+
async fork(path, header, options) {
|
|
71
|
+
const mutations = this.state.createForkMutations(options);
|
|
72
|
+
await publishFileAtomically(this.fs, path, async (tempPath) => {
|
|
73
|
+
const targetStorage = await JsonlSessionStorage.create(this.fs, tempPath, header);
|
|
74
|
+
for (const mutation of mutations) {
|
|
75
|
+
await targetStorage.appendMutation(mutation);
|
|
76
|
+
targetStorage.applyMutation(mutation);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return JsonlSessionStorage.load(this.fs, path);
|
|
80
|
+
}
|
|
81
|
+
async drain() {
|
|
82
|
+
await this.tail;
|
|
83
|
+
}
|
|
84
|
+
async getMetadata() {
|
|
85
|
+
return structuredClone(this.metadata);
|
|
86
|
+
}
|
|
87
|
+
async getLanes() {
|
|
88
|
+
return this.state.getLanes();
|
|
89
|
+
}
|
|
90
|
+
createLane(lane, at) {
|
|
91
|
+
return this.enqueue(async () => {
|
|
92
|
+
this.state.validateNewLane(lane);
|
|
93
|
+
this.state.validateTarget(at);
|
|
94
|
+
const mutation = { kind: "lane", seq: this.state.nextSequence, lane, leafId: at };
|
|
95
|
+
await this.appendMutation(mutation);
|
|
96
|
+
this.applyMutation(mutation);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
moveLane(lane, to) {
|
|
100
|
+
return this.enqueue(async () => {
|
|
101
|
+
this.state.requireLane(lane);
|
|
102
|
+
this.state.validateTarget(to);
|
|
103
|
+
const mutation = { kind: "lane", seq: this.state.nextSequence, lane, leafId: to };
|
|
104
|
+
await this.appendMutation(mutation);
|
|
105
|
+
this.applyMutation(mutation);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
appendEntry(newEntry, lane) {
|
|
109
|
+
return this.enqueue(async () => {
|
|
110
|
+
const parentId = this.state.requireLane(lane);
|
|
111
|
+
this.state.validateUnusedId(newEntry.id);
|
|
112
|
+
const entry = {
|
|
113
|
+
...structuredClone(newEntry),
|
|
114
|
+
parentId,
|
|
115
|
+
seq: this.state.nextSequence,
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
};
|
|
118
|
+
const mutation = { kind: "entry", lane, entry };
|
|
119
|
+
await this.appendMutation(mutation);
|
|
120
|
+
this.applyMutation(mutation);
|
|
121
|
+
return structuredClone(entry);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
appendRecord(newRecord) {
|
|
125
|
+
return this.enqueue(async () => {
|
|
126
|
+
this.state.requireLane(newRecord.lane);
|
|
127
|
+
this.state.validateUnusedId(newRecord.id);
|
|
128
|
+
const currentOpenOperationId = this.state.findOpenOperations(newRecord.lane, { limit: 1 })[0]?.id;
|
|
129
|
+
if (newRecord.type === "operation_started" && currentOpenOperationId !== undefined) {
|
|
130
|
+
throw new SessionError("storage", `Lane ${newRecord.lane} already has an open operation ${currentOpenOperationId}`);
|
|
131
|
+
}
|
|
132
|
+
const record = {
|
|
133
|
+
...structuredClone(newRecord),
|
|
134
|
+
seq: this.state.nextSequence,
|
|
135
|
+
timestamp: Date.now(),
|
|
136
|
+
};
|
|
137
|
+
const mutation = { kind: "record", record };
|
|
138
|
+
await this.appendMutation(mutation);
|
|
139
|
+
this.applyMutation(mutation);
|
|
140
|
+
return structuredClone(record);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async getEntry(id) {
|
|
144
|
+
const entry = this.state.getEntry(id);
|
|
145
|
+
return entry === undefined ? undefined : structuredClone(entry);
|
|
146
|
+
}
|
|
147
|
+
async findEntries(query = {}) {
|
|
148
|
+
return structuredClone(this.state.findEntries(query));
|
|
149
|
+
}
|
|
150
|
+
async findEntriesOnBranch(query) {
|
|
151
|
+
return structuredClone(this.state.findEntriesOnBranch(query));
|
|
152
|
+
}
|
|
153
|
+
async findRecords(query = {}) {
|
|
154
|
+
return structuredClone(this.state.findRecords(query));
|
|
155
|
+
}
|
|
156
|
+
async findOpenOperations(lane, options) {
|
|
157
|
+
return structuredClone(this.state.findOpenOperations(lane, options));
|
|
158
|
+
}
|
|
159
|
+
async getLog(options = {}) {
|
|
160
|
+
return structuredClone(this.state.getLog(options));
|
|
161
|
+
}
|
|
162
|
+
async getName() {
|
|
163
|
+
return this.state.getName();
|
|
164
|
+
}
|
|
165
|
+
setName(name) {
|
|
166
|
+
return this.enqueue(async () => {
|
|
167
|
+
const mutation = { kind: "fact", seq: this.state.nextSequence, fact: "name", name };
|
|
168
|
+
await this.appendMutation(mutation);
|
|
169
|
+
this.applyMutation(mutation);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async getLabel(id) {
|
|
173
|
+
return this.state.getLabel(id);
|
|
174
|
+
}
|
|
175
|
+
setLabel(id, label) {
|
|
176
|
+
return this.enqueue(async () => {
|
|
177
|
+
this.state.validateTarget(id);
|
|
178
|
+
const mutation = {
|
|
179
|
+
kind: "fact",
|
|
180
|
+
seq: this.state.nextSequence,
|
|
181
|
+
fact: "label",
|
|
182
|
+
targetId: id,
|
|
183
|
+
label,
|
|
184
|
+
};
|
|
185
|
+
await this.appendMutation(mutation);
|
|
186
|
+
this.applyMutation(mutation);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async getStats() {
|
|
190
|
+
return structuredClone(this.state.getStats());
|
|
191
|
+
}
|
|
192
|
+
enqueue(operation) {
|
|
193
|
+
const result = this.tail.then(operation);
|
|
194
|
+
this.tail = result.then(() => undefined, () => undefined);
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
async appendMutation(mutation) {
|
|
198
|
+
fileResult(await this.fs.appendFile(this.metadata.path, encodeMutation(mutation)), `Failed to append session ${this.metadata.path}`);
|
|
199
|
+
}
|
|
200
|
+
applyMutation(mutation, path = this.metadata.path, line = this.state.nextSequence + 1) {
|
|
201
|
+
this.state.applyMutation(mutation, (message) => {
|
|
202
|
+
throw invalidFile(path, line, message);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../../src/harness/session/jsonl/storage.ts"],"names":[],"mappings":"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","sourcesContent":["import { type SessionMutation, SessionState } from \"../state.ts\";\nimport {\n\ttype BranchBounds,\n\ttype Entry,\n\ttype EntryQuery,\n\ttype ForkOptions,\n\ttype LanePointer,\n\ttype LaneRecord,\n\ttype LogItem,\n\ttype LogOptions,\n\ttype NewRecord,\n\ttype OperationStartedRecord,\n\ttype ProvisionedEntry,\n\ttype RecordQuery,\n\tSessionError,\n\ttype SessionStats,\n\ttype SessionStorage,\n} from \"../types.ts\";\nimport { encodeHeader, encodeMutation, metadataFromHeader, parseHeader, parseMutation } from \"./codec.ts\";\nimport { fileResult, invalidFile } from \"./errors.ts\";\nimport type { JsonlSessionMetadata, JsonlSessionRepoFileSystem, JsonlV4Header } from \"./types.ts\";\n\n/**\n * Build a complete sibling temporary file, then atomically rename it over the destination.\n * The populate callback must create or overwrite `tempPath` with the complete file. The\n * destination is untouched until the rename commits, so a process crash while populating\n * can leave only the ignored `.tmp` file behind.\n *\n * Rejects when population or rename fails. On rejection, temporary-file removal is\n * best-effort and the original error is preserved. Callers must serialize publications to\n * the same destination because they share its deterministic `.tmp` path.\n */\nasync function publishFileAtomically(\n\tfs: JsonlSessionRepoFileSystem,\n\tdestinationPath: string,\n\tpopulate: (tempPath: string) => Promise<void>,\n): Promise<void> {\n\tconst tempPath = `${destinationPath}.tmp`;\n\ttry {\n\t\tawait populate(tempPath);\n\t\tfileResult(await fs.renameFile(tempPath, destinationPath), `Failed to publish staged file ${destinationPath}`);\n\t} catch (error) {\n\t\tawait fs.remove(tempPath, { force: true });\n\t\tthrow error;\n\t}\n}\n\nexport class JsonlSessionStorage implements SessionStorage<JsonlSessionMetadata> {\n\tprivate readonly fs: JsonlSessionRepoFileSystem;\n\tprivate readonly metadata: JsonlSessionMetadata;\n\tprivate readonly state = new SessionState();\n\tprivate tail: Promise<void> = Promise.resolve();\n\n\tconstructor(fs: JsonlSessionRepoFileSystem, metadata: JsonlSessionMetadata) {\n\t\tthis.fs = fs;\n\t\tthis.metadata = structuredClone(metadata);\n\t}\n\n\tstatic async create(\n\t\tfs: JsonlSessionRepoFileSystem,\n\t\tpath: string,\n\t\theader: JsonlV4Header,\n\t): Promise<JsonlSessionStorage> {\n\t\tfileResult(await fs.writeFile(path, encodeHeader(header)), `Failed to initialize session ${path}`);\n\t\tconst fileInfo = fileResult(await fs.fileInfo(path), `Failed to read session metadata ${path}`);\n\t\treturn new JsonlSessionStorage(fs, metadataFromHeader(header, path, fileInfo.mtimeMs));\n\t}\n\n\tstatic async load(fs: JsonlSessionRepoFileSystem, path: string): Promise<JsonlSessionStorage> {\n\t\tconst content = fileResult(await fs.readTextFile(path), `Failed to read session ${path}`);\n\t\tconst physicalLines = content.split(\"\\n\");\n\t\tif (physicalLines.at(-1) === \"\") physicalLines.pop();\n\t\tif (physicalLines.length === 0 || !physicalLines[0]) throw invalidFile(path, 1, \"is missing a header\");\n\t\tconst header = parseHeader(physicalLines[0], path);\n\t\tconst fileInfo = fileResult(await fs.fileInfo(path), `Failed to read session metadata ${path}`);\n\t\tconst storage = new JsonlSessionStorage(fs, metadataFromHeader(header, path, fileInfo.mtimeMs));\n\t\tfor (let index = 1; index < physicalLines.length; index++) {\n\t\t\tconst line = physicalLines[index]!;\n\t\t\tlet mutation: SessionMutation;\n\t\t\ttry {\n\t\t\t\tmutation = parseMutation(line, path, index + 1);\n\t\t\t} catch (error) {\n\t\t\t\tif (index !== physicalLines.length - 1 || !(error instanceof SessionError) || error.cause === undefined)\n\t\t\t\t\tthrow error;\n\t\t\t\tconst validPrefix = `${physicalLines.slice(0, index).join(\"\\n\")}\\n`;\n\t\t\t\tawait publishFileAtomically(fs, path, async (tempPath) => {\n\t\t\t\t\tfileResult(await fs.writeFile(tempPath, validPrefix), `Failed to stage torn-tail repair ${path}`);\n\t\t\t\t});\n\t\t\t\treturn storage;\n\t\t\t}\n\t\t\tstorage.applyMutation(mutation, path, index + 1);\n\t\t}\n\t\tif (!content.endsWith(\"\\n\")) {\n\t\t\tfileResult(await fs.appendFile(path, \"\\n\"), `Failed to repair unterminated session tail ${path}`);\n\t\t}\n\t\treturn storage;\n\t}\n\n\tasync fork(path: string, header: JsonlV4Header, options: ForkOptions): Promise<JsonlSessionStorage> {\n\t\tconst mutations = this.state.createForkMutations(options);\n\t\tawait publishFileAtomically(this.fs, path, async (tempPath) => {\n\t\t\tconst targetStorage = await JsonlSessionStorage.create(this.fs, tempPath, header);\n\t\t\tfor (const mutation of mutations) {\n\t\t\t\tawait targetStorage.appendMutation(mutation);\n\t\t\t\ttargetStorage.applyMutation(mutation);\n\t\t\t}\n\t\t});\n\t\treturn JsonlSessionStorage.load(this.fs, path);\n\t}\n\n\tasync drain(): Promise<void> {\n\t\tawait this.tail;\n\t}\n\n\tasync getMetadata(): Promise<JsonlSessionMetadata> {\n\t\treturn structuredClone(this.metadata);\n\t}\n\n\tasync getLanes(): Promise<LanePointer[]> {\n\t\treturn this.state.getLanes();\n\t}\n\n\tcreateLane(lane: string, at: string | null): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.state.validateNewLane(lane);\n\t\t\tthis.state.validateTarget(at);\n\t\t\tconst mutation: SessionMutation = { kind: \"lane\", seq: this.state.nextSequence, lane, leafId: at };\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t});\n\t}\n\n\tmoveLane(lane: string, to: string | null): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.state.requireLane(lane);\n\t\t\tthis.state.validateTarget(to);\n\t\t\tconst mutation: SessionMutation = { kind: \"lane\", seq: this.state.nextSequence, lane, leafId: to };\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t});\n\t}\n\n\tappendEntry<TEntry extends Entry>(newEntry: ProvisionedEntry<TEntry>, lane: string): Promise<TEntry> {\n\t\treturn this.enqueue(async () => {\n\t\t\tconst parentId = this.state.requireLane(lane);\n\t\t\tthis.state.validateUnusedId(newEntry.id);\n\t\t\tconst entry = {\n\t\t\t\t...structuredClone(newEntry),\n\t\t\t\tparentId,\n\t\t\t\tseq: this.state.nextSequence,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t} as unknown as TEntry;\n\t\t\tconst mutation: SessionMutation = { kind: \"entry\", lane, entry };\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t\treturn structuredClone(entry);\n\t\t});\n\t}\n\n\tappendRecord<TRecord extends LaneRecord>(newRecord: NewRecord<TRecord>): Promise<TRecord> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.state.requireLane(newRecord.lane);\n\t\t\tthis.state.validateUnusedId(newRecord.id);\n\t\t\tconst currentOpenOperationId = this.state.findOpenOperations(newRecord.lane, { limit: 1 })[0]?.id;\n\t\t\tif (newRecord.type === \"operation_started\" && currentOpenOperationId !== undefined) {\n\t\t\t\tthrow new SessionError(\n\t\t\t\t\t\"storage\",\n\t\t\t\t\t`Lane ${newRecord.lane} already has an open operation ${currentOpenOperationId}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst record = {\n\t\t\t\t...structuredClone(newRecord),\n\t\t\t\tseq: this.state.nextSequence,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t} as unknown as TRecord;\n\t\t\tconst mutation: SessionMutation = { kind: \"record\", record };\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t\treturn structuredClone(record);\n\t\t});\n\t}\n\n\tasync getEntry(id: string): Promise<Entry | undefined> {\n\t\tconst entry = this.state.getEntry(id);\n\t\treturn entry === undefined ? undefined : structuredClone(entry);\n\t}\n\n\tasync findEntries(query: EntryQuery = {}): Promise<Entry[]> {\n\t\treturn structuredClone(this.state.findEntries(query));\n\t}\n\n\tasync findEntriesOnBranch(query: EntryQuery & BranchBounds & { start: string }): Promise<Entry[]> {\n\t\treturn structuredClone(this.state.findEntriesOnBranch(query));\n\t}\n\n\tasync findRecords<K extends LaneRecord[\"type\"]>(\n\t\tquery: RecordQuery & { type: K },\n\t): Promise<Extract<LaneRecord, { type: K }>[]>;\n\tasync findRecords(query?: RecordQuery): Promise<LaneRecord[]>;\n\tasync findRecords(query: RecordQuery = {}): Promise<LaneRecord[]> {\n\t\treturn structuredClone(this.state.findRecords(query));\n\t}\n\n\tasync findOpenOperations(lane: string, options?: { limit?: number }): Promise<OperationStartedRecord[]> {\n\t\treturn structuredClone(this.state.findOpenOperations(lane, options));\n\t}\n\n\tasync getLog(options: LogOptions = {}): Promise<LogItem[]> {\n\t\treturn structuredClone(this.state.getLog(options));\n\t}\n\n\tasync getName(): Promise<string | undefined> {\n\t\treturn this.state.getName();\n\t}\n\n\tsetName(name: string): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tconst mutation: SessionMutation = { kind: \"fact\", seq: this.state.nextSequence, fact: \"name\", name };\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t});\n\t}\n\n\tasync getLabel(id: string): Promise<string | undefined> {\n\t\treturn this.state.getLabel(id);\n\t}\n\n\tsetLabel(id: string, label: string | undefined): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.state.validateTarget(id);\n\t\t\tconst mutation: SessionMutation = {\n\t\t\t\tkind: \"fact\",\n\t\t\t\tseq: this.state.nextSequence,\n\t\t\t\tfact: \"label\",\n\t\t\t\ttargetId: id,\n\t\t\t\tlabel,\n\t\t\t};\n\t\t\tawait this.appendMutation(mutation);\n\t\t\tthis.applyMutation(mutation);\n\t\t});\n\t}\n\n\tasync getStats(): Promise<SessionStats> {\n\t\treturn structuredClone(this.state.getStats());\n\t}\n\n\tprivate enqueue<T>(operation: () => Promise<T>): Promise<T> {\n\t\tconst result = this.tail.then(operation);\n\t\tthis.tail = result.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn result;\n\t}\n\n\tprivate async appendMutation(mutation: SessionMutation): Promise<void> {\n\t\tfileResult(\n\t\t\tawait this.fs.appendFile(this.metadata.path, encodeMutation(mutation)),\n\t\t\t`Failed to append session ${this.metadata.path}`,\n\t\t);\n\t}\n\n\tprivate applyMutation(\n\t\tmutation: SessionMutation,\n\t\tpath = this.metadata.path,\n\t\tline = this.state.nextSequence + 1,\n\t): void {\n\t\tthis.state.applyMutation(mutation, (message) => {\n\t\t\tthrow invalidFile(path, line, message);\n\t\t});\n\t}\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FileSystem } from "../../types.ts";
|
|
2
|
+
import type { JsonValue, SessionCreateOptions, SessionMetadata } from "../types.ts";
|
|
3
|
+
export type JsonlSessionRepoFileSystem = Pick<FileSystem, "absolutePath" | "joinPath" | "readTextFile" | "writeFile" | "appendFile" | "renameFile" | "fileInfo" | "listDir" | "exists" | "createDir" | "remove">;
|
|
4
|
+
export interface JsonlSessionRepoOptions {
|
|
5
|
+
fs: JsonlSessionRepoFileSystem;
|
|
6
|
+
/** Root containing coding-agent-compatible cwd-encoded session directories. */
|
|
7
|
+
sessionsRoot: string;
|
|
8
|
+
}
|
|
9
|
+
export interface JsonlSessionMetadata extends SessionMetadata {
|
|
10
|
+
cwd: string;
|
|
11
|
+
path: string;
|
|
12
|
+
/** Filesystem modification time as milliseconds since Unix epoch. */
|
|
13
|
+
modifiedAt: number;
|
|
14
|
+
sourceFormat: 3 | 4;
|
|
15
|
+
/** Present only when a v3 parent path could not be resolved to a session id. */
|
|
16
|
+
legacyParentSessionPath?: string;
|
|
17
|
+
/** Opaque application-owned metadata. */
|
|
18
|
+
metadata?: Record<string, JsonValue>;
|
|
19
|
+
}
|
|
20
|
+
export interface JsonlSessionCreateOptions extends SessionCreateOptions {
|
|
21
|
+
cwd: string;
|
|
22
|
+
metadata?: Record<string, JsonValue>;
|
|
23
|
+
}
|
|
24
|
+
export interface JsonlSessionListOptions {
|
|
25
|
+
cwd?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface JsonlV4Header {
|
|
28
|
+
kind: "header";
|
|
29
|
+
version: 4;
|
|
30
|
+
id: string;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
cwd: string;
|
|
33
|
+
parentSessionId?: string;
|
|
34
|
+
/** Preserved only when a v3 parent path could not be resolved to a session id. */
|
|
35
|
+
legacyParentSessionPath?: string;
|
|
36
|
+
metadata?: Record<string, JsonValue>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/harness/session/jsonl/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEpF,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAC5C,UAAU,EACR,cAAc,GACd,UAAU,GACV,cAAc,GACd,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,UAAU,GACV,SAAS,GACT,QAAQ,GACR,WAAW,GACX,QAAQ,CACV,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,0BAA0B,CAAC;IAC/B,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAqB,SAAQ,eAAe;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC;IACpB,gFAAgF;IAChF,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,yBAA0B,SAAQ,oBAAoB;IACtE,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,uBAAuB;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,CAAC,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kFAAkF;IAClF,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACrC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/harness/session/jsonl/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { FileSystem } from \"../../types.ts\";\nimport type { JsonValue, SessionCreateOptions, SessionMetadata } from \"../types.ts\";\n\nexport type JsonlSessionRepoFileSystem = Pick<\n\tFileSystem,\n\t| \"absolutePath\"\n\t| \"joinPath\"\n\t| \"readTextFile\"\n\t| \"writeFile\"\n\t| \"appendFile\"\n\t| \"renameFile\"\n\t| \"fileInfo\"\n\t| \"listDir\"\n\t| \"exists\"\n\t| \"createDir\"\n\t| \"remove\"\n>;\n\nexport interface JsonlSessionRepoOptions {\n\tfs: JsonlSessionRepoFileSystem;\n\t/** Root containing coding-agent-compatible cwd-encoded session directories. */\n\tsessionsRoot: string;\n}\n\nexport interface JsonlSessionMetadata extends SessionMetadata {\n\tcwd: string;\n\tpath: string;\n\t/** Filesystem modification time as milliseconds since Unix epoch. */\n\tmodifiedAt: number;\n\tsourceFormat: 3 | 4;\n\t/** Present only when a v3 parent path could not be resolved to a session id. */\n\tlegacyParentSessionPath?: string;\n\t/** Opaque application-owned metadata. */\n\tmetadata?: Record<string, JsonValue>;\n}\n\nexport interface JsonlSessionCreateOptions extends SessionCreateOptions {\n\tcwd: string;\n\tmetadata?: Record<string, JsonValue>;\n}\n\nexport interface JsonlSessionListOptions {\n\tcwd?: string;\n}\n\nexport interface JsonlV4Header {\n\tkind: \"header\";\n\tversion: 4;\n\tid: string;\n\tcreatedAt: number;\n\tcwd: string;\n\tparentSessionId?: string;\n\t/** Preserved only when a v3 parent path could not be resolved to a session id. */\n\tlegacyParentSessionPath?: string;\n\tmetadata?: Record<string, JsonValue>;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.d.ts","sourceRoot":"","sources":["../../../src/harness/session/jsonl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EACX,yBAAyB,EACzB,uBAAuB,EACvB,oBAAoB,EACpB,0BAA0B,EAC1B,uBAAuB,EACvB,aAAa,GACb,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.js","sourceRoot":"","sources":["../../../src/harness/session/jsonl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC","sourcesContent":["export { JsonlSessionRepo } from \"./jsonl/repo.ts\";\nexport type {\n\tJsonlSessionCreateOptions,\n\tJsonlSessionListOptions,\n\tJsonlSessionMetadata,\n\tJsonlSessionRepoFileSystem,\n\tJsonlSessionRepoOptions,\n\tJsonlV4Header,\n} from \"./jsonl/types.ts\";\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Session } from "./session.ts";
|
|
2
|
+
import { type BranchBounds, type Entry, type EntryQuery, type ForkOptions, type LanePointer, type LaneRecord, type LogItem, type LogOptions, type NewRecord, type OperationStartedRecord, type ProvisionedEntry, type RecordQuery, type SessionCreateOptions, type SessionMetadata, type SessionRepo, type SessionStats, type SessionStorage } from "./types.ts";
|
|
3
|
+
export declare class InMemorySessionStorage implements SessionStorage {
|
|
4
|
+
private readonly metadata;
|
|
5
|
+
private readonly state;
|
|
6
|
+
constructor(metadata: SessionMetadata);
|
|
7
|
+
fork(metadata: SessionMetadata, options: ForkOptions & SessionCreateOptions): InMemorySessionStorage;
|
|
8
|
+
getMetadata(): Promise<SessionMetadata>;
|
|
9
|
+
getLanes(): Promise<LanePointer[]>;
|
|
10
|
+
createLane(lane: string, at: string | null): Promise<void>;
|
|
11
|
+
moveLane(lane: string, to: string | null): Promise<void>;
|
|
12
|
+
appendEntry<TEntry extends Entry>(newEntry: ProvisionedEntry<TEntry>, lane: string): Promise<TEntry>;
|
|
13
|
+
appendRecord<TRecord extends LaneRecord>(newRecord: NewRecord<TRecord>): Promise<TRecord>;
|
|
14
|
+
getEntry(id: string): Promise<Entry | undefined>;
|
|
15
|
+
findEntries(query?: EntryQuery): Promise<Entry[]>;
|
|
16
|
+
findEntriesOnBranch(query: EntryQuery & BranchBounds & {
|
|
17
|
+
start: string;
|
|
18
|
+
}): Promise<Entry[]>;
|
|
19
|
+
findRecords<K extends LaneRecord["type"]>(query: RecordQuery & {
|
|
20
|
+
type: K;
|
|
21
|
+
}): Promise<Extract<LaneRecord, {
|
|
22
|
+
type: K;
|
|
23
|
+
}>[]>;
|
|
24
|
+
findRecords(query?: RecordQuery): Promise<LaneRecord[]>;
|
|
25
|
+
findOpenOperations(lane: string, options?: {
|
|
26
|
+
limit?: number;
|
|
27
|
+
}): Promise<OperationStartedRecord[]>;
|
|
28
|
+
getLog(options?: LogOptions): Promise<LogItem[]>;
|
|
29
|
+
getName(): Promise<string | undefined>;
|
|
30
|
+
setName(name: string): Promise<void>;
|
|
31
|
+
getLabel(id: string): Promise<string | undefined>;
|
|
32
|
+
setLabel(id: string, label: string | undefined): Promise<void>;
|
|
33
|
+
getStats(): Promise<SessionStats>;
|
|
34
|
+
}
|
|
35
|
+
export declare class InMemorySessionRepo implements SessionRepo {
|
|
36
|
+
private readonly sessions;
|
|
37
|
+
create(options?: SessionCreateOptions): Promise<Session>;
|
|
38
|
+
open(metadata: SessionMetadata): Promise<Session>;
|
|
39
|
+
list(): Promise<SessionMetadata[]>;
|
|
40
|
+
delete(metadata: SessionMetadata): Promise<void>;
|
|
41
|
+
fork(source: SessionMetadata, options?: ForkOptions & SessionCreateOptions): Promise<Session>;
|
|
42
|
+
private requireStorage;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/harness/session/memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { uuidv7 } from "@earendil-works/pi-ai";
|
|
2
|
+
import { Session } from "./session.js";
|
|
3
|
+
import { SessionState } from "./state.js";
|
|
4
|
+
import { SessionError, } from "./types.js";
|
|
5
|
+
export class InMemorySessionStorage {
|
|
6
|
+
constructor(metadata) {
|
|
7
|
+
this.state = new SessionState();
|
|
8
|
+
this.metadata = structuredClone(metadata);
|
|
9
|
+
}
|
|
10
|
+
fork(metadata, options) {
|
|
11
|
+
const storage = new InMemorySessionStorage(metadata);
|
|
12
|
+
for (const mutation of this.state.createForkMutations(options))
|
|
13
|
+
storage.state.applyMutation(mutation);
|
|
14
|
+
return storage;
|
|
15
|
+
}
|
|
16
|
+
async getMetadata() {
|
|
17
|
+
return structuredClone(this.metadata);
|
|
18
|
+
}
|
|
19
|
+
async getLanes() {
|
|
20
|
+
return this.state.getLanes();
|
|
21
|
+
}
|
|
22
|
+
async createLane(lane, at) {
|
|
23
|
+
this.state.validateNewLane(lane);
|
|
24
|
+
this.state.validateTarget(at);
|
|
25
|
+
this.state.applyMutation({ kind: "lane", seq: this.state.nextSequence, lane, leafId: at });
|
|
26
|
+
}
|
|
27
|
+
async moveLane(lane, to) {
|
|
28
|
+
this.state.requireLane(lane);
|
|
29
|
+
this.state.validateTarget(to);
|
|
30
|
+
this.state.applyMutation({ kind: "lane", seq: this.state.nextSequence, lane, leafId: to });
|
|
31
|
+
}
|
|
32
|
+
async appendEntry(newEntry, lane) {
|
|
33
|
+
const parentId = this.state.requireLane(lane);
|
|
34
|
+
this.state.validateUnusedId(newEntry.id);
|
|
35
|
+
const entry = {
|
|
36
|
+
...structuredClone(newEntry),
|
|
37
|
+
parentId,
|
|
38
|
+
seq: this.state.nextSequence,
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
};
|
|
41
|
+
this.state.applyMutation({ kind: "entry", lane, entry });
|
|
42
|
+
return structuredClone(entry);
|
|
43
|
+
}
|
|
44
|
+
async appendRecord(newRecord) {
|
|
45
|
+
this.state.requireLane(newRecord.lane);
|
|
46
|
+
this.state.validateUnusedId(newRecord.id);
|
|
47
|
+
const currentOpenOperationId = this.state.findOpenOperations(newRecord.lane, { limit: 1 })[0]?.id;
|
|
48
|
+
if (newRecord.type === "operation_started" && currentOpenOperationId !== undefined) {
|
|
49
|
+
throw new SessionError("storage", `Lane ${newRecord.lane} already has an open operation ${currentOpenOperationId}`);
|
|
50
|
+
}
|
|
51
|
+
const record = {
|
|
52
|
+
...structuredClone(newRecord),
|
|
53
|
+
seq: this.state.nextSequence,
|
|
54
|
+
timestamp: Date.now(),
|
|
55
|
+
};
|
|
56
|
+
this.state.applyMutation({ kind: "record", record });
|
|
57
|
+
return structuredClone(record);
|
|
58
|
+
}
|
|
59
|
+
async getEntry(id) {
|
|
60
|
+
const entry = this.state.getEntry(id);
|
|
61
|
+
return entry === undefined ? undefined : structuredClone(entry);
|
|
62
|
+
}
|
|
63
|
+
async findEntries(query = {}) {
|
|
64
|
+
return structuredClone(this.state.findEntries(query));
|
|
65
|
+
}
|
|
66
|
+
async findEntriesOnBranch(query) {
|
|
67
|
+
return structuredClone(this.state.findEntriesOnBranch(query));
|
|
68
|
+
}
|
|
69
|
+
async findRecords(query = {}) {
|
|
70
|
+
return structuredClone(this.state.findRecords(query));
|
|
71
|
+
}
|
|
72
|
+
async findOpenOperations(lane, options) {
|
|
73
|
+
return structuredClone(this.state.findOpenOperations(lane, options));
|
|
74
|
+
}
|
|
75
|
+
async getLog(options = {}) {
|
|
76
|
+
return structuredClone(this.state.getLog(options));
|
|
77
|
+
}
|
|
78
|
+
async getName() {
|
|
79
|
+
return this.state.getName();
|
|
80
|
+
}
|
|
81
|
+
async setName(name) {
|
|
82
|
+
this.state.applyMutation({ kind: "fact", seq: this.state.nextSequence, fact: "name", name });
|
|
83
|
+
}
|
|
84
|
+
async getLabel(id) {
|
|
85
|
+
return this.state.getLabel(id);
|
|
86
|
+
}
|
|
87
|
+
async setLabel(id, label) {
|
|
88
|
+
this.state.validateTarget(id);
|
|
89
|
+
this.state.applyMutation({
|
|
90
|
+
kind: "fact",
|
|
91
|
+
seq: this.state.nextSequence,
|
|
92
|
+
fact: "label",
|
|
93
|
+
targetId: id,
|
|
94
|
+
label,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async getStats() {
|
|
98
|
+
return structuredClone(this.state.getStats());
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export class InMemorySessionRepo {
|
|
102
|
+
constructor() {
|
|
103
|
+
this.sessions = new Map();
|
|
104
|
+
}
|
|
105
|
+
async create(options = {}) {
|
|
106
|
+
const id = options.id ?? uuidv7();
|
|
107
|
+
if (this.sessions.has(id))
|
|
108
|
+
throw new SessionError("already_exists", `Session already exists: ${id}`);
|
|
109
|
+
const storage = new InMemorySessionStorage({
|
|
110
|
+
id,
|
|
111
|
+
createdAt: Date.now(),
|
|
112
|
+
parentSessionId: options.parentSessionId,
|
|
113
|
+
});
|
|
114
|
+
this.sessions.set(id, storage);
|
|
115
|
+
return new Session(storage);
|
|
116
|
+
}
|
|
117
|
+
async open(metadata) {
|
|
118
|
+
return new Session(this.requireStorage(metadata.id));
|
|
119
|
+
}
|
|
120
|
+
async list() {
|
|
121
|
+
return Promise.all([...this.sessions.values()].map((storage) => storage.getMetadata()));
|
|
122
|
+
}
|
|
123
|
+
async delete(metadata) {
|
|
124
|
+
this.sessions.delete(metadata.id);
|
|
125
|
+
}
|
|
126
|
+
async fork(source, options = {}) {
|
|
127
|
+
const sourceStorage = this.requireStorage(source.id);
|
|
128
|
+
const id = options.id ?? uuidv7();
|
|
129
|
+
if (this.sessions.has(id))
|
|
130
|
+
throw new SessionError("already_exists", `Session already exists: ${id}`);
|
|
131
|
+
const storage = sourceStorage.fork({ id, createdAt: Date.now(), parentSessionId: options.parentSessionId ?? source.id }, options);
|
|
132
|
+
this.sessions.set(id, storage);
|
|
133
|
+
return new Session(storage);
|
|
134
|
+
}
|
|
135
|
+
requireStorage(id) {
|
|
136
|
+
const storage = this.sessions.get(id);
|
|
137
|
+
if (!storage)
|
|
138
|
+
throw new SessionError("not_found", `Session not found: ${id}`);
|
|
139
|
+
return storage;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/harness/session/memory.ts"],"names":[],"mappings":"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","sourcesContent":["import { uuidv7 } from \"@earendil-works/pi-ai\";\nimport { Session } from \"./session.ts\";\nimport { SessionState } from \"./state.ts\";\nimport {\n\ttype BranchBounds,\n\ttype Entry,\n\ttype EntryQuery,\n\ttype ForkOptions,\n\ttype LanePointer,\n\ttype LaneRecord,\n\ttype LogItem,\n\ttype LogOptions,\n\ttype NewRecord,\n\ttype OperationStartedRecord,\n\ttype ProvisionedEntry,\n\ttype RecordQuery,\n\ttype SessionCreateOptions,\n\tSessionError,\n\ttype SessionMetadata,\n\ttype SessionRepo,\n\ttype SessionStats,\n\ttype SessionStorage,\n} from \"./types.ts\";\n\nexport class InMemorySessionStorage implements SessionStorage {\n\tprivate readonly metadata: SessionMetadata;\n\tprivate readonly state = new SessionState();\n\n\tconstructor(metadata: SessionMetadata) {\n\t\tthis.metadata = structuredClone(metadata);\n\t}\n\n\tfork(metadata: SessionMetadata, options: ForkOptions & SessionCreateOptions): InMemorySessionStorage {\n\t\tconst storage = new InMemorySessionStorage(metadata);\n\t\tfor (const mutation of this.state.createForkMutations(options)) storage.state.applyMutation(mutation);\n\t\treturn storage;\n\t}\n\n\tasync getMetadata(): Promise<SessionMetadata> {\n\t\treturn structuredClone(this.metadata);\n\t}\n\n\tasync getLanes(): Promise<LanePointer[]> {\n\t\treturn this.state.getLanes();\n\t}\n\n\tasync createLane(lane: string, at: string | null): Promise<void> {\n\t\tthis.state.validateNewLane(lane);\n\t\tthis.state.validateTarget(at);\n\t\tthis.state.applyMutation({ kind: \"lane\", seq: this.state.nextSequence, lane, leafId: at });\n\t}\n\n\tasync moveLane(lane: string, to: string | null): Promise<void> {\n\t\tthis.state.requireLane(lane);\n\t\tthis.state.validateTarget(to);\n\t\tthis.state.applyMutation({ kind: \"lane\", seq: this.state.nextSequence, lane, leafId: to });\n\t}\n\n\tasync appendEntry<TEntry extends Entry>(newEntry: ProvisionedEntry<TEntry>, lane: string): Promise<TEntry> {\n\t\tconst parentId = this.state.requireLane(lane);\n\t\tthis.state.validateUnusedId(newEntry.id);\n\t\tconst entry = {\n\t\t\t...structuredClone(newEntry),\n\t\t\tparentId,\n\t\t\tseq: this.state.nextSequence,\n\t\t\ttimestamp: Date.now(),\n\t\t} as unknown as TEntry;\n\t\tthis.state.applyMutation({ kind: \"entry\", lane, entry });\n\t\treturn structuredClone(entry);\n\t}\n\n\tasync appendRecord<TRecord extends LaneRecord>(newRecord: NewRecord<TRecord>): Promise<TRecord> {\n\t\tthis.state.requireLane(newRecord.lane);\n\t\tthis.state.validateUnusedId(newRecord.id);\n\t\tconst currentOpenOperationId = this.state.findOpenOperations(newRecord.lane, { limit: 1 })[0]?.id;\n\t\tif (newRecord.type === \"operation_started\" && currentOpenOperationId !== undefined) {\n\t\t\tthrow new SessionError(\n\t\t\t\t\"storage\",\n\t\t\t\t`Lane ${newRecord.lane} already has an open operation ${currentOpenOperationId}`,\n\t\t\t);\n\t\t}\n\t\tconst record = {\n\t\t\t...structuredClone(newRecord),\n\t\t\tseq: this.state.nextSequence,\n\t\t\ttimestamp: Date.now(),\n\t\t} as unknown as TRecord;\n\t\tthis.state.applyMutation({ kind: \"record\", record });\n\t\treturn structuredClone(record);\n\t}\n\n\tasync getEntry(id: string): Promise<Entry | undefined> {\n\t\tconst entry = this.state.getEntry(id);\n\t\treturn entry === undefined ? undefined : structuredClone(entry);\n\t}\n\n\tasync findEntries(query: EntryQuery = {}): Promise<Entry[]> {\n\t\treturn structuredClone(this.state.findEntries(query));\n\t}\n\n\tasync findEntriesOnBranch(query: EntryQuery & BranchBounds & { start: string }): Promise<Entry[]> {\n\t\treturn structuredClone(this.state.findEntriesOnBranch(query));\n\t}\n\n\tasync findRecords<K extends LaneRecord[\"type\"]>(\n\t\tquery: RecordQuery & { type: K },\n\t): Promise<Extract<LaneRecord, { type: K }>[]>;\n\tasync findRecords(query?: RecordQuery): Promise<LaneRecord[]>;\n\tasync findRecords(query: RecordQuery = {}): Promise<LaneRecord[]> {\n\t\treturn structuredClone(this.state.findRecords(query));\n\t}\n\n\tasync findOpenOperations(lane: string, options?: { limit?: number }): Promise<OperationStartedRecord[]> {\n\t\treturn structuredClone(this.state.findOpenOperations(lane, options));\n\t}\n\n\tasync getLog(options: LogOptions = {}): Promise<LogItem[]> {\n\t\treturn structuredClone(this.state.getLog(options));\n\t}\n\n\tasync getName(): Promise<string | undefined> {\n\t\treturn this.state.getName();\n\t}\n\n\tasync setName(name: string): Promise<void> {\n\t\tthis.state.applyMutation({ kind: \"fact\", seq: this.state.nextSequence, fact: \"name\", name });\n\t}\n\n\tasync getLabel(id: string): Promise<string | undefined> {\n\t\treturn this.state.getLabel(id);\n\t}\n\n\tasync setLabel(id: string, label: string | undefined): Promise<void> {\n\t\tthis.state.validateTarget(id);\n\t\tthis.state.applyMutation({\n\t\t\tkind: \"fact\",\n\t\t\tseq: this.state.nextSequence,\n\t\t\tfact: \"label\",\n\t\t\ttargetId: id,\n\t\t\tlabel,\n\t\t});\n\t}\n\n\tasync getStats(): Promise<SessionStats> {\n\t\treturn structuredClone(this.state.getStats());\n\t}\n}\n\nexport class InMemorySessionRepo implements SessionRepo {\n\tprivate readonly sessions = new Map<string, InMemorySessionStorage>();\n\n\tasync create(options: SessionCreateOptions = {}): Promise<Session> {\n\t\tconst id = options.id ?? uuidv7();\n\t\tif (this.sessions.has(id)) throw new SessionError(\"already_exists\", `Session already exists: ${id}`);\n\t\tconst storage = new InMemorySessionStorage({\n\t\t\tid,\n\t\t\tcreatedAt: Date.now(),\n\t\t\tparentSessionId: options.parentSessionId,\n\t\t});\n\t\tthis.sessions.set(id, storage);\n\t\treturn new Session(storage);\n\t}\n\n\tasync open(metadata: SessionMetadata): Promise<Session> {\n\t\treturn new Session(this.requireStorage(metadata.id));\n\t}\n\n\tasync list(): Promise<SessionMetadata[]> {\n\t\treturn Promise.all([...this.sessions.values()].map((storage) => storage.getMetadata()));\n\t}\n\n\tasync delete(metadata: SessionMetadata): Promise<void> {\n\t\tthis.sessions.delete(metadata.id);\n\t}\n\n\tasync fork(source: SessionMetadata, options: ForkOptions & SessionCreateOptions = {}): Promise<Session> {\n\t\tconst sourceStorage = this.requireStorage(source.id);\n\t\tconst id = options.id ?? uuidv7();\n\t\tif (this.sessions.has(id)) throw new SessionError(\"already_exists\", `Session already exists: ${id}`);\n\t\tconst storage = sourceStorage.fork(\n\t\t\t{ id, createdAt: Date.now(), parentSessionId: options.parentSessionId ?? source.id },\n\t\t\toptions,\n\t\t);\n\t\tthis.sessions.set(id, storage);\n\t\treturn new Session(storage);\n\t}\n\n\tprivate requireStorage(id: string): InMemorySessionStorage {\n\t\tconst storage = this.sessions.get(id);\n\t\tif (!storage) throw new SessionError(\"not_found\", `Session not found: ${id}`);\n\t\treturn storage;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FileError, Result } from "../types.ts";
|
|
2
|
+
import { type SessionCreateOptions, type SessionMetadata, type SessionRepo } from "./types.ts";
|
|
3
|
+
export interface SessionSearchOptions {
|
|
4
|
+
text: string;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SessionSearchHit<TMetadata extends SessionMetadata = SessionMetadata> {
|
|
8
|
+
metadata: TMetadata;
|
|
9
|
+
entryId: string;
|
|
10
|
+
timestamp: string;
|
|
11
|
+
snippet?: string;
|
|
12
|
+
score?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface SessionSearch<TMetadata extends SessionMetadata = SessionMetadata> {
|
|
15
|
+
search(options: SessionSearchOptions): Promise<SessionSearchHit<TMetadata>[]>;
|
|
16
|
+
}
|
|
17
|
+
export declare function getFileSystemResultOrThrow<TValue>(result: Result<TValue, FileError>, message: string): TValue;
|
|
18
|
+
export declare function createScanningSessionSearch<TMetadata extends SessionMetadata, TCreateOptions extends SessionCreateOptions, TListOptions>(source: Pick<SessionRepo<TMetadata, TCreateOptions, TListOptions>, "list" | "open">): SessionSearch<TMetadata>;
|
|
19
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/harness/session/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAAE,KAAK,oBAAoB,EAAgB,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAE7G,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB,CAAC,SAAS,SAAS,eAAe,GAAG,eAAe;IACpF,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa,CAAC,SAAS,SAAS,eAAe,GAAG,eAAe;IACjF,MAAM,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;CAC9E;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAM7G;AAqCD,wBAAgB,2BAA2B,CAC1C,SAAS,SAAS,eAAe,EACjC,cAAc,SAAS,oBAAoB,EAC3C,YAAY,EACX,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,cAAc,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,aAAa,CAAC,SAAS,CAAC,CAE/G"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SessionError } from "./types.js";
|
|
2
|
+
export function getFileSystemResultOrThrow(result, message) {
|
|
3
|
+
if (!result.ok) {
|
|
4
|
+
const code = result.error.code === "not_found" ? "not_found" : "storage";
|
|
5
|
+
throw new SessionError(code, `${message}: ${result.error.message}`, result.error);
|
|
6
|
+
}
|
|
7
|
+
return result.value;
|
|
8
|
+
}
|
|
9
|
+
class ScanningSessionSearch {
|
|
10
|
+
constructor(source) {
|
|
11
|
+
this.source = source;
|
|
12
|
+
}
|
|
13
|
+
async search(options) {
|
|
14
|
+
const normalizedText = options.text.trim().toLowerCase();
|
|
15
|
+
if (!normalizedText)
|
|
16
|
+
return [];
|
|
17
|
+
const hits = [];
|
|
18
|
+
for (const metadata of await this.source.list()) {
|
|
19
|
+
const cwd = metadata.cwd;
|
|
20
|
+
if (options.cwd !== undefined && cwd !== options.cwd)
|
|
21
|
+
continue;
|
|
22
|
+
const session = await this.source.open(metadata);
|
|
23
|
+
for (const entry of await session.findEntries({ order: "oldestFirst" })) {
|
|
24
|
+
const payload = JSON.stringify(entry);
|
|
25
|
+
if (!payload.toLowerCase().includes(normalizedText))
|
|
26
|
+
continue;
|
|
27
|
+
hits.push({
|
|
28
|
+
metadata,
|
|
29
|
+
entryId: entry.id,
|
|
30
|
+
timestamp: new Date(entry.timestamp).toISOString(),
|
|
31
|
+
snippet: payload,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return hits;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function createScanningSessionSearch(source) {
|
|
39
|
+
return new ScanningSessionSearch(source);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../../src/harness/session/search.ts"],"names":[],"mappings":"AAEA,OAAO,EAA6B,YAAY,EAA0C,MAAM,YAAY,CAAC;AAmB7G,MAAM,UAAU,0BAA0B,CAAS,MAAiC,EAAE,OAAe;IACpG,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;QACzE,MAAM,IAAI,YAAY,CAAC,IAAI,EAAE,GAAG,OAAO,KAAK,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC;AACrB,CAAC;AAOD,MAAM,qBAAqB;IAG1B,YAAY,MAA8C;QACzD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAA6B;QACzC,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACzD,IAAI,CAAC,cAAc;YAAE,OAAO,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAkC,EAAE,CAAC;QAC/C,KAAK,MAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;YACjD,MAAM,GAAG,GAAI,QAA8B,CAAC,GAAG,CAAC;YAChD,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG;gBAAE,SAAS;YAC/D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACjD,KAAK,MAAM,KAAK,IAAI,MAAM,OAAO,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;gBACzE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;gBACtC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;oBAAE,SAAS;gBAC9D,IAAI,CAAC,IAAI,CAAC;oBACT,QAAQ;oBACR,OAAO,EAAE,KAAK,CAAC,EAAE;oBACjB,SAAS,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE;oBAClD,OAAO,EAAE,OAAO;iBAChB,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;CACD;AAED,MAAM,UAAU,2BAA2B,CAIzC,MAAmF;IACpF,OAAO,IAAI,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAC1C,CAAC","sourcesContent":["import type { FileError, Result } from \"../types.ts\";\nimport type { Session } from \"./session.ts\";\nimport { type SessionCreateOptions, SessionError, type SessionMetadata, type SessionRepo } from \"./types.ts\";\n\nexport interface SessionSearchOptions {\n\ttext: string;\n\tcwd?: string;\n}\n\nexport interface SessionSearchHit<TMetadata extends SessionMetadata = SessionMetadata> {\n\tmetadata: TMetadata;\n\tentryId: string;\n\ttimestamp: string;\n\tsnippet?: string;\n\tscore?: number;\n}\n\nexport interface SessionSearch<TMetadata extends SessionMetadata = SessionMetadata> {\n\tsearch(options: SessionSearchOptions): Promise<SessionSearchHit<TMetadata>[]>;\n}\n\nexport function getFileSystemResultOrThrow<TValue>(result: Result<TValue, FileError>, message: string): TValue {\n\tif (!result.ok) {\n\t\tconst code = result.error.code === \"not_found\" ? \"not_found\" : \"storage\";\n\t\tthrow new SessionError(code, `${message}: ${result.error.message}`, result.error);\n\t}\n\treturn result.value;\n}\n\ntype ScanningSessionSearchSource<TMetadata extends SessionMetadata> = {\n\tlist(): Promise<TMetadata[]>;\n\topen(metadata: TMetadata): Promise<Session<TMetadata>>;\n};\n\nclass ScanningSessionSearch<TMetadata extends SessionMetadata = SessionMetadata> implements SessionSearch<TMetadata> {\n\tprivate readonly source: ScanningSessionSearchSource<TMetadata>;\n\n\tconstructor(source: ScanningSessionSearchSource<TMetadata>) {\n\t\tthis.source = source;\n\t}\n\n\tasync search(options: SessionSearchOptions): Promise<SessionSearchHit<TMetadata>[]> {\n\t\tconst normalizedText = options.text.trim().toLowerCase();\n\t\tif (!normalizedText) return [];\n\t\tconst hits: SessionSearchHit<TMetadata>[] = [];\n\t\tfor (const metadata of await this.source.list()) {\n\t\t\tconst cwd = (metadata as { cwd?: unknown }).cwd;\n\t\t\tif (options.cwd !== undefined && cwd !== options.cwd) continue;\n\t\t\tconst session = await this.source.open(metadata);\n\t\t\tfor (const entry of await session.findEntries({ order: \"oldestFirst\" })) {\n\t\t\t\tconst payload = JSON.stringify(entry);\n\t\t\t\tif (!payload.toLowerCase().includes(normalizedText)) continue;\n\t\t\t\thits.push({\n\t\t\t\t\tmetadata,\n\t\t\t\t\tentryId: entry.id,\n\t\t\t\t\ttimestamp: new Date(entry.timestamp).toISOString(),\n\t\t\t\t\tsnippet: payload,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn hits;\n\t}\n}\n\nexport function createScanningSessionSearch<\n\tTMetadata extends SessionMetadata,\n\tTCreateOptions extends SessionCreateOptions,\n\tTListOptions,\n>(source: Pick<SessionRepo<TMetadata, TCreateOptions, TListOptions>, \"list\" | \"open\">): SessionSearch<TMetadata> {\n\treturn new ScanningSessionSearch(source);\n}\n"]}
|