@handy_wote/pi-agent-core 0.81.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +507 -0
- package/dist/agent-loop.d.ts +24 -0
- package/dist/agent-loop.d.ts.map +1 -0
- package/dist/agent-loop.js +549 -0
- package/dist/agent-loop.js.map +1 -0
- package/dist/agent.d.ts +120 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +413 -0
- package/dist/agent.js.map +1 -0
- package/dist/harness/agent-harness.d.ts +92 -0
- package/dist/harness/agent-harness.d.ts.map +1 -0
- package/dist/harness/agent-harness.js +952 -0
- package/dist/harness/agent-harness.js.map +1 -0
- package/dist/harness/compaction/branch-summarization.d.ts +55 -0
- package/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/harness/compaction/branch-summarization.js +173 -0
- package/dist/harness/compaction/branch-summarization.js.map +1 -0
- package/dist/harness/compaction/compaction.d.ts +107 -0
- package/dist/harness/compaction/compaction.d.ts.map +1 -0
- package/dist/harness/compaction/compaction.js +577 -0
- package/dist/harness/compaction/compaction.js.map +1 -0
- package/dist/harness/compaction/utils.d.ts +25 -0
- package/dist/harness/compaction/utils.d.ts.map +1 -0
- package/dist/harness/compaction/utils.js +120 -0
- package/dist/harness/compaction/utils.js.map +1 -0
- package/dist/harness/env/nodejs.d.ts +51 -0
- package/dist/harness/env/nodejs.d.ts.map +1 -0
- package/dist/harness/env/nodejs.js +516 -0
- package/dist/harness/env/nodejs.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 +48 -0
- package/dist/harness/prompt-templates.d.ts.map +1 -0
- package/dist/harness/prompt-templates.js +230 -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 +103 -0
- package/dist/harness/session/jsonl-repo.js.map +1 -0
- package/dist/harness/session/jsonl-storage.d.ts +42 -0
- package/dist/harness/session/jsonl-storage.d.ts.map +1 -0
- package/dist/harness/session/jsonl-storage.js +293 -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 +33 -0
- package/dist/harness/session/memory-storage.d.ts.map +1 -0
- package/dist/harness/session/memory-storage.js +168 -0
- package/dist/harness/session/memory-storage.js.map +1 -0
- package/dist/harness/session/repo-utils.d.ts +11 -0
- package/dist/harness/session/repo-utils.d.ts.map +1 -0
- package/dist/harness/session/repo-utils.js +39 -0
- package/dist/harness/session/repo-utils.js.map +1 -0
- package/dist/harness/session/session.d.ts +49 -0
- package/dist/harness/session/session.d.ts.map +1 -0
- package/dist/harness/session/session.js +263 -0
- package/dist/harness/session/session.js.map +1 -0
- package/dist/harness/skills.d.ts +44 -0
- package/dist/harness/skills.d.ts.map +1 -0
- package/dist/harness/skills.js +311 -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 +665 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +94 -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 +126 -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 +290 -0
- package/dist/harness/utils/truncate.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- 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 +69 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +278 -0
- package/dist/proxy.js.map +1 -0
- package/dist/stream-fn.d.ts +10 -0
- package/dist/stream-fn.d.ts.map +1 -0
- package/dist/stream-fn.js +17 -0
- package/dist/stream-fn.js.map +1 -0
- package/dist/types.d.ts +407 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { uuidv7 } from "@handy_wote/pi-ai";
|
|
2
|
+
import { SessionError, } from "../types.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
|
+
// The uuidv7 prefix is timestamp-derived and nearly constant between calls,
|
|
24
|
+
// so short ids must come from the random tail.
|
|
25
|
+
const id = uuidv7().slice(-8);
|
|
26
|
+
if (!byId.has(id))
|
|
27
|
+
return id;
|
|
28
|
+
}
|
|
29
|
+
return uuidv7();
|
|
30
|
+
}
|
|
31
|
+
function leafIdAfterEntry(entry) {
|
|
32
|
+
return entry.type === "leaf" ? entry.targetId : entry.id;
|
|
33
|
+
}
|
|
34
|
+
export class InMemorySessionStorage {
|
|
35
|
+
metadata;
|
|
36
|
+
entries;
|
|
37
|
+
byId;
|
|
38
|
+
labelsById;
|
|
39
|
+
leafId;
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.entries = options?.entries ? [...options.entries] : [];
|
|
42
|
+
this.byId = new Map(this.entries.map((entry) => [entry.id, entry]));
|
|
43
|
+
this.labelsById = buildLabelsById(this.entries);
|
|
44
|
+
this.leafId = null;
|
|
45
|
+
for (const entry of this.entries)
|
|
46
|
+
this.leafId = leafIdAfterEntry(entry);
|
|
47
|
+
if (this.leafId !== null && !this.byId.has(this.leafId)) {
|
|
48
|
+
throw new SessionError("invalid_session", `Entry ${this.leafId} not found`);
|
|
49
|
+
}
|
|
50
|
+
this.metadata = options?.metadata ?? { id: uuidv7(), createdAt: new Date().toISOString() };
|
|
51
|
+
}
|
|
52
|
+
async getMetadata() {
|
|
53
|
+
return this.metadata;
|
|
54
|
+
}
|
|
55
|
+
async getLeafId() {
|
|
56
|
+
if (this.leafId !== null && !this.byId.has(this.leafId)) {
|
|
57
|
+
throw new SessionError("invalid_session", `Entry ${this.leafId} not found`);
|
|
58
|
+
}
|
|
59
|
+
return this.leafId;
|
|
60
|
+
}
|
|
61
|
+
async setLeafId(leafId) {
|
|
62
|
+
if (leafId !== null && !this.byId.has(leafId)) {
|
|
63
|
+
throw new SessionError("not_found", `Entry ${leafId} not found`);
|
|
64
|
+
}
|
|
65
|
+
const entry = {
|
|
66
|
+
type: "leaf",
|
|
67
|
+
id: generateEntryId(this.byId),
|
|
68
|
+
parentId: this.leafId,
|
|
69
|
+
timestamp: new Date().toISOString(),
|
|
70
|
+
targetId: leafId,
|
|
71
|
+
};
|
|
72
|
+
this.entries.push(entry);
|
|
73
|
+
this.byId.set(entry.id, entry);
|
|
74
|
+
this.leafId = leafId;
|
|
75
|
+
}
|
|
76
|
+
async createEntryId() {
|
|
77
|
+
return generateEntryId(this.byId);
|
|
78
|
+
}
|
|
79
|
+
async appendEntry(entry) {
|
|
80
|
+
this.entries.push(entry);
|
|
81
|
+
this.byId.set(entry.id, entry);
|
|
82
|
+
updateLabelCache(this.labelsById, entry);
|
|
83
|
+
this.leafId = leafIdAfterEntry(entry);
|
|
84
|
+
}
|
|
85
|
+
async getEntry(id) {
|
|
86
|
+
return this.byId.get(id);
|
|
87
|
+
}
|
|
88
|
+
async findEntries(type) {
|
|
89
|
+
return this.entries.filter((entry) => entry.type === type);
|
|
90
|
+
}
|
|
91
|
+
async getLabel(id) {
|
|
92
|
+
return this.labelsById.get(id);
|
|
93
|
+
}
|
|
94
|
+
async getSessionName() {
|
|
95
|
+
const entries = await this.findEntries("session_info");
|
|
96
|
+
return entries[entries.length - 1]?.name?.trim() || undefined;
|
|
97
|
+
}
|
|
98
|
+
async getSessionStats() {
|
|
99
|
+
let messageCount = 0;
|
|
100
|
+
let cachedTokens = 0;
|
|
101
|
+
let uncachedTokens = 0;
|
|
102
|
+
let totalTokens = 0;
|
|
103
|
+
let costTotal = 0;
|
|
104
|
+
for (const entry of this.entries) {
|
|
105
|
+
if (entry.type === "message") {
|
|
106
|
+
messageCount += 1;
|
|
107
|
+
}
|
|
108
|
+
const usage = entry.type === "message"
|
|
109
|
+
? entry.message.role === "assistant"
|
|
110
|
+
? entry.message.usage
|
|
111
|
+
: undefined
|
|
112
|
+
: entry.type === "compaction" || entry.type === "branch_summary"
|
|
113
|
+
? entry.usage
|
|
114
|
+
: undefined;
|
|
115
|
+
if (!usage ||
|
|
116
|
+
typeof usage.input !== "number" ||
|
|
117
|
+
typeof usage.output !== "number" ||
|
|
118
|
+
typeof usage.cacheRead !== "number" ||
|
|
119
|
+
typeof usage.cacheWrite !== "number" ||
|
|
120
|
+
typeof usage.cost?.total !== "number") {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
cachedTokens += usage.cacheRead;
|
|
124
|
+
uncachedTokens += usage.input + usage.cacheWrite;
|
|
125
|
+
totalTokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
126
|
+
costTotal += usage.cost.total;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
messageCount,
|
|
130
|
+
cachedTokens,
|
|
131
|
+
uncachedTokens,
|
|
132
|
+
totalTokens,
|
|
133
|
+
costTotal,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async getPathToRootOrCompaction(leafId) {
|
|
137
|
+
if (leafId === null)
|
|
138
|
+
return [];
|
|
139
|
+
const path = [];
|
|
140
|
+
let stopAtEntryId = null;
|
|
141
|
+
let current = this.byId.get(leafId);
|
|
142
|
+
if (!current)
|
|
143
|
+
throw new SessionError("not_found", `Entry ${leafId} not found`);
|
|
144
|
+
while (current) {
|
|
145
|
+
path.unshift(current);
|
|
146
|
+
if (stopAtEntryId !== null && current.id === stopAtEntryId)
|
|
147
|
+
break;
|
|
148
|
+
if (current.type === "compaction") {
|
|
149
|
+
if (current.retainedTail)
|
|
150
|
+
break;
|
|
151
|
+
stopAtEntryId = current.firstKeptEntryId ?? null;
|
|
152
|
+
}
|
|
153
|
+
if (!current.parentId)
|
|
154
|
+
break;
|
|
155
|
+
const parent = this.byId.get(current.parentId);
|
|
156
|
+
if (!parent)
|
|
157
|
+
throw new SessionError("invalid_session", `Entry ${current.parentId} not found`);
|
|
158
|
+
current = parent;
|
|
159
|
+
}
|
|
160
|
+
return path;
|
|
161
|
+
}
|
|
162
|
+
async getEntries(options) {
|
|
163
|
+
const start = options?.afterEntrySeq ?? 0;
|
|
164
|
+
const end = options?.limit === undefined ? undefined : start + options.limit;
|
|
165
|
+
return this.entries.slice(start, end);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# 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":"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","sourcesContent":["import { uuidv7 } from \"@handy_wote/pi-ai\";\nimport {\n\ttype LeafEntry,\n\ttype SessionEntryCursorOptions,\n\tSessionError,\n\ttype SessionMetadata,\n\ttype SessionStorage,\n\ttype SessionTreeEntry,\n} from \"../types.ts\";\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\t// The uuidv7 prefix is timestamp-derived and nearly constant between calls,\n\t\t// so short ids must come from the random tail.\n\t\tconst id = uuidv7().slice(-8);\n\t\tif (!byId.has(id)) return id;\n\t}\n\treturn uuidv7();\n}\n\nfunction leafIdAfterEntry(entry: SessionTreeEntry): string | null {\n\treturn entry.type === \"leaf\" ? entry.targetId : entry.id;\n}\n\nexport class InMemorySessionStorage<TMetadata extends SessionMetadata = SessionMetadata>\n\timplements SessionStorage<TMetadata>\n{\n\tprivate readonly metadata: TMetadata;\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[]; metadata?: TMetadata }) {\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 = null;\n\t\tfor (const entry of this.entries) this.leafId = leafIdAfterEntry(entry);\n\t\tif (this.leafId !== null && !this.byId.has(this.leafId)) {\n\t\t\tthrow new SessionError(\"invalid_session\", `Entry ${this.leafId} not found`);\n\t\t}\n\t\tthis.metadata = options?.metadata ?? ({ id: uuidv7(), createdAt: new Date().toISOString() } as TMetadata);\n\t}\n\n\tasync getMetadata(): Promise<TMetadata> {\n\t\treturn this.metadata;\n\t}\n\n\tasync getLeafId(): Promise<string | null> {\n\t\tif (this.leafId !== null && !this.byId.has(this.leafId)) {\n\t\t\tthrow new SessionError(\"invalid_session\", `Entry ${this.leafId} not found`);\n\t\t}\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 SessionError(\"not_found\", `Entry ${leafId} not found`);\n\t\t}\n\t\tconst entry: LeafEntry = {\n\t\t\ttype: \"leaf\",\n\t\t\tid: generateEntryId(this.byId),\n\t\t\tparentId: this.leafId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttargetId: leafId,\n\t\t};\n\t\tthis.entries.push(entry);\n\t\tthis.byId.set(entry.id, entry);\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 = leafIdAfterEntry(entry);\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 getSessionName(): Promise<string | undefined> {\n\t\tconst entries = await this.findEntries(\"session_info\");\n\t\treturn entries[entries.length - 1]?.name?.trim() || undefined;\n\t}\n\n\tasync getSessionStats() {\n\t\tlet messageCount = 0;\n\t\tlet cachedTokens = 0;\n\t\tlet uncachedTokens = 0;\n\t\tlet totalTokens = 0;\n\t\tlet costTotal = 0;\n\t\tfor (const entry of this.entries) {\n\t\t\tif (entry.type === \"message\") {\n\t\t\t\tmessageCount += 1;\n\t\t\t}\n\t\t\tconst usage =\n\t\t\t\tentry.type === \"message\"\n\t\t\t\t\t? entry.message.role === \"assistant\"\n\t\t\t\t\t\t? entry.message.usage\n\t\t\t\t\t\t: undefined\n\t\t\t\t\t: entry.type === \"compaction\" || entry.type === \"branch_summary\"\n\t\t\t\t\t\t? entry.usage\n\t\t\t\t\t\t: undefined;\n\t\t\tif (\n\t\t\t\t!usage ||\n\t\t\t\ttypeof usage.input !== \"number\" ||\n\t\t\t\ttypeof usage.output !== \"number\" ||\n\t\t\t\ttypeof usage.cacheRead !== \"number\" ||\n\t\t\t\ttypeof usage.cacheWrite !== \"number\" ||\n\t\t\t\ttypeof usage.cost?.total !== \"number\"\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcachedTokens += usage.cacheRead;\n\t\t\tuncachedTokens += usage.input + usage.cacheWrite;\n\t\t\ttotalTokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n\t\t\tcostTotal += usage.cost.total;\n\t\t}\n\t\treturn {\n\t\t\tmessageCount,\n\t\t\tcachedTokens,\n\t\t\tuncachedTokens,\n\t\t\ttotalTokens,\n\t\t\tcostTotal,\n\t\t};\n\t}\n\n\tasync getPathToRootOrCompaction(leafId: string | null): Promise<SessionTreeEntry[]> {\n\t\tif (leafId === null) return [];\n\t\tconst path: SessionTreeEntry[] = [];\n\t\tlet stopAtEntryId: string | null = null;\n\t\tlet current = this.byId.get(leafId);\n\t\tif (!current) throw new SessionError(\"not_found\", `Entry ${leafId} not found`);\n\t\twhile (current) {\n\t\t\tpath.unshift(current);\n\t\t\tif (stopAtEntryId !== null && current.id === stopAtEntryId) break;\n\t\t\tif (current.type === \"compaction\") {\n\t\t\t\tif (current.retainedTail) break;\n\t\t\t\tstopAtEntryId = current.firstKeptEntryId ?? null;\n\t\t\t}\n\t\t\tif (!current.parentId) break;\n\t\t\tconst parent = this.byId.get(current.parentId);\n\t\t\tif (!parent) throw new SessionError(\"invalid_session\", `Entry ${current.parentId} not found`);\n\t\t\tcurrent = parent;\n\t\t}\n\t\treturn path;\n\t}\n\n\tasync getEntries(options?: SessionEntryCursorOptions): Promise<SessionTreeEntry[]> {\n\t\tconst start = options?.afterEntrySeq ?? 0;\n\t\tconst end = options?.limit === undefined ? undefined : start + options.limit;\n\t\treturn this.entries.slice(start, end);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type FileError, type Result, type SessionMetadata, type SessionStorage, type SessionTreeEntry } from "../types.ts";
|
|
2
|
+
import { Session } from "./session.ts";
|
|
3
|
+
export declare function createSessionId(): string;
|
|
4
|
+
export declare function createTimestamp(): string;
|
|
5
|
+
export declare function toSession<TMetadata extends SessionMetadata>(storage: SessionStorage<TMetadata>): Session<TMetadata>;
|
|
6
|
+
export declare function getFileSystemResultOrThrow<TValue>(result: Result<TValue, FileError>, message: string): TValue;
|
|
7
|
+
export declare function getEntriesToFork(storage: SessionStorage, options: {
|
|
8
|
+
entryId?: string;
|
|
9
|
+
position?: "before" | "at";
|
|
10
|
+
}): Promise<SessionTreeEntry[]>;
|
|
11
|
+
//# sourceMappingURL=repo-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo-utils.d.ts","sourceRoot":"","sources":["../../../src/harness/session/repo-utils.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,SAAS,EACd,KAAK,MAAM,EAEX,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,wBAAgB,SAAS,CAAC,SAAS,SAAS,eAAe,EAAE,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAEnH;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAM7G;AAED,wBAAsB,gBAAgB,CACrC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;CAAE,GACvD,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAgB7B","sourcesContent":["import { uuidv7 } from \"@handy_wote/pi-ai\";\nimport {\n\ttype FileError,\n\ttype Result,\n\tSessionError,\n\ttype SessionMetadata,\n\ttype SessionStorage,\n\ttype SessionTreeEntry,\n} from \"../types.ts\";\nimport { Session } from \"./session.ts\";\n\nexport function createSessionId(): string {\n\treturn uuidv7();\n}\n\nexport function createTimestamp(): string {\n\treturn new Date().toISOString();\n}\n\nexport function toSession<TMetadata extends SessionMetadata>(storage: SessionStorage<TMetadata>): Session<TMetadata> {\n\treturn new Session(storage);\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\nexport async function getEntriesToFork(\n\tstorage: SessionStorage,\n\toptions: { entryId?: string; position?: \"before\" | \"at\" },\n): Promise<SessionTreeEntry[]> {\n\tif (!options.entryId) return storage.getEntries();\n\tconst target = await storage.getEntry(options.entryId);\n\tif (!target) {\n\t\tthrow new SessionError(\"invalid_fork_target\", `Entry ${options.entryId} not found`);\n\t}\n\tlet effectiveLeafId: string | null;\n\tif ((options.position ?? \"before\") === \"at\") {\n\t\teffectiveLeafId = target.id;\n\t} else {\n\t\tif (target.type !== \"message\" || target.message.role !== \"user\") {\n\t\t\tthrow new SessionError(\"invalid_fork_target\", `Entry ${options.entryId} is not a user message`);\n\t\t}\n\t\teffectiveLeafId = target.parentId;\n\t}\n\treturn storage.getPathToRootOrCompaction(effectiveLeafId);\n}\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { uuidv7 } from "@handy_wote/pi-ai";
|
|
2
|
+
import { SessionError, } from "../types.js";
|
|
3
|
+
import { Session } from "./session.js";
|
|
4
|
+
export function createSessionId() {
|
|
5
|
+
return uuidv7();
|
|
6
|
+
}
|
|
7
|
+
export function createTimestamp() {
|
|
8
|
+
return new Date().toISOString();
|
|
9
|
+
}
|
|
10
|
+
export function toSession(storage) {
|
|
11
|
+
return new Session(storage);
|
|
12
|
+
}
|
|
13
|
+
export function getFileSystemResultOrThrow(result, message) {
|
|
14
|
+
if (!result.ok) {
|
|
15
|
+
const code = result.error.code === "not_found" ? "not_found" : "storage";
|
|
16
|
+
throw new SessionError(code, `${message}: ${result.error.message}`, result.error);
|
|
17
|
+
}
|
|
18
|
+
return result.value;
|
|
19
|
+
}
|
|
20
|
+
export async function getEntriesToFork(storage, options) {
|
|
21
|
+
if (!options.entryId)
|
|
22
|
+
return storage.getEntries();
|
|
23
|
+
const target = await storage.getEntry(options.entryId);
|
|
24
|
+
if (!target) {
|
|
25
|
+
throw new SessionError("invalid_fork_target", `Entry ${options.entryId} not found`);
|
|
26
|
+
}
|
|
27
|
+
let effectiveLeafId;
|
|
28
|
+
if ((options.position ?? "before") === "at") {
|
|
29
|
+
effectiveLeafId = target.id;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
if (target.type !== "message" || target.message.role !== "user") {
|
|
33
|
+
throw new SessionError("invalid_fork_target", `Entry ${options.entryId} is not a user message`);
|
|
34
|
+
}
|
|
35
|
+
effectiveLeafId = target.parentId;
|
|
36
|
+
}
|
|
37
|
+
return storage.getPathToRootOrCompaction(effectiveLeafId);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=repo-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo-utils.js","sourceRoot":"","sources":["../../../src/harness/session/repo-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAGN,YAAY,GAIZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,UAAU,eAAe,GAAW;IACzC,OAAO,MAAM,EAAE,CAAC;AAAA,CAChB;AAED,MAAM,UAAU,eAAe,GAAW;IACzC,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAAA,CAChC;AAED,MAAM,UAAU,SAAS,CAAoC,OAAkC,EAAsB;IACpH,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;AAAA,CAC5B;AAED,MAAM,UAAU,0BAA0B,CAAS,MAAiC,EAAE,OAAe,EAAU;IAC9G,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;AAAA,CACpB;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,OAAuB,EACvB,OAAyD,EAC3B;IAC9B,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,OAAO,OAAO,CAAC,UAAU,EAAE,CAAC;IAClD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,IAAI,YAAY,CAAC,qBAAqB,EAAE,SAAS,OAAO,CAAC,OAAO,YAAY,CAAC,CAAC;IACrF,CAAC;IACD,IAAI,eAA8B,CAAC;IACnC,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7C,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC;IAC7B,CAAC;SAAM,CAAC;QACP,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACjE,MAAM,IAAI,YAAY,CAAC,qBAAqB,EAAE,SAAS,OAAO,CAAC,OAAO,wBAAwB,CAAC,CAAC;QACjG,CAAC;QACD,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC;IACnC,CAAC;IACD,OAAO,OAAO,CAAC,yBAAyB,CAAC,eAAe,CAAC,CAAC;AAAA,CAC1D","sourcesContent":["import { uuidv7 } from \"@handy_wote/pi-ai\";\nimport {\n\ttype FileError,\n\ttype Result,\n\tSessionError,\n\ttype SessionMetadata,\n\ttype SessionStorage,\n\ttype SessionTreeEntry,\n} from \"../types.ts\";\nimport { Session } from \"./session.ts\";\n\nexport function createSessionId(): string {\n\treturn uuidv7();\n}\n\nexport function createTimestamp(): string {\n\treturn new Date().toISOString();\n}\n\nexport function toSession<TMetadata extends SessionMetadata>(storage: SessionStorage<TMetadata>): Session<TMetadata> {\n\treturn new Session(storage);\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\nexport async function getEntriesToFork(\n\tstorage: SessionStorage,\n\toptions: { entryId?: string; position?: \"before\" | \"at\" },\n): Promise<SessionTreeEntry[]> {\n\tif (!options.entryId) return storage.getEntries();\n\tconst target = await storage.getEntry(options.entryId);\n\tif (!target) {\n\t\tthrow new SessionError(\"invalid_fork_target\", `Entry ${options.entryId} not found`);\n\t}\n\tlet effectiveLeafId: string | null;\n\tif ((options.position ?? \"before\") === \"at\") {\n\t\teffectiveLeafId = target.id;\n\t} else {\n\t\tif (target.type !== \"message\" || target.message.role !== \"user\") {\n\t\t\tthrow new SessionError(\"invalid_fork_target\", `Entry ${options.entryId} is not a user message`);\n\t\t}\n\t\teffectiveLeafId = target.parentId;\n\t}\n\treturn storage.getPathToRootOrCompaction(effectiveLeafId);\n}\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ImageContent, TextContent, Usage } from "@handy_wote/pi-ai";
|
|
2
|
+
import type { AgentMessage } from "../../types.ts";
|
|
3
|
+
import type { CustomEntry, SessionContext, SessionEntryCursorOptions, SessionMetadata, SessionStats, SessionStorage, SessionTreeEntry } from "../types.ts";
|
|
4
|
+
export type ContextEntryTransform = (entries: readonly SessionTreeEntry[]) => readonly SessionTreeEntry[];
|
|
5
|
+
export type CustomEntryContextMessageProjector = (entry: CustomEntry, index: number, entries: readonly SessionTreeEntry[]) => readonly AgentMessage[] | undefined;
|
|
6
|
+
export interface SessionContextBuildOptions {
|
|
7
|
+
/** Additional entry transforms applied after the default compaction transform. */
|
|
8
|
+
entryTransforms?: readonly ContextEntryTransform[];
|
|
9
|
+
/** Optional custom-entry projectors. Custom entries are omitted from model context by default. */
|
|
10
|
+
entryProjectors?: Readonly<Record<string, CustomEntryContextMessageProjector>>;
|
|
11
|
+
}
|
|
12
|
+
export declare function defaultContextEntryTransform(pathEntries: readonly SessionTreeEntry[]): SessionTreeEntry[];
|
|
13
|
+
export declare function buildContextEntries(pathEntries: readonly SessionTreeEntry[], options?: SessionContextBuildOptions): SessionTreeEntry[];
|
|
14
|
+
export declare function sessionEntryToContextMessages(entry: SessionTreeEntry, index: number, entries: readonly SessionTreeEntry[], options?: SessionContextBuildOptions): AgentMessage[];
|
|
15
|
+
export declare function buildSessionContext(pathEntries: readonly SessionTreeEntry[], options?: SessionContextBuildOptions): SessionContext;
|
|
16
|
+
export declare class Session<TMetadata extends SessionMetadata = SessionMetadata> {
|
|
17
|
+
private storage;
|
|
18
|
+
private contextBuildOptions;
|
|
19
|
+
constructor(storage: SessionStorage<TMetadata>, contextBuildOptions?: SessionContextBuildOptions);
|
|
20
|
+
getMetadata(): Promise<TMetadata>;
|
|
21
|
+
getStorage(): SessionStorage<TMetadata>;
|
|
22
|
+
getLeafId(): Promise<string | null>;
|
|
23
|
+
getEntry(id: string): Promise<SessionTreeEntry | undefined>;
|
|
24
|
+
getEntries(options?: SessionEntryCursorOptions): Promise<SessionTreeEntry[]>;
|
|
25
|
+
getBranch(fromId?: string): Promise<SessionTreeEntry[]>;
|
|
26
|
+
buildContextEntries(options?: SessionContextBuildOptions): Promise<SessionTreeEntry[]>;
|
|
27
|
+
buildContext(options?: SessionContextBuildOptions): Promise<SessionContext>;
|
|
28
|
+
private mergeContextBuildOptions;
|
|
29
|
+
getLabel(id: string): Promise<string | undefined>;
|
|
30
|
+
getSessionStats(): Promise<SessionStats>;
|
|
31
|
+
getSessionName(): Promise<string | undefined>;
|
|
32
|
+
private appendTypedEntry;
|
|
33
|
+
appendMessage(message: AgentMessage): Promise<string>;
|
|
34
|
+
appendThinkingLevelChange(thinkingLevel: string): Promise<string>;
|
|
35
|
+
appendModelChange(provider: string, modelId: string): Promise<string>;
|
|
36
|
+
appendActiveToolsChange(activeToolNames: string[]): Promise<string>;
|
|
37
|
+
appendCompaction<T = unknown>(summary: string, firstKeptEntryId: string | undefined, tokensBefore: number, details?: T, fromHook?: boolean, usage?: Usage, retainedTail?: AgentMessage[]): Promise<string>;
|
|
38
|
+
appendCustomEntry(customType: string, data?: unknown): Promise<string>;
|
|
39
|
+
appendCustomMessageEntry<T = unknown>(customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details?: T): Promise<string>;
|
|
40
|
+
appendLabel(targetId: string, label: string | undefined): Promise<string>;
|
|
41
|
+
appendSessionName(name: string): Promise<string>;
|
|
42
|
+
moveTo(entryId: string | null, summary?: {
|
|
43
|
+
summary: string;
|
|
44
|
+
details?: unknown;
|
|
45
|
+
usage?: Usage;
|
|
46
|
+
fromHook?: boolean;
|
|
47
|
+
}): Promise<string | undefined>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/harness/session/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,OAAO,KAAK,EAIX,WAAW,EAKX,cAAc,EACd,yBAAyB,EAEzB,eAAe,EACf,YAAY,EACZ,cAAc,EACd,gBAAgB,EAEhB,MAAM,aAAa,CAAC;AAGrB,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,KAAK,SAAS,gBAAgB,EAAE,CAAC;AAE1G,MAAM,MAAM,kCAAkC,GAAG,CAChD,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,gBAAgB,EAAE,KAChC,SAAS,YAAY,EAAE,GAAG,SAAS,CAAC;AAEzC,MAAM,WAAW,0BAA0B;IAC1C,kFAAkF;IAClF,eAAe,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,kGAAkG;IAClG,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kCAAkC,CAAC,CAAC,CAAC;CAC/E;AAsBD,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CA+BzG;AAED,wBAAgB,mBAAmB,CAClC,WAAW,EAAE,SAAS,gBAAgB,EAAE,EACxC,OAAO,GAAE,0BAA+B,GACtC,gBAAgB,EAAE,CAMpB;AAED,wBAAgB,6BAA6B,CAC5C,KAAK,EAAE,gBAAgB,EACvB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,OAAO,GAAE,0BAA+B,GACtC,YAAY,EAAE,CA4BhB;AAED,wBAAgB,mBAAmB,CAClC,WAAW,EAAE,SAAS,gBAAgB,EAAE,EACxC,OAAO,GAAE,0BAA+B,GACtC,cAAc,CAOhB;AAED,qBAAa,OAAO,CAAC,SAAS,SAAS,eAAe,GAAG,eAAe;IACvE,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,mBAAmB,CAA6B;IAExD,YAAY,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,EAAE,mBAAmB,GAAE,0BAA+B,EAGnG;IAED,WAAW,IAAI,OAAO,CAAC,SAAS,CAAC,CAEhC;IAED,UAAU,IAAI,cAAc,CAAC,SAAS,CAAC,CAEtC;IAED,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAElC;IAED,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAE1D;IAED,UAAU,CAAC,OAAO,CAAC,EAAE,yBAAyB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAE3E;IAEK,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAG5D;IAEK,mBAAmB,CAAC,OAAO,GAAE,0BAA+B,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAE/F;IAEK,YAAY,CAAC,OAAO,GAAE,0BAA+B,GAAG,OAAO,CAAC,cAAc,CAAC,CAEpF;IAED,OAAO,CAAC,wBAAwB;IAUhC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAEhD;IAED,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAEvC;IAEK,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAElD;YAEa,gBAAgB;IAKxB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ1D;IAEK,yBAAyB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQtE;IAEK,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS1E;IAEK,uBAAuB,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAQxE;IAEK,gBAAgB,CAAC,CAAC,GAAG,OAAO,EACjC,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,CAAC,EACX,QAAQ,CAAC,EAAE,OAAO,EAClB,KAAK,CAAC,EAAE,KAAK,EACb,YAAY,CAAC,EAAE,YAAY,EAAE,GAC3B,OAAO,CAAC,MAAM,CAAC,CAcjB;IAEK,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAS3E;IAEK,wBAAwB,CAAC,CAAC,GAAG,OAAO,EACzC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,EAChD,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,CAAC,GACT,OAAO,CAAC,MAAM,CAAC,CAWjB;IAEK,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAY9E;IAEK,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASrD;IAEK,MAAM,CACX,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GACjF,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiB7B;CACD","sourcesContent":["import type { ImageContent, TextContent, Usage } from \"@handy_wote/pi-ai\";\nimport type { AgentMessage } from \"../../types.ts\";\nimport { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage } from \"../messages.ts\";\nimport type {\n\tActiveToolsChangeEntry,\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tCustomEntry,\n\tCustomMessageEntry,\n\tLabelEntry,\n\tMessageEntry,\n\tModelChangeEntry,\n\tSessionContext,\n\tSessionEntryCursorOptions,\n\tSessionInfoEntry,\n\tSessionMetadata,\n\tSessionStats,\n\tSessionStorage,\n\tSessionTreeEntry,\n\tThinkingLevelChangeEntry,\n} from \"../types.ts\";\nimport { SessionError } from \"../types.ts\";\n\nexport type ContextEntryTransform = (entries: readonly SessionTreeEntry[]) => readonly SessionTreeEntry[];\n\nexport type CustomEntryContextMessageProjector = (\n\tentry: CustomEntry,\n\tindex: number,\n\tentries: readonly SessionTreeEntry[],\n) => readonly AgentMessage[] | undefined;\n\nexport interface SessionContextBuildOptions {\n\t/** Additional entry transforms applied after the default compaction transform. */\n\tentryTransforms?: readonly ContextEntryTransform[];\n\t/** Optional custom-entry projectors. Custom entries are omitted from model context by default. */\n\tentryProjectors?: Readonly<Record<string, CustomEntryContextMessageProjector>>;\n}\n\nfunction deriveSessionContextState(pathEntries: readonly SessionTreeEntry[]): Omit<SessionContext, \"messages\"> {\n\tlet thinkingLevel = \"off\";\n\tlet model: { provider: string; modelId: string } | null = null;\n\tlet activeToolNames: string[] | null = null;\n\n\tfor (const entry of pathEntries) {\n\t\tif (entry.type === \"thinking_level_change\") {\n\t\t\tthinkingLevel = entry.thinkingLevel;\n\t\t} else if (entry.type === \"model_change\") {\n\t\t\tmodel = { provider: entry.provider, modelId: entry.modelId };\n\t\t} else if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\tmodel = { provider: entry.message.provider, modelId: entry.message.model };\n\t\t} else if (entry.type === \"active_tools_change\") {\n\t\t\tactiveToolNames = [...entry.activeToolNames];\n\t\t}\n\t}\n\n\treturn { thinkingLevel, model, activeToolNames };\n}\n\nexport function defaultContextEntryTransform(pathEntries: readonly SessionTreeEntry[]): SessionTreeEntry[] {\n\tlet compaction: CompactionEntry | null = null;\n\tfor (const entry of pathEntries) {\n\t\tif (entry.type === \"compaction\") {\n\t\t\tcompaction = entry;\n\t\t}\n\t}\n\tif (!compaction) {\n\t\treturn [...pathEntries];\n\t}\n\n\tconst entries: SessionTreeEntry[] = [compaction];\n\tconst compactionIdx = pathEntries.findIndex((entry) => entry.type === \"compaction\" && entry.id === compaction.id);\n\tif (compaction.retainedTail) {\n\t\tfor (let i = compactionIdx + 1; i < pathEntries.length; i++) {\n\t\t\tentries.push(pathEntries[i]!);\n\t\t}\n\t\treturn entries;\n\t}\n\tif (compaction.firstKeptEntryId) {\n\t\tlet foundFirstKept = false;\n\t\tfor (let i = 0; i < compactionIdx; i++) {\n\t\t\tconst entry = pathEntries[i]!;\n\t\t\tif (entry.id === compaction.firstKeptEntryId) foundFirstKept = true;\n\t\t\tif (foundFirstKept) entries.push(entry);\n\t\t}\n\t}\n\tfor (let i = compactionIdx + 1; i < pathEntries.length; i++) {\n\t\tentries.push(pathEntries[i]!);\n\t}\n\treturn entries;\n}\n\nexport function buildContextEntries(\n\tpathEntries: readonly SessionTreeEntry[],\n\toptions: SessionContextBuildOptions = {},\n): SessionTreeEntry[] {\n\tlet entries = defaultContextEntryTransform(pathEntries);\n\tfor (const transform of options.entryTransforms ?? []) {\n\t\tentries = [...transform(entries)];\n\t}\n\treturn entries;\n}\n\nexport function sessionEntryToContextMessages(\n\tentry: SessionTreeEntry,\n\tindex: number,\n\tentries: readonly SessionTreeEntry[],\n\toptions: SessionContextBuildOptions = {},\n): AgentMessage[] {\n\tif (entry.type === \"message\") {\n\t\treturn [entry.message as AgentMessage];\n\t}\n\tif (entry.type === \"custom_message\") {\n\t\treturn [\n\t\t\tcreateCustomMessage(\n\t\t\t\tentry.customType,\n\t\t\t\tentry.content as string | (TextContent | ImageContent)[],\n\t\t\t\tentry.display,\n\t\t\t\tentry.details,\n\t\t\t\tentry.timestamp,\n\t\t\t),\n\t\t];\n\t}\n\tif (entry.type === \"compaction\") {\n\t\treturn [\n\t\t\tcreateCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp),\n\t\t\t...(entry.retainedTail ?? []),\n\t\t];\n\t}\n\tif (entry.type === \"branch_summary\" && entry.summary) {\n\t\treturn [createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp)];\n\t}\n\tif (entry.type === \"custom\") {\n\t\treturn [...(options.entryProjectors?.[entry.customType]?.(entry, index, entries) ?? [])];\n\t}\n\treturn [];\n}\n\nexport function buildSessionContext(\n\tpathEntries: readonly SessionTreeEntry[],\n\toptions: SessionContextBuildOptions = {},\n): SessionContext {\n\tconst state = deriveSessionContextState(pathEntries);\n\tconst contextEntries = buildContextEntries(pathEntries, options);\n\tconst messages = contextEntries.flatMap((entry, index) =>\n\t\tsessionEntryToContextMessages(entry, index, contextEntries, options),\n\t);\n\treturn { ...state, messages };\n}\n\nexport class Session<TMetadata extends SessionMetadata = SessionMetadata> {\n\tprivate storage: SessionStorage<TMetadata>;\n\tprivate contextBuildOptions: SessionContextBuildOptions;\n\n\tconstructor(storage: SessionStorage<TMetadata>, contextBuildOptions: SessionContextBuildOptions = {}) {\n\t\tthis.storage = storage;\n\t\tthis.contextBuildOptions = contextBuildOptions;\n\t}\n\n\tgetMetadata(): Promise<TMetadata> {\n\t\treturn this.storage.getMetadata();\n\t}\n\n\tgetStorage(): SessionStorage<TMetadata> {\n\t\treturn this.storage;\n\t}\n\n\tgetLeafId(): Promise<string | null> {\n\t\treturn this.storage.getLeafId();\n\t}\n\n\tgetEntry(id: string): Promise<SessionTreeEntry | undefined> {\n\t\treturn this.storage.getEntry(id);\n\t}\n\n\tgetEntries(options?: SessionEntryCursorOptions): Promise<SessionTreeEntry[]> {\n\t\treturn this.storage.getEntries(options);\n\t}\n\n\tasync getBranch(fromId?: string): Promise<SessionTreeEntry[]> {\n\t\tconst leafId = fromId ?? (await this.storage.getLeafId());\n\t\treturn this.storage.getPathToRootOrCompaction(leafId);\n\t}\n\n\tasync buildContextEntries(options: SessionContextBuildOptions = {}): Promise<SessionTreeEntry[]> {\n\t\treturn buildContextEntries(await this.getBranch(), this.mergeContextBuildOptions(options));\n\t}\n\n\tasync buildContext(options: SessionContextBuildOptions = {}): Promise<SessionContext> {\n\t\treturn buildSessionContext(await this.getBranch(), this.mergeContextBuildOptions(options));\n\t}\n\n\tprivate mergeContextBuildOptions(options: SessionContextBuildOptions): SessionContextBuildOptions {\n\t\treturn {\n\t\t\tentryTransforms: [...(this.contextBuildOptions.entryTransforms ?? []), ...(options.entryTransforms ?? [])],\n\t\t\tentryProjectors: {\n\t\t\t\t...(this.contextBuildOptions.entryProjectors ?? {}),\n\t\t\t\t...(options.entryProjectors ?? {}),\n\t\t\t},\n\t\t};\n\t}\n\n\tgetLabel(id: string): Promise<string | undefined> {\n\t\treturn this.storage.getLabel(id);\n\t}\n\n\tgetSessionStats(): Promise<SessionStats> {\n\t\treturn this.storage.getSessionStats();\n\t}\n\n\tasync getSessionName(): Promise<string | undefined> {\n\t\treturn this.storage.getSessionName();\n\t}\n\n\tprivate async appendTypedEntry<TEntry extends SessionTreeEntry>(entry: TEntry): Promise<string> {\n\t\tawait this.storage.appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\tasync appendMessage(message: AgentMessage): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"message\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t} satisfies MessageEntry);\n\t}\n\n\tasync appendThinkingLevelChange(thinkingLevel: string): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"thinking_level_change\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t} satisfies ThinkingLevelChangeEntry);\n\t}\n\n\tasync appendModelChange(provider: string, modelId: string): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"model_change\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tprovider,\n\t\t\tmodelId,\n\t\t} satisfies ModelChangeEntry);\n\t}\n\n\tasync appendActiveToolsChange(activeToolNames: string[]): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"active_tools_change\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tactiveToolNames: [...activeToolNames],\n\t\t} satisfies ActiveToolsChangeEntry);\n\t}\n\n\tasync appendCompaction<T = unknown>(\n\t\tsummary: string,\n\t\tfirstKeptEntryId: string | undefined,\n\t\ttokensBefore: number,\n\t\tdetails?: T,\n\t\tfromHook?: boolean,\n\t\tusage?: Usage,\n\t\tretainedTail?: AgentMessage[],\n\t): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"compaction\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tsummary,\n\t\t\tfirstKeptEntryId,\n\t\t\ttokensBefore,\n\t\t\tretainedTail,\n\t\t\tdetails,\n\t\t\tusage,\n\t\t\tfromHook,\n\t\t} satisfies CompactionEntry<T>);\n\t}\n\n\tasync appendCustomEntry(customType: string, data?: unknown): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"custom\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcustomType,\n\t\t\tdata,\n\t\t} satisfies CustomEntry);\n\t}\n\n\tasync appendCustomMessageEntry<T = unknown>(\n\t\tcustomType: string,\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\tdisplay: boolean,\n\t\tdetails?: T,\n\t): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"custom_message\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcustomType,\n\t\t\tcontent,\n\t\t\tdisplay,\n\t\t\tdetails,\n\t\t} satisfies CustomMessageEntry<T>);\n\t}\n\n\tasync appendLabel(targetId: string, label: string | undefined): Promise<string> {\n\t\tif (!(await this.storage.getEntry(targetId))) {\n\t\t\tthrow new SessionError(\"not_found\", `Entry ${targetId} not found`);\n\t\t}\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"label\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttargetId,\n\t\t\tlabel,\n\t\t} satisfies LabelEntry);\n\t}\n\n\tasync appendSessionName(name: string): Promise<string> {\n\t\tconst sanitizedName = name.replace(/[\\r\\n]+/g, \" \").trim();\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"session_info\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tname: sanitizedName,\n\t\t} satisfies SessionInfoEntry);\n\t}\n\n\tasync moveTo(\n\t\tentryId: string | null,\n\t\tsummary?: { summary: string; details?: unknown; usage?: Usage; fromHook?: boolean },\n\t): Promise<string | undefined> {\n\t\tif (entryId !== null && !(await this.storage.getEntry(entryId))) {\n\t\t\tthrow new SessionError(\"not_found\", `Entry ${entryId} not found`);\n\t\t}\n\t\tawait this.storage.setLeafId(entryId);\n\t\tif (!summary) return undefined;\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"branch_summary\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: entryId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tfromId: entryId ?? \"root\",\n\t\t\tsummary: summary.summary,\n\t\t\tdetails: summary.details,\n\t\t\tusage: summary.usage,\n\t\t\tfromHook: summary.fromHook,\n\t\t} satisfies BranchSummaryEntry);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage } from "../messages.js";
|
|
2
|
+
import { SessionError } from "../types.js";
|
|
3
|
+
function deriveSessionContextState(pathEntries) {
|
|
4
|
+
let thinkingLevel = "off";
|
|
5
|
+
let model = null;
|
|
6
|
+
let activeToolNames = null;
|
|
7
|
+
for (const entry of pathEntries) {
|
|
8
|
+
if (entry.type === "thinking_level_change") {
|
|
9
|
+
thinkingLevel = entry.thinkingLevel;
|
|
10
|
+
}
|
|
11
|
+
else if (entry.type === "model_change") {
|
|
12
|
+
model = { provider: entry.provider, modelId: entry.modelId };
|
|
13
|
+
}
|
|
14
|
+
else if (entry.type === "message" && entry.message.role === "assistant") {
|
|
15
|
+
model = { provider: entry.message.provider, modelId: entry.message.model };
|
|
16
|
+
}
|
|
17
|
+
else if (entry.type === "active_tools_change") {
|
|
18
|
+
activeToolNames = [...entry.activeToolNames];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return { thinkingLevel, model, activeToolNames };
|
|
22
|
+
}
|
|
23
|
+
export function defaultContextEntryTransform(pathEntries) {
|
|
24
|
+
let compaction = null;
|
|
25
|
+
for (const entry of pathEntries) {
|
|
26
|
+
if (entry.type === "compaction") {
|
|
27
|
+
compaction = entry;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!compaction) {
|
|
31
|
+
return [...pathEntries];
|
|
32
|
+
}
|
|
33
|
+
const entries = [compaction];
|
|
34
|
+
const compactionIdx = pathEntries.findIndex((entry) => entry.type === "compaction" && entry.id === compaction.id);
|
|
35
|
+
if (compaction.retainedTail) {
|
|
36
|
+
for (let i = compactionIdx + 1; i < pathEntries.length; i++) {
|
|
37
|
+
entries.push(pathEntries[i]);
|
|
38
|
+
}
|
|
39
|
+
return entries;
|
|
40
|
+
}
|
|
41
|
+
if (compaction.firstKeptEntryId) {
|
|
42
|
+
let foundFirstKept = false;
|
|
43
|
+
for (let i = 0; i < compactionIdx; i++) {
|
|
44
|
+
const entry = pathEntries[i];
|
|
45
|
+
if (entry.id === compaction.firstKeptEntryId)
|
|
46
|
+
foundFirstKept = true;
|
|
47
|
+
if (foundFirstKept)
|
|
48
|
+
entries.push(entry);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (let i = compactionIdx + 1; i < pathEntries.length; i++) {
|
|
52
|
+
entries.push(pathEntries[i]);
|
|
53
|
+
}
|
|
54
|
+
return entries;
|
|
55
|
+
}
|
|
56
|
+
export function buildContextEntries(pathEntries, options = {}) {
|
|
57
|
+
let entries = defaultContextEntryTransform(pathEntries);
|
|
58
|
+
for (const transform of options.entryTransforms ?? []) {
|
|
59
|
+
entries = [...transform(entries)];
|
|
60
|
+
}
|
|
61
|
+
return entries;
|
|
62
|
+
}
|
|
63
|
+
export function sessionEntryToContextMessages(entry, index, entries, options = {}) {
|
|
64
|
+
if (entry.type === "message") {
|
|
65
|
+
return [entry.message];
|
|
66
|
+
}
|
|
67
|
+
if (entry.type === "custom_message") {
|
|
68
|
+
return [
|
|
69
|
+
createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp),
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
if (entry.type === "compaction") {
|
|
73
|
+
return [
|
|
74
|
+
createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp),
|
|
75
|
+
...(entry.retainedTail ?? []),
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
if (entry.type === "branch_summary" && entry.summary) {
|
|
79
|
+
return [createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp)];
|
|
80
|
+
}
|
|
81
|
+
if (entry.type === "custom") {
|
|
82
|
+
return [...(options.entryProjectors?.[entry.customType]?.(entry, index, entries) ?? [])];
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
export function buildSessionContext(pathEntries, options = {}) {
|
|
87
|
+
const state = deriveSessionContextState(pathEntries);
|
|
88
|
+
const contextEntries = buildContextEntries(pathEntries, options);
|
|
89
|
+
const messages = contextEntries.flatMap((entry, index) => sessionEntryToContextMessages(entry, index, contextEntries, options));
|
|
90
|
+
return { ...state, messages };
|
|
91
|
+
}
|
|
92
|
+
export class Session {
|
|
93
|
+
storage;
|
|
94
|
+
contextBuildOptions;
|
|
95
|
+
constructor(storage, contextBuildOptions = {}) {
|
|
96
|
+
this.storage = storage;
|
|
97
|
+
this.contextBuildOptions = contextBuildOptions;
|
|
98
|
+
}
|
|
99
|
+
getMetadata() {
|
|
100
|
+
return this.storage.getMetadata();
|
|
101
|
+
}
|
|
102
|
+
getStorage() {
|
|
103
|
+
return this.storage;
|
|
104
|
+
}
|
|
105
|
+
getLeafId() {
|
|
106
|
+
return this.storage.getLeafId();
|
|
107
|
+
}
|
|
108
|
+
getEntry(id) {
|
|
109
|
+
return this.storage.getEntry(id);
|
|
110
|
+
}
|
|
111
|
+
getEntries(options) {
|
|
112
|
+
return this.storage.getEntries(options);
|
|
113
|
+
}
|
|
114
|
+
async getBranch(fromId) {
|
|
115
|
+
const leafId = fromId ?? (await this.storage.getLeafId());
|
|
116
|
+
return this.storage.getPathToRootOrCompaction(leafId);
|
|
117
|
+
}
|
|
118
|
+
async buildContextEntries(options = {}) {
|
|
119
|
+
return buildContextEntries(await this.getBranch(), this.mergeContextBuildOptions(options));
|
|
120
|
+
}
|
|
121
|
+
async buildContext(options = {}) {
|
|
122
|
+
return buildSessionContext(await this.getBranch(), this.mergeContextBuildOptions(options));
|
|
123
|
+
}
|
|
124
|
+
mergeContextBuildOptions(options) {
|
|
125
|
+
return {
|
|
126
|
+
entryTransforms: [...(this.contextBuildOptions.entryTransforms ?? []), ...(options.entryTransforms ?? [])],
|
|
127
|
+
entryProjectors: {
|
|
128
|
+
...(this.contextBuildOptions.entryProjectors ?? {}),
|
|
129
|
+
...(options.entryProjectors ?? {}),
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
getLabel(id) {
|
|
134
|
+
return this.storage.getLabel(id);
|
|
135
|
+
}
|
|
136
|
+
getSessionStats() {
|
|
137
|
+
return this.storage.getSessionStats();
|
|
138
|
+
}
|
|
139
|
+
async getSessionName() {
|
|
140
|
+
return this.storage.getSessionName();
|
|
141
|
+
}
|
|
142
|
+
async appendTypedEntry(entry) {
|
|
143
|
+
await this.storage.appendEntry(entry);
|
|
144
|
+
return entry.id;
|
|
145
|
+
}
|
|
146
|
+
async appendMessage(message) {
|
|
147
|
+
return this.appendTypedEntry({
|
|
148
|
+
type: "message",
|
|
149
|
+
id: await this.storage.createEntryId(),
|
|
150
|
+
parentId: await this.storage.getLeafId(),
|
|
151
|
+
timestamp: new Date().toISOString(),
|
|
152
|
+
message,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async appendThinkingLevelChange(thinkingLevel) {
|
|
156
|
+
return this.appendTypedEntry({
|
|
157
|
+
type: "thinking_level_change",
|
|
158
|
+
id: await this.storage.createEntryId(),
|
|
159
|
+
parentId: await this.storage.getLeafId(),
|
|
160
|
+
timestamp: new Date().toISOString(),
|
|
161
|
+
thinkingLevel,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async appendModelChange(provider, modelId) {
|
|
165
|
+
return this.appendTypedEntry({
|
|
166
|
+
type: "model_change",
|
|
167
|
+
id: await this.storage.createEntryId(),
|
|
168
|
+
parentId: await this.storage.getLeafId(),
|
|
169
|
+
timestamp: new Date().toISOString(),
|
|
170
|
+
provider,
|
|
171
|
+
modelId,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
async appendActiveToolsChange(activeToolNames) {
|
|
175
|
+
return this.appendTypedEntry({
|
|
176
|
+
type: "active_tools_change",
|
|
177
|
+
id: await this.storage.createEntryId(),
|
|
178
|
+
parentId: await this.storage.getLeafId(),
|
|
179
|
+
timestamp: new Date().toISOString(),
|
|
180
|
+
activeToolNames: [...activeToolNames],
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
async appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromHook, usage, retainedTail) {
|
|
184
|
+
return this.appendTypedEntry({
|
|
185
|
+
type: "compaction",
|
|
186
|
+
id: await this.storage.createEntryId(),
|
|
187
|
+
parentId: await this.storage.getLeafId(),
|
|
188
|
+
timestamp: new Date().toISOString(),
|
|
189
|
+
summary,
|
|
190
|
+
firstKeptEntryId,
|
|
191
|
+
tokensBefore,
|
|
192
|
+
retainedTail,
|
|
193
|
+
details,
|
|
194
|
+
usage,
|
|
195
|
+
fromHook,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
async appendCustomEntry(customType, data) {
|
|
199
|
+
return this.appendTypedEntry({
|
|
200
|
+
type: "custom",
|
|
201
|
+
id: await this.storage.createEntryId(),
|
|
202
|
+
parentId: await this.storage.getLeafId(),
|
|
203
|
+
timestamp: new Date().toISOString(),
|
|
204
|
+
customType,
|
|
205
|
+
data,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async appendCustomMessageEntry(customType, content, display, details) {
|
|
209
|
+
return this.appendTypedEntry({
|
|
210
|
+
type: "custom_message",
|
|
211
|
+
id: await this.storage.createEntryId(),
|
|
212
|
+
parentId: await this.storage.getLeafId(),
|
|
213
|
+
timestamp: new Date().toISOString(),
|
|
214
|
+
customType,
|
|
215
|
+
content,
|
|
216
|
+
display,
|
|
217
|
+
details,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async appendLabel(targetId, label) {
|
|
221
|
+
if (!(await this.storage.getEntry(targetId))) {
|
|
222
|
+
throw new SessionError("not_found", `Entry ${targetId} not found`);
|
|
223
|
+
}
|
|
224
|
+
return this.appendTypedEntry({
|
|
225
|
+
type: "label",
|
|
226
|
+
id: await this.storage.createEntryId(),
|
|
227
|
+
parentId: await this.storage.getLeafId(),
|
|
228
|
+
timestamp: new Date().toISOString(),
|
|
229
|
+
targetId,
|
|
230
|
+
label,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
async appendSessionName(name) {
|
|
234
|
+
const sanitizedName = name.replace(/[\r\n]+/g, " ").trim();
|
|
235
|
+
return this.appendTypedEntry({
|
|
236
|
+
type: "session_info",
|
|
237
|
+
id: await this.storage.createEntryId(),
|
|
238
|
+
parentId: await this.storage.getLeafId(),
|
|
239
|
+
timestamp: new Date().toISOString(),
|
|
240
|
+
name: sanitizedName,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async moveTo(entryId, summary) {
|
|
244
|
+
if (entryId !== null && !(await this.storage.getEntry(entryId))) {
|
|
245
|
+
throw new SessionError("not_found", `Entry ${entryId} not found`);
|
|
246
|
+
}
|
|
247
|
+
await this.storage.setLeafId(entryId);
|
|
248
|
+
if (!summary)
|
|
249
|
+
return undefined;
|
|
250
|
+
return this.appendTypedEntry({
|
|
251
|
+
type: "branch_summary",
|
|
252
|
+
id: await this.storage.createEntryId(),
|
|
253
|
+
parentId: entryId,
|
|
254
|
+
timestamp: new Date().toISOString(),
|
|
255
|
+
fromId: entryId ?? "root",
|
|
256
|
+
summary: summary.summary,
|
|
257
|
+
details: summary.details,
|
|
258
|
+
usage: summary.usage,
|
|
259
|
+
fromHook: summary.fromHook,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=session.js.map
|