brainclaw 1.26.2 → 1.27.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/README.md +13 -0
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli/register-coordination.js +65 -1
- package/dist/commands/attempt-authority.js +80 -0
- package/dist/commands/harvest.js +140 -61
- package/dist/commands/loop.js +34 -0
- package/dist/commands/loops-handlers.js +87 -14
- package/dist/commands/mcp-catalog.js +42 -18
- package/dist/commands/mcp-schemas.generated.js +44 -0
- package/dist/commands/mcp-write-claims.js +128 -1
- package/dist/commands/mcp-write-coordination.js +146 -76
- package/dist/core/agent-capability.js +1 -1
- package/dist/core/agentrun-reconciler.js +148 -22
- package/dist/core/agentruns.js +254 -29
- package/dist/core/assignment-request-schema.js +7 -0
- package/dist/core/assignment-sweeper.js +5 -3
- package/dist/core/assignments.js +131 -33
- package/dist/core/claim-request-schema.js +7 -0
- package/dist/core/claims.js +53 -2
- package/dist/core/dispatch-status.js +16 -6
- package/dist/core/dispatcher.js +51 -51
- package/dist/core/entity-operations.js +20 -0
- package/dist/core/events.js +4 -0
- package/dist/core/execution-adapters.js +160 -14
- package/dist/core/execution-contract.js +345 -0
- package/dist/core/execution.js +130 -16
- package/dist/core/harness-adapters/base.js +150 -0
- package/dist/core/harness-adapters/claude.js +39 -0
- package/dist/core/harness-adapters/codex.js +57 -0
- package/dist/core/harness-adapters/harvest.js +109 -0
- package/dist/core/harness-adapters/index.js +8 -0
- package/dist/core/harness-adapters/prompt-only.js +13 -0
- package/dist/core/harness-adapters/registry.js +48 -0
- package/dist/core/harness-adapters/result.js +33 -0
- package/dist/core/harness-adapters/types.js +2 -0
- package/dist/core/ideation-loop-close.js +25 -2
- package/dist/core/instruction-templates.js +3 -2
- package/dist/core/loop-turn-dispatch.js +207 -0
- package/dist/core/loops/artifact-contract.js +11 -0
- package/dist/core/loops/attempt-authority.js +476 -0
- package/dist/core/loops/attempt-generations.js +509 -0
- package/dist/core/loops/attempt-reservation.js +197 -35
- package/dist/core/loops/attempt-rollout.js +404 -0
- package/dist/core/loops/attempt-takeover.js +155 -0
- package/dist/core/loops/bootstrap-acquire.js +7 -3
- package/dist/core/loops/evidence.js +187 -0
- package/dist/core/loops/facade-schema.js +41 -10
- package/dist/core/loops/gate-policy.js +485 -0
- package/dist/core/loops/impl-bind.js +37 -79
- package/dist/core/loops/index.js +9 -0
- package/dist/core/loops/iteration-engine.js +31 -19
- package/dist/core/loops/kind-policies.js +90 -0
- package/dist/core/loops/lock.js +71 -13
- package/dist/core/loops/reconcile-turn.js +235 -18
- package/dist/core/loops/result-reducers.js +99 -10
- package/dist/core/loops/store.js +30 -3
- package/dist/core/loops/turn-execution.js +480 -0
- package/dist/core/loops/types.js +113 -2
- package/dist/core/loops/verbs.js +332 -99
- package/dist/core/loops/verify-command.js +31 -8
- package/dist/core/loops/workspace-digest.js +54 -0
- package/dist/core/review-loop-close.js +25 -3
- package/dist/core/review-loop-turn-dispatch.js +210 -161
- package/dist/core/runtime-signals.js +62 -25
- package/dist/core/schema.js +35 -0
- package/dist/core/spawn-check.js +3 -2
- package/dist/core/upgrades/backup.js +27 -4
- package/dist/facts.js +7 -6
- package/dist/facts.json +6 -5
- package/docs/cli.md +49 -1
- package/docs/concepts/attempt-authority.md +407 -0
- package/docs/concepts/evidence-attestations.md +135 -0
- package/docs/concepts/execution-contract.md +166 -0
- package/docs/concepts/harness-adapters.md +166 -0
- package/docs/concepts/ideation-loop.md +5 -4
- package/docs/concepts/loop-engine.md +302 -113
- package/docs/index.md +4 -1
- package/docs/integrations/codex.md +3 -3
- package/docs/integrations/mcp.md +59 -5
- package/docs/loops/debug.md +144 -0
- package/docs/loops/ideation.md +158 -0
- package/docs/loops/implementation.md +154 -0
- package/docs/loops/research.md +136 -0
- package/docs/loops/review.md +200 -0
- package/docs/mcp-schema-changelog.md +14 -5
- package/package.json +1 -1
|
@@ -5,6 +5,56 @@ import { buildClaimEnvPrefix, buildWorkerIdentityEnv } from './execution-profile
|
|
|
5
5
|
import { getCapabilityProfile } from './agent-capability.js';
|
|
6
6
|
import { nowISO } from './ids.js';
|
|
7
7
|
import { ensureRuntimeDirs, getRuntimeLogPath, getRuntimeSignalPath, } from './runtime-signals.js';
|
|
8
|
+
const CONTRACT_BOOTSTRAP_SOURCE = `'use strict';
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const [ackPath, turnId, runId, nonce, expectedContractHash, expectedSnapshotHash, attemptEpoch, workspaceDigest, expectedWorkspaceB64] = process.argv.slice(2);
|
|
11
|
+
const contractHash = process.env.BRAINCLAW_EXECUTION_CONTRACT_HASH || '';
|
|
12
|
+
const snapshotHash = process.env.BRAINCLAW_CAPABILITY_SNAPSHOT_HASH || '';
|
|
13
|
+
const normalize = (value) => {
|
|
14
|
+
let resolved = fs.realpathSync.native(value);
|
|
15
|
+
if (process.platform === 'win32') resolved = resolved.toLowerCase();
|
|
16
|
+
return resolved;
|
|
17
|
+
};
|
|
18
|
+
const expectedWorkspace = Buffer.from(expectedWorkspaceB64 || '', 'base64url').toString('utf8');
|
|
19
|
+
const actualCwd = normalize(process.cwd());
|
|
20
|
+
const accepted = contractHash === expectedContractHash
|
|
21
|
+
&& snapshotHash === expectedSnapshotHash
|
|
22
|
+
&& expectedWorkspace !== ''
|
|
23
|
+
&& actualCwd === normalize(expectedWorkspace);
|
|
24
|
+
let fd;
|
|
25
|
+
try {
|
|
26
|
+
fd = fs.openSync(ackPath, 'wx');
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (error && error.code === 'EEXIST') process.exit(79);
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
fs.writeFileSync(fd, JSON.stringify({
|
|
33
|
+
status: accepted ? 'accepted' : 'rejected',
|
|
34
|
+
turn_id: turnId,
|
|
35
|
+
run_id: runId,
|
|
36
|
+
nonce,
|
|
37
|
+
...(attemptEpoch ? { attempt_epoch: Number(attemptEpoch) } : {}),
|
|
38
|
+
...(workspaceDigest ? { workspace_digest: workspaceDigest } : {}),
|
|
39
|
+
contract_hash: contractHash,
|
|
40
|
+
capability_snapshot_hash: snapshotHash,
|
|
41
|
+
cwd: actualCwd,
|
|
42
|
+
}));
|
|
43
|
+
} finally {
|
|
44
|
+
fs.closeSync(fd);
|
|
45
|
+
}
|
|
46
|
+
if (!accepted) process.exitCode = 78;
|
|
47
|
+
`;
|
|
48
|
+
/** Materialize the tiny child bootstrap used by both cmd.exe and POSIX shells. */
|
|
49
|
+
export function writeContractBootstrapScript(scriptPath) {
|
|
50
|
+
fs.mkdirSync(path.dirname(scriptPath), { recursive: true });
|
|
51
|
+
try {
|
|
52
|
+
if (fs.readFileSync(scriptPath, 'utf8') === CONTRACT_BOOTSTRAP_SOURCE)
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
catch { /* create or replace below */ }
|
|
56
|
+
fs.writeFileSync(scriptPath, CONTRACT_BOOTSTRAP_SOURCE, 'utf8');
|
|
57
|
+
}
|
|
8
58
|
// The turn-echo values are raw-embedded into a shell one-liner (see marker()),
|
|
9
59
|
// so the `[A-Za-z0-9_-]` safety invariant documented on TurnEcho is LOAD-BEARING,
|
|
10
60
|
// not cosmetic. A stray `"` desyncs cmd.exe quote-parity (no sentinel file is
|
|
@@ -18,7 +68,9 @@ const TURN_ECHO_SAFE = /^[A-Za-z0-9_-]+$/;
|
|
|
18
68
|
export function buildAckWrapCommand(bashCommand, paths, isWin32, turnEcho) {
|
|
19
69
|
if (turnEcho) {
|
|
20
70
|
for (const [field, value] of Object.entries(turnEcho)) {
|
|
21
|
-
if (
|
|
71
|
+
if (value === undefined)
|
|
72
|
+
continue;
|
|
73
|
+
if (!TURN_ECHO_SAFE.test(String(value))) {
|
|
22
74
|
throw new Error(`buildAckWrapCommand: turnEcho.${field} must match ${TURN_ECHO_SAFE} to be shell-safe for the completion sentinel (got ${JSON.stringify(value)})`);
|
|
23
75
|
}
|
|
24
76
|
}
|
|
@@ -32,12 +84,42 @@ export function buildAckWrapCommand(bashCommand, paths, isWin32, turnEcho) {
|
|
|
32
84
|
const marker = (p, status) => {
|
|
33
85
|
if (!turnEcho)
|
|
34
86
|
return touch(p);
|
|
35
|
-
const body = JSON.stringify({
|
|
87
|
+
const body = JSON.stringify({
|
|
88
|
+
turn_id: turnEcho.turn_id,
|
|
89
|
+
run_id: turnEcho.run_id,
|
|
90
|
+
nonce: turnEcho.nonce,
|
|
91
|
+
...(turnEcho.contract_hash ? { contract_hash: turnEcho.contract_hash } : {}),
|
|
92
|
+
...(turnEcho.capability_snapshot_hash ? { capability_snapshot_hash: turnEcho.capability_snapshot_hash } : {}),
|
|
93
|
+
...(turnEcho.attempt_epoch !== undefined ? { attempt_epoch: turnEcho.attempt_epoch } : {}),
|
|
94
|
+
...(turnEcho.workspace_digest ? { workspace_digest: turnEcho.workspace_digest } : {}),
|
|
95
|
+
status,
|
|
96
|
+
});
|
|
36
97
|
return isWin32 ? `echo ${body}>"${p}"` : `printf '%s' '${body}' > "${p}"`;
|
|
37
98
|
};
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
99
|
+
const ack = (() => {
|
|
100
|
+
if (!turnEcho?.contract_hash || !turnEcho.capability_snapshot_hash)
|
|
101
|
+
return touch(paths.ackPath);
|
|
102
|
+
if (!paths.contractBootstrapPath) {
|
|
103
|
+
throw new Error('buildAckWrapCommand: contracted turn requires contractBootstrapPath');
|
|
104
|
+
}
|
|
105
|
+
// This child reads the EFFECTIVE environment it received. It writes the
|
|
106
|
+
// actual values and exits non-zero on mismatch, so `&&` prevents worker exec.
|
|
107
|
+
if (!paths.expectedWorkspacePath) {
|
|
108
|
+
throw new Error('buildAckWrapCommand: contracted turn requires expectedWorkspacePath');
|
|
109
|
+
}
|
|
110
|
+
const expectedWorkspaceB64 = Buffer.from(paths.expectedWorkspacePath, 'utf8').toString('base64url');
|
|
111
|
+
return `"${process.execPath}" "${paths.contractBootstrapPath}" "${paths.ackPath}" "${turnEcho.turn_id}" "${turnEcho.run_id}" "${turnEcho.nonce}" "${turnEcho.contract_hash}" "${turnEcho.capability_snapshot_hash}" "${turnEcho.attempt_epoch ?? ''}" "${turnEcho.workspace_digest ?? ''}" "${expectedWorkspaceB64}"`;
|
|
112
|
+
})();
|
|
113
|
+
const stdinRedirect = paths.stdinFilePath ? ` < "${paths.stdinFilePath}"` : '';
|
|
114
|
+
const redirected = `${bashCommand}${stdinRedirect} > "${paths.stdoutLog}" 2> "${paths.stderrLog}"`;
|
|
115
|
+
const cleanup = paths.stdinFilePath
|
|
116
|
+
? isWin32
|
|
117
|
+
? ` & del /q "${paths.stdinFilePath}" > nul 2>&1`
|
|
118
|
+
: `; rm -f -- "${paths.stdinFilePath}"`
|
|
119
|
+
: '';
|
|
120
|
+
return (`${ack} && ` +
|
|
121
|
+
`( ${redirected} && ${marker(paths.completedPath, 'completed')} || ${marker(paths.failedPath, 'failed')} )` +
|
|
122
|
+
cleanup);
|
|
41
123
|
}
|
|
42
124
|
/**
|
|
43
125
|
* Check if a binary is resolvable on the system PATH.
|
|
@@ -115,10 +197,51 @@ export class CliExecutionAdapter {
|
|
|
115
197
|
return { canSpawn: true, reason: 'agent has spawnable profile' };
|
|
116
198
|
}
|
|
117
199
|
prepareManualCommand(invoke, options) {
|
|
200
|
+
const isWin32 = process.platform === 'win32';
|
|
201
|
+
const shell = isWin32 ? 'cmd' : (invoke.shell ? 'bash' : 'sh');
|
|
202
|
+
if (options.turnEcho?.contract_hash
|
|
203
|
+
&& options.turnEcho.capability_snapshot_hash
|
|
204
|
+
&& options.assignmentId
|
|
205
|
+
&& (options.ackRoot ?? options.worktreePath)) {
|
|
206
|
+
const signalRoot = options.ackRoot ?? options.worktreePath;
|
|
207
|
+
ensureRuntimeDirs(signalRoot);
|
|
208
|
+
const runtimeRunId = options.turnEcho?.run_id;
|
|
209
|
+
const ackPath = getRuntimeSignalPath(signalRoot, options.assignmentId, 'ack', runtimeRunId);
|
|
210
|
+
const contractBootstrapPath = `${ackPath}.bootstrap.cjs`;
|
|
211
|
+
writeContractBootstrapScript(contractBootstrapPath);
|
|
212
|
+
const stdinFilePath = isWin32 && invoke.promptDelivery === 'stdin_pipe' && invoke.promptText
|
|
213
|
+
? `${ackPath}.stdin`
|
|
214
|
+
: undefined;
|
|
215
|
+
if (stdinFilePath && invoke.promptText) {
|
|
216
|
+
fs.writeFileSync(stdinFilePath, invoke.promptText, { encoding: 'utf8', mode: 0o600 });
|
|
217
|
+
}
|
|
218
|
+
const wrapped = buildAckWrapCommand(invoke.bashCommand, {
|
|
219
|
+
ackPath,
|
|
220
|
+
completedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'completed', runtimeRunId),
|
|
221
|
+
failedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'failed', runtimeRunId),
|
|
222
|
+
stdoutLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stdout', runtimeRunId),
|
|
223
|
+
stderrLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stderr', runtimeRunId),
|
|
224
|
+
stdinFilePath,
|
|
225
|
+
contractBootstrapPath,
|
|
226
|
+
expectedWorkspacePath: options.worktreePath,
|
|
227
|
+
}, isWin32, options.turnEcho);
|
|
228
|
+
const contractEnv = isWin32
|
|
229
|
+
? [
|
|
230
|
+
options.claimId && options.claimId !== '(dry-run)' ? `set BRAINCLAW_CLAIM_ID=${options.claimId}` : undefined,
|
|
231
|
+
`set BRAINCLAW_EXECUTION_CONTRACT_HASH=${options.turnEcho.contract_hash}`,
|
|
232
|
+
`set BRAINCLAW_CAPABILITY_SNAPSHOT_HASH=${options.turnEcho.capability_snapshot_hash}`,
|
|
233
|
+
].filter((item) => Boolean(item)).join(' && ') + ' && '
|
|
234
|
+
: `export ${[
|
|
235
|
+
options.claimId && options.claimId !== '(dry-run)' ? `BRAINCLAW_CLAIM_ID="${options.claimId}"` : undefined,
|
|
236
|
+
`BRAINCLAW_EXECUTION_CONTRACT_HASH="${options.turnEcho.contract_hash}"`,
|
|
237
|
+
`BRAINCLAW_CAPABILITY_SNAPSHOT_HASH="${options.turnEcho.capability_snapshot_hash}"`,
|
|
238
|
+
].filter((item) => Boolean(item)).join(' ')}; `;
|
|
239
|
+
return { command: `${contractEnv}${wrapped}`, shell, contractWrapped: true };
|
|
240
|
+
}
|
|
118
241
|
const envPrefix = buildManualEnvPrefix(options.claimId);
|
|
119
242
|
return {
|
|
120
243
|
command: `${envPrefix}${invoke.bashCommand}`,
|
|
121
|
-
shell
|
|
244
|
+
shell,
|
|
122
245
|
};
|
|
123
246
|
}
|
|
124
247
|
start(invoke, options) {
|
|
@@ -131,7 +254,16 @@ export class CliExecutionAdapter {
|
|
|
131
254
|
const env = buildWorkerIdentityEnv(process.env, {
|
|
132
255
|
agent: options.agent,
|
|
133
256
|
claimId: options.claimId,
|
|
134
|
-
extraEnv: {
|
|
257
|
+
extraEnv: {
|
|
258
|
+
...buildGitAttributionEnv(options.agent),
|
|
259
|
+
...(invoke.env ?? {}),
|
|
260
|
+
// Contract identity wins over any agent/invoke-provided environment.
|
|
261
|
+
// The child bootstrap independently verifies these effective values.
|
|
262
|
+
...(options.turnEcho?.contract_hash ? { BRAINCLAW_EXECUTION_CONTRACT_HASH: options.turnEcho.contract_hash } : {}),
|
|
263
|
+
...(options.turnEcho?.capability_snapshot_hash
|
|
264
|
+
? { BRAINCLAW_CAPABILITY_SNAPSHOT_HASH: options.turnEcho.capability_snapshot_hash }
|
|
265
|
+
: {}),
|
|
266
|
+
},
|
|
135
267
|
});
|
|
136
268
|
if (invoke.promptDelivery === 'temp_file' && invoke.tempFilePath && invoke.promptText) {
|
|
137
269
|
const dir = path.dirname(invoke.tempFilePath);
|
|
@@ -156,7 +288,8 @@ export class CliExecutionAdapter {
|
|
|
156
288
|
// process just ignores stdout/stderr here. stdin stays a pipe when the
|
|
157
289
|
// prompt is delivered that way (the grouped agent command inherits it).
|
|
158
290
|
const useAckWrap = !!(options.assignmentId && (options.ackRoot ?? options.worktreePath));
|
|
159
|
-
const
|
|
291
|
+
const useWindowsStdinFile = isWin32 && useAckWrap && Boolean(needsStdin);
|
|
292
|
+
const stdinTarget = needsStdin && !useWindowsStdinFile ? 'pipe' : 'ignore';
|
|
160
293
|
const stdio = [stdinTarget, 'ignore', 'ignore'];
|
|
161
294
|
// pln#476 + pln#520 step 4: wrap the spawn so the worker shell touches the
|
|
162
295
|
// pre-exec `ack` sentinel, redirects logs at the shell level, and emits a
|
|
@@ -168,12 +301,25 @@ export class CliExecutionAdapter {
|
|
|
168
301
|
if (useAckWrap) {
|
|
169
302
|
const signalRoot = options.ackRoot ?? options.worktreePath;
|
|
170
303
|
ensureRuntimeDirs(signalRoot);
|
|
304
|
+
const runtimeRunId = options.turnEcho?.run_id;
|
|
305
|
+
const ackPath = getRuntimeSignalPath(signalRoot, options.assignmentId, 'ack', runtimeRunId);
|
|
306
|
+
const contractBootstrapPath = `${ackPath}.bootstrap.cjs`;
|
|
307
|
+
if (options.turnEcho?.contract_hash && options.turnEcho.capability_snapshot_hash) {
|
|
308
|
+
writeContractBootstrapScript(contractBootstrapPath);
|
|
309
|
+
}
|
|
310
|
+
const stdinFilePath = useWindowsStdinFile ? `${ackPath}.stdin` : undefined;
|
|
311
|
+
if (stdinFilePath) {
|
|
312
|
+
fs.writeFileSync(stdinFilePath, invoke.promptText, { encoding: 'utf8', mode: 0o600 });
|
|
313
|
+
}
|
|
171
314
|
const wrappedCmd = buildAckWrapCommand(invoke.bashCommand, {
|
|
172
|
-
ackPath
|
|
173
|
-
completedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'completed'),
|
|
174
|
-
failedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'failed'),
|
|
175
|
-
stdoutLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stdout'),
|
|
176
|
-
stderrLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stderr'),
|
|
315
|
+
ackPath,
|
|
316
|
+
completedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'completed', runtimeRunId),
|
|
317
|
+
failedPath: getRuntimeSignalPath(signalRoot, options.assignmentId, 'failed', runtimeRunId),
|
|
318
|
+
stdoutLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stdout', runtimeRunId),
|
|
319
|
+
stderrLog: getRuntimeLogPath(signalRoot, options.assignmentId, 'stderr', runtimeRunId),
|
|
320
|
+
stdinFilePath,
|
|
321
|
+
contractBootstrapPath,
|
|
322
|
+
expectedWorkspacePath: options.worktreePath,
|
|
177
323
|
}, isWin32, options.turnEcho);
|
|
178
324
|
child = spawn(wrappedCmd, [], {
|
|
179
325
|
detached: !isWin32,
|
|
@@ -201,7 +347,7 @@ export class CliExecutionAdapter {
|
|
|
201
347
|
// On Windows shell:true: this never fires for ENOENT (cmd.exe succeeds);
|
|
202
348
|
// the isBinaryOnPath pre-check above catches that case instead.
|
|
203
349
|
child.on('error', () => { });
|
|
204
|
-
if (needsStdin && child.stdin) {
|
|
350
|
+
if (needsStdin && !useWindowsStdinFile && child.stdin) {
|
|
205
351
|
child.stdin.write(invoke.promptText);
|
|
206
352
|
child.stdin.end();
|
|
207
353
|
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getCapabilityProfile, dispatchCanCommit, } from './agent-capability.js';
|
|
4
|
+
import { ExpectedArtifactSchema } from './loops/artifact-contract.js';
|
|
5
|
+
export const EXECUTION_CONTRACT_VERSION = 1;
|
|
6
|
+
export const EXECUTION_CONTRACT_PROTOCOL_VERSION = 1;
|
|
7
|
+
const RoleCapabilitySchema = z.enum(['execute', 'coordinate', 'review', 'consult']);
|
|
8
|
+
const ExecutionSurfaceSchema = z.enum(['cli', 'ide', 'extension', 'remote']);
|
|
9
|
+
export const CapabilityRequirementSchema = z.object({
|
|
10
|
+
roles: z.array(RoleCapabilitySchema).default(['execute']),
|
|
11
|
+
required_surfaces: z.array(z.enum([
|
|
12
|
+
'mcp',
|
|
13
|
+
'hooks',
|
|
14
|
+
'skills',
|
|
15
|
+
'rules',
|
|
16
|
+
'auto_approve',
|
|
17
|
+
'cli_spawn',
|
|
18
|
+
'commit',
|
|
19
|
+
'inbox',
|
|
20
|
+
])).default([]),
|
|
21
|
+
execution_surfaces: z.array(ExecutionSurfaceSchema).default([]),
|
|
22
|
+
model: z.string().min(1).optional(),
|
|
23
|
+
required_tools: z.array(z.string().min(1)).default([]),
|
|
24
|
+
});
|
|
25
|
+
export const CapabilityResolutionReasonSchema = z.object({
|
|
26
|
+
code: z.enum([
|
|
27
|
+
'agent_profile_missing',
|
|
28
|
+
'role_unsupported',
|
|
29
|
+
'surface_unsupported',
|
|
30
|
+
'execution_surface_mismatch',
|
|
31
|
+
'model_unsupported',
|
|
32
|
+
'tool_catalog_unattested',
|
|
33
|
+
]),
|
|
34
|
+
requirement: z.string().min(1),
|
|
35
|
+
expected: z.string().optional(),
|
|
36
|
+
actual: z.string().optional(),
|
|
37
|
+
});
|
|
38
|
+
export const HarnessCapabilityBindingSchema = z.object({
|
|
39
|
+
adapter_id: z.string().min(1),
|
|
40
|
+
adapter_version: z.string().min(1),
|
|
41
|
+
requested_model: z.string().min(1).optional(),
|
|
42
|
+
resolved_model: z.string().min(1).optional(),
|
|
43
|
+
model_resolution: z.enum(['exact', 'defaulted', 'unattested']),
|
|
44
|
+
});
|
|
45
|
+
export const CapabilitySnapshotSchema = z.object({
|
|
46
|
+
schema_version: z.literal(1),
|
|
47
|
+
agent: z.string().min(1),
|
|
48
|
+
agent_id: z.string().min(1).optional(),
|
|
49
|
+
profile_name: z.string().min(1).optional(),
|
|
50
|
+
accepted: z.boolean(),
|
|
51
|
+
requested: CapabilityRequirementSchema,
|
|
52
|
+
resolved: z.object({
|
|
53
|
+
roles: z.array(RoleCapabilitySchema),
|
|
54
|
+
surfaces: z.array(z.string().min(1)),
|
|
55
|
+
execution_surface: ExecutionSurfaceSchema.optional(),
|
|
56
|
+
model: z.string().min(1).optional(),
|
|
57
|
+
invoke_binary: z.string().min(1).optional(),
|
|
58
|
+
tool_catalog_attested: z.boolean(),
|
|
59
|
+
harness: HarnessCapabilityBindingSchema.optional(),
|
|
60
|
+
}),
|
|
61
|
+
reasons: z.array(CapabilityResolutionReasonSchema),
|
|
62
|
+
});
|
|
63
|
+
export const ExecutionContractSchema = z.object({
|
|
64
|
+
schema_version: z.literal(EXECUTION_CONTRACT_VERSION),
|
|
65
|
+
minimum_reader_version: z.number().int().positive().max(EXECUTION_CONTRACT_VERSION).default(1),
|
|
66
|
+
identity: z.object({
|
|
67
|
+
loop_id: z.string().min(1),
|
|
68
|
+
turn_id: z.string().min(1),
|
|
69
|
+
logical_attempt_epoch: z.number().int().nonnegative(),
|
|
70
|
+
assignment_id: z.string().min(1),
|
|
71
|
+
run_id: z.string().min(1),
|
|
72
|
+
kind: z.enum(['review', 'ideation', 'implementation', 'research', 'debug']),
|
|
73
|
+
phase: z.string().min(1),
|
|
74
|
+
iteration: z.number().int().nonnegative(),
|
|
75
|
+
}),
|
|
76
|
+
artifact_contract: z.object({
|
|
77
|
+
completion_mode: z.enum(['file', 'mcp', 'either']),
|
|
78
|
+
expected_artifacts: z.array(ExpectedArtifactSchema),
|
|
79
|
+
}),
|
|
80
|
+
capability_requirement: CapabilityRequirementSchema,
|
|
81
|
+
workspace_policy: z.object({
|
|
82
|
+
scope: z.string().min(1),
|
|
83
|
+
cwd: z.string().min(1),
|
|
84
|
+
worktree_path: z.string().min(1).optional(),
|
|
85
|
+
isolation: z.enum(['worktree', 'shared_checkout', 'none']),
|
|
86
|
+
write_access: z.enum(['read_only', 'workspace', 'unrestricted']),
|
|
87
|
+
}),
|
|
88
|
+
timeout_policy: z.object({
|
|
89
|
+
dispatch_lease_ms: z.number().int().positive(),
|
|
90
|
+
grant_lease_ms: z.number().int().positive(),
|
|
91
|
+
}),
|
|
92
|
+
evidence_policy: z.object({
|
|
93
|
+
require_turn_id: z.literal(true),
|
|
94
|
+
require_run_id: z.literal(true),
|
|
95
|
+
require_nonce: z.literal(true),
|
|
96
|
+
artifact_hash: z.enum(['required', 'optional']),
|
|
97
|
+
}),
|
|
98
|
+
protocol: z.object({
|
|
99
|
+
name: z.literal('attempt-authority'),
|
|
100
|
+
minimum_version: z.number().int().positive().max(EXECUTION_CONTRACT_PROTOCOL_VERSION),
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
export const ExecutionContractRefSchema = z.object({
|
|
104
|
+
version: z.literal(EXECUTION_CONTRACT_VERSION),
|
|
105
|
+
hash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
106
|
+
snapshot_hash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
107
|
+
turn_id: z.string().min(1),
|
|
108
|
+
});
|
|
109
|
+
export const RuntimeCapabilityObservationSchema = z.object({
|
|
110
|
+
contract_hash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
111
|
+
capability_snapshot_hash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
112
|
+
adapter_id: z.string().min(1).optional(),
|
|
113
|
+
adapter_version: z.string().min(1).optional(),
|
|
114
|
+
observed_surfaces: z.array(z.string().min(1)).default([]),
|
|
115
|
+
observed_model: z.string().min(1).optional(),
|
|
116
|
+
accepted_contract_hash: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
117
|
+
accepted_capability_snapshot_hash: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
118
|
+
});
|
|
119
|
+
function canonicalValue(value) {
|
|
120
|
+
if (typeof value === 'string')
|
|
121
|
+
return value.normalize('NFC');
|
|
122
|
+
if (Array.isArray(value))
|
|
123
|
+
return value.map(canonicalValue);
|
|
124
|
+
if (value && typeof value === 'object') {
|
|
125
|
+
const normalized = Object.entries(value)
|
|
126
|
+
.filter(([, item]) => item !== undefined)
|
|
127
|
+
.map(([key, item]) => [key.normalize('NFC'), canonicalValue(item)])
|
|
128
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
129
|
+
if (new Set(normalized.map(([key]) => key)).size !== normalized.length) {
|
|
130
|
+
throw new Error('canonical execution contract contains duplicate NFC-normalized keys');
|
|
131
|
+
}
|
|
132
|
+
return Object.fromEntries(normalized);
|
|
133
|
+
}
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
export function canonicalExecutionContract(contract) {
|
|
137
|
+
return JSON.stringify(canonicalValue(ExecutionContractSchema.parse(contract)));
|
|
138
|
+
}
|
|
139
|
+
export function executionContractHash(contract) {
|
|
140
|
+
return crypto.createHash('sha256').update(canonicalExecutionContract(contract), 'utf8').digest('hex');
|
|
141
|
+
}
|
|
142
|
+
export function capabilitySnapshotHash(snapshot) {
|
|
143
|
+
const canonical = JSON.stringify(canonicalValue(CapabilitySnapshotSchema.parse(snapshot)));
|
|
144
|
+
return crypto.createHash('sha256').update(canonical, 'utf8').digest('hex');
|
|
145
|
+
}
|
|
146
|
+
function profileSurfaces(profile) {
|
|
147
|
+
const surfaces = [
|
|
148
|
+
profile.hasMcp && 'mcp',
|
|
149
|
+
profile.hasHooks && 'hooks',
|
|
150
|
+
profile.hasSkills && 'skills',
|
|
151
|
+
profile.hasRules && 'rules',
|
|
152
|
+
profile.hasAutoApprove && 'auto_approve',
|
|
153
|
+
profile.runtime.canBeSpawnedCli && 'cli_spawn',
|
|
154
|
+
profile.runtime.inbox && 'inbox',
|
|
155
|
+
dispatchCanCommit(profile) && 'commit',
|
|
156
|
+
].filter((value) => Boolean(value));
|
|
157
|
+
return [...new Set(surfaces)].sort();
|
|
158
|
+
}
|
|
159
|
+
function resolvedModel(profile, requested) {
|
|
160
|
+
if (!requested)
|
|
161
|
+
return profile.default_model;
|
|
162
|
+
if (profile.model_flag || profile.default_model === requested)
|
|
163
|
+
return requested;
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
export function resolveCapabilitySnapshot(agent, requirementInput, agentId, harness) {
|
|
167
|
+
const requested = CapabilityRequirementSchema.parse(requirementInput);
|
|
168
|
+
const harnessBinding = harness ? HarnessCapabilityBindingSchema.parse(harness) : undefined;
|
|
169
|
+
const profile = getCapabilityProfile(agent);
|
|
170
|
+
const reasons = [];
|
|
171
|
+
if (!profile) {
|
|
172
|
+
reasons.push({ code: 'agent_profile_missing', requirement: 'agent_profile', expected: agent });
|
|
173
|
+
return CapabilitySnapshotSchema.parse({
|
|
174
|
+
schema_version: 1,
|
|
175
|
+
agent,
|
|
176
|
+
agent_id: agentId,
|
|
177
|
+
accepted: false,
|
|
178
|
+
requested,
|
|
179
|
+
resolved: { roles: [], surfaces: [], tool_catalog_attested: false },
|
|
180
|
+
reasons,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const surfaces = profileSurfaces(profile);
|
|
184
|
+
for (const role of requested.roles) {
|
|
185
|
+
if (!profile.role_capabilities.includes(role)) {
|
|
186
|
+
reasons.push({
|
|
187
|
+
code: 'role_unsupported',
|
|
188
|
+
requirement: `role:${role}`,
|
|
189
|
+
expected: role,
|
|
190
|
+
actual: profile.role_capabilities.join(','),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
for (const surface of requested.required_surfaces) {
|
|
195
|
+
if (!surfaces.includes(surface)) {
|
|
196
|
+
reasons.push({
|
|
197
|
+
code: 'surface_unsupported',
|
|
198
|
+
requirement: `surface:${surface}`,
|
|
199
|
+
expected: surface,
|
|
200
|
+
actual: surfaces.join(','),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (requested.execution_surfaces.length > 0
|
|
205
|
+
&& !requested.execution_surfaces.includes(profile.execution_env.surface)) {
|
|
206
|
+
reasons.push({
|
|
207
|
+
code: 'execution_surface_mismatch',
|
|
208
|
+
requirement: 'execution_surface',
|
|
209
|
+
expected: requested.execution_surfaces.join(','),
|
|
210
|
+
actual: profile.execution_env.surface,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
const model = resolvedModel(profile, requested.model);
|
|
214
|
+
if (requested.model && !model) {
|
|
215
|
+
reasons.push({
|
|
216
|
+
code: 'model_unsupported',
|
|
217
|
+
requirement: 'model',
|
|
218
|
+
expected: requested.model,
|
|
219
|
+
actual: profile.default_model,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// Current profiles attest transport surfaces, not per-tool catalogs. Refuse
|
|
223
|
+
// named tools rather than guessing from hasMcp/hasSkills.
|
|
224
|
+
if (requested.required_tools.length > 0) {
|
|
225
|
+
reasons.push({
|
|
226
|
+
code: 'tool_catalog_unattested',
|
|
227
|
+
requirement: 'required_tools',
|
|
228
|
+
expected: requested.required_tools.join(','),
|
|
229
|
+
actual: 'unattested',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return CapabilitySnapshotSchema.parse({
|
|
233
|
+
schema_version: 1,
|
|
234
|
+
agent,
|
|
235
|
+
agent_id: agentId,
|
|
236
|
+
profile_name: profile.name,
|
|
237
|
+
accepted: reasons.length === 0,
|
|
238
|
+
requested,
|
|
239
|
+
resolved: {
|
|
240
|
+
roles: profile.role_capabilities,
|
|
241
|
+
surfaces,
|
|
242
|
+
execution_surface: profile.execution_env.surface,
|
|
243
|
+
model,
|
|
244
|
+
invoke_binary: profile.invoke_binary,
|
|
245
|
+
tool_catalog_attested: false,
|
|
246
|
+
harness: harnessBinding,
|
|
247
|
+
},
|
|
248
|
+
reasons,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
export function executionContractRef(contract, snapshot) {
|
|
252
|
+
return {
|
|
253
|
+
version: contract.schema_version,
|
|
254
|
+
hash: executionContractHash(contract),
|
|
255
|
+
snapshot_hash: capabilitySnapshotHash(snapshot),
|
|
256
|
+
turn_id: contract.identity.turn_id,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
export function assertExecutionContractIntegrity(contract, ref, snapshot, selectedAgent) {
|
|
260
|
+
const parsedContract = ExecutionContractSchema.parse(contract);
|
|
261
|
+
const parsedRef = ExecutionContractRefSchema.parse(ref);
|
|
262
|
+
const parsedSnapshot = CapabilitySnapshotSchema.parse(snapshot);
|
|
263
|
+
if (parsedRef.turn_id !== parsedContract.identity.turn_id) {
|
|
264
|
+
throw new Error(`execution contract turn mismatch: ${parsedRef.turn_id} != ${parsedContract.identity.turn_id}`);
|
|
265
|
+
}
|
|
266
|
+
const actualHash = executionContractHash(parsedContract);
|
|
267
|
+
if (parsedRef.hash !== actualHash) {
|
|
268
|
+
throw new Error(`execution contract hash mismatch: ${parsedRef.hash} != ${actualHash}`);
|
|
269
|
+
}
|
|
270
|
+
const actualSnapshotHash = capabilitySnapshotHash(parsedSnapshot);
|
|
271
|
+
if (parsedRef.snapshot_hash !== actualSnapshotHash) {
|
|
272
|
+
throw new Error(`capability snapshot hash mismatch: ${parsedRef.snapshot_hash} != ${actualSnapshotHash}`);
|
|
273
|
+
}
|
|
274
|
+
if (!parsedSnapshot.accepted) {
|
|
275
|
+
throw new Error('execution contract capability snapshot was not accepted');
|
|
276
|
+
}
|
|
277
|
+
if (JSON.stringify(parsedSnapshot.requested) !== JSON.stringify(parsedContract.capability_requirement)) {
|
|
278
|
+
throw new Error('execution contract capability requirement differs from its resolved snapshot');
|
|
279
|
+
}
|
|
280
|
+
if (selectedAgent && parsedSnapshot.agent !== selectedAgent.agent) {
|
|
281
|
+
throw new Error(`capability snapshot agent mismatch: ${parsedSnapshot.agent} != ${selectedAgent.agent}`);
|
|
282
|
+
}
|
|
283
|
+
if (selectedAgent && parsedSnapshot.agent_id !== selectedAgent.agent_id) {
|
|
284
|
+
throw new Error(`capability snapshot agent_id mismatch: ${parsedSnapshot.agent_id ?? 'none'} != ${selectedAgent.agent_id ?? 'none'}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Pre-crossing acceptance emitted by the frozen harness adapter. The child
|
|
289
|
+
* process still confirms the effective environment in its bootstrap ACK after
|
|
290
|
+
* spawn; this attestation proves, before authority crosses, that the selected
|
|
291
|
+
* adapter accepted the exact immutable contract it is about to deliver.
|
|
292
|
+
*/
|
|
293
|
+
export function attestHarnessContractAcceptance(expectedRef, snapshot, binding) {
|
|
294
|
+
const parsedSnapshot = CapabilitySnapshotSchema.parse(snapshot);
|
|
295
|
+
const parsedBinding = HarnessCapabilityBindingSchema.parse(binding);
|
|
296
|
+
if (!parsedSnapshot.accepted) {
|
|
297
|
+
throw new Error('harness cannot accept a rejected capability snapshot');
|
|
298
|
+
}
|
|
299
|
+
const frozen = parsedSnapshot.resolved.harness;
|
|
300
|
+
if (!frozen)
|
|
301
|
+
throw new Error('capability snapshot has no frozen harness binding');
|
|
302
|
+
if (frozen.adapter_id !== parsedBinding.adapter_id
|
|
303
|
+
|| frozen.adapter_version !== parsedBinding.adapter_version
|
|
304
|
+
|| frozen.requested_model !== parsedBinding.requested_model
|
|
305
|
+
|| frozen.resolved_model !== parsedBinding.resolved_model) {
|
|
306
|
+
throw new Error(`harness acceptance mismatch: frozen ${frozen.adapter_id}@${frozen.adapter_version}, `
|
|
307
|
+
+ `selected ${parsedBinding.adapter_id}@${parsedBinding.adapter_version}`);
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
contract_hash: expectedRef.hash,
|
|
311
|
+
capability_snapshot_hash: expectedRef.snapshot_hash,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Deterministic capability selection/reselection. Input order is deliberately
|
|
316
|
+
* irrelevant so replaying the same candidate set produces the same worker.
|
|
317
|
+
* Callers can exclude a failed pre-cross candidate and run the resolver again.
|
|
318
|
+
*/
|
|
319
|
+
export function resolveExecutionCandidate(candidates, requirementInput, options = {}) {
|
|
320
|
+
const requirement = CapabilityRequirementSchema.parse(requirementInput);
|
|
321
|
+
const excluded = new Set((options.exclude ?? []).map(({ agent, agent_id }) => `${agent.normalize('NFC')}\0${(agent_id ?? '').normalize('NFC')}`));
|
|
322
|
+
const ordered = candidates
|
|
323
|
+
.filter(({ agent, agent_id }) => !excluded.has(`${agent.normalize('NFC')}\0${(agent_id ?? '').normalize('NFC')}`))
|
|
324
|
+
.map((candidate) => ({ ...candidate, agent: candidate.agent.normalize('NFC'), agent_id: candidate.agent_id?.normalize('NFC') }))
|
|
325
|
+
.sort((left, right) => (right.preference ?? 0) - (left.preference ?? 0)
|
|
326
|
+
|| left.agent.localeCompare(right.agent, 'en')
|
|
327
|
+
|| (left.agent_id ?? '').localeCompare(right.agent_id ?? '', 'en'));
|
|
328
|
+
const evaluated = ordered.map((candidate) => ({
|
|
329
|
+
...candidate,
|
|
330
|
+
snapshot: resolveCapabilitySnapshot(candidate.agent, requirement, candidate.agent_id),
|
|
331
|
+
}));
|
|
332
|
+
const selected = evaluated.find((candidate) => candidate.snapshot.accepted);
|
|
333
|
+
return selected ? { kind: 'selected', selected, evaluated } : { kind: 'rejected', evaluated };
|
|
334
|
+
}
|
|
335
|
+
export function validateWorkerContractAcceptance(expectedRef, acceptedRef, launchStatus) {
|
|
336
|
+
const expected = { contract_hash: expectedRef.hash, capability_snapshot_hash: expectedRef.snapshot_hash };
|
|
337
|
+
if (expected.contract_hash === acceptedRef.contract_hash
|
|
338
|
+
&& expected.capability_snapshot_hash === acceptedRef.capability_snapshot_hash)
|
|
339
|
+
return { kind: 'accepted' };
|
|
340
|
+
if (launchStatus === 'crossed') {
|
|
341
|
+
return { kind: 'post_crossing_anomaly', expected, accepted: acceptedRef, respawn: false };
|
|
342
|
+
}
|
|
343
|
+
return { kind: 'abort_and_reselect', expected, accepted: acceptedRef };
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=execution-contract.js.map
|