@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.
- package/CHANGELOG.md +42 -1
- package/README.md +239 -2
- package/dist-lib/index.d.ts +58 -3
- package/dist-lib/index.js +736 -239
- package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.d.ts +1 -0
- package/dist-lib/lib/capabilities/completion-engine/abort-polyfill.js +10 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/answering.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/answering.js +17 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/approval.d.ts +4 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/approval.js +29 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.d.ts +4 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/direct-search.js +73 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execute.d.ts +41 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execute.js +49 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/executing.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/executing.js +102 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.d.ts +47 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/execution-plan.js +87 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/finishing.d.ts +2 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/finishing.js +32 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspect-preload.js +75 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/inspecting.js +152 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/loading-skill.js +94 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.d.ts +9 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preload-failure.js +11 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preloading.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preloading.js +114 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preparing.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/preparing.js +198 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejecting.js +71 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejection.d.ts +18 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/rejection.js +70 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/resolving.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/resolving.js +60 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/stream.d.ts +21 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/stream.js +48 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/streaming.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/streaming.js +156 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.d.ts +7 -0
- package/dist-lib/lib/capabilities/completion-engine/actors/wrap-up.js +61 -0
- package/dist-lib/lib/capabilities/completion-engine/driver.d.ts +25 -0
- package/dist-lib/lib/capabilities/completion-engine/driver.js +553 -0
- package/dist-lib/lib/capabilities/completion-engine/durable.d.ts +61 -0
- package/dist-lib/lib/capabilities/completion-engine/durable.js +150 -0
- package/dist-lib/lib/capabilities/completion-engine/operations.d.ts +6 -0
- package/dist-lib/lib/capabilities/completion-engine/operations.js +239 -0
- package/dist-lib/lib/capabilities/completion-engine/ports.d.ts +3 -0
- package/dist-lib/lib/capabilities/completion-engine/ports.js +267 -0
- package/dist-lib/lib/capabilities/completion-engine/session.d.ts +76 -0
- package/dist-lib/lib/capabilities/completion-engine/session.js +70 -0
- package/dist-lib/lib/capabilities/completion-loop/index.d.ts +80 -0
- package/dist-lib/lib/capabilities/completion-loop/index.js +158 -0
- package/dist-lib/lib/capabilities/completion-loop/metrics.d.ts +5 -0
- package/dist-lib/lib/capabilities/completion-loop/metrics.js +29 -0
- package/dist-lib/lib/capabilities/completion-loop/round.d.ts +33 -0
- package/dist-lib/lib/capabilities/completion-loop/round.js +353 -0
- package/dist-lib/lib/capabilities/completion-loop/tool-execution.d.ts +60 -0
- package/dist-lib/lib/capabilities/completion-loop/tool-execution.js +270 -0
- package/dist-lib/lib/capabilities/completion-loop/turns.d.ts +23 -0
- package/dist-lib/lib/capabilities/completion-loop/turns.js +112 -0
- package/dist-lib/lib/capabilities/completion-runtime.d.ts +46 -0
- package/dist-lib/lib/capabilities/completion-runtime.js +74 -0
- package/dist-lib/lib/capabilities/completion.d.ts +18 -335
- package/dist-lib/lib/capabilities/completion.js +20 -1301
- package/dist-lib/lib/capabilities/download.d.ts +1 -1
- package/dist-lib/lib/capabilities/download.js +7 -2
- package/dist-lib/lib/capabilities/invoke.js +5 -3
- package/dist-lib/lib/capabilities/tool-plan.d.ts +35 -0
- package/dist-lib/lib/capabilities/tool-plan.js +66 -0
- package/dist-lib/lib/capabilities/tool-turns.d.ts +11 -2
- package/dist-lib/lib/capabilities/tool-turns.js +30 -2
- package/dist-lib/lib/capabilities/types.d.ts +287 -0
- package/dist-lib/lib/capabilities/types.js +1 -0
- package/dist-lib/lib/completion-lifecycle.d.ts +14 -0
- package/dist-lib/lib/completion-lifecycle.js +126 -0
- package/dist-lib/lib/connect.d.ts +10 -3
- package/dist-lib/lib/connect.js +329 -23
- package/dist-lib/lib/contract-types.d.ts +19 -2
- package/dist-lib/lib/harness-lifecycle.d.ts +66 -0
- package/dist-lib/lib/harness-lifecycle.js +129 -0
- package/dist-lib/lib/helpers/detach-json.d.ts +1 -0
- package/dist-lib/lib/helpers/detach-json.js +146 -0
- package/dist-lib/lib/helpers/markup-scanner.d.ts +26 -0
- package/dist-lib/lib/helpers/markup-scanner.js +156 -0
- package/dist-lib/lib/helpers/markup-stream-filter.js +138 -35
- package/dist-lib/lib/helpers/race-abort.d.ts +9 -4
- package/dist-lib/lib/helpers/race-abort.js +2 -7
- package/dist-lib/lib/helpers/recovery-required.d.ts +1 -0
- package/dist-lib/lib/helpers/recovery-required.js +1 -0
- package/dist-lib/lib/helpers/run-outcome.d.ts +10 -2
- package/dist-lib/lib/helpers/run-outcome.js +8 -5
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +2 -0
- package/dist-lib/lib/helpers/tool-call-markup.js +197 -82
- package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -0
- package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/egress.js +2 -1
- package/dist-lib/lib/hooks/built-in/sandbox.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/sandbox.js +6 -1
- package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -0
- package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
- package/dist-lib/lib/hooks/pipeline.d.ts +4 -1
- package/dist-lib/lib/hooks/pipeline.js +24 -2
- package/dist-lib/lib/hooks/run.d.ts +2 -1
- package/dist-lib/lib/hooks/run.js +19 -7
- package/dist-lib/lib/hooks/types.d.ts +17 -6
- package/dist-lib/lib/machine/completion/actions/approval.d.ts +3 -0
- package/dist-lib/lib/machine/completion/actions/approval.js +16 -0
- package/dist-lib/lib/machine/completion/actions/completion.d.ts +23 -0
- package/dist-lib/lib/machine/completion/actions/completion.js +142 -0
- package/dist-lib/lib/machine/completion/actions/skills.d.ts +9 -0
- package/dist-lib/lib/machine/completion/actions/skills.js +83 -0
- package/dist-lib/lib/machine/completion/actions/tools.d.ts +16 -0
- package/dist-lib/lib/machine/completion/actions/tools.js +173 -0
- package/dist-lib/lib/machine/completion/context.d.ts +9 -0
- package/dist-lib/lib/machine/completion/context.js +44 -0
- package/dist-lib/lib/machine/completion/contract.d.ts +6 -0
- package/dist-lib/lib/machine/completion/contract.js +1 -0
- package/dist-lib/lib/machine/completion/guards/retry.d.ts +9 -0
- package/dist-lib/lib/machine/completion/guards/retry.js +25 -0
- package/dist-lib/lib/machine/completion/implementations.d.ts +81 -0
- package/dist-lib/lib/machine/completion/implementations.js +152 -0
- package/dist-lib/lib/machine/completion/machine.d.ts +375 -0
- package/dist-lib/lib/machine/completion/machine.js +511 -0
- package/dist-lib/lib/machine/completion/ports.d.ts +65 -0
- package/dist-lib/lib/machine/completion/ports.js +224 -0
- package/dist-lib/lib/machine/completion/rules.d.ts +39 -0
- package/dist-lib/lib/machine/completion/rules.js +115 -0
- package/dist-lib/lib/machine/completion/types.d.ts +493 -0
- package/dist-lib/lib/machine/completion/types.js +1 -0
- package/dist-lib/lib/machine/engine.d.ts +93 -0
- package/dist-lib/lib/machine/engine.js +483 -0
- package/dist-lib/lib/machine/errors.d.ts +44 -0
- package/dist-lib/lib/machine/errors.js +373 -0
- package/dist-lib/lib/machine/guards.d.ts +2 -0
- package/dist-lib/lib/machine/guards.js +6 -0
- package/dist-lib/lib/machine/index.d.ts +20 -0
- package/dist-lib/lib/machine/index.js +11 -0
- package/dist-lib/lib/machine/replay-output.d.ts +24 -0
- package/dist-lib/lib/machine/replay-output.js +50 -0
- package/dist-lib/lib/machine/stable-stringify.d.ts +1 -0
- package/dist-lib/lib/machine/stable-stringify.js +11 -0
- package/dist-lib/lib/models/model-handle.d.ts +5 -1
- package/dist-lib/lib/models/model-handle.js +8 -3
- package/dist-lib/lib/models/model-list.d.ts +5 -3
- package/dist-lib/lib/models/model-pool.d.ts +18 -7
- package/dist-lib/lib/models/model-pool.js +126 -38
- package/dist-lib/lib/run-log/history-codec.d.ts +23 -0
- package/dist-lib/lib/run-log/history-codec.js +973 -0
- package/dist-lib/lib/run-log/patch.d.ts +6 -0
- package/dist-lib/lib/run-log/patch.js +93 -0
- package/dist-lib/lib/run-log/persisted-actor.d.ts +18 -0
- package/dist-lib/lib/run-log/persisted-actor.js +218 -0
- package/dist-lib/lib/run-log/protocol-types.d.ts +153 -0
- package/dist-lib/lib/run-log/protocol-types.js +1 -0
- package/dist-lib/lib/run-log/protocol.d.ts +27 -0
- package/dist-lib/lib/run-log/protocol.js +1056 -0
- package/dist-lib/lib/run-log/replay.d.ts +25 -0
- package/dist-lib/lib/run-log/replay.js +129 -0
- package/dist-lib/lib/run-log/run-journal.d.ts +125 -0
- package/dist-lib/lib/run-log/run-journal.js +783 -0
- package/dist-lib/lib/run-log-inspect.d.ts +25 -0
- package/dist-lib/lib/run-log-inspect.js +627 -0
- package/dist-lib/lib/run-log-protocol.d.ts +2 -0
- package/dist-lib/lib/run-log-protocol.js +1 -0
- package/dist-lib/lib/run-log-redact.d.ts +27 -0
- package/dist-lib/lib/run-log-redact.js +1107 -0
- package/dist-lib/lib/serve.js +308 -12
- package/dist-lib/lib/spawn.js +3 -2
- package/dist-lib/lib/tool-call-display.d.ts +1 -21
- package/dist-lib/lib/tools/exec.d.ts +2 -1
- package/dist-lib/lib/tools/exec.js +57 -2
- package/dist-lib/lib/tools/generate-image.js +1 -1
- package/dist-lib/lib/tools/generate-music.js +1 -1
- package/dist-lib/lib/tools/http-request.d.ts +2 -1
- package/dist-lib/lib/tools/http-request.js +2 -1
- package/dist-lib/lib/tools/mcp-call.js +8 -1
- package/dist-lib/lib/tools/operations/builders.d.ts +1 -0
- package/dist-lib/lib/tools/operations/builders.js +1 -0
- package/dist-lib/lib/tools/operations/compile.js +11 -1
- package/dist-lib/lib/tools/operations/manifest.js +1 -0
- package/dist-lib/lib/tools/outbound-guard.d.ts +8 -6
- package/dist-lib/lib/tools/outbound-guard.js +25 -8
- package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
- package/dist-lib/lib/tools/skills/catalog.js +35 -0
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +1 -0
- package/dist-lib/lib/tools/skills/cli-schema.js +25 -0
- package/dist-lib/lib/wire.d.ts +5 -0
- package/dist-lib/lib/wire.js +128 -5
- package/dist-lib/schema/harness.js +22 -6
- package/dist-lib/spec/hrpc/messages.js +100 -18
- package/dist-lib/spec/hyperschema/index.js +100 -18
- package/dist-lib/spec/hyperschema/schema.json +74 -1
- package/dist-lib/spec/hyperschema/types.d.ts +11 -3
- package/package.json +19 -1
- package/patches/@qvac%2Finference@0.20.0.patch +67 -0
- package/patches/bare-stow@0.1.5.patch +68 -0
- package/patches/hypercore-blob-server@1.15.0.patch +15 -0
- package/patches/localwatch@1.4.3.patch +13 -0
- 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,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
|
+
}
|