@aexol/spectral 0.7.1 → 0.7.5
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/CHANGELOG.md +5 -0
- package/dist/agent/agents.js +1 -1
- package/dist/agent/index.js +199 -184
- package/dist/commands/serve.js +0 -3
- package/dist/designer/data/systems/renault/DESIGN.md +1 -1
- package/dist/designer/philosophies.js +668 -0
- package/dist/mcp/sampling-handler.js +1 -1
- package/dist/memory/commands/status.js +1 -1
- package/dist/memory/compaction.js +2 -2
- package/dist/memory/config.js +1 -1
- package/dist/memory/debug-log.js +1 -1
- package/dist/memory/hooks/compaction-hook.js +29 -0
- package/dist/memory/index.js +2 -0
- package/dist/memory/observer.js +2 -2
- package/dist/memory/project-observations-store.js +14 -0
- package/dist/memory/tokens.js +1 -1
- package/dist/memory/tools/read-project-observations.js +82 -0
- package/dist/memory/tools/recall-observation.js +2 -2
- package/dist/pi/agent-core/agent-loop.js +501 -0
- package/dist/pi/agent-core/agent.js +401 -0
- package/dist/pi/agent-core/harness/agent-harness.js +899 -0
- package/dist/pi/agent-core/harness/compaction/branch-summarization.js +173 -0
- package/dist/pi/agent-core/harness/compaction/compaction.js +532 -0
- package/dist/pi/agent-core/harness/compaction/utils.js +130 -0
- package/dist/pi/agent-core/harness/env/nodejs.js +485 -0
- package/dist/pi/agent-core/harness/messages.js +101 -0
- package/dist/pi/agent-core/harness/prompt-templates.js +229 -0
- package/dist/pi/agent-core/harness/session/jsonl-repo.js +100 -0
- package/dist/pi/agent-core/harness/session/jsonl-storage.js +230 -0
- package/dist/pi/agent-core/harness/session/memory-repo.js +41 -0
- package/dist/pi/agent-core/harness/session/memory-storage.js +113 -0
- package/dist/pi/agent-core/harness/session/repo-utils.js +38 -0
- package/dist/pi/agent-core/harness/session/session.js +196 -0
- package/dist/pi/agent-core/harness/session/uuid.js +49 -0
- package/dist/pi/agent-core/harness/skills.js +310 -0
- package/dist/pi/agent-core/harness/system-prompt.js +29 -0
- package/dist/pi/agent-core/harness/types.js +93 -0
- package/dist/pi/agent-core/harness/utils/shell-output.js +125 -0
- package/dist/pi/agent-core/harness/utils/truncate.js +289 -0
- package/dist/pi/agent-core/index.js +24 -0
- package/dist/pi/agent-core/node.js +2 -0
- package/dist/pi/agent-core/proxy.js +277 -0
- package/dist/pi/agent-core/types.js +1 -0
- package/dist/pi/ai/api-registry.js +43 -0
- package/dist/pi/ai/cli.js +120 -0
- package/dist/pi/ai/env-api-keys.js +169 -0
- package/dist/pi/ai/image-models.generated.js +441 -0
- package/dist/pi/ai/image-models.js +22 -0
- package/dist/pi/ai/images-api-registry.js +21 -0
- package/dist/pi/ai/images.js +13 -0
- package/dist/pi/ai/index.js +18 -0
- package/dist/pi/ai/models.generated.js +16220 -0
- package/dist/pi/ai/models.js +70 -0
- package/dist/pi/ai/oauth.js +1 -0
- package/dist/pi/ai/providers/anthropic.js +945 -0
- package/dist/pi/ai/providers/faux.js +367 -0
- package/dist/pi/ai/providers/github-copilot-headers.js +28 -0
- package/dist/pi/ai/providers/openai-completions.js +945 -0
- package/dist/pi/ai/providers/openai-prompt-cache.js +9 -0
- package/dist/pi/ai/providers/register-builtins.js +97 -0
- package/dist/pi/ai/providers/simple-options.js +40 -0
- package/dist/pi/ai/providers/transform-messages.js +183 -0
- package/dist/pi/ai/session-resources.js +21 -0
- package/dist/pi/ai/stream.js +26 -0
- package/dist/pi/ai/types.js +1 -0
- package/dist/pi/ai/utils/diagnostics.js +24 -0
- package/dist/pi/ai/utils/event-stream.js +80 -0
- package/dist/pi/ai/utils/hash.js +13 -0
- package/dist/pi/ai/utils/headers.js +7 -0
- package/dist/pi/ai/utils/json-parse.js +112 -0
- package/dist/pi/ai/utils/node-http-proxy.js +96 -0
- package/dist/pi/ai/utils/oauth/anthropic.js +334 -0
- package/dist/pi/ai/utils/oauth/device-code.js +54 -0
- package/dist/pi/ai/utils/oauth/github-copilot.js +270 -0
- package/dist/pi/ai/utils/oauth/index.js +121 -0
- package/dist/pi/ai/utils/oauth/oauth-page.js +104 -0
- package/dist/pi/ai/utils/oauth/openai-codex.js +384 -0
- package/dist/pi/ai/utils/oauth/pkce.js +30 -0
- package/dist/pi/ai/utils/oauth/types.js +1 -0
- package/dist/pi/ai/utils/overflow.js +150 -0
- package/dist/pi/ai/utils/sanitize-unicode.js +25 -0
- package/dist/pi/ai/utils/typebox-helpers.js +20 -0
- package/dist/pi/ai/utils/validation.js +280 -0
- package/dist/pi/coding-agent/bun/cli.js +7 -0
- package/dist/pi/coding-agent/bun/restore-sandbox-env.js +31 -0
- package/dist/pi/coding-agent/cli/args.js +340 -0
- package/dist/pi/coding-agent/cli/file-processor.js +82 -0
- package/dist/pi/coding-agent/cli/initial-message.js +21 -0
- package/dist/pi/coding-agent/cli.js +17 -0
- package/dist/pi/coding-agent/config.js +414 -0
- package/dist/pi/coding-agent/core/agent-session-runtime.js +299 -0
- package/dist/pi/coding-agent/core/agent-session-services.js +117 -0
- package/dist/pi/coding-agent/core/agent-session.js +2498 -0
- package/dist/pi/coding-agent/core/auth-guidance.js +20 -0
- package/dist/pi/coding-agent/core/auth-storage.js +441 -0
- package/dist/pi/coding-agent/core/bash-executor.js +110 -0
- package/dist/pi/coding-agent/core/compaction/branch-summarization.js +242 -0
- package/dist/pi/coding-agent/core/compaction/compaction.js +624 -0
- package/dist/pi/coding-agent/core/compaction/index.js +6 -0
- package/dist/pi/coding-agent/core/compaction/utils.js +152 -0
- package/dist/pi/coding-agent/core/defaults.js +1 -0
- package/dist/pi/coding-agent/core/diagnostics.js +1 -0
- package/dist/pi/coding-agent/core/event-bus.js +24 -0
- package/dist/pi/coding-agent/core/exec.js +74 -0
- package/dist/pi/coding-agent/core/export-html/ansi-to-html.js +248 -0
- package/dist/pi/coding-agent/core/export-html/index.js +225 -0
- package/dist/pi/coding-agent/core/export-html/tool-renderer.js +107 -0
- package/dist/pi/coding-agent/core/extensions/index.js +8 -0
- package/dist/pi/coding-agent/core/extensions/loader.js +485 -0
- package/dist/pi/coding-agent/core/extensions/runner.js +824 -0
- package/dist/pi/coding-agent/core/extensions/types.js +44 -0
- package/dist/pi/coding-agent/core/extensions/wrapper.js +21 -0
- package/dist/pi/coding-agent/core/footer-data-provider.js +309 -0
- package/dist/pi/coding-agent/core/http-dispatcher.js +47 -0
- package/dist/pi/coding-agent/core/index.js +11 -0
- package/dist/pi/coding-agent/core/keybindings.js +294 -0
- package/dist/pi/coding-agent/core/messages.js +122 -0
- package/dist/pi/coding-agent/core/model-registry.js +728 -0
- package/dist/pi/coding-agent/core/model-resolver.js +494 -0
- package/dist/pi/coding-agent/core/output-guard.js +58 -0
- package/dist/pi/coding-agent/core/package-manager.js +2020 -0
- package/dist/pi/coding-agent/core/prompt-templates.js +237 -0
- package/dist/pi/coding-agent/core/provider-display-names.js +32 -0
- package/dist/pi/coding-agent/core/resolve-config-value.js +125 -0
- package/dist/pi/coding-agent/core/resource-loader.js +733 -0
- package/dist/pi/coding-agent/core/sdk.js +282 -0
- package/dist/pi/coding-agent/core/session-cwd.js +37 -0
- package/dist/pi/coding-agent/core/session-manager.js +1146 -0
- package/dist/pi/coding-agent/core/settings-manager.js +794 -0
- package/dist/pi/coding-agent/core/skills.js +386 -0
- package/dist/pi/coding-agent/core/slash-commands.js +24 -0
- package/dist/pi/coding-agent/core/source-info.js +18 -0
- package/dist/pi/coding-agent/core/system-prompt.js +122 -0
- package/dist/pi/coding-agent/core/telemetry.js +8 -0
- package/dist/pi/coding-agent/core/timings.js +30 -0
- package/dist/pi/coding-agent/core/tools/bash.js +341 -0
- package/dist/pi/coding-agent/core/tools/edit-diff.js +344 -0
- package/dist/pi/coding-agent/core/tools/edit.js +324 -0
- package/dist/pi/coding-agent/core/tools/file-mutation-queue.js +36 -0
- package/dist/pi/coding-agent/core/tools/find.js +297 -0
- package/dist/pi/coding-agent/core/tools/grep.js +303 -0
- package/dist/pi/coding-agent/core/tools/index.js +111 -0
- package/dist/pi/coding-agent/core/tools/ls.js +168 -0
- package/dist/pi/coding-agent/core/tools/output-accumulator.js +183 -0
- package/dist/pi/coding-agent/core/tools/path-utils.js +61 -0
- package/dist/pi/coding-agent/core/tools/read.js +288 -0
- package/dist/pi/coding-agent/core/tools/render-utils.js +48 -0
- package/dist/pi/coding-agent/core/tools/tool-definition-wrapper.js +33 -0
- package/dist/pi/coding-agent/core/tools/truncate.js +214 -0
- package/dist/pi/coding-agent/core/tools/write.js +212 -0
- package/dist/pi/coding-agent/index.js +41 -0
- package/dist/pi/coding-agent/main.js +5 -0
- package/dist/pi/coding-agent/migrations.js +280 -0
- package/dist/pi/coding-agent/modes/index.js +7 -0
- package/dist/pi/coding-agent/modes/interactive/components/diff.js +132 -0
- package/dist/pi/coding-agent/modes/interactive/components/keybinding-hints.js +35 -0
- package/dist/pi/coding-agent/modes/interactive/components/visual-truncate.js +32 -0
- package/dist/pi/coding-agent/modes/interactive/interactive-mode.js +3 -0
- package/dist/pi/coding-agent/modes/interactive/theme/theme.js +1023 -0
- package/dist/pi/coding-agent/modes/print-mode.js +130 -0
- package/dist/pi/coding-agent/modes/rpc/jsonl.js +48 -0
- package/dist/pi/coding-agent/modes/rpc/rpc-client.js +409 -0
- package/dist/pi/coding-agent/modes/rpc/rpc-mode.js +600 -0
- package/dist/pi/coding-agent/modes/rpc/rpc-types.js +7 -0
- package/dist/pi/coding-agent/utils/ansi.js +51 -0
- package/dist/pi/coding-agent/utils/changelog.js +86 -0
- package/dist/pi/coding-agent/utils/child-process.js +87 -0
- package/dist/pi/coding-agent/utils/clipboard-image.js +244 -0
- package/dist/pi/coding-agent/utils/clipboard-native.js +13 -0
- package/dist/pi/coding-agent/utils/clipboard.js +116 -0
- package/dist/pi/coding-agent/utils/exif-orientation.js +157 -0
- package/dist/pi/coding-agent/utils/frontmatter.js +25 -0
- package/dist/pi/coding-agent/utils/fs-watch.js +24 -0
- package/dist/pi/coding-agent/utils/git.js +162 -0
- package/dist/pi/coding-agent/utils/html.js +39 -0
- package/dist/pi/coding-agent/utils/image-convert.js +38 -0
- package/dist/pi/coding-agent/utils/image-resize.js +136 -0
- package/dist/pi/coding-agent/utils/mime.js +68 -0
- package/dist/pi/coding-agent/utils/paths.js +91 -0
- package/dist/pi/coding-agent/utils/photon.js +120 -0
- package/dist/pi/coding-agent/utils/pi-user-agent.js +4 -0
- package/dist/pi/coding-agent/utils/shell.js +194 -0
- package/dist/pi/coding-agent/utils/sleep.js +16 -0
- package/dist/pi/coding-agent/utils/syntax-highlight.js +117 -0
- package/dist/pi/coding-agent/utils/tools-manager.js +327 -0
- package/dist/pi/coding-agent/utils/version-check.js +81 -0
- package/dist/pi/coding-agent/utils/windows-self-update.js +76 -0
- package/dist/pi/tui/autocomplete.js +631 -0
- package/dist/pi/tui/components/box.js +103 -0
- package/dist/pi/tui/components/cancellable-loader.js +34 -0
- package/dist/pi/tui/components/editor.js +1915 -0
- package/dist/pi/tui/components/image.js +88 -0
- package/dist/pi/tui/components/input.js +425 -0
- package/dist/pi/tui/components/loader.js +68 -0
- package/dist/pi/tui/components/markdown.js +633 -0
- package/dist/pi/tui/components/select-list.js +158 -0
- package/dist/pi/tui/components/settings-list.js +184 -0
- package/dist/pi/tui/components/spacer.js +22 -0
- package/dist/pi/tui/components/text.js +88 -0
- package/dist/pi/tui/components/truncated-text.js +50 -0
- package/dist/pi/tui/editor-component.js +1 -0
- package/dist/pi/tui/fuzzy.js +109 -0
- package/dist/pi/tui/index.js +31 -0
- package/dist/pi/tui/keybindings.js +173 -0
- package/dist/pi/tui/keys.js +1172 -0
- package/dist/pi/tui/kill-ring.js +43 -0
- package/dist/pi/tui/stdin-buffer.js +360 -0
- package/dist/pi/tui/terminal-image.js +335 -0
- package/dist/pi/tui/terminal.js +324 -0
- package/dist/pi/tui/tui.js +1076 -0
- package/dist/pi/tui/undo-stack.js +24 -0
- package/dist/pi/tui/utils.js +1016 -0
- package/dist/relay/dispatcher.js +30 -0
- package/dist/server/handlers/queue.js +52 -0
- package/dist/server/pi-bridge.js +9 -1
- package/dist/server/session-stream.js +76 -111
- package/dist/server/storage.js +154 -2
- package/dist/server/title-generator.js +14 -153
- package/package.json +24 -6
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { parse } from "yaml";
|
|
2
|
+
import { toError } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Load prompt templates from one or more paths.
|
|
5
|
+
*
|
|
6
|
+
* Directory inputs load direct `.md` children non-recursively. File inputs load explicit `.md` files. Missing paths and
|
|
7
|
+
* non-markdown files are skipped. Read and parse failures are returned as diagnostics.
|
|
8
|
+
*/
|
|
9
|
+
export async function loadPromptTemplates(env, paths) {
|
|
10
|
+
const promptTemplates = [];
|
|
11
|
+
const diagnostics = [];
|
|
12
|
+
for (const path of Array.isArray(paths) ? paths : [paths]) {
|
|
13
|
+
const infoResult = await env.fileInfo(path);
|
|
14
|
+
if (!infoResult.ok) {
|
|
15
|
+
if (infoResult.error.code !== "not_found") {
|
|
16
|
+
diagnostics.push({
|
|
17
|
+
type: "warning",
|
|
18
|
+
code: "file_info_failed",
|
|
19
|
+
message: infoResult.error.message,
|
|
20
|
+
path,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const info = infoResult.value;
|
|
26
|
+
const kind = await resolveKind(env, info, diagnostics);
|
|
27
|
+
if (kind === "directory") {
|
|
28
|
+
const result = await loadTemplatesFromDir(env, info.path);
|
|
29
|
+
promptTemplates.push(...result.promptTemplates);
|
|
30
|
+
diagnostics.push(...result.diagnostics);
|
|
31
|
+
}
|
|
32
|
+
else if (kind === "file" && info.name.endsWith(".md")) {
|
|
33
|
+
const result = await loadTemplateFromFile(env, info.path);
|
|
34
|
+
if (result.promptTemplate)
|
|
35
|
+
promptTemplates.push(result.promptTemplate);
|
|
36
|
+
diagnostics.push(...result.diagnostics);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { promptTemplates, diagnostics };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Load prompt templates from source-tagged paths.
|
|
43
|
+
*
|
|
44
|
+
* Source values are preserved exactly and attached to every loaded prompt template and diagnostic. The agent package does
|
|
45
|
+
* not interpret source values; applications define their own provenance shape.
|
|
46
|
+
*/
|
|
47
|
+
export async function loadSourcedPromptTemplates(env, inputs, mapPromptTemplate) {
|
|
48
|
+
const promptTemplates = [];
|
|
49
|
+
const diagnostics = [];
|
|
50
|
+
for (const input of inputs) {
|
|
51
|
+
const result = await loadPromptTemplates(env, input.path);
|
|
52
|
+
for (const promptTemplate of result.promptTemplates) {
|
|
53
|
+
promptTemplates.push({
|
|
54
|
+
promptTemplate: mapPromptTemplate
|
|
55
|
+
? mapPromptTemplate(promptTemplate, input.source)
|
|
56
|
+
: promptTemplate,
|
|
57
|
+
source: input.source,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
for (const diagnostic of result.diagnostics)
|
|
61
|
+
diagnostics.push({ ...diagnostic, source: input.source });
|
|
62
|
+
}
|
|
63
|
+
return { promptTemplates, diagnostics };
|
|
64
|
+
}
|
|
65
|
+
async function loadTemplatesFromDir(env, dir) {
|
|
66
|
+
const promptTemplates = [];
|
|
67
|
+
const diagnostics = [];
|
|
68
|
+
const entriesResult = await env.listDir(dir);
|
|
69
|
+
if (!entriesResult.ok) {
|
|
70
|
+
diagnostics.push({
|
|
71
|
+
type: "warning",
|
|
72
|
+
code: "list_failed",
|
|
73
|
+
message: entriesResult.error.message,
|
|
74
|
+
path: dir,
|
|
75
|
+
});
|
|
76
|
+
return { promptTemplates, diagnostics };
|
|
77
|
+
}
|
|
78
|
+
const entries = entriesResult.value;
|
|
79
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
80
|
+
const kind = await resolveKind(env, entry, diagnostics);
|
|
81
|
+
if (kind !== "file" || !entry.name.endsWith(".md"))
|
|
82
|
+
continue;
|
|
83
|
+
const result = await loadTemplateFromFile(env, entry.path);
|
|
84
|
+
if (result.promptTemplate)
|
|
85
|
+
promptTemplates.push(result.promptTemplate);
|
|
86
|
+
diagnostics.push(...result.diagnostics);
|
|
87
|
+
}
|
|
88
|
+
return { promptTemplates, diagnostics };
|
|
89
|
+
}
|
|
90
|
+
async function loadTemplateFromFile(env, filePath) {
|
|
91
|
+
const diagnostics = [];
|
|
92
|
+
const rawContent = await env.readTextFile(filePath);
|
|
93
|
+
if (!rawContent.ok) {
|
|
94
|
+
diagnostics.push({
|
|
95
|
+
type: "warning",
|
|
96
|
+
code: "read_failed",
|
|
97
|
+
message: rawContent.error.message,
|
|
98
|
+
path: filePath,
|
|
99
|
+
});
|
|
100
|
+
return { promptTemplate: null, diagnostics };
|
|
101
|
+
}
|
|
102
|
+
const parsed = parseFrontmatter(rawContent.value);
|
|
103
|
+
if (!parsed.ok) {
|
|
104
|
+
diagnostics.push({
|
|
105
|
+
type: "warning",
|
|
106
|
+
code: "parse_failed",
|
|
107
|
+
message: parsed.error.message,
|
|
108
|
+
path: filePath,
|
|
109
|
+
});
|
|
110
|
+
return { promptTemplate: null, diagnostics };
|
|
111
|
+
}
|
|
112
|
+
const { frontmatter, body } = parsed.value;
|
|
113
|
+
const firstLine = body.split("\n").find((line) => line.trim());
|
|
114
|
+
let description = typeof frontmatter.description === "string" ? frontmatter.description : "";
|
|
115
|
+
if (!description && firstLine) {
|
|
116
|
+
description = firstLine.slice(0, 60);
|
|
117
|
+
if (firstLine.length > 60)
|
|
118
|
+
description += "...";
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
promptTemplate: {
|
|
122
|
+
name: basenameEnvPath(filePath).replace(/\.md$/i, ""),
|
|
123
|
+
description,
|
|
124
|
+
content: body,
|
|
125
|
+
},
|
|
126
|
+
diagnostics,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async function resolveKind(env, info, diagnostics) {
|
|
130
|
+
if (info.kind === "file" || info.kind === "directory")
|
|
131
|
+
return info.kind;
|
|
132
|
+
const canonicalPath = await env.canonicalPath(info.path);
|
|
133
|
+
if (!canonicalPath.ok) {
|
|
134
|
+
if (canonicalPath.error.code !== "not_found") {
|
|
135
|
+
diagnostics.push({
|
|
136
|
+
type: "warning",
|
|
137
|
+
code: "file_info_failed",
|
|
138
|
+
message: canonicalPath.error.message,
|
|
139
|
+
path: info.path,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
const target = await env.fileInfo(canonicalPath.value);
|
|
145
|
+
if (!target.ok) {
|
|
146
|
+
if (target.error.code !== "not_found") {
|
|
147
|
+
diagnostics.push({
|
|
148
|
+
type: "warning",
|
|
149
|
+
code: "file_info_failed",
|
|
150
|
+
message: target.error.message,
|
|
151
|
+
path: info.path,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
return target.value.kind === "file" || target.value.kind === "directory" ? target.value.kind : undefined;
|
|
157
|
+
}
|
|
158
|
+
function parseFrontmatter(content) {
|
|
159
|
+
try {
|
|
160
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
161
|
+
if (!normalized.startsWith("---"))
|
|
162
|
+
return { ok: true, value: { frontmatter: {}, body: normalized } };
|
|
163
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
164
|
+
if (endIndex === -1)
|
|
165
|
+
return { ok: true, value: { frontmatter: {}, body: normalized } };
|
|
166
|
+
const yamlString = normalized.slice(4, endIndex);
|
|
167
|
+
const body = normalized.slice(endIndex + 4).trim();
|
|
168
|
+
return { ok: true, value: { frontmatter: (parse(yamlString) ?? {}), body } };
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
return { ok: false, error: toError(error) };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function basenameEnvPath(path) {
|
|
175
|
+
const normalized = path.replace(/\/+$/, "");
|
|
176
|
+
const slashIndex = normalized.lastIndexOf("/");
|
|
177
|
+
return slashIndex === -1 ? normalized : normalized.slice(slashIndex + 1);
|
|
178
|
+
}
|
|
179
|
+
/** Parse an argument string using simple shell-style single and double quotes. */
|
|
180
|
+
export function parseCommandArgs(argsString) {
|
|
181
|
+
const args = [];
|
|
182
|
+
let current = "";
|
|
183
|
+
let inQuote = null;
|
|
184
|
+
for (let i = 0; i < argsString.length; i++) {
|
|
185
|
+
const char = argsString[i];
|
|
186
|
+
if (inQuote) {
|
|
187
|
+
if (char === inQuote)
|
|
188
|
+
inQuote = null;
|
|
189
|
+
else
|
|
190
|
+
current += char;
|
|
191
|
+
}
|
|
192
|
+
else if (char === '"' || char === "'") {
|
|
193
|
+
inQuote = char;
|
|
194
|
+
}
|
|
195
|
+
else if (char === " " || char === "\t") {
|
|
196
|
+
if (current) {
|
|
197
|
+
args.push(current);
|
|
198
|
+
current = "";
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
current += char;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (current)
|
|
206
|
+
args.push(current);
|
|
207
|
+
return args;
|
|
208
|
+
}
|
|
209
|
+
/** Substitute prompt template placeholders (`$1`, `$@`, `$ARGUMENTS`, `${@:N}`, `${@:N:L}`) with command arguments. */
|
|
210
|
+
export function substituteArgs(content, args) {
|
|
211
|
+
let result = content;
|
|
212
|
+
result = result.replace(/\$(\d+)/g, (_, num) => args[parseInt(num, 10) - 1] ?? "");
|
|
213
|
+
result = result.replace(/\$\{@:(\d+)(?::(\d+))?\}/g, (_, startStr, lengthStr) => {
|
|
214
|
+
let start = parseInt(startStr, 10) - 1;
|
|
215
|
+
if (start < 0)
|
|
216
|
+
start = 0;
|
|
217
|
+
if (lengthStr)
|
|
218
|
+
return args.slice(start, start + parseInt(lengthStr, 10)).join(" ");
|
|
219
|
+
return args.slice(start).join(" ");
|
|
220
|
+
});
|
|
221
|
+
const allArgs = args.join(" ");
|
|
222
|
+
result = result.replace(/\$ARGUMENTS/g, allArgs);
|
|
223
|
+
result = result.replace(/\$@/g, allArgs);
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
/** Format a prompt template invocation with positional arguments. */
|
|
227
|
+
export function formatPromptTemplateInvocation(template, args = []) {
|
|
228
|
+
return substituteArgs(template.content, args);
|
|
229
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { SessionError, toError } from "../types.js";
|
|
2
|
+
import { JsonlSessionStorage, loadJsonlSessionMetadata } from "./jsonl-storage.js";
|
|
3
|
+
import { createSessionId, createTimestamp, getEntriesToFork, getFileSystemResultOrThrow, toSession, } from "./repo-utils.js";
|
|
4
|
+
function encodeCwd(cwd) {
|
|
5
|
+
return `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
6
|
+
}
|
|
7
|
+
export class JsonlSessionRepo {
|
|
8
|
+
fs;
|
|
9
|
+
sessionsRootInput;
|
|
10
|
+
sessionsRoot;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.fs = options.fs;
|
|
13
|
+
this.sessionsRootInput = options.sessionsRoot;
|
|
14
|
+
}
|
|
15
|
+
async getSessionsRoot() {
|
|
16
|
+
if (!this.sessionsRoot) {
|
|
17
|
+
this.sessionsRoot = getFileSystemResultOrThrow(await this.fs.absolutePath(this.sessionsRootInput), `Failed to resolve sessions root ${this.sessionsRootInput}`);
|
|
18
|
+
}
|
|
19
|
+
return this.sessionsRoot;
|
|
20
|
+
}
|
|
21
|
+
async getSessionDir(cwd) {
|
|
22
|
+
return getFileSystemResultOrThrow(await this.fs.joinPath([await this.getSessionsRoot(), encodeCwd(cwd)]), `Failed to resolve session directory for ${cwd}`);
|
|
23
|
+
}
|
|
24
|
+
async createSessionFilePath(cwd, sessionId, timestamp) {
|
|
25
|
+
return getFileSystemResultOrThrow(await this.fs.joinPath([
|
|
26
|
+
await this.getSessionDir(cwd),
|
|
27
|
+
`${timestamp.replace(/[:.]/g, "-")}_${sessionId}.jsonl`,
|
|
28
|
+
]), `Failed to resolve session file path for ${sessionId}`);
|
|
29
|
+
}
|
|
30
|
+
async create(options) {
|
|
31
|
+
const id = options.id ?? createSessionId();
|
|
32
|
+
const createdAt = createTimestamp();
|
|
33
|
+
const sessionDir = await this.getSessionDir(options.cwd);
|
|
34
|
+
getFileSystemResultOrThrow(await this.fs.createDir(sessionDir, { recursive: true }), `Failed to create session directory ${sessionDir}`);
|
|
35
|
+
const filePath = await this.createSessionFilePath(options.cwd, id, createdAt);
|
|
36
|
+
const storage = await JsonlSessionStorage.create(this.fs, filePath, {
|
|
37
|
+
cwd: options.cwd,
|
|
38
|
+
sessionId: id,
|
|
39
|
+
parentSessionPath: options.parentSessionPath,
|
|
40
|
+
});
|
|
41
|
+
return toSession(storage);
|
|
42
|
+
}
|
|
43
|
+
async open(metadata) {
|
|
44
|
+
if (!getFileSystemResultOrThrow(await this.fs.exists(metadata.path), `Failed to check session ${metadata.path}`)) {
|
|
45
|
+
throw new SessionError("not_found", `Session not found: ${metadata.path}`);
|
|
46
|
+
}
|
|
47
|
+
const storage = await JsonlSessionStorage.open(this.fs, metadata.path);
|
|
48
|
+
return toSession(storage);
|
|
49
|
+
}
|
|
50
|
+
async list(options = {}) {
|
|
51
|
+
const dirs = options.cwd ? [await this.getSessionDir(options.cwd)] : await this.listSessionDirs();
|
|
52
|
+
const sessions = [];
|
|
53
|
+
for (const dir of dirs) {
|
|
54
|
+
if (!getFileSystemResultOrThrow(await this.fs.exists(dir), `Failed to check session directory ${dir}`)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const files = getFileSystemResultOrThrow(await this.fs.listDir(dir), `Failed to list sessions in ${dir}`).filter((file) => file.kind !== "directory" && file.name.endsWith(".jsonl"));
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
try {
|
|
60
|
+
sessions.push(await loadJsonlSessionMetadata(this.fs, file.path));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const cause = toError(error);
|
|
64
|
+
if (!(cause instanceof SessionError) || cause.code !== "invalid_session")
|
|
65
|
+
throw cause;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
70
|
+
return sessions;
|
|
71
|
+
}
|
|
72
|
+
async delete(metadata) {
|
|
73
|
+
getFileSystemResultOrThrow(await this.fs.remove(metadata.path, { force: true }), `Failed to delete session ${metadata.path}`);
|
|
74
|
+
}
|
|
75
|
+
async fork(sourceMetadata, options) {
|
|
76
|
+
const source = await this.open(sourceMetadata);
|
|
77
|
+
const forkedEntries = await getEntriesToFork(source.getStorage(), options);
|
|
78
|
+
const id = options.id ?? createSessionId();
|
|
79
|
+
const createdAt = createTimestamp();
|
|
80
|
+
const sessionDir = await this.getSessionDir(options.cwd);
|
|
81
|
+
getFileSystemResultOrThrow(await this.fs.createDir(sessionDir, { recursive: true }), `Failed to create session directory ${sessionDir}`);
|
|
82
|
+
const storage = await JsonlSessionStorage.create(this.fs, await this.createSessionFilePath(options.cwd, id, createdAt), {
|
|
83
|
+
cwd: options.cwd,
|
|
84
|
+
sessionId: id,
|
|
85
|
+
parentSessionPath: options.parentSessionPath ?? sourceMetadata.path,
|
|
86
|
+
});
|
|
87
|
+
for (const entry of forkedEntries) {
|
|
88
|
+
await storage.appendEntry(entry);
|
|
89
|
+
}
|
|
90
|
+
return toSession(storage);
|
|
91
|
+
}
|
|
92
|
+
async listSessionDirs() {
|
|
93
|
+
const sessionsRoot = await this.getSessionsRoot();
|
|
94
|
+
if (!getFileSystemResultOrThrow(await this.fs.exists(sessionsRoot), `Failed to check sessions root ${sessionsRoot}`)) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const entries = getFileSystemResultOrThrow(await this.fs.listDir(sessionsRoot), `Failed to list sessions root ${sessionsRoot}`);
|
|
98
|
+
return entries.filter((entry) => entry.kind === "directory").map((entry) => entry.path);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { SessionError, toError } from "../types.js";
|
|
2
|
+
import { getFileSystemResultOrThrow } from "./repo-utils.js";
|
|
3
|
+
import { uuidv7 } from "./uuid.js";
|
|
4
|
+
function updateLabelCache(labelsById, entry) {
|
|
5
|
+
if (entry.type !== "label")
|
|
6
|
+
return;
|
|
7
|
+
const label = entry.label?.trim();
|
|
8
|
+
if (label) {
|
|
9
|
+
labelsById.set(entry.targetId, label);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
labelsById.delete(entry.targetId);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function buildLabelsById(entries) {
|
|
16
|
+
const labelsById = new Map();
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
updateLabelCache(labelsById, entry);
|
|
19
|
+
}
|
|
20
|
+
return labelsById;
|
|
21
|
+
}
|
|
22
|
+
function generateEntryId(byId) {
|
|
23
|
+
for (let i = 0; i < 100; i++) {
|
|
24
|
+
const id = uuidv7().slice(0, 8);
|
|
25
|
+
if (!byId.has(id))
|
|
26
|
+
return id;
|
|
27
|
+
}
|
|
28
|
+
return uuidv7();
|
|
29
|
+
}
|
|
30
|
+
function isRecord(value) {
|
|
31
|
+
return typeof value === "object" && value !== null;
|
|
32
|
+
}
|
|
33
|
+
function invalidSession(filePath, message, cause) {
|
|
34
|
+
return new SessionError("invalid_session", `Invalid JSONL session file ${filePath}: ${message}`, cause);
|
|
35
|
+
}
|
|
36
|
+
function invalidEntry(filePath, lineNumber, message, cause) {
|
|
37
|
+
return new SessionError("invalid_entry", `Invalid JSONL session file ${filePath}: line ${lineNumber} ${message}`, cause);
|
|
38
|
+
}
|
|
39
|
+
function parseHeaderLine(line, filePath) {
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = JSON.parse(line);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw invalidSession(filePath, "first line is not a valid session header", toError(error));
|
|
46
|
+
}
|
|
47
|
+
if (!isRecord(parsed))
|
|
48
|
+
throw invalidSession(filePath, "first line is not a valid session header");
|
|
49
|
+
if (parsed.type !== "session")
|
|
50
|
+
throw invalidSession(filePath, "first line is not a valid session header");
|
|
51
|
+
if (parsed.version !== 3)
|
|
52
|
+
throw invalidSession(filePath, "unsupported session version");
|
|
53
|
+
if (typeof parsed.id !== "string" || !parsed.id)
|
|
54
|
+
throw invalidSession(filePath, "session header is missing id");
|
|
55
|
+
if (typeof parsed.timestamp !== "string" || !parsed.timestamp) {
|
|
56
|
+
throw invalidSession(filePath, "session header is missing timestamp");
|
|
57
|
+
}
|
|
58
|
+
if (typeof parsed.cwd !== "string" || !parsed.cwd)
|
|
59
|
+
throw invalidSession(filePath, "session header is missing cwd");
|
|
60
|
+
if (parsed.parentSession !== undefined && typeof parsed.parentSession !== "string") {
|
|
61
|
+
throw invalidSession(filePath, "session header parentSession must be a string");
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
type: "session",
|
|
65
|
+
version: 3,
|
|
66
|
+
id: parsed.id,
|
|
67
|
+
timestamp: parsed.timestamp,
|
|
68
|
+
cwd: parsed.cwd,
|
|
69
|
+
parentSession: parsed.parentSession,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function parseEntryLine(line, filePath, lineNumber) {
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(line);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw invalidEntry(filePath, lineNumber, "is not valid JSON", toError(error));
|
|
79
|
+
}
|
|
80
|
+
if (!isRecord(parsed))
|
|
81
|
+
throw invalidEntry(filePath, lineNumber, "is not a valid session entry");
|
|
82
|
+
if (typeof parsed.type !== "string")
|
|
83
|
+
throw invalidEntry(filePath, lineNumber, "is missing entry type");
|
|
84
|
+
if (typeof parsed.id !== "string" || !parsed.id)
|
|
85
|
+
throw invalidEntry(filePath, lineNumber, "is missing entry id");
|
|
86
|
+
if (parsed.parentId !== null && typeof parsed.parentId !== "string") {
|
|
87
|
+
throw invalidEntry(filePath, lineNumber, "has invalid parentId");
|
|
88
|
+
}
|
|
89
|
+
if (typeof parsed.timestamp !== "string" || !parsed.timestamp) {
|
|
90
|
+
throw invalidEntry(filePath, lineNumber, "is missing timestamp");
|
|
91
|
+
}
|
|
92
|
+
if (parsed.type === "leaf" && parsed.targetId !== null && typeof parsed.targetId !== "string") {
|
|
93
|
+
throw invalidEntry(filePath, lineNumber, "has invalid targetId");
|
|
94
|
+
}
|
|
95
|
+
return parsed;
|
|
96
|
+
}
|
|
97
|
+
function leafIdAfterEntry(entry) {
|
|
98
|
+
return entry.type === "leaf" ? entry.targetId : entry.id;
|
|
99
|
+
}
|
|
100
|
+
function headerToSessionMetadata(header, path) {
|
|
101
|
+
return {
|
|
102
|
+
id: header.id,
|
|
103
|
+
createdAt: header.timestamp,
|
|
104
|
+
cwd: header.cwd,
|
|
105
|
+
path,
|
|
106
|
+
parentSessionPath: header.parentSession,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export async function loadJsonlSessionMetadata(fs, filePath) {
|
|
110
|
+
const lines = getFileSystemResultOrThrow(await fs.readTextLines(filePath, { maxLines: 1 }), `Failed to read session header ${filePath}`);
|
|
111
|
+
const line = lines[0];
|
|
112
|
+
if (line?.trim())
|
|
113
|
+
return headerToSessionMetadata(parseHeaderLine(line, filePath), filePath);
|
|
114
|
+
throw invalidSession(filePath, "missing session header");
|
|
115
|
+
}
|
|
116
|
+
async function loadJsonlStorage(fs, filePath) {
|
|
117
|
+
const content = getFileSystemResultOrThrow(await fs.readTextFile(filePath), `Failed to read session ${filePath}`);
|
|
118
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
119
|
+
if (lines.length === 0) {
|
|
120
|
+
throw invalidSession(filePath, "missing session header");
|
|
121
|
+
}
|
|
122
|
+
const header = parseHeaderLine(lines[0], filePath);
|
|
123
|
+
const entries = [];
|
|
124
|
+
let leafId = null;
|
|
125
|
+
for (let i = 1; i < lines.length; i++) {
|
|
126
|
+
const entry = parseEntryLine(lines[i], filePath, i + 1);
|
|
127
|
+
entries.push(entry);
|
|
128
|
+
leafId = leafIdAfterEntry(entry);
|
|
129
|
+
}
|
|
130
|
+
return { header, entries, leafId };
|
|
131
|
+
}
|
|
132
|
+
export class JsonlSessionStorage {
|
|
133
|
+
fs;
|
|
134
|
+
filePath;
|
|
135
|
+
metadata;
|
|
136
|
+
entries;
|
|
137
|
+
byId;
|
|
138
|
+
labelsById;
|
|
139
|
+
currentLeafId;
|
|
140
|
+
constructor(fs, filePath, header, entries, leafId) {
|
|
141
|
+
this.fs = fs;
|
|
142
|
+
this.filePath = filePath;
|
|
143
|
+
this.metadata = headerToSessionMetadata(header, this.filePath);
|
|
144
|
+
this.entries = entries;
|
|
145
|
+
this.byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
146
|
+
this.labelsById = buildLabelsById(entries);
|
|
147
|
+
this.currentLeafId = leafId;
|
|
148
|
+
}
|
|
149
|
+
static async open(fs, filePath) {
|
|
150
|
+
const loaded = await loadJsonlStorage(fs, filePath);
|
|
151
|
+
return new JsonlSessionStorage(fs, filePath, loaded.header, loaded.entries, loaded.leafId);
|
|
152
|
+
}
|
|
153
|
+
static async create(fs, filePath, options) {
|
|
154
|
+
const header = {
|
|
155
|
+
type: "session",
|
|
156
|
+
version: 3,
|
|
157
|
+
id: options.sessionId,
|
|
158
|
+
timestamp: new Date().toISOString(),
|
|
159
|
+
cwd: options.cwd,
|
|
160
|
+
parentSession: options.parentSessionPath,
|
|
161
|
+
};
|
|
162
|
+
getFileSystemResultOrThrow(await fs.writeFile(filePath, `${JSON.stringify(header)}\n`), `Failed to create session ${filePath}`);
|
|
163
|
+
return new JsonlSessionStorage(fs, filePath, header, [], null);
|
|
164
|
+
}
|
|
165
|
+
async getMetadata() {
|
|
166
|
+
return this.metadata;
|
|
167
|
+
}
|
|
168
|
+
async getLeafId() {
|
|
169
|
+
if (this.currentLeafId !== null && !this.byId.has(this.currentLeafId)) {
|
|
170
|
+
throw new SessionError("invalid_session", `Entry ${this.currentLeafId} not found`);
|
|
171
|
+
}
|
|
172
|
+
return this.currentLeafId;
|
|
173
|
+
}
|
|
174
|
+
async setLeafId(leafId) {
|
|
175
|
+
if (leafId !== null && !this.byId.has(leafId)) {
|
|
176
|
+
throw new SessionError("not_found", `Entry ${leafId} not found`);
|
|
177
|
+
}
|
|
178
|
+
const entry = {
|
|
179
|
+
type: "leaf",
|
|
180
|
+
id: generateEntryId(this.byId),
|
|
181
|
+
parentId: this.currentLeafId,
|
|
182
|
+
timestamp: new Date().toISOString(),
|
|
183
|
+
targetId: leafId,
|
|
184
|
+
};
|
|
185
|
+
getFileSystemResultOrThrow(await this.fs.appendFile(this.filePath, `${JSON.stringify(entry)}\n`), `Failed to append session leaf ${entry.id}`);
|
|
186
|
+
this.entries.push(entry);
|
|
187
|
+
this.byId.set(entry.id, entry);
|
|
188
|
+
this.currentLeafId = leafId;
|
|
189
|
+
}
|
|
190
|
+
async createEntryId() {
|
|
191
|
+
return generateEntryId(this.byId);
|
|
192
|
+
}
|
|
193
|
+
async appendEntry(entry) {
|
|
194
|
+
getFileSystemResultOrThrow(await this.fs.appendFile(this.filePath, `${JSON.stringify(entry)}\n`), `Failed to append session entry ${entry.id}`);
|
|
195
|
+
this.entries.push(entry);
|
|
196
|
+
this.byId.set(entry.id, entry);
|
|
197
|
+
updateLabelCache(this.labelsById, entry);
|
|
198
|
+
this.currentLeafId = leafIdAfterEntry(entry);
|
|
199
|
+
}
|
|
200
|
+
async getEntry(id) {
|
|
201
|
+
return this.byId.get(id);
|
|
202
|
+
}
|
|
203
|
+
async findEntries(type) {
|
|
204
|
+
return this.entries.filter((entry) => entry.type === type);
|
|
205
|
+
}
|
|
206
|
+
async getLabel(id) {
|
|
207
|
+
return this.labelsById.get(id);
|
|
208
|
+
}
|
|
209
|
+
async getPathToRoot(leafId) {
|
|
210
|
+
if (leafId === null)
|
|
211
|
+
return [];
|
|
212
|
+
const path = [];
|
|
213
|
+
let current = this.byId.get(leafId);
|
|
214
|
+
if (!current)
|
|
215
|
+
throw new SessionError("not_found", `Entry ${leafId} not found`);
|
|
216
|
+
while (current) {
|
|
217
|
+
path.unshift(current);
|
|
218
|
+
if (!current.parentId)
|
|
219
|
+
break;
|
|
220
|
+
const parent = this.byId.get(current.parentId);
|
|
221
|
+
if (!parent)
|
|
222
|
+
throw new SessionError("invalid_session", `Entry ${current.parentId} not found`);
|
|
223
|
+
current = parent;
|
|
224
|
+
}
|
|
225
|
+
return path;
|
|
226
|
+
}
|
|
227
|
+
async getEntries() {
|
|
228
|
+
return [...this.entries];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SessionError } from "../types.js";
|
|
2
|
+
import { InMemorySessionStorage } from "./memory-storage.js";
|
|
3
|
+
import { createSessionId, createTimestamp, getEntriesToFork, toSession } from "./repo-utils.js";
|
|
4
|
+
export class InMemorySessionRepo {
|
|
5
|
+
sessions = new Map();
|
|
6
|
+
async create(options = {}) {
|
|
7
|
+
const metadata = {
|
|
8
|
+
id: options.id ?? createSessionId(),
|
|
9
|
+
createdAt: createTimestamp(),
|
|
10
|
+
};
|
|
11
|
+
const storage = new InMemorySessionStorage({ metadata });
|
|
12
|
+
const session = toSession(storage);
|
|
13
|
+
this.sessions.set(metadata.id, session);
|
|
14
|
+
return session;
|
|
15
|
+
}
|
|
16
|
+
async open(metadata) {
|
|
17
|
+
const session = this.sessions.get(metadata.id);
|
|
18
|
+
if (!session) {
|
|
19
|
+
throw new SessionError("not_found", `Session not found: ${metadata.id}`);
|
|
20
|
+
}
|
|
21
|
+
return session;
|
|
22
|
+
}
|
|
23
|
+
async list() {
|
|
24
|
+
return Promise.all([...this.sessions.values()].map((session) => session.getMetadata()));
|
|
25
|
+
}
|
|
26
|
+
async delete(metadata) {
|
|
27
|
+
this.sessions.delete(metadata.id);
|
|
28
|
+
}
|
|
29
|
+
async fork(sourceMetadata, options) {
|
|
30
|
+
const source = await this.open(sourceMetadata);
|
|
31
|
+
const forkedEntries = await getEntriesToFork(source.getStorage(), options);
|
|
32
|
+
const metadata = {
|
|
33
|
+
id: options.id ?? createSessionId(),
|
|
34
|
+
createdAt: createTimestamp(),
|
|
35
|
+
};
|
|
36
|
+
const storage = new InMemorySessionStorage({ metadata, entries: forkedEntries });
|
|
37
|
+
const session = toSession(storage);
|
|
38
|
+
this.sessions.set(metadata.id, session);
|
|
39
|
+
return session;
|
|
40
|
+
}
|
|
41
|
+
}
|