world-model-optimizer 0.2.0__py3-none-any.whl
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.
- llm_waterfall/LICENSE +21 -0
- llm_waterfall/__init__.py +53 -0
- llm_waterfall/adapters/__init__.py +36 -0
- llm_waterfall/adapters/anthropic.py +105 -0
- llm_waterfall/adapters/aws_mantle.py +47 -0
- llm_waterfall/adapters/azure_openai.py +71 -0
- llm_waterfall/adapters/base.py +51 -0
- llm_waterfall/adapters/bedrock.py +309 -0
- llm_waterfall/adapters/openai.py +130 -0
- llm_waterfall/classify.py +184 -0
- llm_waterfall/pricing.py +110 -0
- llm_waterfall/py.typed +0 -0
- llm_waterfall/types.py +295 -0
- llm_waterfall/waterfall.py +255 -0
- wmo/__init__.py +38 -0
- wmo/agents/__init__.py +7 -0
- wmo/agents/default.py +29 -0
- wmo/agents/meta.py +55 -0
- wmo/agents/optimizer.py +55 -0
- wmo/agents/project.py +928 -0
- wmo/cli/__init__.py +5 -0
- wmo/cli/agent_session.py +1123 -0
- wmo/cli/app.py +2489 -0
- wmo/cli/e2b_cmds.py +212 -0
- wmo/cli/eval_closed_loop.py +207 -0
- wmo/cli/harness_app.py +1147 -0
- wmo/cli/harness_distill.py +659 -0
- wmo/cli/hosted_session.py +880 -0
- wmo/cli/ingest_cmd.py +165 -0
- wmo/cli/model_roles.py +82 -0
- wmo/cli/platform_cmds.py +372 -0
- wmo/cli/route_app.py +274 -0
- wmo/cli/session_state.py +243 -0
- wmo/cli/ui.py +1107 -0
- wmo/cli/workspace_sync.py +504 -0
- wmo/config/__init__.py +60 -0
- wmo/config/card.py +129 -0
- wmo/config/config.py +367 -0
- wmo/config/dotenv.py +67 -0
- wmo/config/settings.py +128 -0
- wmo/config/store.py +177 -0
- wmo/conftest.py +19 -0
- wmo/connect/__init__.py +88 -0
- wmo/connect/apps.py +78 -0
- wmo/connect/brave.py +284 -0
- wmo/connect/connector.py +79 -0
- wmo/connect/credentials.py +164 -0
- wmo/connect/github.py +321 -0
- wmo/connect/google.py +627 -0
- wmo/connect/notion.py +790 -0
- wmo/connect/oauth.py +461 -0
- wmo/connect/slack.py +555 -0
- wmo/connect/store.py +199 -0
- wmo/connect/types.py +156 -0
- wmo/core/__init__.py +21 -0
- wmo/core/parsing.py +281 -0
- wmo/core/render.py +271 -0
- wmo/core/text.py +40 -0
- wmo/core/types.py +116 -0
- wmo/distill/__init__.py +14 -0
- wmo/distill/agents.py +140 -0
- wmo/distill/config.py +1006 -0
- wmo/distill/cost.py +437 -0
- wmo/distill/data.py +921 -0
- wmo/distill/deadlines.py +254 -0
- wmo/distill/fake_tinker.py +734 -0
- wmo/distill/gate.py +122 -0
- wmo/distill/loop.py +3499 -0
- wmo/distill/renderers.py +399 -0
- wmo/distill/rendering.py +620 -0
- wmo/distill/rollouts.py +726 -0
- wmo/distill/samples.py +195 -0
- wmo/distill/store.py +829 -0
- wmo/distill/teacher.py +714 -0
- wmo/distill/tokens.py +535 -0
- wmo/distill/tracking.py +552 -0
- wmo/distill/tripwire.py +411 -0
- wmo/distill/xtoken/byte_offsets.py +152 -0
- wmo/distill/xtoken/chunks.py +457 -0
- wmo/distill/xtoken/prompt_logprobs.py +475 -0
- wmo/distill/xtoken/teacher_render.py +346 -0
- wmo/engine/__init__.py +28 -0
- wmo/engine/autoconfig.py +367 -0
- wmo/engine/build.py +346 -0
- wmo/engine/demo.py +77 -0
- wmo/engine/eval_suites.py +245 -0
- wmo/engine/grounding.py +491 -0
- wmo/engine/knowledge.py +291 -0
- wmo/engine/loader.py +36 -0
- wmo/engine/play.py +92 -0
- wmo/engine/prompts.py +99 -0
- wmo/engine/replay.py +443 -0
- wmo/engine/reporting.py +58 -0
- wmo/engine/workspace.py +468 -0
- wmo/engine/world_model.py +568 -0
- wmo/env/__init__.py +22 -0
- wmo/env/base.py +121 -0
- wmo/env/closed_loop.py +229 -0
- wmo/env/episode.py +107 -0
- wmo/env/llm_agent.py +93 -0
- wmo/env/scenarios.py +73 -0
- wmo/evals/__init__.py +52 -0
- wmo/evals/agreement.py +110 -0
- wmo/evals/base.py +45 -0
- wmo/evals/closed_loop.py +480 -0
- wmo/evals/failover.py +96 -0
- wmo/evals/gold.py +127 -0
- wmo/evals/grid.py +394 -0
- wmo/evals/grid_plot.py +205 -0
- wmo/evals/harbor/__init__.py +27 -0
- wmo/evals/harbor/agent.py +573 -0
- wmo/evals/harbor/ctrf.py +171 -0
- wmo/evals/harbor/e2b_environment.py +587 -0
- wmo/evals/harbor/e2b_template_policy.py +144 -0
- wmo/evals/harbor/scorer.py +875 -0
- wmo/evals/harbor/tasks.py +140 -0
- wmo/evals/open_loop.py +194 -0
- wmo/evals/tasks.py +53 -0
- wmo/harness/__init__.py +51 -0
- wmo/harness/code_runtime.py +288 -0
- wmo/harness/create.py +1191 -0
- wmo/harness/delta.py +220 -0
- wmo/harness/doc.py +556 -0
- wmo/harness/e2b_ledger.py +342 -0
- wmo/harness/e2b_reap.py +476 -0
- wmo/harness/e2b_sandbox.py +350 -0
- wmo/harness/environment.py +35 -0
- wmo/harness/live_session.py +543 -0
- wmo/harness/mutate.py +343 -0
- wmo/harness/pi_e2b.py +1710 -0
- wmo/harness/pi_entry/entry.ts +268 -0
- wmo/harness/pi_entry/runner_frames.ts +92 -0
- wmo/harness/pi_entry/runner_live.ts +587 -0
- wmo/harness/pi_entry/runner_service.ts +270 -0
- wmo/harness/pi_entry/runner_stdio.ts +374 -0
- wmo/harness/pi_entry/runner_termination.ts +142 -0
- wmo/harness/pi_local.py +262 -0
- wmo/harness/pi_runtime.py +495 -0
- wmo/harness/pi_vendor.py +65 -0
- wmo/harness/population.py +509 -0
- wmo/harness/project_proposer.py +569 -0
- wmo/harness/proposer.py +977 -0
- wmo/harness/runner_link.py +619 -0
- wmo/harness/runtime.py +389 -0
- wmo/harness/scoring.py +247 -0
- wmo/harness/skills.py +116 -0
- wmo/harness/source_tree.py +319 -0
- wmo/harness/store.py +176 -0
- wmo/harness/tools.py +105 -0
- wmo/harness/vendor/manifest.sha256 +58 -0
- wmo/harness/vendor/pi-agent/CHANGELOG.md +556 -0
- wmo/harness/vendor/pi-agent/LICENSE +21 -0
- wmo/harness/vendor/pi-agent/README.md +488 -0
- wmo/harness/vendor/pi-agent/VENDOR.md +39 -0
- wmo/harness/vendor/pi-agent/docs/agent-harness.md +486 -0
- wmo/harness/vendor/pi-agent/docs/durable-harness.md +212 -0
- wmo/harness/vendor/pi-agent/docs/hooks.md +445 -0
- wmo/harness/vendor/pi-agent/docs/models.md +966 -0
- wmo/harness/vendor/pi-agent/docs/observability.md +376 -0
- wmo/harness/vendor/pi-agent/package.json +60 -0
- wmo/harness/vendor/pi-agent/src/agent-loop.ts +748 -0
- wmo/harness/vendor/pi-agent/src/agent.ts +575 -0
- wmo/harness/vendor/pi-agent/src/harness/agent-harness.ts +1029 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/branch-summarization.ts +261 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/compaction.ts +747 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/utils.ts +144 -0
- wmo/harness/vendor/pi-agent/src/harness/env/nodejs.ts +550 -0
- wmo/harness/vendor/pi-agent/src/harness/messages.ts +164 -0
- wmo/harness/vendor/pi-agent/src/harness/prompt-templates.ts +267 -0
- wmo/harness/vendor/pi-agent/src/harness/session/jsonl-repo.ts +177 -0
- wmo/harness/vendor/pi-agent/src/harness/session/jsonl-storage.ts +293 -0
- wmo/harness/vendor/pi-agent/src/harness/session/memory-repo.ts +50 -0
- wmo/harness/vendor/pi-agent/src/harness/session/memory-storage.ts +131 -0
- wmo/harness/vendor/pi-agent/src/harness/session/repo-utils.ts +51 -0
- wmo/harness/vendor/pi-agent/src/harness/session/session.ts +267 -0
- wmo/harness/vendor/pi-agent/src/harness/session/uuid.ts +54 -0
- wmo/harness/vendor/pi-agent/src/harness/skills.ts +375 -0
- wmo/harness/vendor/pi-agent/src/harness/system-prompt.ts +34 -0
- wmo/harness/vendor/pi-agent/src/harness/types.ts +836 -0
- wmo/harness/vendor/pi-agent/src/harness/utils/shell-output.ts +135 -0
- wmo/harness/vendor/pi-agent/src/harness/utils/truncate.ts +344 -0
- wmo/harness/vendor/pi-agent/src/index.ts +44 -0
- wmo/harness/vendor/pi-agent/src/node.ts +2 -0
- wmo/harness/vendor/pi-agent/src/proxy.ts +367 -0
- wmo/harness/vendor/pi-agent/src/types.ts +428 -0
- wmo/harness/vendor/pi-agent/test/agent-loop.test.ts +1351 -0
- wmo/harness/vendor/pi-agent/test/agent.test.ts +699 -0
- wmo/harness/vendor/pi-agent/test/e2e.test.ts +404 -0
- wmo/harness/vendor/pi-agent/test/harness/agent-harness-stream.test.ts +213 -0
- wmo/harness/vendor/pi-agent/test/harness/agent-harness.test.ts +608 -0
- wmo/harness/vendor/pi-agent/test/harness/compaction.test.ts +655 -0
- wmo/harness/vendor/pi-agent/test/harness/nodejs-env.test.ts +321 -0
- wmo/harness/vendor/pi-agent/test/harness/prompt-templates.test.ts +90 -0
- wmo/harness/vendor/pi-agent/test/harness/repo.test.ts +68 -0
- wmo/harness/vendor/pi-agent/test/harness/resource-formatting.test.ts +24 -0
- wmo/harness/vendor/pi-agent/test/harness/session-test-utils.ts +55 -0
- wmo/harness/vendor/pi-agent/test/harness/session-uuid.test.ts +50 -0
- wmo/harness/vendor/pi-agent/test/harness/session.test.ts +156 -0
- wmo/harness/vendor/pi-agent/test/harness/skills.test.ts +116 -0
- wmo/harness/vendor/pi-agent/test/harness/storage.test.ts +299 -0
- wmo/harness/vendor/pi-agent/test/harness/system-prompt.test.ts +66 -0
- wmo/harness/vendor/pi-agent/test/harness/truncate.test.ts +169 -0
- wmo/harness/vendor/pi-agent/test/scratch/simple.ts +72 -0
- wmo/harness/vendor/pi-agent/test/utils/calculate.ts +32 -0
- wmo/harness/vendor/pi-agent/test/utils/get-current-time.ts +46 -0
- wmo/harness/vendor/pi-agent/tsconfig.build.json +13 -0
- wmo/harness/vendor/pi-agent/vitest.config.ts +19 -0
- wmo/harness/vendor/pi-agent/vitest.harness.config.ts +28 -0
- wmo/harness/vendor/vendor_pi.sh +59 -0
- wmo/harness/workspace_patch.py +270 -0
- wmo/ingest/__init__.py +47 -0
- wmo/ingest/adapter.py +72 -0
- wmo/ingest/base.py +114 -0
- wmo/ingest/braintrust.py +339 -0
- wmo/ingest/detect.py +126 -0
- wmo/ingest/langfuse.py +291 -0
- wmo/ingest/langsmith.py +444 -0
- wmo/ingest/mastra.py +330 -0
- wmo/ingest/messages.py +170 -0
- wmo/ingest/normalize.py +679 -0
- wmo/ingest/otel_genai.py +69 -0
- wmo/ingest/otel_writer.py +100 -0
- wmo/ingest/phoenix.py +150 -0
- wmo/ingest/postgres.py +246 -0
- wmo/ingest/posthog.py +320 -0
- wmo/ingest/quality.py +28 -0
- wmo/ingest/stream.py +209 -0
- wmo/ingest/testdata/sample_otlp.json +60 -0
- wmo/ingest/testdata/sample_spans.jsonl +3 -0
- wmo/optimize/__init__.py +25 -0
- wmo/optimize/base.py +143 -0
- wmo/optimize/gepa.py +806 -0
- wmo/optimize/judge.py +262 -0
- wmo/optimize/judge_quality.py +359 -0
- wmo/optimize/knn.py +468 -0
- wmo/optimize/numeric.py +152 -0
- wmo/optimize/outcomes.py +103 -0
- wmo/optimize/policy.py +669 -0
- wmo/optimize/report.py +231 -0
- wmo/optimize/reward.py +129 -0
- wmo/optimize/routing.py +373 -0
- wmo/platform/__init__.py +6 -0
- wmo/platform/auth.py +115 -0
- wmo/platform/client.py +551 -0
- wmo/platform/credentials.py +126 -0
- wmo/platform/transfer.py +158 -0
- wmo/providers/__init__.py +40 -0
- wmo/providers/_bedrock_chat.py +155 -0
- wmo/providers/_openai_common.py +182 -0
- wmo/providers/_responses_common.py +472 -0
- wmo/providers/anthropic.py +134 -0
- wmo/providers/azure_openai.py +296 -0
- wmo/providers/base.py +300 -0
- wmo/providers/bedrock.py +312 -0
- wmo/providers/models.py +205 -0
- wmo/providers/openai.py +143 -0
- wmo/providers/openai_responses.py +240 -0
- wmo/providers/pool.py +170 -0
- wmo/providers/registry.py +73 -0
- wmo/providers/retry.py +151 -0
- wmo/providers/tinker.py +936 -0
- wmo/providers/waterfall.py +336 -0
- wmo/research/__init__.py +81 -0
- wmo/research/ablation.py +133 -0
- wmo/research/concurrency_plot.py +523 -0
- wmo/research/concurrency_run.py +240 -0
- wmo/research/concurrency_scaling.py +270 -0
- wmo/research/gepa_scaling.py +274 -0
- wmo/research/pipeline.py +198 -0
- wmo/research/scaling_split.py +82 -0
- wmo/research/scenario_fidelity.py +198 -0
- wmo/research/scenario_recovery.py +92 -0
- wmo/research/seed_stability.py +90 -0
- wmo/research/trace_scaling.py +348 -0
- wmo/retrieval/__init__.py +6 -0
- wmo/retrieval/embedders.py +105 -0
- wmo/retrieval/leakfree.py +52 -0
- wmo/retrieval/retriever.py +173 -0
- wmo/scenarios/__init__.py +58 -0
- wmo/scenarios/builder.py +152 -0
- wmo/scenarios/mining/__init__.py +27 -0
- wmo/scenarios/mining/clustering.py +171 -0
- wmo/scenarios/mining/facets.py +226 -0
- wmo/scenarios/mining/selection.py +220 -0
- wmo/scenarios/synthesis/__init__.py +6 -0
- wmo/scenarios/synthesis/scenario_set.py +63 -0
- wmo/scenarios/synthesis/synthesizer.py +85 -0
- wmo/scenarios/verification/__init__.py +17 -0
- wmo/scenarios/verification/judge.py +97 -0
- wmo/scenarios/verification/verify.py +135 -0
- wmo/serving/__init__.py +5 -0
- wmo/serving/builds.py +451 -0
- wmo/serving/chat.py +878 -0
- wmo/serving/endpoint_config.py +64 -0
- wmo/serving/savings.py +250 -0
- wmo/serving/server.py +553 -0
- wmo/serving/traces_source.py +206 -0
- wmo/telemetry.py +213 -0
- wmo/tracking/__init__.py +36 -0
- wmo/tracking/clock.py +24 -0
- wmo/tracking/metered.py +125 -0
- wmo/tracking/pricing.py +99 -0
- wmo/tracking/store.py +31 -0
- wmo/tracking/tracker.py +149 -0
- world_model_optimizer-0.2.0.dist-info/METADATA +203 -0
- world_model_optimizer-0.2.0.dist-info/RECORD +308 -0
- world_model_optimizer-0.2.0.dist-info/WHEEL +4 -0
- world_model_optimizer-0.2.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { ImageContent, Message, TextContent } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { AgentMessage } from "../types.ts";
|
|
3
|
+
|
|
4
|
+
export const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:
|
|
5
|
+
|
|
6
|
+
<summary>
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
export const COMPACTION_SUMMARY_SUFFIX = `
|
|
10
|
+
</summary>`;
|
|
11
|
+
|
|
12
|
+
export const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch that this conversation came back from:
|
|
13
|
+
|
|
14
|
+
<summary>
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
export const BRANCH_SUMMARY_SUFFIX = `</summary>`;
|
|
18
|
+
|
|
19
|
+
export interface BashExecutionMessage {
|
|
20
|
+
role: "bashExecution";
|
|
21
|
+
command: string;
|
|
22
|
+
output: string;
|
|
23
|
+
exitCode: number | undefined;
|
|
24
|
+
cancelled: boolean;
|
|
25
|
+
truncated: boolean;
|
|
26
|
+
fullOutputPath?: string;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
excludeFromContext?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CustomMessage<T = unknown> {
|
|
32
|
+
role: "custom";
|
|
33
|
+
customType: string;
|
|
34
|
+
content: string | (TextContent | ImageContent)[];
|
|
35
|
+
display: boolean;
|
|
36
|
+
details?: T;
|
|
37
|
+
timestamp: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface BranchSummaryMessage {
|
|
41
|
+
role: "branchSummary";
|
|
42
|
+
summary: string;
|
|
43
|
+
fromId: string;
|
|
44
|
+
timestamp: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface CompactionSummaryMessage {
|
|
48
|
+
role: "compactionSummary";
|
|
49
|
+
summary: string;
|
|
50
|
+
tokensBefore: number;
|
|
51
|
+
timestamp: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare module "../types.ts" {
|
|
55
|
+
interface CustomAgentMessages {
|
|
56
|
+
bashExecution: BashExecutionMessage;
|
|
57
|
+
custom: CustomMessage;
|
|
58
|
+
branchSummary: BranchSummaryMessage;
|
|
59
|
+
compactionSummary: CompactionSummaryMessage;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function bashExecutionToText(msg: BashExecutionMessage): string {
|
|
64
|
+
let text = `Ran \`${msg.command}\`\n`;
|
|
65
|
+
if (msg.output) {
|
|
66
|
+
text += `\`\`\`\n${msg.output}\n\`\`\``;
|
|
67
|
+
} else {
|
|
68
|
+
text += "(no output)";
|
|
69
|
+
}
|
|
70
|
+
if (msg.cancelled) {
|
|
71
|
+
text += "\n\n(command cancelled)";
|
|
72
|
+
} else if (msg.exitCode !== null && msg.exitCode !== undefined && msg.exitCode !== 0) {
|
|
73
|
+
text += `\n\nCommand exited with code ${msg.exitCode}`;
|
|
74
|
+
}
|
|
75
|
+
if (msg.truncated && msg.fullOutputPath) {
|
|
76
|
+
text += `\n\n[Output truncated. Full output: ${msg.fullOutputPath}]`;
|
|
77
|
+
}
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createBranchSummaryMessage(summary: string, fromId: string, timestamp: string): BranchSummaryMessage {
|
|
82
|
+
return {
|
|
83
|
+
role: "branchSummary",
|
|
84
|
+
summary,
|
|
85
|
+
fromId,
|
|
86
|
+
timestamp: new Date(timestamp).getTime(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createCompactionSummaryMessage(
|
|
91
|
+
summary: string,
|
|
92
|
+
tokensBefore: number,
|
|
93
|
+
timestamp: string,
|
|
94
|
+
): CompactionSummaryMessage {
|
|
95
|
+
return {
|
|
96
|
+
role: "compactionSummary",
|
|
97
|
+
summary,
|
|
98
|
+
tokensBefore,
|
|
99
|
+
timestamp: new Date(timestamp).getTime(),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function createCustomMessage(
|
|
104
|
+
customType: string,
|
|
105
|
+
content: string | (TextContent | ImageContent)[],
|
|
106
|
+
display: boolean,
|
|
107
|
+
details: unknown | undefined,
|
|
108
|
+
timestamp: string,
|
|
109
|
+
): CustomMessage {
|
|
110
|
+
return {
|
|
111
|
+
role: "custom",
|
|
112
|
+
customType,
|
|
113
|
+
content,
|
|
114
|
+
display,
|
|
115
|
+
details,
|
|
116
|
+
timestamp: new Date(timestamp).getTime(),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
121
|
+
return messages
|
|
122
|
+
.map((m): Message | undefined => {
|
|
123
|
+
switch (m.role) {
|
|
124
|
+
case "bashExecution":
|
|
125
|
+
if (m.excludeFromContext) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
role: "user",
|
|
130
|
+
content: [{ type: "text", text: bashExecutionToText(m) }],
|
|
131
|
+
timestamp: m.timestamp,
|
|
132
|
+
};
|
|
133
|
+
case "custom": {
|
|
134
|
+
const content = typeof m.content === "string" ? [{ type: "text" as const, text: m.content }] : m.content;
|
|
135
|
+
return {
|
|
136
|
+
role: "user",
|
|
137
|
+
content,
|
|
138
|
+
timestamp: m.timestamp,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
case "branchSummary":
|
|
142
|
+
return {
|
|
143
|
+
role: "user",
|
|
144
|
+
content: [{ type: "text" as const, text: BRANCH_SUMMARY_PREFIX + m.summary + BRANCH_SUMMARY_SUFFIX }],
|
|
145
|
+
timestamp: m.timestamp,
|
|
146
|
+
};
|
|
147
|
+
case "compactionSummary":
|
|
148
|
+
return {
|
|
149
|
+
role: "user",
|
|
150
|
+
content: [
|
|
151
|
+
{ type: "text" as const, text: COMPACTION_SUMMARY_PREFIX + m.summary + COMPACTION_SUMMARY_SUFFIX },
|
|
152
|
+
],
|
|
153
|
+
timestamp: m.timestamp,
|
|
154
|
+
};
|
|
155
|
+
case "user":
|
|
156
|
+
case "assistant":
|
|
157
|
+
case "toolResult":
|
|
158
|
+
return m;
|
|
159
|
+
default:
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
.filter((m): m is Message => m !== undefined);
|
|
164
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { parse } from "yaml";
|
|
2
|
+
import { type ExecutionEnv, type FileInfo, type PromptTemplate, type Result, toError } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
export type PromptTemplateDiagnosticCode = "file_info_failed" | "list_failed" | "read_failed" | "parse_failed";
|
|
5
|
+
|
|
6
|
+
/** Warning produced while loading prompt templates. */
|
|
7
|
+
export interface PromptTemplateDiagnostic {
|
|
8
|
+
/** Diagnostic severity. Currently only warnings are emitted. */
|
|
9
|
+
type: "warning";
|
|
10
|
+
/** Stable diagnostic code. */
|
|
11
|
+
code: PromptTemplateDiagnosticCode;
|
|
12
|
+
/** Human-readable diagnostic message. */
|
|
13
|
+
message: string;
|
|
14
|
+
/** Path associated with the diagnostic. */
|
|
15
|
+
path: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface PromptTemplateFrontmatter {
|
|
19
|
+
description?: string;
|
|
20
|
+
"argument-hint"?: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Load prompt templates from one or more paths.
|
|
26
|
+
*
|
|
27
|
+
* Directory inputs load direct `.md` children non-recursively. File inputs load explicit `.md` files. Missing paths and
|
|
28
|
+
* non-markdown files are skipped. Read and parse failures are returned as diagnostics.
|
|
29
|
+
*/
|
|
30
|
+
export async function loadPromptTemplates(
|
|
31
|
+
env: ExecutionEnv,
|
|
32
|
+
paths: string | string[],
|
|
33
|
+
): Promise<{ promptTemplates: PromptTemplate[]; diagnostics: PromptTemplateDiagnostic[] }> {
|
|
34
|
+
const promptTemplates: PromptTemplate[] = [];
|
|
35
|
+
const diagnostics: PromptTemplateDiagnostic[] = [];
|
|
36
|
+
for (const path of Array.isArray(paths) ? paths : [paths]) {
|
|
37
|
+
const infoResult = await env.fileInfo(path);
|
|
38
|
+
if (!infoResult.ok) {
|
|
39
|
+
if (infoResult.error.code !== "not_found") {
|
|
40
|
+
diagnostics.push({
|
|
41
|
+
type: "warning",
|
|
42
|
+
code: "file_info_failed",
|
|
43
|
+
message: infoResult.error.message,
|
|
44
|
+
path,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const info = infoResult.value;
|
|
50
|
+
const kind = await resolveKind(env, info, diagnostics);
|
|
51
|
+
if (kind === "directory") {
|
|
52
|
+
const result = await loadTemplatesFromDir(env, info.path);
|
|
53
|
+
promptTemplates.push(...result.promptTemplates);
|
|
54
|
+
diagnostics.push(...result.diagnostics);
|
|
55
|
+
} else if (kind === "file" && info.name.endsWith(".md")) {
|
|
56
|
+
const result = await loadTemplateFromFile(env, info.path);
|
|
57
|
+
if (result.promptTemplate) promptTemplates.push(result.promptTemplate);
|
|
58
|
+
diagnostics.push(...result.diagnostics);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { promptTemplates, diagnostics };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Load prompt templates from source-tagged paths.
|
|
66
|
+
*
|
|
67
|
+
* Source values are preserved exactly and attached to every loaded prompt template and diagnostic. The agent package does
|
|
68
|
+
* not interpret source values; applications define their own provenance shape.
|
|
69
|
+
*/
|
|
70
|
+
export async function loadSourcedPromptTemplates<TSource, TPromptTemplate extends PromptTemplate = PromptTemplate>(
|
|
71
|
+
env: ExecutionEnv,
|
|
72
|
+
inputs: Array<{ path: string; source: TSource }>,
|
|
73
|
+
mapPromptTemplate?: (promptTemplate: PromptTemplate, source: TSource) => TPromptTemplate,
|
|
74
|
+
): Promise<{
|
|
75
|
+
promptTemplates: Array<{ promptTemplate: TPromptTemplate; source: TSource }>;
|
|
76
|
+
diagnostics: Array<PromptTemplateDiagnostic & { source: TSource }>;
|
|
77
|
+
}> {
|
|
78
|
+
const promptTemplates: Array<{ promptTemplate: TPromptTemplate; source: TSource }> = [];
|
|
79
|
+
const diagnostics: Array<PromptTemplateDiagnostic & { source: TSource }> = [];
|
|
80
|
+
for (const input of inputs) {
|
|
81
|
+
const result = await loadPromptTemplates(env, input.path);
|
|
82
|
+
for (const promptTemplate of result.promptTemplates) {
|
|
83
|
+
promptTemplates.push({
|
|
84
|
+
promptTemplate: mapPromptTemplate
|
|
85
|
+
? mapPromptTemplate(promptTemplate, input.source)
|
|
86
|
+
: (promptTemplate as TPromptTemplate),
|
|
87
|
+
source: input.source,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
for (const diagnostic of result.diagnostics) diagnostics.push({ ...diagnostic, source: input.source });
|
|
91
|
+
}
|
|
92
|
+
return { promptTemplates, diagnostics };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function loadTemplatesFromDir(
|
|
96
|
+
env: ExecutionEnv,
|
|
97
|
+
dir: string,
|
|
98
|
+
): Promise<{ promptTemplates: PromptTemplate[]; diagnostics: PromptTemplateDiagnostic[] }> {
|
|
99
|
+
const promptTemplates: PromptTemplate[] = [];
|
|
100
|
+
const diagnostics: PromptTemplateDiagnostic[] = [];
|
|
101
|
+
const entriesResult = await env.listDir(dir);
|
|
102
|
+
if (!entriesResult.ok) {
|
|
103
|
+
diagnostics.push({
|
|
104
|
+
type: "warning",
|
|
105
|
+
code: "list_failed",
|
|
106
|
+
message: entriesResult.error.message,
|
|
107
|
+
path: dir,
|
|
108
|
+
});
|
|
109
|
+
return { promptTemplates, diagnostics };
|
|
110
|
+
}
|
|
111
|
+
const entries = entriesResult.value;
|
|
112
|
+
|
|
113
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
114
|
+
const kind = await resolveKind(env, entry, diagnostics);
|
|
115
|
+
if (kind !== "file" || !entry.name.endsWith(".md")) continue;
|
|
116
|
+
const result = await loadTemplateFromFile(env, entry.path);
|
|
117
|
+
if (result.promptTemplate) promptTemplates.push(result.promptTemplate);
|
|
118
|
+
diagnostics.push(...result.diagnostics);
|
|
119
|
+
}
|
|
120
|
+
return { promptTemplates, diagnostics };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function loadTemplateFromFile(
|
|
124
|
+
env: ExecutionEnv,
|
|
125
|
+
filePath: string,
|
|
126
|
+
): Promise<{ promptTemplate: PromptTemplate | null; diagnostics: PromptTemplateDiagnostic[] }> {
|
|
127
|
+
const diagnostics: PromptTemplateDiagnostic[] = [];
|
|
128
|
+
const rawContent = await env.readTextFile(filePath);
|
|
129
|
+
if (!rawContent.ok) {
|
|
130
|
+
diagnostics.push({
|
|
131
|
+
type: "warning",
|
|
132
|
+
code: "read_failed",
|
|
133
|
+
message: rawContent.error.message,
|
|
134
|
+
path: filePath,
|
|
135
|
+
});
|
|
136
|
+
return { promptTemplate: null, diagnostics };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const parsed = parseFrontmatter<PromptTemplateFrontmatter>(rawContent.value);
|
|
140
|
+
if (!parsed.ok) {
|
|
141
|
+
diagnostics.push({
|
|
142
|
+
type: "warning",
|
|
143
|
+
code: "parse_failed",
|
|
144
|
+
message: parsed.error.message,
|
|
145
|
+
path: filePath,
|
|
146
|
+
});
|
|
147
|
+
return { promptTemplate: null, diagnostics };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const { frontmatter, body } = parsed.value;
|
|
151
|
+
const firstLine = body.split("\n").find((line) => line.trim());
|
|
152
|
+
let description = typeof frontmatter.description === "string" ? frontmatter.description : "";
|
|
153
|
+
if (!description && firstLine) {
|
|
154
|
+
description = firstLine.slice(0, 60);
|
|
155
|
+
if (firstLine.length > 60) description += "...";
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
promptTemplate: {
|
|
159
|
+
name: basenameEnvPath(filePath).replace(/\.md$/i, ""),
|
|
160
|
+
description,
|
|
161
|
+
content: body,
|
|
162
|
+
},
|
|
163
|
+
diagnostics,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function resolveKind(
|
|
168
|
+
env: ExecutionEnv,
|
|
169
|
+
info: FileInfo,
|
|
170
|
+
diagnostics: PromptTemplateDiagnostic[],
|
|
171
|
+
): Promise<"file" | "directory" | undefined> {
|
|
172
|
+
if (info.kind === "file" || info.kind === "directory") return info.kind;
|
|
173
|
+
const canonicalPath = await env.canonicalPath(info.path);
|
|
174
|
+
if (!canonicalPath.ok) {
|
|
175
|
+
if (canonicalPath.error.code !== "not_found") {
|
|
176
|
+
diagnostics.push({
|
|
177
|
+
type: "warning",
|
|
178
|
+
code: "file_info_failed",
|
|
179
|
+
message: canonicalPath.error.message,
|
|
180
|
+
path: info.path,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
const target = await env.fileInfo(canonicalPath.value);
|
|
186
|
+
if (!target.ok) {
|
|
187
|
+
if (target.error.code !== "not_found") {
|
|
188
|
+
diagnostics.push({
|
|
189
|
+
type: "warning",
|
|
190
|
+
code: "file_info_failed",
|
|
191
|
+
message: target.error.message,
|
|
192
|
+
path: info.path,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
return target.value.kind === "file" || target.value.kind === "directory" ? target.value.kind : undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function parseFrontmatter<T extends Record<string, unknown>>(
|
|
201
|
+
content: string,
|
|
202
|
+
): Result<{ frontmatter: T; body: string }, Error> {
|
|
203
|
+
try {
|
|
204
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
205
|
+
if (!normalized.startsWith("---")) return { ok: true, value: { frontmatter: {} as T, body: normalized } };
|
|
206
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
207
|
+
if (endIndex === -1) return { ok: true, value: { frontmatter: {} as T, body: normalized } };
|
|
208
|
+
const yamlString = normalized.slice(4, endIndex);
|
|
209
|
+
const body = normalized.slice(endIndex + 4).trim();
|
|
210
|
+
return { ok: true, value: { frontmatter: (parse(yamlString) ?? {}) as T, body } };
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return { ok: false, error: toError(error) };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function basenameEnvPath(path: string): string {
|
|
217
|
+
const normalized = path.replace(/\/+$/, "");
|
|
218
|
+
const slashIndex = normalized.lastIndexOf("/");
|
|
219
|
+
return slashIndex === -1 ? normalized : normalized.slice(slashIndex + 1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Parse an argument string using simple shell-style single and double quotes. */
|
|
223
|
+
export function parseCommandArgs(argsString: string): string[] {
|
|
224
|
+
const args: string[] = [];
|
|
225
|
+
let current = "";
|
|
226
|
+
let inQuote: string | null = null;
|
|
227
|
+
|
|
228
|
+
for (let i = 0; i < argsString.length; i++) {
|
|
229
|
+
const char = argsString[i]!;
|
|
230
|
+
if (inQuote) {
|
|
231
|
+
if (char === inQuote) inQuote = null;
|
|
232
|
+
else current += char;
|
|
233
|
+
} else if (char === '"' || char === "'") {
|
|
234
|
+
inQuote = char;
|
|
235
|
+
} else if (char === " " || char === "\t") {
|
|
236
|
+
if (current) {
|
|
237
|
+
args.push(current);
|
|
238
|
+
current = "";
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
current += char;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (current) args.push(current);
|
|
245
|
+
return args;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Substitute prompt template placeholders (`$1`, `$@`, `$ARGUMENTS`, `${@:N}`, `${@:N:L}`) with command arguments. */
|
|
249
|
+
export function substituteArgs(content: string, args: string[]): string {
|
|
250
|
+
let result = content;
|
|
251
|
+
result = result.replace(/\$(\d+)/g, (_, num: string) => args[parseInt(num, 10) - 1] ?? "");
|
|
252
|
+
result = result.replace(/\$\{@:(\d+)(?::(\d+))?\}/g, (_, startStr: string, lengthStr?: string) => {
|
|
253
|
+
let start = parseInt(startStr, 10) - 1;
|
|
254
|
+
if (start < 0) start = 0;
|
|
255
|
+
if (lengthStr) return args.slice(start, start + parseInt(lengthStr, 10)).join(" ");
|
|
256
|
+
return args.slice(start).join(" ");
|
|
257
|
+
});
|
|
258
|
+
const allArgs = args.join(" ");
|
|
259
|
+
result = result.replace(/\$ARGUMENTS/g, allArgs);
|
|
260
|
+
result = result.replace(/\$@/g, allArgs);
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Format a prompt template invocation with positional arguments. */
|
|
265
|
+
export function formatPromptTemplateInvocation(template: PromptTemplate, args: string[] = []): string {
|
|
266
|
+
return substituteArgs(template.content, args);
|
|
267
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FileSystem,
|
|
3
|
+
JsonlSessionCreateOptions,
|
|
4
|
+
JsonlSessionListOptions,
|
|
5
|
+
JsonlSessionMetadata,
|
|
6
|
+
JsonlSessionRepoApi,
|
|
7
|
+
Session,
|
|
8
|
+
} from "../types.ts";
|
|
9
|
+
import { SessionError, toError } from "../types.ts";
|
|
10
|
+
import { JsonlSessionStorage, loadJsonlSessionMetadata } from "./jsonl-storage.ts";
|
|
11
|
+
import {
|
|
12
|
+
createSessionId,
|
|
13
|
+
createTimestamp,
|
|
14
|
+
getEntriesToFork,
|
|
15
|
+
getFileSystemResultOrThrow,
|
|
16
|
+
toSession,
|
|
17
|
+
} from "./repo-utils.ts";
|
|
18
|
+
|
|
19
|
+
type JsonlSessionRepoFileSystem = Pick<
|
|
20
|
+
FileSystem,
|
|
21
|
+
| "cwd"
|
|
22
|
+
| "absolutePath"
|
|
23
|
+
| "joinPath"
|
|
24
|
+
| "readTextFile"
|
|
25
|
+
| "readTextLines"
|
|
26
|
+
| "writeFile"
|
|
27
|
+
| "appendFile"
|
|
28
|
+
| "listDir"
|
|
29
|
+
| "exists"
|
|
30
|
+
| "createDir"
|
|
31
|
+
| "remove"
|
|
32
|
+
>;
|
|
33
|
+
|
|
34
|
+
function encodeCwd(cwd: string): string {
|
|
35
|
+
return `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class JsonlSessionRepo implements JsonlSessionRepoApi {
|
|
39
|
+
private readonly fs: JsonlSessionRepoFileSystem;
|
|
40
|
+
private readonly sessionsRootInput: string;
|
|
41
|
+
private sessionsRoot: string | undefined;
|
|
42
|
+
|
|
43
|
+
constructor(options: { fs: JsonlSessionRepoFileSystem; sessionsRoot: string }) {
|
|
44
|
+
this.fs = options.fs;
|
|
45
|
+
this.sessionsRootInput = options.sessionsRoot;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private async getSessionsRoot(): Promise<string> {
|
|
49
|
+
if (!this.sessionsRoot) {
|
|
50
|
+
this.sessionsRoot = getFileSystemResultOrThrow(
|
|
51
|
+
await this.fs.absolutePath(this.sessionsRootInput),
|
|
52
|
+
`Failed to resolve sessions root ${this.sessionsRootInput}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return this.sessionsRoot;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private async getSessionDir(cwd: string): Promise<string> {
|
|
59
|
+
return getFileSystemResultOrThrow(
|
|
60
|
+
await this.fs.joinPath([await this.getSessionsRoot(), encodeCwd(cwd)]),
|
|
61
|
+
`Failed to resolve session directory for ${cwd}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async createSessionFilePath(cwd: string, sessionId: string, timestamp: string): Promise<string> {
|
|
66
|
+
return getFileSystemResultOrThrow(
|
|
67
|
+
await this.fs.joinPath([
|
|
68
|
+
await this.getSessionDir(cwd),
|
|
69
|
+
`${timestamp.replace(/[:.]/g, "-")}_${sessionId}.jsonl`,
|
|
70
|
+
]),
|
|
71
|
+
`Failed to resolve session file path for ${sessionId}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async create(options: JsonlSessionCreateOptions): Promise<Session<JsonlSessionMetadata>> {
|
|
76
|
+
const id = options.id ?? createSessionId();
|
|
77
|
+
const createdAt = createTimestamp();
|
|
78
|
+
const sessionDir = await this.getSessionDir(options.cwd);
|
|
79
|
+
getFileSystemResultOrThrow(
|
|
80
|
+
await this.fs.createDir(sessionDir, { recursive: true }),
|
|
81
|
+
`Failed to create session directory ${sessionDir}`,
|
|
82
|
+
);
|
|
83
|
+
const filePath = await this.createSessionFilePath(options.cwd, id, createdAt);
|
|
84
|
+
const storage = await JsonlSessionStorage.create(this.fs, filePath, {
|
|
85
|
+
cwd: options.cwd,
|
|
86
|
+
sessionId: id,
|
|
87
|
+
parentSessionPath: options.parentSessionPath,
|
|
88
|
+
});
|
|
89
|
+
return toSession(storage);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async open(metadata: JsonlSessionMetadata): Promise<Session<JsonlSessionMetadata>> {
|
|
93
|
+
if (
|
|
94
|
+
!getFileSystemResultOrThrow(await this.fs.exists(metadata.path), `Failed to check session ${metadata.path}`)
|
|
95
|
+
) {
|
|
96
|
+
throw new SessionError("not_found", `Session not found: ${metadata.path}`);
|
|
97
|
+
}
|
|
98
|
+
const storage = await JsonlSessionStorage.open(this.fs, metadata.path);
|
|
99
|
+
return toSession(storage);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async list(options: JsonlSessionListOptions = {}): Promise<JsonlSessionMetadata[]> {
|
|
103
|
+
const dirs = options.cwd ? [await this.getSessionDir(options.cwd)] : await this.listSessionDirs();
|
|
104
|
+
const sessions: JsonlSessionMetadata[] = [];
|
|
105
|
+
for (const dir of dirs) {
|
|
106
|
+
if (!getFileSystemResultOrThrow(await this.fs.exists(dir), `Failed to check session directory ${dir}`)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const files = getFileSystemResultOrThrow(
|
|
110
|
+
await this.fs.listDir(dir),
|
|
111
|
+
`Failed to list sessions in ${dir}`,
|
|
112
|
+
).filter((file) => file.kind !== "directory" && file.name.endsWith(".jsonl"));
|
|
113
|
+
for (const file of files) {
|
|
114
|
+
try {
|
|
115
|
+
sessions.push(await loadJsonlSessionMetadata(this.fs, file.path));
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const cause = toError(error);
|
|
118
|
+
if (!(cause instanceof SessionError) || cause.code !== "invalid_session") throw cause;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
123
|
+
return sessions;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async delete(metadata: JsonlSessionMetadata): Promise<void> {
|
|
127
|
+
getFileSystemResultOrThrow(
|
|
128
|
+
await this.fs.remove(metadata.path, { force: true }),
|
|
129
|
+
`Failed to delete session ${metadata.path}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async fork(
|
|
134
|
+
sourceMetadata: JsonlSessionMetadata,
|
|
135
|
+
options: JsonlSessionCreateOptions & { entryId?: string; position?: "before" | "at"; id?: string },
|
|
136
|
+
): Promise<Session<JsonlSessionMetadata>> {
|
|
137
|
+
const source = await this.open(sourceMetadata);
|
|
138
|
+
const forkedEntries = await getEntriesToFork(source.getStorage(), options);
|
|
139
|
+
const id = options.id ?? createSessionId();
|
|
140
|
+
const createdAt = createTimestamp();
|
|
141
|
+
const sessionDir = await this.getSessionDir(options.cwd);
|
|
142
|
+
getFileSystemResultOrThrow(
|
|
143
|
+
await this.fs.createDir(sessionDir, { recursive: true }),
|
|
144
|
+
`Failed to create session directory ${sessionDir}`,
|
|
145
|
+
);
|
|
146
|
+
const storage = await JsonlSessionStorage.create(
|
|
147
|
+
this.fs,
|
|
148
|
+
await this.createSessionFilePath(options.cwd, id, createdAt),
|
|
149
|
+
{
|
|
150
|
+
cwd: options.cwd,
|
|
151
|
+
sessionId: id,
|
|
152
|
+
parentSessionPath: options.parentSessionPath ?? sourceMetadata.path,
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
for (const entry of forkedEntries) {
|
|
156
|
+
await storage.appendEntry(entry);
|
|
157
|
+
}
|
|
158
|
+
return toSession(storage);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private async listSessionDirs(): Promise<string[]> {
|
|
162
|
+
const sessionsRoot = await this.getSessionsRoot();
|
|
163
|
+
if (
|
|
164
|
+
!getFileSystemResultOrThrow(
|
|
165
|
+
await this.fs.exists(sessionsRoot),
|
|
166
|
+
`Failed to check sessions root ${sessionsRoot}`,
|
|
167
|
+
)
|
|
168
|
+
) {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
const entries = getFileSystemResultOrThrow(
|
|
172
|
+
await this.fs.listDir(sessionsRoot),
|
|
173
|
+
`Failed to list sessions root ${sessionsRoot}`,
|
|
174
|
+
);
|
|
175
|
+
return entries.filter((entry) => entry.kind === "directory").map((entry) => entry.path);
|
|
176
|
+
}
|
|
177
|
+
}
|