@elyracode/agent-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +488 -0
  2. package/dist/agent-loop.d.ts +24 -0
  3. package/dist/agent-loop.d.ts.map +1 -0
  4. package/dist/agent-loop.js +479 -0
  5. package/dist/agent-loop.js.map +1 -0
  6. package/dist/agent.d.ts +118 -0
  7. package/dist/agent.d.ts.map +1 -0
  8. package/dist/agent.js +402 -0
  9. package/dist/agent.js.map +1 -0
  10. package/dist/harness/agent-harness.d.ts +78 -0
  11. package/dist/harness/agent-harness.d.ts.map +1 -0
  12. package/dist/harness/agent-harness.js +602 -0
  13. package/dist/harness/agent-harness.js.map +1 -0
  14. package/dist/harness/compaction/branch-summarization.d.ts +88 -0
  15. package/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
  16. package/dist/harness/compaction/branch-summarization.js +243 -0
  17. package/dist/harness/compaction/branch-summarization.js.map +1 -0
  18. package/dist/harness/compaction/compaction.d.ts +122 -0
  19. package/dist/harness/compaction/compaction.d.ts.map +1 -0
  20. package/dist/harness/compaction/compaction.js +616 -0
  21. package/dist/harness/compaction/compaction.js.map +1 -0
  22. package/dist/harness/compaction/utils.d.ts +38 -0
  23. package/dist/harness/compaction/utils.d.ts.map +1 -0
  24. package/dist/harness/compaction/utils.js +153 -0
  25. package/dist/harness/compaction/utils.js.map +1 -0
  26. package/dist/harness/env/nodejs.d.ts +44 -0
  27. package/dist/harness/env/nodejs.d.ts.map +1 -0
  28. package/dist/harness/env/nodejs.js +348 -0
  29. package/dist/harness/env/nodejs.js.map +1 -0
  30. package/dist/harness/execution-env.d.ts +4 -0
  31. package/dist/harness/execution-env.d.ts.map +1 -0
  32. package/dist/harness/execution-env.js +3 -0
  33. package/dist/harness/execution-env.js.map +1 -0
  34. package/dist/harness/messages.d.ts +51 -0
  35. package/dist/harness/messages.d.ts.map +1 -0
  36. package/dist/harness/messages.js +102 -0
  37. package/dist/harness/messages.js.map +1 -0
  38. package/dist/harness/prompt-templates.d.ts +45 -0
  39. package/dist/harness/prompt-templates.d.ts.map +1 -0
  40. package/dist/harness/prompt-templates.js +200 -0
  41. package/dist/harness/prompt-templates.js.map +1 -0
  42. package/dist/harness/session/repo/jsonl.d.ts +20 -0
  43. package/dist/harness/session/repo/jsonl.d.ts.map +1 -0
  44. package/dist/harness/session/repo/jsonl.js +92 -0
  45. package/dist/harness/session/repo/jsonl.js.map +1 -0
  46. package/dist/harness/session/repo/memory.d.ts +18 -0
  47. package/dist/harness/session/repo/memory.d.ts.map +1 -0
  48. package/dist/harness/session/repo/memory.js +42 -0
  49. package/dist/harness/session/repo/memory.js.map +1 -0
  50. package/dist/harness/session/repo/shared.d.ts +10 -0
  51. package/dist/harness/session/repo/shared.d.ts.map +1 -0
  52. package/dist/harness/session/repo/shared.js +31 -0
  53. package/dist/harness/session/repo/shared.js.map +1 -0
  54. package/dist/harness/session/session.d.ts +32 -0
  55. package/dist/harness/session/session.d.ts.map +1 -0
  56. package/dist/harness/session/session.js +196 -0
  57. package/dist/harness/session/session.js.map +1 -0
  58. package/dist/harness/session/storage/jsonl.d.ts +30 -0
  59. package/dist/harness/session/storage/jsonl.d.ts.map +1 -0
  60. package/dist/harness/session/storage/jsonl.js +170 -0
  61. package/dist/harness/session/storage/jsonl.js.map +1 -0
  62. package/dist/harness/session/storage/memory.d.ts +26 -0
  63. package/dist/harness/session/storage/memory.d.ts.map +1 -0
  64. package/dist/harness/session/storage/memory.js +90 -0
  65. package/dist/harness/session/storage/memory.js.map +1 -0
  66. package/dist/harness/skills.d.ts +41 -0
  67. package/dist/harness/skills.d.ts.map +1 -0
  68. package/dist/harness/skills.js +259 -0
  69. package/dist/harness/skills.js.map +1 -0
  70. package/dist/harness/system-prompt.d.ts +3 -0
  71. package/dist/harness/system-prompt.d.ts.map +1 -0
  72. package/dist/harness/system-prompt.js +30 -0
  73. package/dist/harness/system-prompt.js.map +1 -0
  74. package/dist/harness/types.d.ts +497 -0
  75. package/dist/harness/types.d.ts.map +1 -0
  76. package/dist/harness/types.js +16 -0
  77. package/dist/harness/types.js.map +1 -0
  78. package/dist/harness/utils/shell-output.d.ts +14 -0
  79. package/dist/harness/utils/shell-output.d.ts.map +1 -0
  80. package/dist/harness/utils/shell-output.js +97 -0
  81. package/dist/harness/utils/shell-output.js.map +1 -0
  82. package/dist/harness/utils/truncate.d.ts +70 -0
  83. package/dist/harness/utils/truncate.d.ts.map +1 -0
  84. package/dist/harness/utils/truncate.js +205 -0
  85. package/dist/harness/utils/truncate.js.map +1 -0
  86. package/dist/index.d.ts +20 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +25 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/proxy.d.ts +69 -0
  91. package/dist/proxy.d.ts.map +1 -0
  92. package/dist/proxy.js +278 -0
  93. package/dist/proxy.js.map +1 -0
  94. package/dist/types.d.ts +386 -0
  95. package/dist/types.d.ts.map +1 -0
  96. package/dist/types.js +2 -0
  97. package/dist/types.js.map +1 -0
  98. package/package.json +47 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-templates.d.ts","sourceRoot":"","sources":["../../src/harness/prompt-templates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAY,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzE,uDAAuD;AACvD,MAAM,WAAW,wBAAwB;IACxC,gEAAgE;IAChE,IAAI,EAAE,SAAS,CAAC;IAChB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACb;AAQD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,YAAY,EACjB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GACtB,OAAO,CAAC;IAAE,eAAe,EAAE,cAAc,EAAE,CAAC;IAAC,WAAW,EAAE,wBAAwB,EAAE,CAAA;CAAE,CAAC,CAkBzF;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,eAAe,SAAS,cAAc,GAAG,cAAc,EAChH,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,EAChD,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,KAAK,eAAe,GACtF,OAAO,CAAC;IACV,eAAe,EAAE,KAAK,CAAC;QAAE,cAAc,EAAE,eAAe,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC7E,WAAW,EAAE,KAAK,CAAC,wBAAwB,GAAG;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACnE,CAAC,CAgBD;AAqGD,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAuB7D;AAED,uHAAuH;AACvH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAatE;AAED,qEAAqE;AACrE,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,MAAM,CAEpG","sourcesContent":["import { parse } from \"yaml\";\nimport type { ExecutionEnv, FileInfo, PromptTemplate } from \"./types.js\";\n\n/** Warning produced while loading prompt templates. */\nexport interface PromptTemplateDiagnostic {\n\t/** Diagnostic severity. Currently only warnings are emitted. */\n\ttype: \"warning\";\n\t/** Human-readable diagnostic message. */\n\tmessage: string;\n\t/** Path associated with the diagnostic. */\n\tpath: string;\n}\n\ninterface PromptTemplateFrontmatter {\n\tdescription?: string;\n\t\"argument-hint\"?: string;\n\t[key: string]: unknown;\n}\n\n/**\n * Load prompt templates from one or more paths.\n *\n * Directory inputs load direct `.md` children non-recursively. File inputs load explicit `.md` files. Missing paths and\n * non-markdown files are skipped. Read and parse failures are returned as diagnostics.\n */\nexport async function loadPromptTemplates(\n\tenv: ExecutionEnv,\n\tpaths: string | string[],\n): Promise<{ promptTemplates: PromptTemplate[]; diagnostics: PromptTemplateDiagnostic[] }> {\n\tconst promptTemplates: PromptTemplate[] = [];\n\tconst diagnostics: PromptTemplateDiagnostic[] = [];\n\tfor (const path of Array.isArray(paths) ? paths : [paths]) {\n\t\tconst info = await safeFileInfo(env, path);\n\t\tif (!info) continue;\n\t\tconst kind = await resolveKind(env, info);\n\t\tif (kind === \"directory\") {\n\t\t\tconst result = await loadTemplatesFromDir(env, info.path);\n\t\t\tpromptTemplates.push(...result.promptTemplates);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t} else if (kind === \"file\" && info.name.endsWith(\".md\")) {\n\t\t\tconst result = await loadTemplateFromFile(env, info.path);\n\t\t\tif (result.promptTemplate) promptTemplates.push(result.promptTemplate);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t}\n\treturn { promptTemplates, diagnostics };\n}\n\n/**\n * Load prompt templates from source-tagged paths.\n *\n * Source values are preserved exactly and attached to every loaded prompt template and diagnostic. The agent package does\n * not interpret source values; applications define their own provenance shape.\n */\nexport async function loadSourcedPromptTemplates<TSource, TPromptTemplate extends PromptTemplate = PromptTemplate>(\n\tenv: ExecutionEnv,\n\tinputs: Array<{ path: string; source: TSource }>,\n\tmapPromptTemplate?: (promptTemplate: PromptTemplate, source: TSource) => TPromptTemplate,\n): Promise<{\n\tpromptTemplates: Array<{ promptTemplate: TPromptTemplate; source: TSource }>;\n\tdiagnostics: Array<PromptTemplateDiagnostic & { source: TSource }>;\n}> {\n\tconst promptTemplates: Array<{ promptTemplate: TPromptTemplate; source: TSource }> = [];\n\tconst diagnostics: Array<PromptTemplateDiagnostic & { source: TSource }> = [];\n\tfor (const input of inputs) {\n\t\tconst result = await loadPromptTemplates(env, input.path);\n\t\tfor (const promptTemplate of result.promptTemplates) {\n\t\t\tpromptTemplates.push({\n\t\t\t\tpromptTemplate: mapPromptTemplate\n\t\t\t\t\t? mapPromptTemplate(promptTemplate, input.source)\n\t\t\t\t\t: (promptTemplate as TPromptTemplate),\n\t\t\t\tsource: input.source,\n\t\t\t});\n\t\t}\n\t\tfor (const diagnostic of result.diagnostics) diagnostics.push({ ...diagnostic, source: input.source });\n\t}\n\treturn { promptTemplates, diagnostics };\n}\n\nasync function loadTemplatesFromDir(\n\tenv: ExecutionEnv,\n\tdir: string,\n): Promise<{ promptTemplates: PromptTemplate[]; diagnostics: PromptTemplateDiagnostic[] }> {\n\tconst promptTemplates: PromptTemplate[] = [];\n\tconst diagnostics: PromptTemplateDiagnostic[] = [];\n\tlet entries: FileInfo[];\n\ttry {\n\t\tentries = await env.listDir(dir);\n\t} catch (error) {\n\t\tdiagnostics.push({\n\t\t\ttype: \"warning\",\n\t\t\tmessage: errorMessage(error, \"failed to list prompt template directory\"),\n\t\t\tpath: dir,\n\t\t});\n\t\treturn { promptTemplates, diagnostics };\n\t}\n\n\tfor (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n\t\tconst kind = await resolveKind(env, entry);\n\t\tif (kind !== \"file\" || !entry.name.endsWith(\".md\")) continue;\n\t\tconst result = await loadTemplateFromFile(env, entry.path);\n\t\tif (result.promptTemplate) promptTemplates.push(result.promptTemplate);\n\t\tdiagnostics.push(...result.diagnostics);\n\t}\n\treturn { promptTemplates, diagnostics };\n}\n\nasync function loadTemplateFromFile(\n\tenv: ExecutionEnv,\n\tfilePath: string,\n): Promise<{ promptTemplate: PromptTemplate | null; diagnostics: PromptTemplateDiagnostic[] }> {\n\tconst diagnostics: PromptTemplateDiagnostic[] = [];\n\ttry {\n\t\tconst rawContent = await env.readTextFile(filePath);\n\t\tconst { frontmatter, body } = parseFrontmatter<PromptTemplateFrontmatter>(rawContent);\n\t\tconst firstLine = body.split(\"\\n\").find((line) => line.trim());\n\t\tlet description = typeof frontmatter.description === \"string\" ? frontmatter.description : \"\";\n\t\tif (!description && firstLine) {\n\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t}\n\t\treturn {\n\t\t\tpromptTemplate: {\n\t\t\t\tname: basenameEnvPath(filePath).replace(/\\.md$/i, \"\"),\n\t\t\t\tdescription,\n\t\t\t\tcontent: body,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tdiagnostics.push({\n\t\t\ttype: \"warning\",\n\t\t\tmessage: errorMessage(error, \"failed to load prompt template\"),\n\t\t\tpath: filePath,\n\t\t});\n\t\treturn { promptTemplate: null, diagnostics };\n\t}\n}\n\nasync function safeFileInfo(env: ExecutionEnv, path: string): Promise<FileInfo | undefined> {\n\ttry {\n\t\treturn await env.fileInfo(path);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nasync function resolveKind(env: ExecutionEnv, info: FileInfo): Promise<\"file\" | \"directory\" | undefined> {\n\tif (info.kind === \"file\" || info.kind === \"directory\") return info.kind;\n\ttry {\n\t\tconst realPath = await env.realPath(info.path);\n\t\tconst target = await env.fileInfo(realPath);\n\t\treturn target.kind === \"file\" || target.kind === \"directory\" ? target.kind : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction parseFrontmatter<T extends Record<string, unknown>>(content: string): { frontmatter: T; body: string } {\n\tconst normalized = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\tif (!normalized.startsWith(\"---\")) return { frontmatter: {} as T, body: normalized };\n\tconst endIndex = normalized.indexOf(\"\\n---\", 3);\n\tif (endIndex === -1) return { frontmatter: {} as T, body: normalized };\n\tconst yamlString = normalized.slice(4, endIndex);\n\tconst body = normalized.slice(endIndex + 4).trim();\n\treturn { frontmatter: (parse(yamlString) ?? {}) as T, body };\n}\n\nfunction basenameEnvPath(path: string): string {\n\tconst normalized = path.replace(/\\/+$/, \"\");\n\tconst slashIndex = normalized.lastIndexOf(\"/\");\n\treturn slashIndex === -1 ? normalized : normalized.slice(slashIndex + 1);\n}\n\nfunction errorMessage(error: unknown, fallback: string): string {\n\treturn error instanceof Error ? error.message : fallback;\n}\n\n/** Parse an argument string using simple shell-style single and double quotes. */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i]!;\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) inQuote = null;\n\t\t\telse current += char;\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\tif (current) args.push(current);\n\treturn args;\n}\n\n/** Substitute prompt template placeholders (`$1`, `$@`, `$ARGUMENTS`, `${@:N}`, `${@:N:L}`) with command arguments. */\nexport function substituteArgs(content: string, args: string[]): string {\n\tlet result = content;\n\tresult = result.replace(/\\$(\\d+)/g, (_, num: string) => args[parseInt(num, 10) - 1] ?? \"\");\n\tresult = result.replace(/\\$\\{@:(\\d+)(?::(\\d+))?\\}/g, (_, startStr: string, lengthStr?: string) => {\n\t\tlet start = parseInt(startStr, 10) - 1;\n\t\tif (start < 0) start = 0;\n\t\tif (lengthStr) return args.slice(start, start + parseInt(lengthStr, 10)).join(\" \");\n\t\treturn args.slice(start).join(\" \");\n\t});\n\tconst allArgs = args.join(\" \");\n\tresult = result.replace(/\\$ARGUMENTS/g, allArgs);\n\tresult = result.replace(/\\$@/g, allArgs);\n\treturn result;\n}\n\n/** Format a prompt template invocation with positional arguments. */\nexport function formatPromptTemplateInvocation(template: PromptTemplate, args: string[] = []): string {\n\treturn substituteArgs(template.content, args);\n}\n"]}
@@ -0,0 +1,200 @@
1
+ import { parse } from "yaml";
2
+ /**
3
+ * Load prompt templates from one or more paths.
4
+ *
5
+ * Directory inputs load direct `.md` children non-recursively. File inputs load explicit `.md` files. Missing paths and
6
+ * non-markdown files are skipped. Read and parse failures are returned as diagnostics.
7
+ */
8
+ export async function loadPromptTemplates(env, paths) {
9
+ const promptTemplates = [];
10
+ const diagnostics = [];
11
+ for (const path of Array.isArray(paths) ? paths : [paths]) {
12
+ const info = await safeFileInfo(env, path);
13
+ if (!info)
14
+ continue;
15
+ const kind = await resolveKind(env, info);
16
+ if (kind === "directory") {
17
+ const result = await loadTemplatesFromDir(env, info.path);
18
+ promptTemplates.push(...result.promptTemplates);
19
+ diagnostics.push(...result.diagnostics);
20
+ }
21
+ else if (kind === "file" && info.name.endsWith(".md")) {
22
+ const result = await loadTemplateFromFile(env, info.path);
23
+ if (result.promptTemplate)
24
+ promptTemplates.push(result.promptTemplate);
25
+ diagnostics.push(...result.diagnostics);
26
+ }
27
+ }
28
+ return { promptTemplates, diagnostics };
29
+ }
30
+ /**
31
+ * Load prompt templates from source-tagged paths.
32
+ *
33
+ * Source values are preserved exactly and attached to every loaded prompt template and diagnostic. The agent package does
34
+ * not interpret source values; applications define their own provenance shape.
35
+ */
36
+ export async function loadSourcedPromptTemplates(env, inputs, mapPromptTemplate) {
37
+ const promptTemplates = [];
38
+ const diagnostics = [];
39
+ for (const input of inputs) {
40
+ const result = await loadPromptTemplates(env, input.path);
41
+ for (const promptTemplate of result.promptTemplates) {
42
+ promptTemplates.push({
43
+ promptTemplate: mapPromptTemplate
44
+ ? mapPromptTemplate(promptTemplate, input.source)
45
+ : promptTemplate,
46
+ source: input.source,
47
+ });
48
+ }
49
+ for (const diagnostic of result.diagnostics)
50
+ diagnostics.push({ ...diagnostic, source: input.source });
51
+ }
52
+ return { promptTemplates, diagnostics };
53
+ }
54
+ async function loadTemplatesFromDir(env, dir) {
55
+ const promptTemplates = [];
56
+ const diagnostics = [];
57
+ let entries;
58
+ try {
59
+ entries = await env.listDir(dir);
60
+ }
61
+ catch (error) {
62
+ diagnostics.push({
63
+ type: "warning",
64
+ message: errorMessage(error, "failed to list prompt template directory"),
65
+ path: dir,
66
+ });
67
+ return { promptTemplates, diagnostics };
68
+ }
69
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
70
+ const kind = await resolveKind(env, entry);
71
+ if (kind !== "file" || !entry.name.endsWith(".md"))
72
+ continue;
73
+ const result = await loadTemplateFromFile(env, entry.path);
74
+ if (result.promptTemplate)
75
+ promptTemplates.push(result.promptTemplate);
76
+ diagnostics.push(...result.diagnostics);
77
+ }
78
+ return { promptTemplates, diagnostics };
79
+ }
80
+ async function loadTemplateFromFile(env, filePath) {
81
+ const diagnostics = [];
82
+ try {
83
+ const rawContent = await env.readTextFile(filePath);
84
+ const { frontmatter, body } = parseFrontmatter(rawContent);
85
+ const firstLine = body.split("\n").find((line) => line.trim());
86
+ let description = typeof frontmatter.description === "string" ? frontmatter.description : "";
87
+ if (!description && firstLine) {
88
+ description = firstLine.slice(0, 60);
89
+ if (firstLine.length > 60)
90
+ description += "...";
91
+ }
92
+ return {
93
+ promptTemplate: {
94
+ name: basenameEnvPath(filePath).replace(/\.md$/i, ""),
95
+ description,
96
+ content: body,
97
+ },
98
+ diagnostics,
99
+ };
100
+ }
101
+ catch (error) {
102
+ diagnostics.push({
103
+ type: "warning",
104
+ message: errorMessage(error, "failed to load prompt template"),
105
+ path: filePath,
106
+ });
107
+ return { promptTemplate: null, diagnostics };
108
+ }
109
+ }
110
+ async function safeFileInfo(env, path) {
111
+ try {
112
+ return await env.fileInfo(path);
113
+ }
114
+ catch {
115
+ return undefined;
116
+ }
117
+ }
118
+ async function resolveKind(env, info) {
119
+ if (info.kind === "file" || info.kind === "directory")
120
+ return info.kind;
121
+ try {
122
+ const realPath = await env.realPath(info.path);
123
+ const target = await env.fileInfo(realPath);
124
+ return target.kind === "file" || target.kind === "directory" ? target.kind : undefined;
125
+ }
126
+ catch {
127
+ return undefined;
128
+ }
129
+ }
130
+ function parseFrontmatter(content) {
131
+ const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
132
+ if (!normalized.startsWith("---"))
133
+ return { frontmatter: {}, body: normalized };
134
+ const endIndex = normalized.indexOf("\n---", 3);
135
+ if (endIndex === -1)
136
+ return { frontmatter: {}, body: normalized };
137
+ const yamlString = normalized.slice(4, endIndex);
138
+ const body = normalized.slice(endIndex + 4).trim();
139
+ return { frontmatter: (parse(yamlString) ?? {}), body };
140
+ }
141
+ function basenameEnvPath(path) {
142
+ const normalized = path.replace(/\/+$/, "");
143
+ const slashIndex = normalized.lastIndexOf("/");
144
+ return slashIndex === -1 ? normalized : normalized.slice(slashIndex + 1);
145
+ }
146
+ function errorMessage(error, fallback) {
147
+ return error instanceof Error ? error.message : fallback;
148
+ }
149
+ /** Parse an argument string using simple shell-style single and double quotes. */
150
+ export function parseCommandArgs(argsString) {
151
+ const args = [];
152
+ let current = "";
153
+ let inQuote = null;
154
+ for (let i = 0; i < argsString.length; i++) {
155
+ const char = argsString[i];
156
+ if (inQuote) {
157
+ if (char === inQuote)
158
+ inQuote = null;
159
+ else
160
+ current += char;
161
+ }
162
+ else if (char === '"' || char === "'") {
163
+ inQuote = char;
164
+ }
165
+ else if (char === " " || char === "\t") {
166
+ if (current) {
167
+ args.push(current);
168
+ current = "";
169
+ }
170
+ }
171
+ else {
172
+ current += char;
173
+ }
174
+ }
175
+ if (current)
176
+ args.push(current);
177
+ return args;
178
+ }
179
+ /** Substitute prompt template placeholders (`$1`, `$@`, `$ARGUMENTS`, `${@:N}`, `${@:N:L}`) with command arguments. */
180
+ export function substituteArgs(content, args) {
181
+ let result = content;
182
+ result = result.replace(/\$(\d+)/g, (_, num) => args[parseInt(num, 10) - 1] ?? "");
183
+ result = result.replace(/\$\{@:(\d+)(?::(\d+))?\}/g, (_, startStr, lengthStr) => {
184
+ let start = parseInt(startStr, 10) - 1;
185
+ if (start < 0)
186
+ start = 0;
187
+ if (lengthStr)
188
+ return args.slice(start, start + parseInt(lengthStr, 10)).join(" ");
189
+ return args.slice(start).join(" ");
190
+ });
191
+ const allArgs = args.join(" ");
192
+ result = result.replace(/\$ARGUMENTS/g, allArgs);
193
+ result = result.replace(/\$@/g, allArgs);
194
+ return result;
195
+ }
196
+ /** Format a prompt template invocation with positional arguments. */
197
+ export function formatPromptTemplateInvocation(template, args = []) {
198
+ return substituteArgs(template.content, args);
199
+ }
200
+ //# sourceMappingURL=prompt-templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-templates.js","sourceRoot":"","sources":["../../src/harness/prompt-templates.ts"],"names":[],"mappings":"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","sourcesContent":["import { parse } from \"yaml\";\nimport type { ExecutionEnv, FileInfo, PromptTemplate } from \"./types.js\";\n\n/** Warning produced while loading prompt templates. */\nexport interface PromptTemplateDiagnostic {\n\t/** Diagnostic severity. Currently only warnings are emitted. */\n\ttype: \"warning\";\n\t/** Human-readable diagnostic message. */\n\tmessage: string;\n\t/** Path associated with the diagnostic. */\n\tpath: string;\n}\n\ninterface PromptTemplateFrontmatter {\n\tdescription?: string;\n\t\"argument-hint\"?: string;\n\t[key: string]: unknown;\n}\n\n/**\n * Load prompt templates from one or more paths.\n *\n * Directory inputs load direct `.md` children non-recursively. File inputs load explicit `.md` files. Missing paths and\n * non-markdown files are skipped. Read and parse failures are returned as diagnostics.\n */\nexport async function loadPromptTemplates(\n\tenv: ExecutionEnv,\n\tpaths: string | string[],\n): Promise<{ promptTemplates: PromptTemplate[]; diagnostics: PromptTemplateDiagnostic[] }> {\n\tconst promptTemplates: PromptTemplate[] = [];\n\tconst diagnostics: PromptTemplateDiagnostic[] = [];\n\tfor (const path of Array.isArray(paths) ? paths : [paths]) {\n\t\tconst info = await safeFileInfo(env, path);\n\t\tif (!info) continue;\n\t\tconst kind = await resolveKind(env, info);\n\t\tif (kind === \"directory\") {\n\t\t\tconst result = await loadTemplatesFromDir(env, info.path);\n\t\t\tpromptTemplates.push(...result.promptTemplates);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t} else if (kind === \"file\" && info.name.endsWith(\".md\")) {\n\t\t\tconst result = await loadTemplateFromFile(env, info.path);\n\t\t\tif (result.promptTemplate) promptTemplates.push(result.promptTemplate);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t}\n\treturn { promptTemplates, diagnostics };\n}\n\n/**\n * Load prompt templates from source-tagged paths.\n *\n * Source values are preserved exactly and attached to every loaded prompt template and diagnostic. The agent package does\n * not interpret source values; applications define their own provenance shape.\n */\nexport async function loadSourcedPromptTemplates<TSource, TPromptTemplate extends PromptTemplate = PromptTemplate>(\n\tenv: ExecutionEnv,\n\tinputs: Array<{ path: string; source: TSource }>,\n\tmapPromptTemplate?: (promptTemplate: PromptTemplate, source: TSource) => TPromptTemplate,\n): Promise<{\n\tpromptTemplates: Array<{ promptTemplate: TPromptTemplate; source: TSource }>;\n\tdiagnostics: Array<PromptTemplateDiagnostic & { source: TSource }>;\n}> {\n\tconst promptTemplates: Array<{ promptTemplate: TPromptTemplate; source: TSource }> = [];\n\tconst diagnostics: Array<PromptTemplateDiagnostic & { source: TSource }> = [];\n\tfor (const input of inputs) {\n\t\tconst result = await loadPromptTemplates(env, input.path);\n\t\tfor (const promptTemplate of result.promptTemplates) {\n\t\t\tpromptTemplates.push({\n\t\t\t\tpromptTemplate: mapPromptTemplate\n\t\t\t\t\t? mapPromptTemplate(promptTemplate, input.source)\n\t\t\t\t\t: (promptTemplate as TPromptTemplate),\n\t\t\t\tsource: input.source,\n\t\t\t});\n\t\t}\n\t\tfor (const diagnostic of result.diagnostics) diagnostics.push({ ...diagnostic, source: input.source });\n\t}\n\treturn { promptTemplates, diagnostics };\n}\n\nasync function loadTemplatesFromDir(\n\tenv: ExecutionEnv,\n\tdir: string,\n): Promise<{ promptTemplates: PromptTemplate[]; diagnostics: PromptTemplateDiagnostic[] }> {\n\tconst promptTemplates: PromptTemplate[] = [];\n\tconst diagnostics: PromptTemplateDiagnostic[] = [];\n\tlet entries: FileInfo[];\n\ttry {\n\t\tentries = await env.listDir(dir);\n\t} catch (error) {\n\t\tdiagnostics.push({\n\t\t\ttype: \"warning\",\n\t\t\tmessage: errorMessage(error, \"failed to list prompt template directory\"),\n\t\t\tpath: dir,\n\t\t});\n\t\treturn { promptTemplates, diagnostics };\n\t}\n\n\tfor (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n\t\tconst kind = await resolveKind(env, entry);\n\t\tif (kind !== \"file\" || !entry.name.endsWith(\".md\")) continue;\n\t\tconst result = await loadTemplateFromFile(env, entry.path);\n\t\tif (result.promptTemplate) promptTemplates.push(result.promptTemplate);\n\t\tdiagnostics.push(...result.diagnostics);\n\t}\n\treturn { promptTemplates, diagnostics };\n}\n\nasync function loadTemplateFromFile(\n\tenv: ExecutionEnv,\n\tfilePath: string,\n): Promise<{ promptTemplate: PromptTemplate | null; diagnostics: PromptTemplateDiagnostic[] }> {\n\tconst diagnostics: PromptTemplateDiagnostic[] = [];\n\ttry {\n\t\tconst rawContent = await env.readTextFile(filePath);\n\t\tconst { frontmatter, body } = parseFrontmatter<PromptTemplateFrontmatter>(rawContent);\n\t\tconst firstLine = body.split(\"\\n\").find((line) => line.trim());\n\t\tlet description = typeof frontmatter.description === \"string\" ? frontmatter.description : \"\";\n\t\tif (!description && firstLine) {\n\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t}\n\t\treturn {\n\t\t\tpromptTemplate: {\n\t\t\t\tname: basenameEnvPath(filePath).replace(/\\.md$/i, \"\"),\n\t\t\t\tdescription,\n\t\t\t\tcontent: body,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tdiagnostics.push({\n\t\t\ttype: \"warning\",\n\t\t\tmessage: errorMessage(error, \"failed to load prompt template\"),\n\t\t\tpath: filePath,\n\t\t});\n\t\treturn { promptTemplate: null, diagnostics };\n\t}\n}\n\nasync function safeFileInfo(env: ExecutionEnv, path: string): Promise<FileInfo | undefined> {\n\ttry {\n\t\treturn await env.fileInfo(path);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nasync function resolveKind(env: ExecutionEnv, info: FileInfo): Promise<\"file\" | \"directory\" | undefined> {\n\tif (info.kind === \"file\" || info.kind === \"directory\") return info.kind;\n\ttry {\n\t\tconst realPath = await env.realPath(info.path);\n\t\tconst target = await env.fileInfo(realPath);\n\t\treturn target.kind === \"file\" || target.kind === \"directory\" ? target.kind : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction parseFrontmatter<T extends Record<string, unknown>>(content: string): { frontmatter: T; body: string } {\n\tconst normalized = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\tif (!normalized.startsWith(\"---\")) return { frontmatter: {} as T, body: normalized };\n\tconst endIndex = normalized.indexOf(\"\\n---\", 3);\n\tif (endIndex === -1) return { frontmatter: {} as T, body: normalized };\n\tconst yamlString = normalized.slice(4, endIndex);\n\tconst body = normalized.slice(endIndex + 4).trim();\n\treturn { frontmatter: (parse(yamlString) ?? {}) as T, body };\n}\n\nfunction basenameEnvPath(path: string): string {\n\tconst normalized = path.replace(/\\/+$/, \"\");\n\tconst slashIndex = normalized.lastIndexOf(\"/\");\n\treturn slashIndex === -1 ? normalized : normalized.slice(slashIndex + 1);\n}\n\nfunction errorMessage(error: unknown, fallback: string): string {\n\treturn error instanceof Error ? error.message : fallback;\n}\n\n/** Parse an argument string using simple shell-style single and double quotes. */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i]!;\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) inQuote = null;\n\t\t\telse current += char;\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\tif (current) args.push(current);\n\treturn args;\n}\n\n/** Substitute prompt template placeholders (`$1`, `$@`, `$ARGUMENTS`, `${@:N}`, `${@:N:L}`) with command arguments. */\nexport function substituteArgs(content: string, args: string[]): string {\n\tlet result = content;\n\tresult = result.replace(/\\$(\\d+)/g, (_, num: string) => args[parseInt(num, 10) - 1] ?? \"\");\n\tresult = result.replace(/\\$\\{@:(\\d+)(?::(\\d+))?\\}/g, (_, startStr: string, lengthStr?: string) => {\n\t\tlet start = parseInt(startStr, 10) - 1;\n\t\tif (start < 0) start = 0;\n\t\tif (lengthStr) return args.slice(start, start + parseInt(lengthStr, 10)).join(\" \");\n\t\treturn args.slice(start).join(\" \");\n\t});\n\tconst allArgs = args.join(\" \");\n\tresult = result.replace(/\\$ARGUMENTS/g, allArgs);\n\tresult = result.replace(/\\$@/g, allArgs);\n\treturn result;\n}\n\n/** Format a prompt template invocation with positional arguments. */\nexport function formatPromptTemplateInvocation(template: PromptTemplate, args: string[] = []): string {\n\treturn substituteArgs(template.content, args);\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import type { JsonlSessionCreateOptions, JsonlSessionListOptions, JsonlSessionMetadata, JsonlSessionRepoApi, Session } from "../../types.js";
2
+ export declare class JsonlSessionRepo implements JsonlSessionRepoApi {
3
+ private sessionsRoot;
4
+ constructor(options: {
5
+ sessionsRoot: string;
6
+ });
7
+ private getSessionDir;
8
+ private createSessionFilePath;
9
+ create(options: JsonlSessionCreateOptions): Promise<Session<JsonlSessionMetadata>>;
10
+ open(metadata: JsonlSessionMetadata): Promise<Session<JsonlSessionMetadata>>;
11
+ list(options?: JsonlSessionListOptions): Promise<JsonlSessionMetadata[]>;
12
+ delete(metadata: JsonlSessionMetadata): Promise<void>;
13
+ fork(sourceMetadata: JsonlSessionMetadata, options: JsonlSessionCreateOptions & {
14
+ entryId?: string;
15
+ position?: "before" | "at";
16
+ id?: string;
17
+ }): Promise<Session<JsonlSessionMetadata>>;
18
+ private listSessionDirs;
19
+ }
20
+ //# sourceMappingURL=jsonl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonl.d.ts","sourceRoot":"","sources":["../../../../src/harness/session/repo/jsonl.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,yBAAyB,EACzB,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,EACnB,OAAO,EACP,MAAM,gBAAgB,CAAC;AAiBxB,qBAAa,gBAAiB,YAAW,mBAAmB;IAC3D,OAAO,CAAC,YAAY,CAAS;IAE7B,YAAY,OAAO,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,EAE5C;IAED,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,qBAAqB;IAIvB,MAAM,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAWvF;IAEK,IAAI,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAMjF;IAEK,IAAI,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAgBjF;IAEK,MAAM,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1D;IAEK,IAAI,CACT,cAAc,EAAE,oBAAoB,EACpC,OAAO,EAAE,yBAAyB,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAChG,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAcxC;YAEa,eAAe;CAK7B","sourcesContent":["import { constants } from \"node:fs\";\nimport { access, mkdir, readdir, rm } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport type {\n\tJsonlSessionCreateOptions,\n\tJsonlSessionListOptions,\n\tJsonlSessionMetadata,\n\tJsonlSessionRepoApi,\n\tSession,\n} from \"../../types.js\";\nimport { JsonlSessionStorage, loadJsonlSessionMetadata } from \"../storage/jsonl.js\";\nimport { createSessionId, createTimestamp, getEntriesToFork, toSession } from \"./shared.js\";\n\nasync function exists(path: string): Promise<boolean> {\n\ttry {\n\t\tawait access(path, constants.F_OK);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction encodeCwd(cwd: string): string {\n\treturn `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n}\n\nexport class JsonlSessionRepo implements JsonlSessionRepoApi {\n\tprivate sessionsRoot: string;\n\n\tconstructor(options: { sessionsRoot: string }) {\n\t\tthis.sessionsRoot = resolve(options.sessionsRoot);\n\t}\n\n\tprivate getSessionDir(cwd: string): string {\n\t\treturn join(this.sessionsRoot, encodeCwd(cwd));\n\t}\n\n\tprivate createSessionFilePath(cwd: string, sessionId: string, timestamp: string): string {\n\t\treturn join(this.getSessionDir(cwd), `${timestamp.replace(/[:.]/g, \"-\")}_${sessionId}.jsonl`);\n\t}\n\n\tasync create(options: JsonlSessionCreateOptions): Promise<Session<JsonlSessionMetadata>> {\n\t\tawait mkdir(this.sessionsRoot, { recursive: true });\n\t\tconst id = options.id ?? createSessionId();\n\t\tconst createdAt = createTimestamp();\n\t\tconst filePath = this.createSessionFilePath(options.cwd, id, createdAt);\n\t\tconst storage = await JsonlSessionStorage.create(filePath, {\n\t\t\tcwd: options.cwd,\n\t\t\tsessionId: id,\n\t\t\tparentSessionPath: options.parentSessionPath,\n\t\t});\n\t\treturn toSession(storage);\n\t}\n\n\tasync open(metadata: JsonlSessionMetadata): Promise<Session<JsonlSessionMetadata>> {\n\t\tif (!(await exists(metadata.path))) {\n\t\t\tthrow new Error(`Session not found: ${metadata.path}`);\n\t\t}\n\t\tconst storage = await JsonlSessionStorage.open(metadata.path);\n\t\treturn toSession(storage);\n\t}\n\n\tasync list(options: JsonlSessionListOptions = {}): Promise<JsonlSessionMetadata[]> {\n\t\tconst dirs = options.cwd ? [this.getSessionDir(options.cwd)] : await this.listSessionDirs();\n\t\tconst sessions: JsonlSessionMetadata[] = [];\n\t\tfor (const dir of dirs) {\n\t\t\tif (!(await exists(dir))) continue;\n\t\t\tconst files = (await readdir(dir)).filter((file) => file.endsWith(\".jsonl\")).map((file) => join(dir, file));\n\t\t\tfor (const filePath of files) {\n\t\t\t\ttry {\n\t\t\t\t\tsessions.push(await loadJsonlSessionMetadata(filePath));\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore invalid session files when listing a directory.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());\n\t\treturn sessions;\n\t}\n\n\tasync delete(metadata: JsonlSessionMetadata): Promise<void> {\n\t\tawait rm(metadata.path, { force: true });\n\t}\n\n\tasync fork(\n\t\tsourceMetadata: JsonlSessionMetadata,\n\t\toptions: JsonlSessionCreateOptions & { entryId?: string; position?: \"before\" | \"at\"; id?: string },\n\t): Promise<Session<JsonlSessionMetadata>> {\n\t\tconst source = await this.open(sourceMetadata);\n\t\tconst forkedEntries = await getEntriesToFork(source.getStorage(), options);\n\t\tconst id = options.id ?? createSessionId();\n\t\tconst createdAt = createTimestamp();\n\t\tconst storage = await JsonlSessionStorage.create(this.createSessionFilePath(options.cwd, id, createdAt), {\n\t\t\tcwd: options.cwd,\n\t\t\tsessionId: id,\n\t\t\tparentSessionPath: options.parentSessionPath ?? sourceMetadata.path,\n\t\t});\n\t\tfor (const entry of forkedEntries) {\n\t\t\tawait storage.appendEntry(entry);\n\t\t}\n\t\treturn toSession(storage);\n\t}\n\n\tprivate async listSessionDirs(): Promise<string[]> {\n\t\tif (!(await exists(this.sessionsRoot))) return [];\n\t\tconst entries = await readdir(this.sessionsRoot, { withFileTypes: true });\n\t\treturn entries.filter((entry) => entry.isDirectory()).map((entry) => join(this.sessionsRoot, entry.name));\n\t}\n}\n"]}
@@ -0,0 +1,92 @@
1
+ import { constants } from "node:fs";
2
+ import { access, mkdir, readdir, rm } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import { JsonlSessionStorage, loadJsonlSessionMetadata } from "../storage/jsonl.js";
5
+ import { createSessionId, createTimestamp, getEntriesToFork, toSession } from "./shared.js";
6
+ async function exists(path) {
7
+ try {
8
+ await access(path, constants.F_OK);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ function encodeCwd(cwd) {
16
+ return `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
17
+ }
18
+ export class JsonlSessionRepo {
19
+ sessionsRoot;
20
+ constructor(options) {
21
+ this.sessionsRoot = resolve(options.sessionsRoot);
22
+ }
23
+ getSessionDir(cwd) {
24
+ return join(this.sessionsRoot, encodeCwd(cwd));
25
+ }
26
+ createSessionFilePath(cwd, sessionId, timestamp) {
27
+ return join(this.getSessionDir(cwd), `${timestamp.replace(/[:.]/g, "-")}_${sessionId}.jsonl`);
28
+ }
29
+ async create(options) {
30
+ await mkdir(this.sessionsRoot, { recursive: true });
31
+ const id = options.id ?? createSessionId();
32
+ const createdAt = createTimestamp();
33
+ const filePath = this.createSessionFilePath(options.cwd, id, createdAt);
34
+ const storage = await JsonlSessionStorage.create(filePath, {
35
+ cwd: options.cwd,
36
+ sessionId: id,
37
+ parentSessionPath: options.parentSessionPath,
38
+ });
39
+ return toSession(storage);
40
+ }
41
+ async open(metadata) {
42
+ if (!(await exists(metadata.path))) {
43
+ throw new Error(`Session not found: ${metadata.path}`);
44
+ }
45
+ const storage = await JsonlSessionStorage.open(metadata.path);
46
+ return toSession(storage);
47
+ }
48
+ async list(options = {}) {
49
+ const dirs = options.cwd ? [this.getSessionDir(options.cwd)] : await this.listSessionDirs();
50
+ const sessions = [];
51
+ for (const dir of dirs) {
52
+ if (!(await exists(dir)))
53
+ continue;
54
+ const files = (await readdir(dir)).filter((file) => file.endsWith(".jsonl")).map((file) => join(dir, file));
55
+ for (const filePath of files) {
56
+ try {
57
+ sessions.push(await loadJsonlSessionMetadata(filePath));
58
+ }
59
+ catch {
60
+ // Ignore invalid session files when listing a directory.
61
+ }
62
+ }
63
+ }
64
+ sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
65
+ return sessions;
66
+ }
67
+ async delete(metadata) {
68
+ await rm(metadata.path, { force: true });
69
+ }
70
+ async fork(sourceMetadata, options) {
71
+ const source = await this.open(sourceMetadata);
72
+ const forkedEntries = await getEntriesToFork(source.getStorage(), options);
73
+ const id = options.id ?? createSessionId();
74
+ const createdAt = createTimestamp();
75
+ const storage = await JsonlSessionStorage.create(this.createSessionFilePath(options.cwd, id, createdAt), {
76
+ cwd: options.cwd,
77
+ sessionId: id,
78
+ parentSessionPath: options.parentSessionPath ?? sourceMetadata.path,
79
+ });
80
+ for (const entry of forkedEntries) {
81
+ await storage.appendEntry(entry);
82
+ }
83
+ return toSession(storage);
84
+ }
85
+ async listSessionDirs() {
86
+ if (!(await exists(this.sessionsRoot)))
87
+ return [];
88
+ const entries = await readdir(this.sessionsRoot, { withFileTypes: true });
89
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => join(this.sessionsRoot, entry.name));
90
+ }
91
+ }
92
+ //# sourceMappingURL=jsonl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonl.js","sourceRoot":"","sources":["../../../../src/harness/session/repo/jsonl.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from \"node:fs\";\nimport { access, mkdir, readdir, rm } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport type {\n\tJsonlSessionCreateOptions,\n\tJsonlSessionListOptions,\n\tJsonlSessionMetadata,\n\tJsonlSessionRepoApi,\n\tSession,\n} from \"../../types.js\";\nimport { JsonlSessionStorage, loadJsonlSessionMetadata } from \"../storage/jsonl.js\";\nimport { createSessionId, createTimestamp, getEntriesToFork, toSession } from \"./shared.js\";\n\nasync function exists(path: string): Promise<boolean> {\n\ttry {\n\t\tawait access(path, constants.F_OK);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction encodeCwd(cwd: string): string {\n\treturn `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n}\n\nexport class JsonlSessionRepo implements JsonlSessionRepoApi {\n\tprivate sessionsRoot: string;\n\n\tconstructor(options: { sessionsRoot: string }) {\n\t\tthis.sessionsRoot = resolve(options.sessionsRoot);\n\t}\n\n\tprivate getSessionDir(cwd: string): string {\n\t\treturn join(this.sessionsRoot, encodeCwd(cwd));\n\t}\n\n\tprivate createSessionFilePath(cwd: string, sessionId: string, timestamp: string): string {\n\t\treturn join(this.getSessionDir(cwd), `${timestamp.replace(/[:.]/g, \"-\")}_${sessionId}.jsonl`);\n\t}\n\n\tasync create(options: JsonlSessionCreateOptions): Promise<Session<JsonlSessionMetadata>> {\n\t\tawait mkdir(this.sessionsRoot, { recursive: true });\n\t\tconst id = options.id ?? createSessionId();\n\t\tconst createdAt = createTimestamp();\n\t\tconst filePath = this.createSessionFilePath(options.cwd, id, createdAt);\n\t\tconst storage = await JsonlSessionStorage.create(filePath, {\n\t\t\tcwd: options.cwd,\n\t\t\tsessionId: id,\n\t\t\tparentSessionPath: options.parentSessionPath,\n\t\t});\n\t\treturn toSession(storage);\n\t}\n\n\tasync open(metadata: JsonlSessionMetadata): Promise<Session<JsonlSessionMetadata>> {\n\t\tif (!(await exists(metadata.path))) {\n\t\t\tthrow new Error(`Session not found: ${metadata.path}`);\n\t\t}\n\t\tconst storage = await JsonlSessionStorage.open(metadata.path);\n\t\treturn toSession(storage);\n\t}\n\n\tasync list(options: JsonlSessionListOptions = {}): Promise<JsonlSessionMetadata[]> {\n\t\tconst dirs = options.cwd ? [this.getSessionDir(options.cwd)] : await this.listSessionDirs();\n\t\tconst sessions: JsonlSessionMetadata[] = [];\n\t\tfor (const dir of dirs) {\n\t\t\tif (!(await exists(dir))) continue;\n\t\t\tconst files = (await readdir(dir)).filter((file) => file.endsWith(\".jsonl\")).map((file) => join(dir, file));\n\t\t\tfor (const filePath of files) {\n\t\t\t\ttry {\n\t\t\t\t\tsessions.push(await loadJsonlSessionMetadata(filePath));\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore invalid session files when listing a directory.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());\n\t\treturn sessions;\n\t}\n\n\tasync delete(metadata: JsonlSessionMetadata): Promise<void> {\n\t\tawait rm(metadata.path, { force: true });\n\t}\n\n\tasync fork(\n\t\tsourceMetadata: JsonlSessionMetadata,\n\t\toptions: JsonlSessionCreateOptions & { entryId?: string; position?: \"before\" | \"at\"; id?: string },\n\t): Promise<Session<JsonlSessionMetadata>> {\n\t\tconst source = await this.open(sourceMetadata);\n\t\tconst forkedEntries = await getEntriesToFork(source.getStorage(), options);\n\t\tconst id = options.id ?? createSessionId();\n\t\tconst createdAt = createTimestamp();\n\t\tconst storage = await JsonlSessionStorage.create(this.createSessionFilePath(options.cwd, id, createdAt), {\n\t\t\tcwd: options.cwd,\n\t\t\tsessionId: id,\n\t\t\tparentSessionPath: options.parentSessionPath ?? sourceMetadata.path,\n\t\t});\n\t\tfor (const entry of forkedEntries) {\n\t\t\tawait storage.appendEntry(entry);\n\t\t}\n\t\treturn toSession(storage);\n\t}\n\n\tprivate async listSessionDirs(): Promise<string[]> {\n\t\tif (!(await exists(this.sessionsRoot))) return [];\n\t\tconst entries = await readdir(this.sessionsRoot, { withFileTypes: true });\n\t\treturn entries.filter((entry) => entry.isDirectory()).map((entry) => join(this.sessionsRoot, entry.name));\n\t}\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import type { Session, SessionMetadata, SessionRepo } from "../../types.js";
2
+ export declare class InMemorySessionRepo implements SessionRepo<SessionMetadata, {
3
+ id?: string;
4
+ }, void> {
5
+ private sessions;
6
+ create(options?: {
7
+ id?: string;
8
+ }): Promise<Session<SessionMetadata>>;
9
+ open(metadata: SessionMetadata): Promise<Session<SessionMetadata>>;
10
+ list(): Promise<SessionMetadata[]>;
11
+ delete(metadata: SessionMetadata): Promise<void>;
12
+ fork(sourceMetadata: SessionMetadata, options: {
13
+ entryId?: string;
14
+ position?: "before" | "at";
15
+ id?: string;
16
+ }): Promise<Session<SessionMetadata>>;
17
+ }
18
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../../src/harness/session/repo/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI5E,qBAAa,mBAAoB,YAAW,WAAW,CAAC,eAAe,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,CAAC;IAC9F,OAAO,CAAC,QAAQ,CAA+C;IAEzD,MAAM,CAAC,OAAO,GAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAS7E;IAEK,IAAI,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAMvE;IAEK,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAEvC;IAEK,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAErD;IAEK,IAAI,CACT,cAAc,EAAE,eAAe,EAC/B,OAAO,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GACpE,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAYnC;CACD","sourcesContent":["import type { Session, SessionMetadata, SessionRepo } from \"../../types.js\";\nimport { InMemorySessionStorage } from \"../storage/memory.js\";\nimport { createSessionId, createTimestamp, getEntriesToFork, toSession } from \"./shared.js\";\n\nexport class InMemorySessionRepo implements SessionRepo<SessionMetadata, { id?: string }, void> {\n\tprivate sessions = new Map<string, Session<SessionMetadata>>();\n\n\tasync create(options: { id?: string } = {}): Promise<Session<SessionMetadata>> {\n\t\tconst metadata: SessionMetadata = {\n\t\t\tid: options.id ?? createSessionId(),\n\t\t\tcreatedAt: createTimestamp(),\n\t\t};\n\t\tconst storage = new InMemorySessionStorage({ metadata });\n\t\tconst session = toSession(storage);\n\t\tthis.sessions.set(metadata.id, session);\n\t\treturn session;\n\t}\n\n\tasync open(metadata: SessionMetadata): Promise<Session<SessionMetadata>> {\n\t\tconst session = this.sessions.get(metadata.id);\n\t\tif (!session) {\n\t\t\tthrow new Error(`Session not found: ${metadata.id}`);\n\t\t}\n\t\treturn session;\n\t}\n\n\tasync list(): Promise<SessionMetadata[]> {\n\t\treturn Promise.all([...this.sessions.values()].map((session) => session.getMetadata()));\n\t}\n\n\tasync delete(metadata: SessionMetadata): Promise<void> {\n\t\tthis.sessions.delete(metadata.id);\n\t}\n\n\tasync fork(\n\t\tsourceMetadata: SessionMetadata,\n\t\toptions: { entryId?: string; position?: \"before\" | \"at\"; id?: string },\n\t): Promise<Session<SessionMetadata>> {\n\t\tconst source = await this.open(sourceMetadata);\n\t\tconst forkedEntries = await getEntriesToFork(source.getStorage(), options);\n\t\tconst metadata: SessionMetadata = {\n\t\t\tid: options.id ?? createSessionId(),\n\t\t\tcreatedAt: createTimestamp(),\n\t\t};\n\t\tconst leafId = forkedEntries[forkedEntries.length - 1]?.id ?? null;\n\t\tconst storage = new InMemorySessionStorage({ metadata, entries: forkedEntries, leafId });\n\t\tconst session = toSession(storage);\n\t\tthis.sessions.set(metadata.id, session);\n\t\treturn session;\n\t}\n}\n"]}
@@ -0,0 +1,42 @@
1
+ import { InMemorySessionStorage } from "../storage/memory.js";
2
+ import { createSessionId, createTimestamp, getEntriesToFork, toSession } from "./shared.js";
3
+ export class InMemorySessionRepo {
4
+ sessions = new Map();
5
+ async create(options = {}) {
6
+ const metadata = {
7
+ id: options.id ?? createSessionId(),
8
+ createdAt: createTimestamp(),
9
+ };
10
+ const storage = new InMemorySessionStorage({ metadata });
11
+ const session = toSession(storage);
12
+ this.sessions.set(metadata.id, session);
13
+ return session;
14
+ }
15
+ async open(metadata) {
16
+ const session = this.sessions.get(metadata.id);
17
+ if (!session) {
18
+ throw new Error(`Session not found: ${metadata.id}`);
19
+ }
20
+ return session;
21
+ }
22
+ async list() {
23
+ return Promise.all([...this.sessions.values()].map((session) => session.getMetadata()));
24
+ }
25
+ async delete(metadata) {
26
+ this.sessions.delete(metadata.id);
27
+ }
28
+ async fork(sourceMetadata, options) {
29
+ const source = await this.open(sourceMetadata);
30
+ const forkedEntries = await getEntriesToFork(source.getStorage(), options);
31
+ const metadata = {
32
+ id: options.id ?? createSessionId(),
33
+ createdAt: createTimestamp(),
34
+ };
35
+ const leafId = forkedEntries[forkedEntries.length - 1]?.id ?? null;
36
+ const storage = new InMemorySessionStorage({ metadata, entries: forkedEntries, leafId });
37
+ const session = toSession(storage);
38
+ this.sessions.set(metadata.id, session);
39
+ return session;
40
+ }
41
+ }
42
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../../src/harness/session/repo/memory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE5F,MAAM,OAAO,mBAAmB;IACvB,QAAQ,GAAG,IAAI,GAAG,EAAoC,CAAC;IAE/D,KAAK,CAAC,MAAM,CAAC,OAAO,GAAoB,EAAE,EAAqC;QAC9E,MAAM,QAAQ,GAAoB;YACjC,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,eAAe,EAAE;YACnC,SAAS,EAAE,eAAe,EAAE;SAC5B,CAAC;QACF,MAAM,OAAO,GAAG,IAAI,sBAAsB,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,OAAO,CAAC;IAAA,CACf;IAED,KAAK,CAAC,IAAI,CAAC,QAAyB,EAAqC;QACxE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,OAAO,CAAC;IAAA,CACf;IAED,KAAK,CAAC,IAAI,GAA+B;QACxC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAAA,CACxF;IAED,KAAK,CAAC,MAAM,CAAC,QAAyB,EAAiB;QACtD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAAA,CAClC;IAED,KAAK,CAAC,IAAI,CACT,cAA+B,EAC/B,OAAsE,EAClC;QACpC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC/C,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,OAAO,CAAC,CAAC;QAC3E,MAAM,QAAQ,GAAoB;YACjC,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,eAAe,EAAE;YACnC,SAAS,EAAE,eAAe,EAAE;SAC5B,CAAC;QACF,MAAM,MAAM,GAAG,aAAa,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;QACnE,MAAM,OAAO,GAAG,IAAI,sBAAsB,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC;QACzF,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,OAAO,CAAC;IAAA,CACf;CACD","sourcesContent":["import type { Session, SessionMetadata, SessionRepo } from \"../../types.js\";\nimport { InMemorySessionStorage } from \"../storage/memory.js\";\nimport { createSessionId, createTimestamp, getEntriesToFork, toSession } from \"./shared.js\";\n\nexport class InMemorySessionRepo implements SessionRepo<SessionMetadata, { id?: string }, void> {\n\tprivate sessions = new Map<string, Session<SessionMetadata>>();\n\n\tasync create(options: { id?: string } = {}): Promise<Session<SessionMetadata>> {\n\t\tconst metadata: SessionMetadata = {\n\t\t\tid: options.id ?? createSessionId(),\n\t\t\tcreatedAt: createTimestamp(),\n\t\t};\n\t\tconst storage = new InMemorySessionStorage({ metadata });\n\t\tconst session = toSession(storage);\n\t\tthis.sessions.set(metadata.id, session);\n\t\treturn session;\n\t}\n\n\tasync open(metadata: SessionMetadata): Promise<Session<SessionMetadata>> {\n\t\tconst session = this.sessions.get(metadata.id);\n\t\tif (!session) {\n\t\t\tthrow new Error(`Session not found: ${metadata.id}`);\n\t\t}\n\t\treturn session;\n\t}\n\n\tasync list(): Promise<SessionMetadata[]> {\n\t\treturn Promise.all([...this.sessions.values()].map((session) => session.getMetadata()));\n\t}\n\n\tasync delete(metadata: SessionMetadata): Promise<void> {\n\t\tthis.sessions.delete(metadata.id);\n\t}\n\n\tasync fork(\n\t\tsourceMetadata: SessionMetadata,\n\t\toptions: { entryId?: string; position?: \"before\" | \"at\"; id?: string },\n\t): Promise<Session<SessionMetadata>> {\n\t\tconst source = await this.open(sourceMetadata);\n\t\tconst forkedEntries = await getEntriesToFork(source.getStorage(), options);\n\t\tconst metadata: SessionMetadata = {\n\t\t\tid: options.id ?? createSessionId(),\n\t\t\tcreatedAt: createTimestamp(),\n\t\t};\n\t\tconst leafId = forkedEntries[forkedEntries.length - 1]?.id ?? null;\n\t\tconst storage = new InMemorySessionStorage({ metadata, entries: forkedEntries, leafId });\n\t\tconst session = toSession(storage);\n\t\tthis.sessions.set(metadata.id, session);\n\t\treturn session;\n\t}\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { SessionMetadata, SessionStorage, SessionTreeEntry } from "../../types.js";
2
+ import { Session } from "../session.js";
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 getEntriesToFork(storage: SessionStorage, options: {
7
+ entryId?: string;
8
+ position?: "before" | "at";
9
+ }): Promise<SessionTreeEntry[]>;
10
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../src/harness/session/repo/shared.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,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,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 { v7 as uuidv7 } from \"uuid\";\nimport type { SessionMetadata, SessionStorage, SessionTreeEntry } from \"../../types.js\";\nimport { Session } from \"../session.js\";\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 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 Error(`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 Error(`Entry ${options.entryId} is not a user message`);\n\t\t}\n\t\teffectiveLeafId = target.parentId;\n\t}\n\treturn storage.getPathToRoot(effectiveLeafId);\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import { v7 as uuidv7 } from "uuid";
2
+ import { Session } from "../session.js";
3
+ export function createSessionId() {
4
+ return uuidv7();
5
+ }
6
+ export function createTimestamp() {
7
+ return new Date().toISOString();
8
+ }
9
+ export function toSession(storage) {
10
+ return new Session(storage);
11
+ }
12
+ export async function getEntriesToFork(storage, options) {
13
+ if (!options.entryId)
14
+ return storage.getEntries();
15
+ const target = await storage.getEntry(options.entryId);
16
+ if (!target) {
17
+ throw new Error(`Entry ${options.entryId} not found`);
18
+ }
19
+ let effectiveLeafId;
20
+ if ((options.position ?? "before") === "at") {
21
+ effectiveLeafId = target.id;
22
+ }
23
+ else {
24
+ if (target.type !== "message" || target.message.role !== "user") {
25
+ throw new Error(`Entry ${options.entryId} is not a user message`);
26
+ }
27
+ effectiveLeafId = target.parentId;
28
+ }
29
+ return storage.getPathToRoot(effectiveLeafId);
30
+ }
31
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../../src/harness/session/repo/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,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,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,KAAK,CAAC,SAAS,OAAO,CAAC,OAAO,YAAY,CAAC,CAAC;IACvD,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,KAAK,CAAC,SAAS,OAAO,CAAC,OAAO,wBAAwB,CAAC,CAAC;QACnE,CAAC;QACD,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC;IACnC,CAAC;IACD,OAAO,OAAO,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;AAAA,CAC9C","sourcesContent":["import { v7 as uuidv7 } from \"uuid\";\nimport type { SessionMetadata, SessionStorage, SessionTreeEntry } from \"../../types.js\";\nimport { Session } from \"../session.js\";\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 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 Error(`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 Error(`Entry ${options.entryId} is not a user message`);\n\t\t}\n\t\teffectiveLeafId = target.parentId;\n\t}\n\treturn storage.getPathToRoot(effectiveLeafId);\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import type { ImageContent, TextContent } from "@elyracode/ai";
2
+ import type { AgentMessage } from "../../types.js";
3
+ import type { SessionContext, SessionMetadata, SessionStorage, SessionTreeEntry } from "../types.js";
4
+ export declare function buildSessionContext(pathEntries: SessionTreeEntry[]): SessionContext;
5
+ export declare class Session<TMetadata extends SessionMetadata = SessionMetadata> {
6
+ private storage;
7
+ constructor(storage: SessionStorage<TMetadata>);
8
+ getMetadata(): Promise<TMetadata>;
9
+ getStorage(): SessionStorage<TMetadata>;
10
+ getLeafId(): Promise<string | null>;
11
+ getEntry(id: string): Promise<SessionTreeEntry | undefined>;
12
+ getEntries(): Promise<SessionTreeEntry[]>;
13
+ getBranch(fromId?: string): Promise<SessionTreeEntry[]>;
14
+ buildContext(): Promise<SessionContext>;
15
+ getLabel(id: string): Promise<string | undefined>;
16
+ getSessionName(): Promise<string | undefined>;
17
+ private appendTypedEntry;
18
+ appendMessage(message: AgentMessage): Promise<string>;
19
+ appendThinkingLevelChange(thinkingLevel: string): Promise<string>;
20
+ appendModelChange(provider: string, modelId: string): Promise<string>;
21
+ appendCompaction<T = unknown>(summary: string, firstKeptEntryId: string, tokensBefore: number, details?: T, fromHook?: boolean): Promise<string>;
22
+ appendCustomEntry(customType: string, data?: unknown): Promise<string>;
23
+ appendCustomMessageEntry<T = unknown>(customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details?: T): Promise<string>;
24
+ appendLabel(targetId: string, label: string | undefined): Promise<string>;
25
+ appendSessionName(name: string): Promise<string>;
26
+ moveTo(entryId: string | null, summary?: {
27
+ summary: string;
28
+ details?: unknown;
29
+ fromHook?: boolean;
30
+ }): Promise<string | undefined>;
31
+ }
32
+ //# 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,MAAM,eAAe,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,OAAO,KAAK,EAQX,cAAc,EAEd,eAAe,EACf,cAAc,EACd,gBAAgB,EAEhB,MAAM,aAAa,CAAC;AAErB,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,gBAAgB,EAAE,GAAG,cAAc,CAuDnF;AAED,qBAAa,OAAO,CAAC,SAAS,SAAS,eAAe,GAAG,eAAe;IACvE,OAAO,CAAC,OAAO,CAA4B;IAE3C,YAAY,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,EAE7C;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,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAExC;IAEK,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAG5D;IAEK,YAAY,IAAI,OAAO,CAAC,cAAc,CAAC,CAE5C;IAED,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAEhD;IAEK,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAGlD;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,gBAAgB,CAAC,CAAC,GAAG,OAAO,EACjC,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,CAAC,EACX,QAAQ,CAAC,EAAE,OAAO,GAChB,OAAO,CAAC,MAAM,CAAC,CAYjB;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,CAQrD;IAEK,MAAM,CACX,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAClE,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAgB7B;CACD","sourcesContent":["import type { ImageContent, TextContent } from \"@elyracode/ai\";\nimport type { AgentMessage } from \"../../types.js\";\nimport { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage } from \"../messages.js\";\nimport type {\n\tBranchSummaryEntry,\n\tCompactionEntry,\n\tCustomEntry,\n\tCustomMessageEntry,\n\tLabelEntry,\n\tMessageEntry,\n\tModelChangeEntry,\n\tSessionContext,\n\tSessionInfoEntry,\n\tSessionMetadata,\n\tSessionStorage,\n\tSessionTreeEntry,\n\tThinkingLevelChangeEntry,\n} from \"../types.js\";\n\nexport function buildSessionContext(pathEntries: SessionTreeEntry[]): SessionContext {\n\tlet thinkingLevel = \"off\";\n\tlet model: { provider: string; modelId: string } | null = null;\n\tlet compaction: CompactionEntry | null = null;\n\n\tfor (const entry of pathEntries) {\n\t\tif (entry.type === \"thinking_level_change\") {\n\t\t\tthinkingLevel = entry.thinkingLevel;\n\t\t} else if (entry.type === \"model_change\") {\n\t\t\tmodel = { provider: entry.provider, modelId: entry.modelId };\n\t\t} else if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\tmodel = { provider: entry.message.provider, modelId: entry.message.model };\n\t\t} else if (entry.type === \"compaction\") {\n\t\t\tcompaction = entry;\n\t\t}\n\t}\n\n\tconst messages: AgentMessage[] = [];\n\tconst appendMessage = (entry: SessionTreeEntry) => {\n\t\tif (entry.type === \"message\") {\n\t\t\tmessages.push(entry.message as AgentMessage);\n\t\t} else if (entry.type === \"custom_message\") {\n\t\t\tmessages.push(\n\t\t\t\tcreateCustomMessage(\n\t\t\t\t\tentry.customType,\n\t\t\t\t\tentry.content as string | (TextContent | ImageContent)[],\n\t\t\t\t\tentry.display,\n\t\t\t\t\tentry.details,\n\t\t\t\t\tentry.timestamp,\n\t\t\t\t),\n\t\t\t);\n\t\t} else if (entry.type === \"branch_summary\" && entry.summary) {\n\t\t\tmessages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp));\n\t\t}\n\t};\n\n\tif (compaction) {\n\t\tmessages.push(createCompactionSummaryMessage(compaction.summary, compaction.tokensBefore, compaction.timestamp));\n\t\tconst compactionIdx = pathEntries.findIndex((e) => e.type === \"compaction\" && e.id === compaction.id);\n\t\tlet foundFirstKept = false;\n\t\tfor (let i = 0; i < compactionIdx; i++) {\n\t\t\tconst entry = pathEntries[i]!;\n\t\t\tif (entry.id === compaction.firstKeptEntryId) foundFirstKept = true;\n\t\t\tif (foundFirstKept) appendMessage(entry);\n\t\t}\n\t\tfor (let i = compactionIdx + 1; i < pathEntries.length; i++) {\n\t\t\tappendMessage(pathEntries[i]!);\n\t\t}\n\t} else {\n\t\tfor (const entry of pathEntries) {\n\t\t\tappendMessage(entry);\n\t\t}\n\t}\n\n\treturn { messages, thinkingLevel, model };\n}\n\nexport class Session<TMetadata extends SessionMetadata = SessionMetadata> {\n\tprivate storage: SessionStorage<TMetadata>;\n\n\tconstructor(storage: SessionStorage<TMetadata>) {\n\t\tthis.storage = storage;\n\t}\n\n\tgetMetadata(): Promise<TMetadata> {\n\t\treturn this.storage.getMetadata();\n\t}\n\n\tgetStorage(): SessionStorage<TMetadata> {\n\t\treturn this.storage;\n\t}\n\n\tgetLeafId(): Promise<string | null> {\n\t\treturn this.storage.getLeafId();\n\t}\n\n\tgetEntry(id: string): Promise<SessionTreeEntry | undefined> {\n\t\treturn this.storage.getEntry(id);\n\t}\n\n\tgetEntries(): Promise<SessionTreeEntry[]> {\n\t\treturn this.storage.getEntries();\n\t}\n\n\tasync getBranch(fromId?: string): Promise<SessionTreeEntry[]> {\n\t\tconst leafId = fromId ?? (await this.storage.getLeafId());\n\t\treturn this.storage.getPathToRoot(leafId);\n\t}\n\n\tasync buildContext(): Promise<SessionContext> {\n\t\treturn buildSessionContext(await this.getBranch());\n\t}\n\n\tgetLabel(id: string): Promise<string | undefined> {\n\t\treturn this.storage.getLabel(id);\n\t}\n\n\tasync getSessionName(): Promise<string | undefined> {\n\t\tconst entries = await this.storage.findEntries(\"session_info\");\n\t\treturn entries[entries.length - 1]?.name?.trim() || undefined;\n\t}\n\n\tprivate async appendTypedEntry<TEntry extends SessionTreeEntry>(entry: TEntry): Promise<string> {\n\t\tawait this.storage.appendEntry(entry);\n\t\treturn entry.id;\n\t}\n\n\tasync appendMessage(message: AgentMessage): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"message\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tmessage,\n\t\t} satisfies MessageEntry);\n\t}\n\n\tasync appendThinkingLevelChange(thinkingLevel: string): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"thinking_level_change\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tthinkingLevel,\n\t\t} satisfies ThinkingLevelChangeEntry);\n\t}\n\n\tasync appendModelChange(provider: string, modelId: string): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"model_change\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tprovider,\n\t\t\tmodelId,\n\t\t} satisfies ModelChangeEntry);\n\t}\n\n\tasync appendCompaction<T = unknown>(\n\t\tsummary: string,\n\t\tfirstKeptEntryId: string,\n\t\ttokensBefore: number,\n\t\tdetails?: T,\n\t\tfromHook?: boolean,\n\t): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"compaction\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tsummary,\n\t\t\tfirstKeptEntryId,\n\t\t\ttokensBefore,\n\t\t\tdetails,\n\t\t\tfromHook,\n\t\t} satisfies CompactionEntry<T>);\n\t}\n\n\tasync appendCustomEntry(customType: string, data?: unknown): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"custom\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcustomType,\n\t\t\tdata,\n\t\t} satisfies CustomEntry);\n\t}\n\n\tasync appendCustomMessageEntry<T = unknown>(\n\t\tcustomType: string,\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\tdisplay: boolean,\n\t\tdetails?: T,\n\t): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"custom_message\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcustomType,\n\t\t\tcontent,\n\t\t\tdisplay,\n\t\t\tdetails,\n\t\t} satisfies CustomMessageEntry<T>);\n\t}\n\n\tasync appendLabel(targetId: string, label: string | undefined): Promise<string> {\n\t\tif (!(await this.storage.getEntry(targetId))) {\n\t\t\tthrow new Error(`Entry ${targetId} not found`);\n\t\t}\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"label\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttargetId,\n\t\t\tlabel,\n\t\t} satisfies LabelEntry);\n\t}\n\n\tasync appendSessionName(name: string): Promise<string> {\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"session_info\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: await this.storage.getLeafId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tname: name.trim(),\n\t\t} satisfies SessionInfoEntry);\n\t}\n\n\tasync moveTo(\n\t\tentryId: string | null,\n\t\tsummary?: { summary: string; details?: unknown; fromHook?: boolean },\n\t): Promise<string | undefined> {\n\t\tif (entryId !== null && !(await this.storage.getEntry(entryId))) {\n\t\t\tthrow new Error(`Entry ${entryId} not found`);\n\t\t}\n\t\tawait this.storage.setLeafId(entryId);\n\t\tif (!summary) return undefined;\n\t\treturn this.appendTypedEntry({\n\t\t\ttype: \"branch_summary\",\n\t\t\tid: await this.storage.createEntryId(),\n\t\t\tparentId: entryId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tfromId: entryId ?? \"root\",\n\t\t\tsummary: summary.summary,\n\t\t\tdetails: summary.details,\n\t\t\tfromHook: summary.fromHook,\n\t\t} satisfies BranchSummaryEntry);\n\t}\n}\n"]}