@qvac/harness 0.7.0 → 1.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +239 -2
  3. package/dist-lib/index.d.ts +58 -3
  4. package/dist-lib/index.js +736 -239
  5. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.d.ts +1 -0
  6. package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.js +10 -0
  7. package/dist-lib/lib/capabilities/completion-engine/actors/answering.d.ts +3 -0
  8. package/dist-lib/lib/capabilities/completion-engine/actors/answering.js +17 -0
  9. package/dist-lib/lib/capabilities/completion-engine/actors/approval.d.ts +4 -0
  10. package/dist-lib/lib/capabilities/completion-engine/actors/approval.js +29 -0
  11. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.d.ts +4 -0
  12. package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.js +73 -0
  13. package/dist-lib/lib/capabilities/completion-engine/actors/execute.d.ts +41 -0
  14. package/dist-lib/lib/capabilities/completion-engine/actors/execute.js +49 -0
  15. package/dist-lib/lib/capabilities/completion-engine/actors/executing.d.ts +3 -0
  16. package/dist-lib/lib/capabilities/completion-engine/actors/executing.js +102 -0
  17. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.d.ts +47 -0
  18. package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.js +87 -0
  19. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.d.ts +2 -0
  20. package/dist-lib/lib/capabilities/completion-engine/actors/finishing.js +32 -0
  21. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.d.ts +3 -0
  22. package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.js +75 -0
  23. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.d.ts +3 -0
  24. package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.js +152 -0
  25. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.d.ts +3 -0
  26. package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.js +94 -0
  27. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.d.ts +9 -0
  28. package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.js +11 -0
  29. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.d.ts +3 -0
  30. package/dist-lib/lib/capabilities/completion-engine/actors/preloading.js +114 -0
  31. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.d.ts +3 -0
  32. package/dist-lib/lib/capabilities/completion-engine/actors/preparing.js +198 -0
  33. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.d.ts +3 -0
  34. package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.js +71 -0
  35. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.d.ts +18 -0
  36. package/dist-lib/lib/capabilities/completion-engine/actors/rejection.js +70 -0
  37. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.d.ts +3 -0
  38. package/dist-lib/lib/capabilities/completion-engine/actors/resolving.js +60 -0
  39. package/dist-lib/lib/capabilities/completion-engine/actors/stream.d.ts +21 -0
  40. package/dist-lib/lib/capabilities/completion-engine/actors/stream.js +48 -0
  41. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.d.ts +3 -0
  42. package/dist-lib/lib/capabilities/completion-engine/actors/streaming.js +156 -0
  43. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.d.ts +7 -0
  44. package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.js +61 -0
  45. package/dist-lib/lib/capabilities/completion-engine/driver.d.ts +25 -0
  46. package/dist-lib/lib/capabilities/completion-engine/driver.js +553 -0
  47. package/dist-lib/lib/capabilities/completion-engine/durable.d.ts +61 -0
  48. package/dist-lib/lib/capabilities/completion-engine/durable.js +150 -0
  49. package/dist-lib/lib/capabilities/completion-engine/operations.d.ts +6 -0
  50. package/dist-lib/lib/capabilities/completion-engine/operations.js +239 -0
  51. package/dist-lib/lib/capabilities/completion-engine/ports.d.ts +3 -0
  52. package/dist-lib/lib/capabilities/completion-engine/ports.js +267 -0
  53. package/dist-lib/lib/capabilities/completion-engine/session.d.ts +76 -0
  54. package/dist-lib/lib/capabilities/completion-engine/session.js +70 -0
  55. package/dist-lib/lib/capabilities/completion-loop/index.d.ts +80 -0
  56. package/dist-lib/lib/capabilities/completion-loop/index.js +158 -0
  57. package/dist-lib/lib/capabilities/completion-loop/metrics.d.ts +5 -0
  58. package/dist-lib/lib/capabilities/completion-loop/metrics.js +29 -0
  59. package/dist-lib/lib/capabilities/completion-loop/round.d.ts +33 -0
  60. package/dist-lib/lib/capabilities/completion-loop/round.js +353 -0
  61. package/dist-lib/lib/capabilities/completion-loop/tool-execution.d.ts +60 -0
  62. package/dist-lib/lib/capabilities/completion-loop/tool-execution.js +270 -0
  63. package/dist-lib/lib/capabilities/completion-loop/turns.d.ts +23 -0
  64. package/dist-lib/lib/capabilities/completion-loop/turns.js +112 -0
  65. package/dist-lib/lib/capabilities/completion-runtime.d.ts +46 -0
  66. package/dist-lib/lib/capabilities/completion-runtime.js +74 -0
  67. package/dist-lib/lib/capabilities/completion.d.ts +18 -335
  68. package/dist-lib/lib/capabilities/completion.js +20 -1301
  69. package/dist-lib/lib/capabilities/download.d.ts +1 -1
  70. package/dist-lib/lib/capabilities/download.js +7 -2
  71. package/dist-lib/lib/capabilities/invoke.js +5 -3
  72. package/dist-lib/lib/capabilities/tool-plan.d.ts +35 -0
  73. package/dist-lib/lib/capabilities/tool-plan.js +66 -0
  74. package/dist-lib/lib/capabilities/tool-turns.d.ts +11 -2
  75. package/dist-lib/lib/capabilities/tool-turns.js +30 -2
  76. package/dist-lib/lib/capabilities/types.d.ts +287 -0
  77. package/dist-lib/lib/capabilities/types.js +1 -0
  78. package/dist-lib/lib/completion-lifecycle.d.ts +14 -0
  79. package/dist-lib/lib/completion-lifecycle.js +126 -0
  80. package/dist-lib/lib/connect.d.ts +10 -3
  81. package/dist-lib/lib/connect.js +329 -23
  82. package/dist-lib/lib/contract-types.d.ts +19 -2
  83. package/dist-lib/lib/harness-lifecycle.d.ts +66 -0
  84. package/dist-lib/lib/harness-lifecycle.js +129 -0
  85. package/dist-lib/lib/helpers/detach-json.d.ts +1 -0
  86. package/dist-lib/lib/helpers/detach-json.js +146 -0
  87. package/dist-lib/lib/helpers/markup-scanner.d.ts +26 -0
  88. package/dist-lib/lib/helpers/markup-scanner.js +156 -0
  89. package/dist-lib/lib/helpers/markup-stream-filter.js +138 -35
  90. package/dist-lib/lib/helpers/race-abort.d.ts +9 -4
  91. package/dist-lib/lib/helpers/race-abort.js +2 -7
  92. package/dist-lib/lib/helpers/recovery-required.d.ts +1 -0
  93. package/dist-lib/lib/helpers/recovery-required.js +1 -0
  94. package/dist-lib/lib/helpers/run-outcome.d.ts +10 -2
  95. package/dist-lib/lib/helpers/run-outcome.js +8 -5
  96. package/dist-lib/lib/helpers/tool-call-markup.d.ts +2 -0
  97. package/dist-lib/lib/helpers/tool-call-markup.js +197 -82
  98. package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -0
  99. package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -0
  100. package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -0
  101. package/dist-lib/lib/hooks/built-in/egress.js +2 -1
  102. package/dist-lib/lib/hooks/built-in/sandbox.d.ts +1 -0
  103. package/dist-lib/lib/hooks/built-in/sandbox.js +6 -1
  104. package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -0
  105. package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
  106. package/dist-lib/lib/hooks/pipeline.d.ts +4 -1
  107. package/dist-lib/lib/hooks/pipeline.js +24 -2
  108. package/dist-lib/lib/hooks/run.d.ts +2 -1
  109. package/dist-lib/lib/hooks/run.js +19 -7
  110. package/dist-lib/lib/hooks/types.d.ts +17 -6
  111. package/dist-lib/lib/machine/completion/actions/approval.d.ts +3 -0
  112. package/dist-lib/lib/machine/completion/actions/approval.js +16 -0
  113. package/dist-lib/lib/machine/completion/actions/completion.d.ts +23 -0
  114. package/dist-lib/lib/machine/completion/actions/completion.js +142 -0
  115. package/dist-lib/lib/machine/completion/actions/skills.d.ts +9 -0
  116. package/dist-lib/lib/machine/completion/actions/skills.js +83 -0
  117. package/dist-lib/lib/machine/completion/actions/tools.d.ts +16 -0
  118. package/dist-lib/lib/machine/completion/actions/tools.js +173 -0
  119. package/dist-lib/lib/machine/completion/context.d.ts +9 -0
  120. package/dist-lib/lib/machine/completion/context.js +44 -0
  121. package/dist-lib/lib/machine/completion/contract.d.ts +6 -0
  122. package/dist-lib/lib/machine/completion/contract.js +1 -0
  123. package/dist-lib/lib/machine/completion/guards/retry.d.ts +9 -0
  124. package/dist-lib/lib/machine/completion/guards/retry.js +25 -0
  125. package/dist-lib/lib/machine/completion/implementations.d.ts +81 -0
  126. package/dist-lib/lib/machine/completion/implementations.js +152 -0
  127. package/dist-lib/lib/machine/completion/machine.d.ts +375 -0
  128. package/dist-lib/lib/machine/completion/machine.js +511 -0
  129. package/dist-lib/lib/machine/completion/ports.d.ts +65 -0
  130. package/dist-lib/lib/machine/completion/ports.js +224 -0
  131. package/dist-lib/lib/machine/completion/rules.d.ts +39 -0
  132. package/dist-lib/lib/machine/completion/rules.js +115 -0
  133. package/dist-lib/lib/machine/completion/types.d.ts +493 -0
  134. package/dist-lib/lib/machine/completion/types.js +1 -0
  135. package/dist-lib/lib/machine/engine.d.ts +93 -0
  136. package/dist-lib/lib/machine/engine.js +483 -0
  137. package/dist-lib/lib/machine/errors.d.ts +44 -0
  138. package/dist-lib/lib/machine/errors.js +373 -0
  139. package/dist-lib/lib/machine/guards.d.ts +2 -0
  140. package/dist-lib/lib/machine/guards.js +6 -0
  141. package/dist-lib/lib/machine/index.d.ts +20 -0
  142. package/dist-lib/lib/machine/index.js +11 -0
  143. package/dist-lib/lib/machine/replay-output.d.ts +24 -0
  144. package/dist-lib/lib/machine/replay-output.js +50 -0
  145. package/dist-lib/lib/machine/stable-stringify.d.ts +1 -0
  146. package/dist-lib/lib/machine/stable-stringify.js +11 -0
  147. package/dist-lib/lib/models/model-handle.d.ts +5 -1
  148. package/dist-lib/lib/models/model-handle.js +8 -3
  149. package/dist-lib/lib/models/model-list.d.ts +5 -3
  150. package/dist-lib/lib/models/model-pool.d.ts +18 -7
  151. package/dist-lib/lib/models/model-pool.js +126 -38
  152. package/dist-lib/lib/run-log/history-codec.d.ts +23 -0
  153. package/dist-lib/lib/run-log/history-codec.js +973 -0
  154. package/dist-lib/lib/run-log/patch.d.ts +6 -0
  155. package/dist-lib/lib/run-log/patch.js +93 -0
  156. package/dist-lib/lib/run-log/persisted-actor.d.ts +18 -0
  157. package/dist-lib/lib/run-log/persisted-actor.js +218 -0
  158. package/dist-lib/lib/run-log/protocol-types.d.ts +153 -0
  159. package/dist-lib/lib/run-log/protocol-types.js +1 -0
  160. package/dist-lib/lib/run-log/protocol.d.ts +27 -0
  161. package/dist-lib/lib/run-log/protocol.js +1056 -0
  162. package/dist-lib/lib/run-log/replay.d.ts +25 -0
  163. package/dist-lib/lib/run-log/replay.js +129 -0
  164. package/dist-lib/lib/run-log/run-journal.d.ts +125 -0
  165. package/dist-lib/lib/run-log/run-journal.js +783 -0
  166. package/dist-lib/lib/run-log-inspect.d.ts +25 -0
  167. package/dist-lib/lib/run-log-inspect.js +627 -0
  168. package/dist-lib/lib/run-log-protocol.d.ts +2 -0
  169. package/dist-lib/lib/run-log-protocol.js +1 -0
  170. package/dist-lib/lib/run-log-redact.d.ts +27 -0
  171. package/dist-lib/lib/run-log-redact.js +1107 -0
  172. package/dist-lib/lib/serve.js +308 -12
  173. package/dist-lib/lib/spawn.js +3 -2
  174. package/dist-lib/lib/tool-call-display.d.ts +1 -21
  175. package/dist-lib/lib/tools/exec.d.ts +2 -1
  176. package/dist-lib/lib/tools/exec.js +57 -2
  177. package/dist-lib/lib/tools/generate-image.js +1 -1
  178. package/dist-lib/lib/tools/generate-music.js +1 -1
  179. package/dist-lib/lib/tools/http-request.d.ts +2 -1
  180. package/dist-lib/lib/tools/http-request.js +2 -1
  181. package/dist-lib/lib/tools/mcp-call.js +8 -1
  182. package/dist-lib/lib/tools/operations/builders.d.ts +1 -0
  183. package/dist-lib/lib/tools/operations/builders.js +1 -0
  184. package/dist-lib/lib/tools/operations/compile.js +11 -1
  185. package/dist-lib/lib/tools/operations/manifest.js +1 -0
  186. package/dist-lib/lib/tools/outbound-guard.d.ts +8 -6
  187. package/dist-lib/lib/tools/outbound-guard.js +25 -8
  188. package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
  189. package/dist-lib/lib/tools/skills/catalog.js +35 -0
  190. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +1 -0
  191. package/dist-lib/lib/tools/skills/cli-schema.js +25 -0
  192. package/dist-lib/lib/wire.d.ts +5 -0
  193. package/dist-lib/lib/wire.js +128 -5
  194. package/dist-lib/schema/harness.js +22 -6
  195. package/dist-lib/spec/hrpc/messages.js +100 -18
  196. package/dist-lib/spec/hyperschema/index.js +100 -18
  197. package/dist-lib/spec/hyperschema/schema.json +74 -1
  198. package/dist-lib/spec/hyperschema/types.d.ts +11 -3
  199. package/package.json +19 -1
  200. package/patches/@qvac%2Finference@0.20.0.patch +67 -0
  201. package/patches/bare-stow@0.1.5.patch +68 -0
  202. package/patches/hypercore-blob-server@1.15.0.patch +15 -0
  203. package/patches/localwatch@1.4.3.patch +13 -0
  204. package/patches/pyodide@314.0.2.patch +144 -0
@@ -0,0 +1,150 @@
1
+ import { createHash } from 'crypto';
2
+ import { isRecord } from '../../guards.js';
3
+ import { copy, stableStringify } from '../../machine/index.js';
4
+ import { createRunOutcomeTracker } from '../../helpers/run-outcome.js';
5
+ import { runToolContext } from '../completion-loop/index.js';
6
+ function rootIdentity(root) {
7
+ return root.kind === 'local'
8
+ ? `local:${root.root}`
9
+ : `hyper:${root.key}:${root.prefix}:${root.writable}`;
10
+ }
11
+ export function capabilityPolicy(session) {
12
+ const context = session.toolContext();
13
+ return {
14
+ behaviorDigest: session.behaviorDigest,
15
+ roots: (context.fsRoots ?? []).map(rootIdentity).sort(),
16
+ writeRoots: session.writableRoots.map(rootIdentity).sort(),
17
+ skills: [...session.activation.enabledSkills].sort(),
18
+ skillContent: session.inContextSkills
19
+ .map((skill) => [skill.name, skill.contentDigest ?? null])
20
+ .sort(([left], [right]) => left.localeCompare(right)),
21
+ grantedOps: [...session.grantedOps.entries()]
22
+ .map(([bin, validators]) => [bin, validators.map((validator) => validator.fingerprint).sort()])
23
+ .sort(([left], [right]) => left.localeCompare(right))
24
+ };
25
+ }
26
+ const schemaDigests = new WeakMap();
27
+ export function schemaDigest(parameters) {
28
+ const serialized = stableStringify(parameters);
29
+ const cached = schemaDigests.get(parameters);
30
+ if (cached?.serialized === serialized) {
31
+ return cached.digest;
32
+ }
33
+ const digest = createHash('sha256').update(serialized).digest('hex');
34
+ schemaDigests.set(parameters, { serialized, digest });
35
+ return digest;
36
+ }
37
+ const durableFactsCache = new WeakMap();
38
+ function toolFacts(tools) {
39
+ return tools.map(mapToolFacts);
40
+ }
41
+ function mapToolFacts({ name, description, prompt, policyVersion, parameters, toolset, autoApprove, externalWrite, owningSkill, skillGated, rootsRequired, writeRootsRequired, compiled }) {
42
+ return {
43
+ name,
44
+ description,
45
+ prompt: prompt ?? null,
46
+ policyVersion: policyVersion ?? null,
47
+ parametersDigest: schemaDigest(parameters),
48
+ toolset,
49
+ autoApprove: autoApprove === true,
50
+ externalWrite: externalWrite === true,
51
+ owningSkill: owningSkill ?? null,
52
+ skillGated: skillGated === true,
53
+ rootsRequired: rootsRequired === true,
54
+ writeRootsRequired: writeRootsRequired === true,
55
+ compiled: compiled === true
56
+ };
57
+ }
58
+ function makeDurableFactsSource(session) {
59
+ return {
60
+ bindings: [...session.bindings],
61
+ runId: session.runId,
62
+ tools: toolFacts(session.enabled),
63
+ policy: capabilityPolicy(session),
64
+ loadedThisTurn: [...session.loadedThisTurn],
65
+ loadedFilesThisTurn: [...session.loadedFilesThisTurn],
66
+ loadedSkillFiles: [...session.loadedSkillFiles].map(([name, files]) => [
67
+ name,
68
+ [...files]
69
+ ]),
70
+ nudgedSkills: [...session.nudgedSkills],
71
+ backends: [...session.backends],
72
+ outcome: session.runOutcomes.snapshot(),
73
+ skillIcon: session.skillIcon,
74
+ runBudget: { ...session.runBudget },
75
+ startedAt: session.startedAt,
76
+ runStarted: session.runStarted,
77
+ promptParts: session.promptParts,
78
+ activation: {
79
+ ...session.activation,
80
+ enabledSkills: [...session.activation.enabledSkills],
81
+ loadedSkills: [...session.activation.loadedSkills]
82
+ }
83
+ };
84
+ }
85
+ export function captureFacts(session) {
86
+ const source = makeDurableFactsSource(session);
87
+ const serialized = JSON.stringify(source);
88
+ if (serialized === undefined) {
89
+ return copy(source);
90
+ }
91
+ const cached = durableFactsCache.get(session);
92
+ if (cached?.serialized === serialized) {
93
+ return cached.snapshot;
94
+ }
95
+ const snapshot = JSON.parse(serialized);
96
+ freezeJsonTree(snapshot);
97
+ durableFactsCache.set(session, { serialized, snapshot });
98
+ return snapshot;
99
+ }
100
+ export function captureToolOperationFacts(session) {
101
+ return copy({
102
+ backends: [...session.backends],
103
+ runBudget: { outputBytesUsed: session.runBudget.outputBytesUsed },
104
+ outcome: { confirmedWrites: session.runOutcomes.snapshot().confirmedWrites }
105
+ });
106
+ }
107
+ export function restoreFacts(deps, facts) {
108
+ facts = copy(facts);
109
+ const session = deps.session;
110
+ session.bindings = [...facts.bindings];
111
+ session.runId = facts.runId;
112
+ session.loadedThisTurn = new Set(facts.loadedThisTurn);
113
+ session.loadedFilesThisTurn = new Set(facts.loadedFilesThisTurn);
114
+ session.loadedSkillFiles = new Map(facts.loadedSkillFiles.map(([name, files]) => [name, new Set(files)]));
115
+ session.nudgedSkills = new Set(facts.nudgedSkills);
116
+ session.backends = [...facts.backends];
117
+ session.runOutcomes = createRunOutcomeTracker(facts.outcome);
118
+ session.landed = session.runOutcomes.confirmedWrites;
119
+ session.skillIcon = facts.skillIcon;
120
+ session.runBudget = { ...facts.runBudget };
121
+ session.toolContext = runToolContext(deps.args, session.runBudget);
122
+ session.startedAt = facts.startedAt ?? session.startedAt;
123
+ session.runStarted = facts.runStarted;
124
+ session.promptParts = facts.promptParts;
125
+ session.activation = { ...facts.activation, loadedSkills: new Set(facts.activation.loadedSkills) };
126
+ }
127
+ function freezeJsonTree(root) {
128
+ if (!isRecord(root)) {
129
+ return;
130
+ }
131
+ const pending = [root];
132
+ const visited = new WeakSet();
133
+ while (pending.length > 0) {
134
+ const current = pending.pop();
135
+ if (current === undefined || visited.has(current)) {
136
+ continue;
137
+ }
138
+ visited.add(current);
139
+ enqueueJsonChildren(current, pending);
140
+ Object.freeze(current);
141
+ }
142
+ }
143
+ function enqueueJsonChildren(current, pending) {
144
+ for (const key of Reflect.ownKeys(current)) {
145
+ const child = Reflect.get(current, key);
146
+ if (isRecord(child)) {
147
+ pending.push(child);
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,6 @@
1
+ import type { CompletionArgs } from '../completion-loop/index.ts';
2
+ import type { CompletionDeps } from './session.ts';
3
+ import { type ToolOperationFacts } from './durable.ts';
4
+ import type { CompletionMachineContext as CompletionContext } from '../../machine/index.ts';
5
+ export declare function toolOperationFacts(value: unknown): ToolOperationFacts;
6
+ export declare function bindOperations(deps: CompletionDeps, context: CompletionContext, stage: string): CompletionArgs;
@@ -0,0 +1,239 @@
1
+ import { createHash } from 'crypto';
2
+ import { normalizeToolExecutionOutcome, writeResult } from '../../helpers/run-outcome.js';
3
+ import { isMetadataMethod } from '../../tools/mcp/protocol.js';
4
+ import { captureFacts, captureToolOperationFacts, restoreFacts } from './durable.js';
5
+ import { AbortedError } from '../../helpers/race-abort.js';
6
+ import { ToolPolicyChanged } from '../../machine/index.js';
7
+ import { isRecord } from '../../guards.js';
8
+ import { detachJson } from '../../helpers/detach-json.js';
9
+ const basePipelines = new WeakMap();
10
+ function digestArgs(payload) {
11
+ const entries = Object.entries(payload);
12
+ const messages = entries.find(([key]) => key === 'messages')?.[1];
13
+ if (!Array.isArray(messages)) {
14
+ return Object.fromEntries(entries);
15
+ }
16
+ const rest = Object.fromEntries(entries.filter(([key]) => key !== 'messages'));
17
+ return {
18
+ ...rest,
19
+ messages: {
20
+ count: messages.length,
21
+ sha256: createHash('sha256').update(JSON.stringify(messages)).digest('hex')
22
+ }
23
+ };
24
+ }
25
+ function isBackendUse(value) {
26
+ return (isRecord(value) &&
27
+ typeof value.tool === 'string' &&
28
+ typeof value.requestedGpu === 'boolean' &&
29
+ (value.device === 'gpu' || value.device === 'cpu' || value.device === 'unknown') &&
30
+ typeof value.backend === 'string');
31
+ }
32
+ export function toolOperationFacts(value) {
33
+ if (!isRecord(value) || !Array.isArray(value.backends) || !isRecord(value.runBudget)) {
34
+ throw new TypeError('Malformed tool operation facts');
35
+ }
36
+ const backends = value.backends.filter(isBackendUse);
37
+ const runBudget = value.runBudget.outputBytesUsed;
38
+ const outcome = value.outcome;
39
+ if (backends.length !== value.backends.length ||
40
+ typeof runBudget !== 'number' ||
41
+ !Number.isSafeInteger(runBudget) ||
42
+ runBudget < 0 ||
43
+ !isRecord(outcome) ||
44
+ !Array.isArray(outcome.confirmedWrites)) {
45
+ throw new TypeError('Malformed tool operation facts');
46
+ }
47
+ const confirmedWrites = outcome.confirmedWrites.filter((entry) => typeof entry === 'string');
48
+ if (confirmedWrites.length !== outcome.confirmedWrites.length) {
49
+ throw new TypeError('Malformed tool operation facts');
50
+ }
51
+ return { backends, runBudget: { outputBytesUsed: runBudget }, outcome: { confirmedWrites } };
52
+ }
53
+ function runHookOperation({ deps, base, phase, id, name, payload, check, run }) {
54
+ check();
55
+ if (!base.hasPhase(phase)) {
56
+ return run();
57
+ }
58
+ return deps.operations.operation({ id, name, args: digestArgs(payload), effect: 'read' }, run, undefined, deps.args.signal);
59
+ }
60
+ function wrapHostCallback({ deps, check, prefix, stage, callId, name, effect, callback }) {
61
+ let count = 0;
62
+ return async (input) => {
63
+ const release = deps.session.trackInvoke();
64
+ try {
65
+ check();
66
+ const journalInput = detachHostCallbackInput(input);
67
+ const callbackInput = detachHostCallbackInput(journalInput);
68
+ return await deps.operations.operation({
69
+ id: `${prefix}:${stage}:${callId}:host-${name}-${count++}`,
70
+ name: `host:${name}`,
71
+ args: [journalInput],
72
+ effect
73
+ }, () => callback(callbackInput).then((result) => detachJson(result)), undefined, deps.args.signal);
74
+ }
75
+ finally {
76
+ release();
77
+ }
78
+ };
79
+ }
80
+ function detachHostCallbackInput(input) {
81
+ const detached = detachJson(input);
82
+ if (isRecord(input) && input.bytes instanceof Uint8Array) {
83
+ Reflect.set(detached, 'bytes', Uint8Array.from(input.bytes));
84
+ }
85
+ return detached;
86
+ }
87
+ export function bindOperations(deps, context, stage) {
88
+ const args = { ...deps.args };
89
+ const prefix = `${context.runId}:round-${context.round}`;
90
+ const check = () => {
91
+ if (args.signal?.aborted)
92
+ throw new AbortedError();
93
+ };
94
+ args.tools = args.tools?.map((tool) => ({
95
+ ...tool,
96
+ execute: async (resolved, runtime) => {
97
+ const call = runtime?.callId ??
98
+ context.currentCall?.id ??
99
+ `preload-${context.skillPreload?.name ?? tool.name}`;
100
+ const id = `${prefix}:${call}:execute`;
101
+ const effect = tool.effect(detachJson(resolved), runtime);
102
+ if (stage === 'preloading' &&
103
+ context.skillPreload &&
104
+ tool.name === 'skill' &&
105
+ (context.skillPreload.facts?.effect !== 'read' || effect !== 'read')) {
106
+ throw new ToolPolicyChanged(tool.name);
107
+ }
108
+ if (context.execution &&
109
+ context.currentCall?.name === tool.name &&
110
+ effect !== context.execution.effect) {
111
+ throw new ToolPolicyChanged(tool.name);
112
+ }
113
+ check();
114
+ const result = await deps.operations.operation({
115
+ id,
116
+ name: tool.name,
117
+ args: resolved,
118
+ effect
119
+ }, async () => {
120
+ const result = await tool.execute(resolved, runtime);
121
+ confirm(result);
122
+ return result;
123
+ }, {
124
+ capture: () => captureToolOperationFacts(deps.session),
125
+ restore: (facts) => {
126
+ const recorded = toolOperationFacts(facts);
127
+ const current = captureFacts(deps.session);
128
+ restoreFacts(deps, {
129
+ ...current,
130
+ backends: recorded.backends.length > current.backends.length
131
+ ? recorded.backends
132
+ : current.backends,
133
+ runBudget: {
134
+ deadlineMs: current.runBudget.deadlineMs,
135
+ outputBytesUsed: Math.max(current.runBudget.outputBytesUsed, recorded.runBudget.outputBytesUsed)
136
+ },
137
+ outcome: {
138
+ ...current.outcome,
139
+ confirmedWrites: recorded.outcome.confirmedWrites.length > current.outcome.confirmedWrites.length
140
+ ? recorded.outcome.confirmedWrites
141
+ : current.outcome.confirmedWrites
142
+ }
143
+ });
144
+ }
145
+ }, args.signal);
146
+ const receipt = deps.operations.getReceipt(id);
147
+ if (receipt?.status === 'result' && receipt.facts === undefined) {
148
+ confirm(result);
149
+ await deps.operations.commitResultFacts(id, captureToolOperationFacts(deps.session));
150
+ }
151
+ if (args.signal?.aborted && deps.operations.getReceipt(id)?.status === 'result') {
152
+ deps.commitSettledFacts?.(id);
153
+ }
154
+ return result;
155
+ function confirm(result) {
156
+ if (effect === 'write' &&
157
+ normalizeToolExecutionOutcome(result, { kind: 'success' }).kind === 'success') {
158
+ const written = tool.landedWrite?.(detachJson(resolved), detachJson(result)) ??
159
+ ((tool.toolset === 'mcp-call' && !isMetadataMethod(resolved.method)) ||
160
+ (tool.compiled === true && tool.toolset === 'http-request')
161
+ ? writeResult(result)
162
+ : null);
163
+ if (written) {
164
+ deps.session.landed.push(written);
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }));
170
+ if (args.attach) {
171
+ args.attach = wrapHostCallback({
172
+ deps,
173
+ check,
174
+ prefix,
175
+ stage,
176
+ callId: context.currentCall?.id ?? 'prepare',
177
+ name: 'attach',
178
+ effect: 'write',
179
+ callback: args.attach
180
+ });
181
+ }
182
+ if (args.askUser) {
183
+ args.askUser = wrapHostCallback({
184
+ deps,
185
+ check,
186
+ prefix,
187
+ stage,
188
+ callId: context.currentCall?.id ?? 'prepare',
189
+ name: 'askUser',
190
+ effect: 'read',
191
+ callback: args.askUser
192
+ });
193
+ }
194
+ const base = basePipelines.get(deps.session) ?? deps.session.pipeline;
195
+ basePipelines.set(deps.session, base);
196
+ const pipeline = Object.create(base);
197
+ deps.session.pipeline = pipeline;
198
+ let startCount = 0;
199
+ pipeline.onRunStart = async (payload) => {
200
+ await runHookOperation({
201
+ deps,
202
+ base,
203
+ phase: 'runStart',
204
+ id: `${prefix}:${stage}:run:hook-onRunStart-${startCount++}`,
205
+ name: 'hook:onRunStart',
206
+ payload,
207
+ check,
208
+ run: () => base.onRunStart(payload)
209
+ });
210
+ };
211
+ let preToolCount = 0;
212
+ pipeline.runPreToolFold = async (payload) => {
213
+ const { signal: _signal, ...durable } = payload;
214
+ return runHookOperation({
215
+ deps,
216
+ base,
217
+ phase: 'preToolCall',
218
+ id: `${prefix}:${stage}:${payload.callId}:hook-runPreToolFold-${preToolCount++}`,
219
+ name: 'hook:runPreToolFold',
220
+ payload: durable,
221
+ check,
222
+ run: () => base.runPreToolFold(payload)
223
+ });
224
+ };
225
+ let postToolCount = 0;
226
+ pipeline.runPostToolFold = async (payload) => {
227
+ return runHookOperation({
228
+ deps,
229
+ base,
230
+ phase: 'postToolCall',
231
+ id: `${prefix}:${stage}:${payload.callId}:hook-runPostToolFold-${postToolCount++}`,
232
+ name: 'hook:runPostToolFold',
233
+ payload,
234
+ check,
235
+ run: () => base.runPostToolFold(payload)
236
+ });
237
+ };
238
+ return args;
239
+ }
@@ -0,0 +1,3 @@
1
+ import type { EnginePorts } from '../../machine/index.ts';
2
+ import type { CompletionDeps } from './session.ts';
3
+ export declare function completionPorts(deps: CompletionDeps): EnginePorts;
@@ -0,0 +1,267 @@
1
+ import { runToolContext } from '../completion-loop/index.js';
2
+ import { AbortedError } from '../../helpers/race-abort.js';
3
+ import { RecoveryRequired } from '../../helpers/recovery-required.js';
4
+ import { stableStringify } from '../../machine/index.js';
5
+ import { approval } from './actors/approval.js';
6
+ import { answering } from './actors/answering.js';
7
+ import { directSearch } from './actors/direct-search.js';
8
+ import { executing } from './actors/executing.js';
9
+ import { finishing } from './actors/finishing.js';
10
+ import { inspecting } from './actors/inspecting.js';
11
+ import { inspectPreload } from './actors/inspect-preload.js';
12
+ import { loadingSkill } from './actors/loading-skill.js';
13
+ import { preloading } from './actors/preloading.js';
14
+ import { preparing } from './actors/preparing.js';
15
+ import { rejecting } from './actors/rejecting.js';
16
+ import { resolving } from './actors/resolving.js';
17
+ import { streaming } from './actors/streaming.js';
18
+ import { wrapUp } from './actors/wrap-up.js';
19
+ import { toolContext } from './actors/execution-plan.js';
20
+ import { capabilityPolicy, captureFacts, restoreFacts, schemaDigest } from './durable.js';
21
+ import { bindOperations, toolOperationFacts } from './operations.js';
22
+ import { errorMessage, isRecord } from '../../guards.js';
23
+ import { detachJson } from '../../helpers/detach-json.js';
24
+ export function completionPorts(deps) {
25
+ return {
26
+ stages: {
27
+ preparing: stage(deps, preparing),
28
+ inspectPreload: stage(deps, inspectPreload),
29
+ preloading: stage(deps, preloading),
30
+ searching: stage(deps, directSearch),
31
+ generating: stage(deps, streaming),
32
+ resolving: stage(deps, resolving),
33
+ answering: stage(deps, answering),
34
+ inspecting: stage(deps, inspecting),
35
+ rejecting: stage(deps, rejecting),
36
+ loadingSkill: stage(deps, loadingSkill),
37
+ approving: stage(deps, approval),
38
+ executing: stage(deps, executing),
39
+ wrapping: stage(deps, wrapUp),
40
+ finishing: stage(deps, finishing)
41
+ },
42
+ rebuild: (cx) => rebuildSession(deps, cx),
43
+ lifecycle: (phase, context) => deps.session.pipeline.onRunLifecycle({
44
+ phase,
45
+ runId: context.runId,
46
+ agentId: context.agentId
47
+ }),
48
+ settledResultFacts: (operationId, current) => {
49
+ const receipt = deps.operations.getReceipt(operationId);
50
+ if (receipt?.status !== 'result' || receipt.facts === undefined) {
51
+ return undefined;
52
+ }
53
+ const recorded = toolOperationFacts(receipt.facts);
54
+ const fallback = current ? undefined : captureFacts(deps.session);
55
+ const baseline = current ?? fallback;
56
+ if (!baseline) {
57
+ return undefined;
58
+ }
59
+ const currentBackends = Array.isArray(baseline.backends) ? baseline.backends : [];
60
+ const currentBudget = isRecord(baseline.runBudget)
61
+ ? baseline.runBudget
62
+ : captureFacts(deps.session).runBudget;
63
+ const currentOutcome = isRecord(baseline.outcome) ? baseline.outcome : {};
64
+ const currentWrites = [];
65
+ if (Array.isArray(currentOutcome.confirmedWrites)) {
66
+ for (const entry of currentOutcome.confirmedWrites) {
67
+ if (typeof entry === 'string') {
68
+ currentWrites.push(entry);
69
+ }
70
+ }
71
+ }
72
+ return {
73
+ ...baseline,
74
+ backends: recorded.backends.length > currentBackends.length ? recorded.backends : currentBackends,
75
+ runBudget: {
76
+ ...currentBudget,
77
+ outputBytesUsed: Math.max(typeof currentBudget.outputBytesUsed === 'number' ? currentBudget.outputBytesUsed : 0, recorded.runBudget.outputBytesUsed)
78
+ },
79
+ outcome: {
80
+ ...currentOutcome,
81
+ confirmedWrites: recorded.outcome.confirmedWrites.length > currentWrites.length
82
+ ? recorded.outcome.confirmedWrites
83
+ : currentWrites
84
+ }
85
+ };
86
+ },
87
+ now: deps.session.now
88
+ };
89
+ }
90
+ function stage(deps, run) {
91
+ return async (cx) => {
92
+ const invocation = bind(deps, cx);
93
+ try {
94
+ return await run(invocation);
95
+ }
96
+ finally {
97
+ invocation.input.commitFacts?.();
98
+ }
99
+ };
100
+ }
101
+ function bind(deps, cx) {
102
+ const { context, signal, stage } = cx;
103
+ const session = deps.session;
104
+ const local = {
105
+ ...deps,
106
+ args: { ...deps.args, signal: signal }
107
+ };
108
+ local.commitFacts = () => cx.commit(captureFacts(session));
109
+ local.commitSettledFacts = (operationId) => cx.commitSettledResult(operationId);
110
+ local.args = bindOperations(local, context, stage);
111
+ const bound = new Map(local.args.tools?.map((tool) => [tool.name, tool]));
112
+ session.enabled = session.enabled.map((tool) => bound.get(tool.name) ?? tool);
113
+ const acquireRound = local.args.acquireRound;
114
+ if (acquireRound) {
115
+ local.args.acquireRound = async (roundSignal) => {
116
+ try {
117
+ return await acquireRound(roundSignal);
118
+ }
119
+ catch (error) {
120
+ if (signal.aborted || roundSignal?.aborted) {
121
+ throw new AbortedError();
122
+ }
123
+ throw new RecoveryRequired({
124
+ id: `${context.runId}:runtime:model`,
125
+ name: `Model runtime unavailable: ${errorMessage(error)}`,
126
+ args: { model: local.args.model ?? local.args.modelId },
127
+ effect: 'read'
128
+ });
129
+ }
130
+ };
131
+ }
132
+ session.toolContext = runToolContext(local.args, session.runBudget);
133
+ return {
134
+ input: local,
135
+ context,
136
+ signal,
137
+ emit: (event) => {
138
+ if (signal.aborted)
139
+ return;
140
+ if (event.type === 'tool-result') {
141
+ local.commitFacts?.();
142
+ }
143
+ const output = {
144
+ ...detachEventPayload(event),
145
+ runId: context.runId,
146
+ round: eventRound(event, context)
147
+ };
148
+ if (context.currentCall) {
149
+ output.callId = context.currentCall.id;
150
+ }
151
+ else if (stage === 'preloading' && context.skillPreload) {
152
+ output.callId = `preload-${context.skillPreload.name}`;
153
+ }
154
+ cx.emit(output);
155
+ }
156
+ };
157
+ }
158
+ function detachEventPayload(event) {
159
+ switch (event.type) {
160
+ case 'inference-call':
161
+ return { ...event, call: detachJson(event.call) };
162
+ case 'inference-sent':
163
+ return {
164
+ ...event,
165
+ messages: detachJson(event.messages),
166
+ tokens: detachJson(event.tokens)
167
+ };
168
+ case 'metrics':
169
+ return {
170
+ ...event,
171
+ backends: detachJson(event.backends),
172
+ usage: detachJson(event.usage)
173
+ };
174
+ case 'tool-call': {
175
+ const payload = {
176
+ ...event,
177
+ args: detachJson(event.args),
178
+ display: detachJson(event.display)
179
+ };
180
+ if (payload.rawCall === undefined) {
181
+ delete payload.rawCall;
182
+ }
183
+ return payload;
184
+ }
185
+ case 'tool-activity':
186
+ return {
187
+ ...event,
188
+ args: detachJson(event.args),
189
+ resource: detachJson(event.resource)
190
+ };
191
+ case 'tool-result':
192
+ return { ...event, result: detachJson(event.result) };
193
+ default:
194
+ return event;
195
+ }
196
+ }
197
+ function eventRound(event, context) {
198
+ if (event.type === 'inference-sent')
199
+ return event.round;
200
+ if (event.type === 'inference-call')
201
+ return event.call.round;
202
+ return context.round;
203
+ }
204
+ async function rebuildSession(deps, cx) {
205
+ const { context } = cx;
206
+ const durable = context.durable;
207
+ if (durable) {
208
+ restoreFacts(deps, durable);
209
+ }
210
+ const missingBindings = durable?.bindings.filter((name) => typeof deps.args[name] !== 'function') ?? [];
211
+ if (missingBindings.length) {
212
+ throw new RecoveryRequired({
213
+ id: `${context.runId}:runtime:callbacks`,
214
+ name: `Supply runtime callbacks: ${missingBindings.join(', ')}`,
215
+ args: { callbacks: missingBindings },
216
+ effect: 'read'
217
+ });
218
+ }
219
+ const invocation = bind(deps, cx);
220
+ await preparing({ ...invocation, reconstruct: true });
221
+ if (durable) {
222
+ restoreFacts(invocation.input, durable);
223
+ }
224
+ const session = deps.session;
225
+ session.reselectTools?.(new Set([...(context.skills.snapshot?.loaded ?? []), ...session.loadedThisTurn]));
226
+ const missing = context.availableTools.filter((name) => !session.enabled.some((tool) => tool.name === name));
227
+ const changed = durable?.tools
228
+ .filter((saved) => {
229
+ const tool = session.enabled.find((tool) => tool.name === saved.name);
230
+ return (!tool ||
231
+ saved.description !== tool.description ||
232
+ stableStringify(saved.prompt) !== stableStringify(tool.prompt ?? null) ||
233
+ saved.policyVersion !== (tool.policyVersion ?? null) ||
234
+ saved.parametersDigest !== schemaDigest(tool.parameters) ||
235
+ saved.toolset !== tool.toolset ||
236
+ saved.autoApprove !== (tool.autoApprove === true) ||
237
+ saved.externalWrite !== (tool.externalWrite === true) ||
238
+ saved.owningSkill !== (tool.owningSkill ?? null) ||
239
+ saved.skillGated !== (tool.skillGated === true) ||
240
+ saved.rootsRequired !== (tool.rootsRequired === true) ||
241
+ saved.writeRootsRequired !== (tool.writeRootsRequired === true) ||
242
+ saved.compiled !== (tool.compiled === true));
243
+ })
244
+ .map((tool) => tool.name) ?? [];
245
+ const added = durable?.runStarted
246
+ ? session.enabled
247
+ .filter((tool) => !durable.tools.some((saved) => saved.name === tool.name))
248
+ .map((tool) => tool.name)
249
+ : [];
250
+ const policyChanged = durable?.runStarted &&
251
+ stableStringify(durable.policy) !== stableStringify(capabilityPolicy(session));
252
+ const call = context.currentCall;
253
+ const inFlight = call && session.enabled.find((tool) => tool.name === call.name);
254
+ const effectChanged = call &&
255
+ inFlight &&
256
+ context.execution &&
257
+ inFlight.effect(detachJson(call.args), toolContext({ deps: invocation.input, context, name: call.name })) !== context.execution.effect
258
+ ? [call.name]
259
+ : [];
260
+ const drifted = [...new Set([...missing, ...changed, ...added, ...effectChanged])];
261
+ if (drifted.length) {
262
+ throw new Error(`Tools changed since this run paused: ${drifted.join(', ')}`);
263
+ }
264
+ if (policyChanged) {
265
+ throw new Error('Capabilities changed since this run paused');
266
+ }
267
+ }