@nylorun/harness 0.11.1-beta → 0.13.0-beta

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 (131) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +124 -5
  3. package/dist/definition/agent-definition.d.ts +11 -0
  4. package/dist/definition/agent-definition.js +12 -0
  5. package/dist/definition/assemble.d.ts +22 -0
  6. package/dist/{build → definition}/assemble.js +6 -7
  7. package/dist/definition/bind-agent.d.ts +9 -0
  8. package/dist/definition/bind-agent.js +21 -0
  9. package/dist/{build → definition}/bind-tool.d.ts +2 -1
  10. package/dist/{build → definition}/bind-tool.js +1 -0
  11. package/dist/definition/bound.d.ts +16 -0
  12. package/dist/definition/builder.d.ts +30 -0
  13. package/dist/definition/builder.js +94 -0
  14. package/dist/definition/declaration.d.ts +3 -0
  15. package/dist/definition/declaration.js +53 -0
  16. package/dist/{build → definition}/helpers.d.ts +1 -1
  17. package/dist/{build → definition}/manifest.d.ts +3 -1
  18. package/dist/definition/manifest.js +45 -0
  19. package/dist/{session → definition}/output-contract.d.ts +2 -2
  20. package/dist/{session → definition}/output-contract.js +1 -1
  21. package/dist/definition/registry.d.ts +10 -0
  22. package/dist/definition/registry.js +48 -0
  23. package/dist/{build → definition}/schema.d.ts +4 -4
  24. package/dist/errors.d.ts +1 -1
  25. package/dist/execution/dispatch.d.ts +2 -0
  26. package/dist/execution/dispatch.js +164 -0
  27. package/dist/execution/initial-state.d.ts +3 -0
  28. package/dist/execution/initial-state.js +14 -0
  29. package/dist/execution/invocation.d.ts +21 -0
  30. package/dist/execution/invocation.js +32 -0
  31. package/dist/{model → execution/model}/adapters.d.ts +2 -2
  32. package/dist/{model → execution/model}/adapters.js +2 -2
  33. package/dist/{model → execution/model}/normalize.d.ts +2 -2
  34. package/dist/{model → execution/model}/normalize.js +2 -2
  35. package/dist/{model → execution/model}/prepared.d.ts +3 -3
  36. package/dist/{step → execution/model}/project.d.ts +1 -1
  37. package/dist/{step → execution/model}/project.js +2 -10
  38. package/dist/execution/observe.d.ts +17 -0
  39. package/dist/execution/observe.js +53 -0
  40. package/dist/execution/options.d.ts +10 -0
  41. package/dist/execution/options.js +49 -0
  42. package/dist/execution/resume.d.ts +4 -0
  43. package/dist/execution/resume.js +52 -0
  44. package/dist/execution/run.d.ts +5 -0
  45. package/dist/execution/run.js +256 -0
  46. package/dist/execution/state.d.ts +8 -0
  47. package/dist/execution/state.js +208 -0
  48. package/dist/{step → execution/step}/canonicalize.d.ts +2 -2
  49. package/dist/{step → execution/step}/canonicalize.js +2 -2
  50. package/dist/{step → execution/step}/compose.d.ts +3 -2
  51. package/dist/{step → execution/step}/compose.js +5 -5
  52. package/dist/{step → execution/step}/context-draft.d.ts +3 -5
  53. package/dist/{step → execution/step}/context-draft.js +5 -22
  54. package/dist/execution/step/describe.d.ts +9 -0
  55. package/dist/execution/step/describe.js +22 -0
  56. package/dist/{step → execution/step}/model-configuration.d.ts +4 -3
  57. package/dist/{step → execution/step}/model-configuration.js +3 -3
  58. package/dist/execution/step/resolve.d.ts +11 -0
  59. package/dist/{step → execution/step}/resolve.js +5 -3
  60. package/dist/execution/step/run.d.ts +29 -0
  61. package/dist/{step → execution/step}/run.js +19 -53
  62. package/dist/execution/step/runtime.d.ts +32 -0
  63. package/dist/execution/step/runtime.js +1 -0
  64. package/dist/{step → execution/step}/seal.d.ts +13 -8
  65. package/dist/{step → execution/step}/seal.js +8 -6
  66. package/dist/{step → execution/step}/slot-assembly.d.ts +4 -4
  67. package/dist/{step → execution/step}/slot-assembly.js +4 -4
  68. package/dist/{step → execution/step}/step-context.d.ts +11 -12
  69. package/dist/{step → execution/step}/step-context.js +5 -27
  70. package/dist/execution/tool-result.d.ts +4 -0
  71. package/dist/execution/tool-result.js +30 -0
  72. package/dist/execution/transcript.d.ts +2 -0
  73. package/dist/{session/seed.js → execution/transcript.js} +9 -39
  74. package/dist/index.d.ts +15 -12
  75. package/dist/index.js +5 -5
  76. package/dist/types/agent.d.ts +9 -0
  77. package/dist/types/agent.js +1 -0
  78. package/dist/types/execution.d.ts +97 -0
  79. package/dist/types/execution.js +1 -0
  80. package/dist/types/manifest.d.ts +16 -13
  81. package/dist/types/middleware.d.ts +11 -40
  82. package/dist/types/model.d.ts +10 -10
  83. package/dist/types/observe.d.ts +165 -0
  84. package/dist/types/observe.js +1 -0
  85. package/dist/types/shared.d.ts +1 -225
  86. package/dist/types/tool.d.ts +21 -19
  87. package/dist/types/transcript.d.ts +65 -0
  88. package/dist/types/transcript.js +1 -0
  89. package/dist/utils/canonical.d.ts +1 -0
  90. package/dist/utils/canonical.js +10 -0
  91. package/dist/utils/immutable.d.ts +1 -0
  92. package/dist/utils/immutable.js +8 -0
  93. package/package.json +5 -5
  94. package/dist/build/agent.d.ts +0 -18
  95. package/dist/build/agent.js +0 -37
  96. package/dist/build/assemble.d.ts +0 -8
  97. package/dist/build/builder.d.ts +0 -46
  98. package/dist/build/builder.js +0 -151
  99. package/dist/build/manifest.js +0 -20
  100. package/dist/session/capability-state.d.ts +0 -14
  101. package/dist/session/capability-state.js +0 -67
  102. package/dist/session/event-log.d.ts +0 -12
  103. package/dist/session/event-log.js +0 -63
  104. package/dist/session/input-queue.d.ts +0 -41
  105. package/dist/session/input-queue.js +0 -94
  106. package/dist/session/record.d.ts +0 -11
  107. package/dist/session/record.js +0 -26
  108. package/dist/session/scheduler.d.ts +0 -62
  109. package/dist/session/scheduler.js +0 -485
  110. package/dist/session/seed.d.ts +0 -10
  111. package/dist/session/session.d.ts +0 -18
  112. package/dist/session/session.js +0 -103
  113. package/dist/session/state.d.ts +0 -15
  114. package/dist/session/state.js +0 -48
  115. package/dist/session/submission-stream.d.ts +0 -16
  116. package/dist/session/submission-stream.js +0 -46
  117. package/dist/step/resolve.d.ts +0 -10
  118. package/dist/step/run.d.ts +0 -33
  119. package/dist/turn/plan-runner.d.ts +0 -70
  120. package/dist/turn/plan-runner.js +0 -388
  121. package/dist/turn/runner.d.ts +0 -69
  122. package/dist/turn/runner.js +0 -158
  123. package/dist/types/session.d.ts +0 -227
  124. package/dist/utils/maps.d.ts +0 -1
  125. package/dist/utils/maps.js +0 -37
  126. package/dist/utils/observe.d.ts +0 -8
  127. package/dist/utils/observe.js +0 -29
  128. /package/dist/{types/session.js → definition/bound.js} +0 -0
  129. /package/dist/{build → definition}/helpers.js +0 -0
  130. /package/dist/{build → definition}/schema.js +0 -0
  131. /package/dist/{model → execution/model}/prepared.js +0 -0
@@ -1,16 +0,0 @@
1
- import type { InputCompletion, InputHandle, SessionEvent } from "../types/session.js";
2
- import type { JsonValue } from "../types/shared.js";
3
- export declare class SubmissionStream implements InputHandle<JsonValue> {
4
- readonly inputId: string;
5
- readonly completed: Promise<InputCompletion<JsonValue>>;
6
- private resolveCompletion;
7
- private rejectCompletion;
8
- private readonly events;
9
- private readonly cleanups;
10
- private done;
11
- constructor(inputId: string);
12
- emit(event: SessionEvent<JsonValue>): void;
13
- finish(status: InputCompletion<JsonValue>["status"]): void;
14
- fail(error: unknown): void;
15
- onFinish(cleanup: () => void): void;
16
- }
@@ -1,46 +0,0 @@
1
- export class SubmissionStream {
2
- inputId;
3
- completed;
4
- resolveCompletion;
5
- rejectCompletion;
6
- events = [];
7
- cleanups = [];
8
- done = false;
9
- constructor(inputId) {
10
- this.inputId = inputId;
11
- this.completed = new Promise((resolve, reject) => {
12
- this.resolveCompletion = resolve;
13
- this.rejectCompletion = reject;
14
- });
15
- }
16
- emit(event) {
17
- if (!this.done)
18
- this.events.push(event);
19
- }
20
- finish(status) {
21
- if (this.done)
22
- return;
23
- this.done = true;
24
- while (this.cleanups.length)
25
- this.cleanups.pop()();
26
- this.resolveCompletion(Object.freeze({
27
- inputId: this.inputId,
28
- status,
29
- events: Object.freeze([...this.events]),
30
- }));
31
- }
32
- fail(error) {
33
- if (this.done)
34
- return;
35
- this.done = true;
36
- while (this.cleanups.length)
37
- this.cleanups.pop()();
38
- this.rejectCompletion(error);
39
- }
40
- onFinish(cleanup) {
41
- if (this.done)
42
- cleanup();
43
- else
44
- this.cleanups.push(cleanup);
45
- }
46
- }
@@ -1,10 +0,0 @@
1
- import type { InputEvent } from "../types/session.js";
2
- import type { ModelRequest } from "../types/model.js";
3
- import type { ToolResult } from "../types/tool.js";
4
- import type { StepContext } from "./step-context.js";
5
- export declare function resolveModelRequest(input: {
6
- context: StepContext;
7
- arrivals: readonly InputEvent[];
8
- toolResults: readonly ToolResult[];
9
- output?: import("../session/output-contract.js").TurnOutputContract;
10
- }): ModelRequest;
@@ -1,33 +0,0 @@
1
- import type { ModelCandidate } from "../types/model.js";
2
- import type { ActiveModelExecutionRecord, InputEvent, SessionSnapshot } from "../types/session.js";
3
- import type { LoopAgent } from "../build/agent.js";
4
- import type { ObserveEmit } from "../utils/observe.js";
5
- import { type SealedStepOutput } from "./seal.js";
6
- import type { CapabilityStateRegistry } from "../session/capability-state.js";
7
- export interface StepRunResult {
8
- readonly stepId: string;
9
- readonly state: SessionSnapshot;
10
- readonly candidate?: ModelCandidate;
11
- readonly output: SealedStepOutput;
12
- readonly requestedModelId?: string;
13
- }
14
- export declare function runStep(input: {
15
- agent: LoopAgent;
16
- observe: ObserveEmit;
17
- state: SessionSnapshot;
18
- sessionId: string;
19
- turnId: string;
20
- stepId: string;
21
- turnNumber: number;
22
- stepNumber: number;
23
- arrivals: readonly InputEvent[];
24
- toolResults: readonly import("../types/tool.js").ToolResult[];
25
- signal: AbortSignal;
26
- session: Readonly<{
27
- readonly userId?: string;
28
- readonly context?: import("../types/shared.js").JsonObject;
29
- }>;
30
- states: CapabilityStateRegistry;
31
- output?: import("../session/output-contract.js").TurnOutputContract;
32
- recordModelRequested(state: SessionSnapshot, active: ActiveModelExecutionRecord): Promise<SessionSnapshot>;
33
- }): Promise<StepRunResult>;
@@ -1,70 +0,0 @@
1
- import type { ObserveEmit } from "../utils/observe.js";
2
- import type { InputEvent } from "../types/session.js";
3
- import type { ActiveInteractionExecutionRecord, ActiveToolsExecutionRecord } from "../types/session.js";
4
- import type { RequiredInteraction, ToolExecutionResume, ToolResult } from "../types/tool.js";
5
- import type { JsonValue } from "../types/shared.js";
6
- import type { CapabilityStateRegistry } from "../session/capability-state.js";
7
- import type { ExecutablePlanEntry, InternalToolPlan } from "../step/seal.js";
8
- export interface DeferredToolCall {
9
- readonly callId: string;
10
- readonly toolName: string;
11
- readonly args: JsonValue;
12
- readonly invocationId: string;
13
- readonly owner: ExecutablePlanEntry["owner"];
14
- readonly token?: JsonValue;
15
- }
16
- export type ToolPlanProgress = {
17
- readonly kind: "completed";
18
- readonly results: readonly ToolResult[];
19
- } | {
20
- readonly kind: "interaction-required";
21
- readonly interaction: RequiredInteraction;
22
- } | {
23
- readonly kind: "deferred";
24
- readonly settled: readonly ToolResult[];
25
- readonly deferred: readonly DeferredToolCall[];
26
- };
27
- export interface ToolPlanRunContext {
28
- readonly signal: AbortSignal;
29
- readonly observe: ObserveEmit;
30
- readonly ids: {
31
- readonly sessionId: string;
32
- readonly turnId: string;
33
- readonly stepId: string;
34
- };
35
- readonly states?: CapabilityStateRegistry;
36
- }
37
- /** Owns one sealed plan's deterministic interaction and concurrent execution progress. */
38
- export declare class ToolPlanRunner {
39
- private readonly plan;
40
- private readonly results;
41
- private readonly deferred;
42
- private readonly resumes;
43
- private readonly pending;
44
- private readonly settlementEvents;
45
- private interactionIndex;
46
- private executeStarted;
47
- private resumeExecute?;
48
- constructor(plan: InternalToolPlan);
49
- get interactionId(): string | undefined;
50
- get interactionKind(): RequiredInteraction["kind"] | undefined;
51
- get interactionCallId(): string | undefined;
52
- get interactionToolName(): string | undefined;
53
- cancelledResults(reason: string): readonly ToolResult[];
54
- /** Publishes settlement facts only after their authoritative state was recorded. */
55
- publishSettlements(observe: ObserveEmit): void;
56
- activeToolsRecord(turnId: string, stepId: string): ActiveToolsExecutionRecord;
57
- activeInteractionRecord(turnId: string, stepId: string, resume?: ToolExecutionResume): ActiveInteractionExecutionRecord;
58
- interactionResume(event: InputEvent): ToolExecutionResume;
59
- run(context: ToolPlanRunContext, resume?: InputEvent): Promise<ToolPlanProgress>;
60
- private acceptResume;
61
- private executeBatch;
62
- private execute;
63
- private takeResume;
64
- private enqueueInteraction;
65
- private interactionRequired;
66
- private orderedResults;
67
- private orderedSettledResults;
68
- private resultsInOrder;
69
- private throwIfAborted;
70
- }
@@ -1,388 +0,0 @@
1
- import { HarnessError, isHarnessError } from "../errors.js";
2
- import { createId } from "../utils/ids.js";
3
- import { assertJson, copyJson, copyJsonObject } from "../utils/immutable.js";
4
- /** Owns one sealed plan's deterministic interaction and concurrent execution progress. */
5
- export class ToolPlanRunner {
6
- plan;
7
- results;
8
- deferred = new Map();
9
- resumes = new Map();
10
- pending = [];
11
- settlementEvents = [];
12
- interactionIndex = 0;
13
- executeStarted = false;
14
- resumeExecute;
15
- constructor(plan) {
16
- this.plan = plan;
17
- this.results = new Map(plan.immediateResults.map((item) => [item.callId, item]));
18
- }
19
- get interactionId() {
20
- return this.pending[0]?.interaction.id;
21
- }
22
- get interactionKind() {
23
- return this.pending[0]?.interaction.kind;
24
- }
25
- get interactionCallId() {
26
- const pending = this.pending[0];
27
- return pending ? this.plan.executable[pending.index]?.call.callId : undefined;
28
- }
29
- get interactionToolName() {
30
- const pending = this.pending[0];
31
- return pending ? this.plan.executable[pending.index]?.call.toolName : undefined;
32
- }
33
- cancelledResults(reason) {
34
- return this.resultsInOrder("tool.cancelled", reason);
35
- }
36
- /** Publishes settlement facts only after their authoritative state was recorded. */
37
- publishSettlements(observe) {
38
- for (const event of this.settlementEvents.splice(0))
39
- observe(event);
40
- }
41
- activeToolsRecord(turnId, stepId) {
42
- return Object.freeze({
43
- kind: "tools",
44
- turnId,
45
- stepId,
46
- calls: Object.freeze(this.plan.executable.map((entry) => {
47
- const result = this.results.get(entry.call.callId);
48
- const deferred = this.deferred.get(entry.call.callId);
49
- return Object.freeze({
50
- callId: entry.call.callId,
51
- toolName: entry.call.toolName,
52
- args: copyJson(entry.call.args),
53
- invocationId: entry.invocationId,
54
- owner: entry.owner,
55
- status: result
56
- ? "settled"
57
- : deferred
58
- ? "deferred"
59
- : "pending",
60
- ...(result ? { result } : {}),
61
- ...(deferred?.token === undefined ? {} : { token: deferred.token }),
62
- });
63
- })),
64
- });
65
- }
66
- activeInteractionRecord(turnId, stepId, resume) {
67
- const pending = this.pending[0];
68
- const entry = pending ? this.plan.executable[pending.index] : undefined;
69
- if (!pending || !entry)
70
- throw new HarnessError("interaction.invalid", "Missing pending interaction state");
71
- return Object.freeze({
72
- kind: "interaction",
73
- turnId,
74
- stepId,
75
- callId: entry.call.callId,
76
- toolName: entry.call.toolName,
77
- interaction: pending.interaction,
78
- ...(pending.token === undefined ? {} : { token: pending.token }),
79
- ...(resume === undefined ? {} : { resume }),
80
- tools: this.activeToolsRecord(turnId, stepId),
81
- });
82
- }
83
- interactionResume(event) {
84
- const pending = this.pending[0];
85
- if (!pending)
86
- throw new HarnessError("interaction.invalid", "Missing pending interaction state");
87
- return resumeValue(event, pending);
88
- }
89
- async run(context, resume) {
90
- this.acceptResume(resume);
91
- while (this.interactionIndex < this.plan.executable.length) {
92
- this.throwIfAborted(context.signal);
93
- const entry = this.plan.executable[this.interactionIndex];
94
- if (this.results.has(entry.call.callId)) {
95
- this.interactionIndex += 1;
96
- continue;
97
- }
98
- if (entry.interaction) {
99
- this.enqueueInteraction({ index: this.interactionIndex, interaction: entry.interaction });
100
- return this.interactionRequired();
101
- }
102
- this.interactionIndex += 1;
103
- }
104
- if (this.resumeExecute) {
105
- const entry = this.resumeExecute;
106
- this.resumeExecute = undefined;
107
- await this.executeBatch([entry], context);
108
- }
109
- else if (!this.executeStarted) {
110
- this.executeStarted = true;
111
- await this.executeBatch(this.plan.executable.filter((entry) => !this.results.has(entry.call.callId)), context);
112
- }
113
- if (this.pending.length)
114
- return this.interactionRequired();
115
- if (this.deferred.size)
116
- return {
117
- kind: "deferred",
118
- settled: this.orderedSettledResults(),
119
- deferred: Object.freeze(this.plan.executable.flatMap((entry) => {
120
- const value = this.deferred.get(entry.call.callId);
121
- return value ? [value] : [];
122
- })),
123
- };
124
- return { kind: "completed", results: this.orderedResults() };
125
- }
126
- acceptResume(resume) {
127
- const pending = this.pending.shift();
128
- if (!pending)
129
- return;
130
- if (!resume)
131
- throw new HarnessError("interaction.missing-resume", "A pending tool plan requires a correlated interaction input");
132
- const value = resumeValue(resume, pending);
133
- const entry = this.plan.executable[pending.index];
134
- if (value.kind === "approval" && value.approved === false) {
135
- this.results.set(entry.call.callId, Object.freeze({
136
- callId: entry.call.callId,
137
- toolName: entry.call.toolName,
138
- kind: "denied",
139
- reason: "Approval rejected",
140
- }));
141
- this.interactionIndex = Math.max(this.interactionIndex, pending.index + 1);
142
- return;
143
- }
144
- this.resumes.set(entry.call.callId, value);
145
- if (this.executeStarted)
146
- this.resumeExecute = entry;
147
- else
148
- this.interactionIndex = Math.max(this.interactionIndex, pending.index + 1);
149
- }
150
- async executeBatch(entries, context) {
151
- const settled = await Promise.allSettled(entries.map((entry) => this.execute(entry, context)));
152
- if (context.signal.aborted)
153
- throw context.signal.reason;
154
- for (const outcome of settled) {
155
- if (outcome.status === "rejected")
156
- throw outcome.reason;
157
- if (outcome.value)
158
- this.enqueueInteraction(outcome.value);
159
- }
160
- }
161
- async execute(entry, context) {
162
- context.observe({
163
- type: "tool.started",
164
- sessionId: context.ids.sessionId,
165
- turnId: context.ids.turnId,
166
- stepId: context.ids.stepId,
167
- toolName: entry.call.toolName,
168
- callId: entry.call.callId,
169
- invocationId: entry.invocationId,
170
- middlewareId: entry.owner.middlewareId,
171
- slot: entry.owner.slot,
172
- attributes: { args: copyJson(entry.call.args) },
173
- });
174
- try {
175
- const toolContext = {
176
- sessionId: context.ids.sessionId,
177
- turnId: context.ids.turnId,
178
- stepId: context.ids.stepId,
179
- callId: entry.call.callId,
180
- invocationId: entry.invocationId,
181
- signal: context.signal,
182
- resume: this.takeResume(entry.call.callId),
183
- };
184
- if (context.states?.has(entry.owner.middlewareId))
185
- Object.defineProperty(toolContext, "state", {
186
- enumerable: true,
187
- get: () => context.states.get(entry.owner.middlewareId),
188
- });
189
- const outcome = await entry.execute(entry.call.args, Object.freeze(toolContext));
190
- this.throwIfAborted(context.signal);
191
- if (!outcome || typeof outcome !== "object" || typeof outcome.kind !== "string")
192
- throw new HarnessError("tool.invalid-tool-result", "Tool returned an invalid outcome");
193
- if (outcome.kind === "interaction-required")
194
- return interactionBarrier(this.plan.executable.indexOf(entry), outcome);
195
- if (outcome.kind === "deferred") {
196
- const value = Object.freeze({
197
- callId: entry.call.callId,
198
- toolName: entry.call.toolName,
199
- args: copyJson(entry.call.args),
200
- invocationId: entry.invocationId,
201
- owner: entry.owner,
202
- ...(outcome.token === undefined ? {} : { token: copyJson(outcome.token) }),
203
- });
204
- this.deferred.set(entry.call.callId, value);
205
- this.settlementEvents.push({
206
- type: "tool.deferred",
207
- sessionId: context.ids.sessionId,
208
- turnId: context.ids.turnId,
209
- stepId: context.ids.stepId,
210
- toolName: entry.call.toolName,
211
- callId: entry.call.callId,
212
- invocationId: entry.invocationId,
213
- middlewareId: entry.owner.middlewareId,
214
- slot: entry.owner.slot,
215
- attributes: outcome.token === undefined ? {} : { token: copyJson(outcome.token) },
216
- });
217
- return undefined;
218
- }
219
- const result = resultFrom(entry, outcome);
220
- this.results.set(entry.call.callId, result);
221
- this.settlementEvents.push(completedEvent(entry, context, result));
222
- }
223
- catch (error) {
224
- this.throwIfAborted(context.signal);
225
- const result = failed(entry, isHarnessError(error) ? error.code : "tool.execution-failed", error);
226
- this.results.set(entry.call.callId, result);
227
- this.settlementEvents.push(completedEvent(entry, context, result));
228
- }
229
- return undefined;
230
- }
231
- takeResume(callId) {
232
- const value = this.resumes.get(callId);
233
- this.resumes.delete(callId);
234
- return value;
235
- }
236
- enqueueInteraction(pending) {
237
- const position = this.pending.findIndex((item) => item.index > pending.index);
238
- if (position === -1)
239
- this.pending.push(pending);
240
- else
241
- this.pending.splice(position, 0, pending);
242
- }
243
- interactionRequired() {
244
- const pending = this.pending[0];
245
- if (!pending)
246
- throw new HarnessError("interaction.invalid", "Missing pending interaction");
247
- return { kind: "interaction-required", interaction: pending.interaction };
248
- }
249
- orderedResults() {
250
- return this.resultsInOrder("tool.missing-result", "Tool call did not produce a result");
251
- }
252
- orderedSettledResults() {
253
- return Object.freeze(this.plan.order.flatMap(({ callId }) => {
254
- const result = this.results.get(callId);
255
- return result ? [result] : [];
256
- }));
257
- }
258
- resultsInOrder(code, message) {
259
- return Object.freeze(this.plan.order.map(({ callId, toolName }) => this.results.get(callId) ??
260
- Object.freeze({ kind: "failed", callId, toolName, code, message })));
261
- }
262
- throwIfAborted(signal) {
263
- if (signal.aborted)
264
- throw signal.reason;
265
- }
266
- }
267
- function interactionBarrier(index, outcome) {
268
- return {
269
- index,
270
- interaction: normalizeInteraction(outcome.interaction),
271
- ...(outcome.token === undefined ? {} : { token: copyJson(outcome.token) }),
272
- };
273
- }
274
- function normalizeInteraction(value) {
275
- if (!value || typeof value !== "object")
276
- throw new HarnessError("interaction.invalid", "Interaction must be an object");
277
- if (value.kind !== "approval" && value.kind !== "response")
278
- throw new HarnessError("interaction.invalid", "Interaction kind must be approval or response");
279
- if (typeof value.prompt !== "string")
280
- throw new HarnessError("interaction.invalid", "Interaction prompt must be a string");
281
- if (value.id !== undefined && (typeof value.id !== "string" || !value.id))
282
- throw new HarnessError("interaction.invalid", "Interaction id must be a non-empty string");
283
- return Object.freeze({
284
- id: value.id ?? createId("interaction"),
285
- kind: value.kind,
286
- prompt: value.prompt,
287
- ...(value.metadata === undefined
288
- ? {}
289
- : { metadata: copyJsonObject(value.metadata, "interaction metadata") }),
290
- });
291
- }
292
- function resumeValue(event, pending) {
293
- if (event.kind === "approve")
294
- return Object.freeze({
295
- interactionId: event.interactionId,
296
- kind: "approval",
297
- approved: event.approved,
298
- ...(pending.token === undefined ? {} : { token: pending.token }),
299
- });
300
- if (event.kind === "respond")
301
- return Object.freeze({
302
- interactionId: event.interactionId,
303
- kind: "response",
304
- value: event.value,
305
- ...(pending.token === undefined ? {} : { token: pending.token }),
306
- });
307
- throw new HarnessError("interaction.uncorrelated-resume", "Only correlated interaction input can resume a plan");
308
- }
309
- function resultFrom(entry, outcome) {
310
- const base = { callId: entry.call.callId, toolName: entry.call.toolName };
311
- switch (outcome.kind) {
312
- case "completed": {
313
- const output = validatedOutput(entry, outcome.output);
314
- if (!output.ok)
315
- return Object.freeze({
316
- ...base,
317
- kind: "failed",
318
- code: "tool.invalid-output",
319
- message: output.message,
320
- details: output.details,
321
- });
322
- return Object.freeze({ ...base, kind: "completed", output: output.value });
323
- }
324
- case "denied":
325
- if (typeof outcome.reason !== "string")
326
- throw new HarnessError("tool.invalid-tool-result", "Tool denial reason must be a string");
327
- return Object.freeze({ ...base, kind: "denied", reason: outcome.reason });
328
- case "failed":
329
- if (typeof outcome.code !== "string" || typeof outcome.message !== "string")
330
- throw new HarnessError("tool.invalid-tool-result", "Tool failure code and message must be strings");
331
- return Object.freeze({
332
- ...base,
333
- kind: "failed",
334
- code: outcome.code,
335
- message: outcome.message,
336
- });
337
- }
338
- }
339
- function validatedOutput(entry, value) {
340
- const validation = entry.outputSchema?.validate(value) ?? { ok: true, value };
341
- if (!validation.ok)
342
- return validationFailure("output", validation.issues);
343
- try {
344
- assertJson(validation.value, `output for '${entry.call.toolName}'`);
345
- return { ok: true, value: copyJson(validation.value) };
346
- }
347
- catch (error) {
348
- return validationFailure("output", [
349
- Object.freeze({
350
- path: Object.freeze([]),
351
- code: "invalid_json",
352
- message: error instanceof Error ? error.message : String(error),
353
- }),
354
- ]);
355
- }
356
- }
357
- function validationFailure(phase, issues) {
358
- return {
359
- ok: false,
360
- message: issues.map((item) => `${item.path.join(".") || "(root)"}: ${item.message}`).join("; "),
361
- details: Object.freeze({ phase, issues: Object.freeze([...issues]) }),
362
- };
363
- }
364
- function completedEvent(entry, context, result) {
365
- return {
366
- type: "tool.completed",
367
- sessionId: context.ids.sessionId,
368
- turnId: context.ids.turnId,
369
- stepId: context.ids.stepId,
370
- toolName: entry.call.toolName,
371
- callId: entry.call.callId,
372
- invocationId: entry.invocationId,
373
- middlewareId: entry.owner.middlewareId,
374
- slot: entry.owner.slot,
375
- outcome: result.kind,
376
- ...(result.kind === "failed" ? { code: result.code } : {}),
377
- attributes: copyJson(result),
378
- };
379
- }
380
- function failed(entry, code, error) {
381
- return Object.freeze({
382
- callId: entry.call.callId,
383
- toolName: entry.call.toolName,
384
- kind: "failed",
385
- code,
386
- message: error instanceof Error ? error.message : String(error),
387
- });
388
- }
@@ -1,69 +0,0 @@
1
- import type { ActiveExecutionRecord, InputEvent, SessionEvent, SessionRecord, SessionSnapshot } from "../types/session.js";
2
- import type { JsonObject, JsonValue, Tripwire } from "../types/shared.js";
3
- import type { RequiredInteraction } from "../types/tool.js";
4
- import type { LoopAgent } from "../build/agent.js";
5
- import type { ObserveEmit } from "../utils/observe.js";
6
- import { ToolPlanRunner } from "./plan-runner.js";
7
- import type { CapabilityStateRegistry } from "../session/capability-state.js";
8
- import type { TurnOutputContract } from "../session/output-contract.js";
9
- export interface PendingTurn {
10
- readonly plan: ToolPlanRunner;
11
- readonly turnId: string;
12
- readonly stepId: string;
13
- readonly stepNumber: number;
14
- readonly output?: TurnOutputContract;
15
- }
16
- export type TurnProgress = {
17
- readonly kind: "final";
18
- readonly state: SessionSnapshot;
19
- readonly turnId: string;
20
- readonly stepId: string;
21
- readonly output: JsonValue;
22
- } | {
23
- readonly kind: "interaction-required";
24
- readonly state: SessionSnapshot;
25
- readonly pending: PendingTurn;
26
- readonly interaction: RequiredInteraction;
27
- } | {
28
- readonly kind: "deferred";
29
- readonly state: SessionSnapshot;
30
- readonly turnId: string;
31
- readonly stepId: string;
32
- readonly active: ActiveExecutionRecord;
33
- } | {
34
- readonly kind: "tripwire";
35
- readonly state: SessionSnapshot;
36
- readonly turnId: string;
37
- readonly stepId?: string;
38
- readonly tripwire: Tripwire;
39
- };
40
- export interface TurnRunContext {
41
- readonly signal: AbortSignal;
42
- readonly states: CapabilityStateRegistry;
43
- readonly observe: ObserveEmit;
44
- readonly assertCurrent: () => void;
45
- readonly onPlanActive: (pending: PendingTurn | undefined) => void;
46
- readonly commit: (state: SessionSnapshot, transition: SessionRecord["transition"], active?: ActiveExecutionRecord) => Promise<SessionSnapshot>;
47
- readonly onConversation: (event: SessionEvent<JsonValue>) => void;
48
- readonly claimInterrupts: (state: SessionSnapshot, turnId: string) => Promise<{
49
- readonly state: SessionSnapshot;
50
- readonly arrivals: readonly InputEvent[];
51
- }>;
52
- }
53
- /** Advances one Turn until it finalizes, trips policy, or reaches a waiting barrier. */
54
- export declare class TurnRunner {
55
- private readonly agent;
56
- private readonly sessionId;
57
- private readonly session;
58
- constructor(agent: LoopAgent, sessionId: string, session: Readonly<{
59
- readonly userId?: string;
60
- readonly context?: JsonObject;
61
- }>);
62
- start(state: SessionSnapshot, event: InputEvent, context: TurnRunContext, output?: TurnOutputContract): Promise<TurnProgress>;
63
- continue(state: SessionSnapshot, context: TurnRunContext): Promise<TurnProgress>;
64
- resume(state: SessionSnapshot, pending: PendingTurn, event: InputEvent, context: TurnRunContext): Promise<TurnProgress>;
65
- private advance;
66
- private waitForInteraction;
67
- private waitForDeferred;
68
- private runPlan;
69
- }