@arnilo/prism 0.0.1 → 0.0.2

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 (120) hide show
  1. package/CHANGELOG.md +4 -2
  2. package/README.md +17 -7
  3. package/dist/agent-definitions.d.ts +12 -0
  4. package/dist/agent-definitions.js +131 -0
  5. package/dist/agent-loops.d.ts +14 -0
  6. package/dist/agent-loops.js +161 -0
  7. package/dist/agents.js +263 -76
  8. package/dist/cache-helpers.d.ts +28 -0
  9. package/dist/cache-helpers.js +73 -0
  10. package/dist/cli-runner.d.ts +38 -2
  11. package/dist/cli-runner.js +167 -5
  12. package/dist/compaction.js +2 -0
  13. package/dist/config.js +47 -12
  14. package/dist/contracts.d.ts +581 -6
  15. package/dist/contracts.js +41 -1
  16. package/dist/contribution-parsing.d.ts +19 -0
  17. package/dist/contribution-parsing.js +124 -0
  18. package/dist/contributions.d.ts +13 -3
  19. package/dist/contributions.js +96 -20
  20. package/dist/extensions.js +3 -0
  21. package/dist/index.d.ts +19 -9
  22. package/dist/index.js +10 -4
  23. package/dist/input.d.ts +7 -1
  24. package/dist/input.js +52 -11
  25. package/dist/instruction-injection.d.ts +28 -0
  26. package/dist/instruction-injection.js +55 -0
  27. package/dist/manifests.d.ts +1 -1
  28. package/dist/manifests.js +3 -3
  29. package/dist/models.d.ts +4 -1
  30. package/dist/models.js +5 -2
  31. package/dist/node/agent-definitions.d.ts +98 -0
  32. package/dist/node/agent-definitions.js +389 -0
  33. package/dist/node/contribution-discovery.d.ts +17 -0
  34. package/dist/node/contribution-discovery.js +163 -0
  35. package/dist/node/instruction-injectors.d.ts +32 -0
  36. package/dist/node/instruction-injectors.js +72 -0
  37. package/dist/node/session-store-jsonl.d.ts +1 -1
  38. package/dist/node/session-store-jsonl.js +42 -4
  39. package/dist/node/system-project-prompts.d.ts +30 -0
  40. package/dist/node/system-project-prompts.js +53 -0
  41. package/dist/provider-events.d.ts +3 -1
  42. package/dist/provider-events.js +34 -0
  43. package/dist/provider-request-policy.js +15 -1
  44. package/dist/providers/openai-compatible.js +1 -1
  45. package/dist/providers.d.ts +6 -2
  46. package/dist/providers.js +15 -1
  47. package/dist/redaction.d.ts +2 -1
  48. package/dist/redaction.js +3 -0
  49. package/dist/registry-options.d.ts +5 -0
  50. package/dist/registry-options.js +5 -0
  51. package/dist/rpc.d.ts +6 -2
  52. package/dist/rpc.js +71 -13
  53. package/dist/session-stores.d.ts +3 -1
  54. package/dist/session-stores.js +67 -6
  55. package/dist/skills.d.ts +4 -1
  56. package/dist/skills.js +3 -1
  57. package/dist/system-prompts.js +6 -2
  58. package/dist/testing/compaction-conformance.d.ts +17 -0
  59. package/dist/testing/compaction-conformance.js +61 -0
  60. package/dist/testing/extension-conformance.d.ts +26 -0
  61. package/dist/testing/extension-conformance.js +55 -0
  62. package/dist/testing/provider-conformance.d.ts +7 -0
  63. package/dist/testing/provider-conformance.js +18 -31
  64. package/dist/testing/session-store-conformance.d.ts +20 -0
  65. package/dist/testing/session-store-conformance.js +92 -0
  66. package/dist/testing/tool-conformance.d.ts +39 -0
  67. package/dist/testing/tool-conformance.js +79 -0
  68. package/dist/tools.d.ts +7 -2
  69. package/dist/tools.js +50 -13
  70. package/docs/agent-definitions.md +251 -0
  71. package/docs/agent-events.md +199 -0
  72. package/docs/agent-loops.md +217 -0
  73. package/docs/agent-session-runtime.md +20 -8
  74. package/docs/cli-rpc.md +39 -4
  75. package/docs/compaction-and-retry.md +2 -2
  76. package/docs/compaction-conformance.md +76 -0
  77. package/docs/compaction-llm.md +6 -3
  78. package/docs/compaction-observational-memory.md +4 -4
  79. package/docs/configuration-and-manifests.md +6 -1
  80. package/docs/context-and-skills.md +79 -6
  81. package/docs/contribution-discovery.md +149 -0
  82. package/docs/contribution-registries.md +9 -6
  83. package/docs/credentials-and-redaction.md +2 -0
  84. package/docs/customization.md +191 -0
  85. package/docs/database-persistence.md +407 -0
  86. package/docs/extension-authoring.md +193 -0
  87. package/docs/extension-conformance.md +80 -0
  88. package/docs/extensions.md +6 -0
  89. package/docs/host-security.md +141 -0
  90. package/docs/index.md +40 -19
  91. package/docs/input-and-prompt-assembly.md +19 -3
  92. package/docs/instruction-injection.md +183 -0
  93. package/docs/migration.md +201 -0
  94. package/docs/model-registry.md +122 -0
  95. package/docs/node-jsonl-session-store.md +5 -4
  96. package/docs/performance.md +127 -0
  97. package/docs/provider-caching.md +206 -0
  98. package/docs/provider-conformance.md +32 -5
  99. package/docs/provider-layer.md +51 -11
  100. package/docs/provider-packages.md +65 -5
  101. package/docs/provider-request-policies.md +113 -0
  102. package/docs/providers/kimi.md +22 -0
  103. package/docs/providers/neuralwatt.md +388 -0
  104. package/docs/providers/openai-compatible.md +1 -0
  105. package/docs/providers/openai.md +21 -0
  106. package/docs/providers/opencode-go.md +31 -3
  107. package/docs/providers/openrouter.md +29 -0
  108. package/docs/providers/zai.md +17 -0
  109. package/docs/public-contracts.md +87 -12
  110. package/docs/release-and-install.md +76 -26
  111. package/docs/runs-and-usage.md +236 -0
  112. package/docs/session-store-conformance.md +78 -0
  113. package/docs/session-stores-and-branching.md +10 -6
  114. package/docs/session-stores.md +126 -0
  115. package/docs/settings-auth-trust-security.md +18 -4
  116. package/docs/structured-output.md +247 -0
  117. package/docs/system-prompts.md +104 -2
  118. package/docs/tool-conformance.md +87 -0
  119. package/docs/tools.md +64 -8
  120. package/package.json +35 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.2] - 2026-07-05
11
+
10
12
  ### Added
11
13
 
12
14
  - Added `LICENSE` (MIT) and `CHANGELOG.md` to the published `prism` package.
@@ -19,8 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
21
  `dist/**/*.map` from published tarballs; source maps remain emitted locally
20
22
  for debugging but are no longer shipped.
21
23
  - Core tarball now ships the `/docs` hub.
22
- - Made `prism` a required peer dependency for all first-party workspace packages; it is no longer optional. The peer range remains `0.0.1` and will widen to `^1.0.0` at the 1.x stable release.
23
- - Pinned the no-network `npm test` budget at < 30s on Node 20 (measured baseline ~22s); recorded in `roadmap.md` Phase 17.
24
+ - Made `prism` a required peer dependency for all first-party workspace packages; it is no longer optional. The peer range remains `0.0.2` and will widen to `^1.0.0` at the 1.x stable release.
25
+ - Pinned the no-network `npm test` budget at < 60s on Node 20 (measured baseline ~45s) after the default suite grew to include every first-party package, offline install smoke, packaging guards, docs examples, and workspace tests.
24
26
 
25
27
  ## [0.0.1] - 2026-06-22
26
28
 
package/README.md CHANGED
@@ -13,10 +13,14 @@ packages. Prism defines contracts, not apps.
13
13
  compact, and navigate branches.
14
14
  - **Providers and models**: provider/model registries, provider event helpers,
15
15
  credential redaction helpers, mock provider, and an optional
16
- OpenAI-compatible provider subpath.
16
+ OpenAI-compatible provider subpath. Cache support is provider-specific:
17
+ OpenAI/OpenRouter use best-effort explicit cache hints, NeuralWatt uses
18
+ best-effort implicit prefix caching, and other providers have route/model-specific
19
+ or no cache-control support; see [docs/provider-caching.md](docs/provider-caching.md).
17
20
  - **First-party packages**: `@arnilo/prism-provider-openai`, `@arnilo/prism-provider-opencode-go`,
18
21
  `@arnilo/prism-provider-openrouter`, `@arnilo/prism-provider-zai`, `@arnilo/prism-provider-kimi`,
19
- `@arnilo/prism-compaction-llm`, and `@arnilo/prism-compaction-observational-memory`.
22
+ `@arnilo/prism-provider-neuralwatt`, `@arnilo/prism-compaction-llm`, and
23
+ `@arnilo/prism-compaction-observational-memory`.
20
24
  - **Tools, context, skills**: host-owned tool registry with allow/deny filtering
21
25
  and dispatch, context providers, and a skill registry with progressive
22
26
  disclosure.
@@ -63,11 +67,16 @@ const agent = createAgent({
63
67
 
64
68
  const session = createAgentSession({ agent });
65
69
 
70
+ // Consume the event stream concurrently with the run. `subscribe()` only
71
+ // emits while a run is in progress, so the loop and `run()` must run together;
72
+ // awaiting the loop before calling `run()` would deadlock.
66
73
  (async () => {
67
- for await (const event of session.subscribe()) {
68
- // AgentEvent: agent_started, message_delta, turn_finished, ...
69
- }
70
- await session.run("Hi");
74
+ const consumer = (async () => {
75
+ for await (const event of session.subscribe()) {
76
+ // AgentEvent: agent_started, message_delta, turn_finished, ...
77
+ }
78
+ })();
79
+ await Promise.all([consumer, session.run("Hi")]);
71
80
  })();
72
81
  ```
73
82
 
@@ -115,9 +124,10 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
115
124
  | `@arnilo/prism-provider-openrouter` | OpenRouter provider with per-model cache control |
116
125
  | `@arnilo/prism-provider-zai` | ZAI GLM provider |
117
126
  | `@arnilo/prism-provider-kimi` | Kimi For Coding provider |
127
+ | `@arnilo/prism-provider-neuralwatt` | NeuralWatt provider with implicit vLLM prefix caching |
118
128
  | `@arnilo/prism-compaction-llm` | provider-backed compaction strategy |
119
129
  | `@arnilo/prism-compaction-observational-memory` | source-backed memory + recall tool |
120
- | `@arnilo/prism-providers` | umbrella: all 5 provider adapters |
130
+ | `@arnilo/prism-providers` | umbrella: all 6 provider adapters |
121
131
  | `@arnilo/prism-compaction` | umbrella: both compaction strategies |
122
132
  | `@arnilo/prism-all` | umbrella: core + providers + compaction |
123
133
 
@@ -0,0 +1,12 @@
1
+ import type { Agent, AgentDefinition, AgentDefinitionResolutionContext } from "./contracts.js";
2
+ /** Resolve an {@link AgentDefinition} into a runnable {@link Agent}.
3
+ *
4
+ * If the definition provides a `create()` escape hatch, that factory is called
5
+ * with a config built from the declarative fields and `context.overrides` are
6
+ * merged into the returned agent. Otherwise the declarative fields are resolved
7
+ * against the supplied registries and turned into an agent via
8
+ * {@link createAgent}.
9
+ *
10
+ * The host controls scope by which registries it passes. Missing dependencies
11
+ * fail closed at resolution time, before any provider turn. */
12
+ export declare function resolveAgentDefinition(def: AgentDefinition, context: AgentDefinitionResolutionContext): Promise<Agent> | Agent;
@@ -0,0 +1,131 @@
1
+ import { createAgent } from "./agents.js";
2
+ import { resolveActiveSkills, createSkillRegistry } from "./skills.js";
3
+ import { createToolRegistry } from "./tools.js";
4
+ /** Resolve an {@link AgentDefinition} into a runnable {@link Agent}.
5
+ *
6
+ * If the definition provides a `create()` escape hatch, that factory is called
7
+ * with a config built from the declarative fields and `context.overrides` are
8
+ * merged into the returned agent. Otherwise the declarative fields are resolved
9
+ * against the supplied registries and turned into an agent via
10
+ * {@link createAgent}.
11
+ *
12
+ * The host controls scope by which registries it passes. Missing dependencies
13
+ * fail closed at resolution time, before any provider turn. */
14
+ export function resolveAgentDefinition(def, context) {
15
+ const baseConfig = buildBaseConfig(def, context);
16
+ if (def.create) {
17
+ const agentOrPromise = def.create(baseConfig);
18
+ return applyAgentOverrides(agentOrPromise, context.overrides);
19
+ }
20
+ return createAgent(applyConfigOverrides(baseConfig, context.overrides));
21
+ }
22
+ function buildBaseConfig(def, context) {
23
+ const model = resolveModel(def.name, def.model, context);
24
+ const tools = resolveTools(def.tools, context);
25
+ const skills = resolveSkills(def.skills, tools, context);
26
+ return {
27
+ model,
28
+ ...resolveProviderOptions(model, context),
29
+ ...(tools !== undefined && { tools }),
30
+ ...(skills !== undefined && { skills }),
31
+ ...(def.context !== undefined && { context: resolveContextProviders(def.name, def.context, context) }),
32
+ ...(def.systemPrompt !== undefined && { systemPrompt: def.systemPrompt }),
33
+ ...(def.instructions !== undefined && { instructions: def.instructions }),
34
+ ...(def.loop !== undefined && { loop: def.loop }),
35
+ ...(def.metadata !== undefined && { metadata: def.metadata }),
36
+ };
37
+ }
38
+ function resolveModel(agentName, model, context) {
39
+ if (!model)
40
+ throw new Error(`Agent "${agentName}" has no model`);
41
+ if (typeof model !== "string")
42
+ return model;
43
+ const slash = model.indexOf("/");
44
+ if (slash === -1) {
45
+ throw new Error(`Unknown model: ${model}`);
46
+ }
47
+ const provider = model.slice(0, slash);
48
+ const modelId = model.slice(slash + 1);
49
+ if (context.registries?.models) {
50
+ return context.registries.models.resolve(provider, modelId);
51
+ }
52
+ return { provider, model: modelId };
53
+ }
54
+ function resolveProviderOptions(model, context) {
55
+ if (context.providerSource) {
56
+ return { providerSource: context.providerSource };
57
+ }
58
+ if (context.registries?.providers) {
59
+ return { provider: context.registries.providers.resolve(model) };
60
+ }
61
+ return undefined;
62
+ }
63
+ function resolveTools(names, context) {
64
+ const source = context.tools ?? context.registries?.tools;
65
+ if (!names) {
66
+ if (!context.activateAllCapabilities)
67
+ return undefined;
68
+ if (!source)
69
+ return undefined;
70
+ return asToolRegistry(source);
71
+ }
72
+ if (!source)
73
+ throw new Error("No tool registry in scope");
74
+ const available = listFromToolSource(source);
75
+ const resolved = names.map((name) => {
76
+ const tool = available.find((item) => item.name === name);
77
+ if (!tool)
78
+ throw new Error(`Unknown tool: ${name}`);
79
+ return tool;
80
+ });
81
+ return createToolRegistry(resolved);
82
+ }
83
+ function asToolRegistry(source) {
84
+ if (hasList(source)) {
85
+ return createToolRegistry(source.list());
86
+ }
87
+ return createToolRegistry(source);
88
+ }
89
+ function listFromToolSource(source) {
90
+ if (hasList(source)) {
91
+ return source.list();
92
+ }
93
+ return source;
94
+ }
95
+ function hasList(value) {
96
+ return typeof value === "object" && value !== null && "list" in value && typeof value.list === "function";
97
+ }
98
+ function resolveSkills(names, tools, context) {
99
+ const registry = context.skillsRegistry ??
100
+ (context.registries?.skills ? createSkillRegistry(context.registries.skills.list()) : undefined);
101
+ if (!names && !context.activateAllCapabilities)
102
+ return undefined;
103
+ if (!registry) {
104
+ if (!names)
105
+ return undefined;
106
+ throw new Error("No skill registry in scope");
107
+ }
108
+ const toolList = tools?.list() ?? [];
109
+ const activeNames = names ?? registry.list().map((skill) => skill.name);
110
+ return resolveActiveSkills({ registry, names: activeNames, tools: toolList });
111
+ }
112
+ function resolveContextProviders(agentName, names, context) {
113
+ const registry = context.registries?.contextProviders;
114
+ if (!registry)
115
+ throw new Error(`Agent "${agentName}" declares context providers but no registry is in scope`);
116
+ return names.map((name) => registry.resolve(name));
117
+ }
118
+ function applyConfigOverrides(config, overrides) {
119
+ if (!overrides)
120
+ return config;
121
+ return { ...config, ...overrides };
122
+ }
123
+ function applyAgentOverrides(agent, overrides) {
124
+ if (!overrides)
125
+ return agent;
126
+ if (agent instanceof Promise) {
127
+ return agent.then((resolved) => createAgent({ ...resolved.config, ...overrides }));
128
+ }
129
+ return createAgent({ ...agent.config, ...overrides });
130
+ }
131
+ //# sourceMappingURL=agent-definitions.js.map
@@ -0,0 +1,14 @@
1
+ import type { AgentLoopOptions, AgentLoopStrategy, ArtifactParser, ArtifactRepairer, ArtifactValidator } from "./contracts.js";
2
+ export declare const singleShotLoop: AgentLoopStrategy;
3
+ export declare function isAgentLoopOptions(value: AgentLoopStrategy | AgentLoopOptions | undefined): value is AgentLoopOptions;
4
+ export declare function generateValidateReviseLoop(opts: {
5
+ readonly validator: ArtifactValidator<unknown>;
6
+ readonly parser?: ArtifactParser<unknown>;
7
+ readonly repairer?: ArtifactRepairer<unknown>;
8
+ readonly maxRevisions?: number;
9
+ }): AgentLoopStrategy;
10
+ export declare function resolveLoop(options: {
11
+ loop?: AgentLoopStrategy | AgentLoopOptions;
12
+ }, config: {
13
+ loop?: AgentLoopStrategy | AgentLoopOptions;
14
+ }): AgentLoopStrategy;
@@ -0,0 +1,161 @@
1
+ import { inputMessages } from "./input.js";
2
+ function throwIfAborted(signal) {
3
+ if (signal.aborted)
4
+ throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
5
+ }
6
+ function toolResultMessage(result) {
7
+ return {
8
+ role: "tool",
9
+ content: [{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error }, ...(result.content ?? [])],
10
+ metadata: result.metadata,
11
+ };
12
+ }
13
+ // ponytail: SingleShotLoop extracted bit-for-bit from the former inline turn
14
+ // loop in RuntimeAgentSession.run. The runtime owns provider calls, retry,
15
+ // abort, store, events via LoopContext; this only orchestrates turns. Phase 28
16
+ // fires artifact_* events here as a noop seam — single-shot emits zero.
17
+ export const singleShotLoop = {
18
+ name: "single-shot",
19
+ async run(ctx) {
20
+ let usage;
21
+ const toolResults = [];
22
+ let toolRounds = 0;
23
+ let nextInput = ctx.input;
24
+ for (let turn = 1;; turn += 1) {
25
+ throwIfAborted(ctx.signal);
26
+ ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
27
+ const request = await ctx.assemble(nextInput, toolResults, turn);
28
+ throwIfAborted(ctx.signal);
29
+ const { content, calls, messageId, started, usage: turnUsage } = await ctx.generate(request);
30
+ usage = turnUsage ?? usage;
31
+ if (turn === 1)
32
+ ctx.history.push(...ctx.inputMessages);
33
+ if (started) {
34
+ const message = { id: messageId, role: "assistant", content };
35
+ ctx.history.push(message);
36
+ await ctx.appendMessage(message);
37
+ ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
38
+ }
39
+ ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
40
+ if (calls.length === 0 || toolRounds >= ctx.maxToolRounds)
41
+ break;
42
+ toolRounds += 1;
43
+ for (const call of calls) {
44
+ const result = await ctx.dispatchToolCall(call);
45
+ toolResults.push(result);
46
+ await ctx.appendMessage(toolResultMessage(result));
47
+ throwIfAborted(ctx.signal);
48
+ }
49
+ nextInput = [];
50
+ }
51
+ return usage;
52
+ },
53
+ };
54
+ export function isAgentLoopOptions(value) {
55
+ return typeof value === "object" && value !== null && "strategy" in value;
56
+ }
57
+ function defaultRepairer() {
58
+ return (_value, failure) => ({
59
+ role: "user",
60
+ content: [{ type: "text", text: failure.errors?.map((e) => e.message).join("\n") ?? "invalid artifact" }],
61
+ });
62
+ }
63
+ // ponytail: GenerateValidateReviseLoop reuses LoopContext primitives only —
64
+ // no provider/retry/store/event re-implementation. T is host-defined, Prism
65
+ // never instantiates it. No tools in artifact revisions (roadmap scope is
66
+ // generate→validate→revise; tool coupling deferred). Phase 28 fires
67
+ // artifact_* events at the marked seams (noop here).
68
+ export function generateValidateReviseLoop(opts) {
69
+ const max = opts.maxRevisions ?? 3;
70
+ const repairer = opts.repairer ?? defaultRepairer();
71
+ return {
72
+ name: "generate-validate-revise",
73
+ async run(ctx) {
74
+ let usage;
75
+ let nextInput = ctx.input;
76
+ for (let turn = 1; turn <= max + 1; turn += 1) {
77
+ throwIfAborted(ctx.signal);
78
+ ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
79
+ const request = await ctx.assemble(nextInput, undefined, turn);
80
+ throwIfAborted(ctx.signal);
81
+ const { content, messageId, started, usage: turnUsage } = await ctx.generate(request);
82
+ usage = turnUsage ?? usage;
83
+ if (turn === 1)
84
+ ctx.history.push(...ctx.inputMessages);
85
+ if (started) {
86
+ const message = { id: messageId, role: "assistant", content };
87
+ ctx.history.push(message);
88
+ await ctx.appendMessage(message);
89
+ ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
90
+ }
91
+ ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
92
+ const text = content
93
+ .filter((b) => b.type === "text")
94
+ .map((b) => b.text)
95
+ .join("");
96
+ const artifactCtx = {
97
+ sessionId: ctx.sessionId,
98
+ runId: ctx.runId,
99
+ turn,
100
+ signal: ctx.signal,
101
+ metadata: ctx.metadata,
102
+ };
103
+ const parsed = opts.parser
104
+ ? await opts.parser(text, artifactCtx)
105
+ : { ok: true, value: text };
106
+ // Parse failure ends the loop silently (terminal parse errors stay on `error`).
107
+ if (!parsed.ok || parsed.value === undefined)
108
+ return usage;
109
+ const attempt = turn;
110
+ ctx.emit({ type: "artifact_validation_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt });
111
+ const result = await opts.validator(parsed.value, artifactCtx);
112
+ ctx.emit({ type: "artifact_validation_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
113
+ if (result.ok) {
114
+ ctx.emit({ type: "artifact_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
115
+ return usage;
116
+ }
117
+ if (turn > max) {
118
+ ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
119
+ return usage;
120
+ }
121
+ ctx.emit({ type: "artifact_revision_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, failure: result });
122
+ const repair = await repairer(parsed.value, result, artifactCtx);
123
+ const repairMessages = inputMessages(repair).map((m) => ({ ...m, id: randomId("msg") }));
124
+ for (const message of repairMessages) {
125
+ ctx.history.push(message);
126
+ await ctx.appendMessage(message);
127
+ }
128
+ nextInput = repairMessages;
129
+ continue;
130
+ }
131
+ return usage;
132
+ },
133
+ };
134
+ }
135
+ // ponytail: resolveLoop returns the loop to run. RunOptions.loop wins over
136
+ // AgentConfig.loop; default is singleShotLoop. generate-validate-revise maps
137
+ // its options to the factory; unknown strategy throws.
138
+ export function resolveLoop(options, config) {
139
+ const loop = options.loop ?? config.loop;
140
+ if (!loop)
141
+ return singleShotLoop;
142
+ if (isAgentLoopOptions(loop)) {
143
+ const { strategy } = loop;
144
+ if (strategy === "single-shot")
145
+ return singleShotLoop;
146
+ if (strategy === "generate-validate-revise") {
147
+ return generateValidateReviseLoop({
148
+ validator: loop.validator,
149
+ parser: loop.parser,
150
+ repairer: loop.repairer,
151
+ maxRevisions: loop.maxRevisions,
152
+ });
153
+ }
154
+ throw new Error(`Unknown agent loop strategy: ${strategy}`);
155
+ }
156
+ return loop;
157
+ }
158
+ function randomId(prefix) {
159
+ return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
160
+ }
161
+ //# sourceMappingURL=agent-loops.js.map