brainclaw 1.26.2 → 1.28.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 (88) hide show
  1. package/README.md +13 -0
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli/register-coordination.js +65 -1
  4. package/dist/commands/attempt-authority.js +80 -0
  5. package/dist/commands/harvest.js +140 -61
  6. package/dist/commands/loop.js +34 -0
  7. package/dist/commands/loops-handlers.js +143 -15
  8. package/dist/commands/mcp-catalog.js +52 -18
  9. package/dist/commands/mcp-schemas.generated.js +64 -0
  10. package/dist/commands/mcp-write-claims.js +128 -1
  11. package/dist/commands/mcp-write-coordination.js +149 -76
  12. package/dist/core/agent-capability.js +1 -1
  13. package/dist/core/agentrun-reconciler.js +148 -22
  14. package/dist/core/agentruns.js +254 -29
  15. package/dist/core/assignment-request-schema.js +7 -0
  16. package/dist/core/assignment-sweeper.js +5 -3
  17. package/dist/core/assignments.js +131 -33
  18. package/dist/core/claim-request-schema.js +7 -0
  19. package/dist/core/claims.js +53 -2
  20. package/dist/core/dispatch-status.js +16 -6
  21. package/dist/core/dispatcher.js +51 -51
  22. package/dist/core/entity-operations.js +20 -0
  23. package/dist/core/events.js +4 -0
  24. package/dist/core/execution-adapters.js +189 -14
  25. package/dist/core/execution-contract.js +345 -0
  26. package/dist/core/execution.js +130 -16
  27. package/dist/core/facade-schema.js +3 -0
  28. package/dist/core/harness-adapters/base.js +150 -0
  29. package/dist/core/harness-adapters/claude.js +39 -0
  30. package/dist/core/harness-adapters/codex.js +57 -0
  31. package/dist/core/harness-adapters/harvest.js +109 -0
  32. package/dist/core/harness-adapters/index.js +8 -0
  33. package/dist/core/harness-adapters/prompt-only.js +13 -0
  34. package/dist/core/harness-adapters/registry.js +48 -0
  35. package/dist/core/harness-adapters/result.js +33 -0
  36. package/dist/core/harness-adapters/types.js +2 -0
  37. package/dist/core/ideation-loop-close.js +25 -2
  38. package/dist/core/instruction-templates.js +3 -2
  39. package/dist/core/loop-turn-dispatch.js +235 -0
  40. package/dist/core/loops/artifact-contract.js +11 -0
  41. package/dist/core/loops/attempt-authority.js +496 -0
  42. package/dist/core/loops/attempt-generations.js +509 -0
  43. package/dist/core/loops/attempt-reservation.js +197 -35
  44. package/dist/core/loops/attempt-rollout.js +404 -0
  45. package/dist/core/loops/attempt-takeover.js +155 -0
  46. package/dist/core/loops/bootstrap-acquire.js +7 -3
  47. package/dist/core/loops/brief-assembly.js +21 -4
  48. package/dist/core/loops/evidence.js +188 -0
  49. package/dist/core/loops/facade-schema.js +75 -11
  50. package/dist/core/loops/gate-policy.js +533 -0
  51. package/dist/core/loops/impl-bind.js +91 -81
  52. package/dist/core/loops/index.js +9 -0
  53. package/dist/core/loops/iteration-engine.js +31 -19
  54. package/dist/core/loops/kind-policies.js +90 -0
  55. package/dist/core/loops/lock.js +71 -13
  56. package/dist/core/loops/reconcile-turn.js +237 -18
  57. package/dist/core/loops/result-reducers.js +113 -10
  58. package/dist/core/loops/store.js +34 -3
  59. package/dist/core/loops/turn-execution.js +480 -0
  60. package/dist/core/loops/types.js +127 -3
  61. package/dist/core/loops/verbs.js +335 -99
  62. package/dist/core/loops/verify-command.js +105 -20
  63. package/dist/core/loops/workspace-digest.js +54 -0
  64. package/dist/core/review-loop-close.js +25 -3
  65. package/dist/core/review-loop-turn-dispatch.js +210 -161
  66. package/dist/core/runtime-signals.js +62 -25
  67. package/dist/core/schema.js +40 -0
  68. package/dist/core/spawn-check.js +3 -2
  69. package/dist/core/upgrades/backup.js +27 -4
  70. package/dist/facts.js +9 -8
  71. package/dist/facts.json +8 -7
  72. package/docs/cli.md +49 -1
  73. package/docs/concepts/attempt-authority.md +407 -0
  74. package/docs/concepts/evidence-attestations.md +135 -0
  75. package/docs/concepts/execution-contract.md +166 -0
  76. package/docs/concepts/harness-adapters.md +166 -0
  77. package/docs/concepts/ideation-loop.md +5 -4
  78. package/docs/concepts/loop-engine.md +302 -113
  79. package/docs/index.md +4 -1
  80. package/docs/integrations/codex.md +3 -3
  81. package/docs/integrations/mcp.md +59 -5
  82. package/docs/loops/debug.md +144 -0
  83. package/docs/loops/ideation.md +158 -0
  84. package/docs/loops/implementation.md +174 -0
  85. package/docs/loops/research.md +136 -0
  86. package/docs/loops/review.md +200 -0
  87. package/docs/mcp-schema-changelog.md +18 -5
  88. package/package.json +1 -1
@@ -0,0 +1,48 @@
1
+ import { ClaudeHarnessAdapter } from './claude.js';
2
+ import { CodexHarnessAdapter } from './codex.js';
3
+ import { PromptOnlyHarnessAdapter } from './prompt-only.js';
4
+ const promptOnly = new PromptOnlyHarnessAdapter();
5
+ const nativeAdapters = [new CodexHarnessAdapter(), new ClaudeHarnessAdapter()];
6
+ export function nativeHarnessEnabled(env = process.env) {
7
+ return env.BRAINCLAW_NATIVE_HARNESS === '1';
8
+ }
9
+ export function selectHarnessAdapter(agent, native = nativeHarnessEnabled()) {
10
+ if (!native)
11
+ return promptOnly;
12
+ return nativeAdapters.find((adapter) => adapter.matches(agent)) ?? promptOnly;
13
+ }
14
+ export function resolveHarnessBinding(agent, requestedModel, native, probeOptions) {
15
+ return selectHarnessAdapter(agent, native).resolve(agent, requestedModel, probeOptions);
16
+ }
17
+ export function prepareHarnessInvocation(input) {
18
+ const binding = input.binding ?? resolveHarnessBinding(input.capability_snapshot?.agent ?? '', input.capability_snapshot?.requested.model, input.native);
19
+ const adapter = selectHarnessAdapter(binding.agent, input.native ?? binding.adapter_id !== 'prompt-only');
20
+ if (adapter.id !== binding.adapter_id || adapter.version !== binding.adapter_version) {
21
+ throw new Error(`harness_binding_mismatch: frozen ${binding.adapter_id}@${binding.adapter_version}, selected ${adapter.id}@${adapter.version}`);
22
+ }
23
+ return adapter.prepare({ ...input, binding });
24
+ }
25
+ export function buildHarnessInvocation(agent, prompt, options = {}) {
26
+ let binding;
27
+ try {
28
+ binding = options.binding ?? resolveHarnessBinding(agent, options.model, options.native);
29
+ }
30
+ catch (error) {
31
+ if (error instanceof Error && / is unavailable for /.test(error.message))
32
+ return undefined;
33
+ throw error;
34
+ }
35
+ return prepareHarnessInvocation({
36
+ binding,
37
+ prompt,
38
+ mode: options.mode ?? 'worker',
39
+ platform: options.platform,
40
+ native: options.native,
41
+ contract: options.contract,
42
+ capability_snapshot: options.capability_snapshot,
43
+ });
44
+ }
45
+ export function listHarnessAdapters() {
46
+ return [promptOnly, ...nativeAdapters];
47
+ }
48
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1,33 @@
1
+ import { listHarnessAdapters } from './registry.js';
2
+ export function parseHarnessOutcome(adapterId, observation, adapterVersion) {
3
+ const adapter = listHarnessAdapters().find((candidate) => candidate.id === adapterId);
4
+ if (!adapter)
5
+ throw new Error(`unknown harness adapter: ${adapterId}`);
6
+ if (adapterVersion && adapter.version !== adapterVersion) {
7
+ throw new Error(`harness adapter version mismatch: installed ${adapter.id}@${adapter.version}, frozen ${adapterId}@${adapterVersion}`);
8
+ }
9
+ return adapter.parseOutcome(observation);
10
+ }
11
+ /**
12
+ * Convert an untrusted harness claim to the existing LaneResult ingress shape.
13
+ * This is deliberately normalization only: reconcileTurn still validates
14
+ * attempt identity and reducers/evidence/gates remain the authority boundary.
15
+ */
16
+ export function normalizeHarnessClaimToLaneResult(claim, identity) {
17
+ const status = claim.status === 'completed'
18
+ ? 'completed'
19
+ : claim.status === 'blocked' ? 'blocked' : 'failed';
20
+ return {
21
+ ...identity,
22
+ status,
23
+ summary: claim.summary,
24
+ body: claim.body,
25
+ artifact_type: claim.artifact_type,
26
+ review_verdict: claim.review_verdict,
27
+ review_summary: claim.review_verdict ? claim.summary : undefined,
28
+ notes: claim.diagnostics.length > 0
29
+ ? claim.diagnostics.map((item) => `${item.kind}:${item.code}: ${item.message}`).join('\n')
30
+ : undefined,
31
+ };
32
+ }
33
+ //# sourceMappingURL=result.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -1,5 +1,5 @@
1
1
  import { getLoop } from './loops/store.js';
2
- import { complete_turn, advance, evaluatePhaseAdvanceGate } from './loops/verbs.js';
2
+ import { complete_turn, completeTurnWithEvidence, advance, evaluatePhaseAdvanceGate } from './loops/verbs.js';
3
3
  import { withLoopLock } from './loops/lock.js';
4
4
  import { LOOP_ARTIFACT_BODY_MAX_BYTES } from './loops/types.js';
5
5
  /** ideate-loop:lop_xxx[:slot] → the loop id (dispatch sets `ideate-loop:${loopId}:${slotId}`). */
@@ -137,7 +137,30 @@ export function closeIdeationLoopFromLaneResult(assignment, lane, actor, cwd) {
137
137
  // artifact stops satisfying the current gate by construction — no
138
138
  // separate refusal path, and the content is preserved rather than lost.
139
139
  const dispatchPhase = slot.phase ?? loop.current_phase;
140
- complete_turn({ id: loopId, slot_id: slot.slot_id, actor, outcome: 'done', artifact: { phase: dispatchPhase, type: 'critique', body: capCritique(critique) } }, cwd);
140
+ completeTurnWithEvidence({
141
+ id: loopId,
142
+ slot_id: slot.slot_id,
143
+ actor,
144
+ outcome: 'done',
145
+ artifact: { phase: dispatchPhase, type: 'critique', body: capCritique(critique) },
146
+ // This adapter is the trusted bridge from a harvested, assignment-bound
147
+ // LANE-RESULT to the slot that produced it. Without an explicit slot
148
+ // context, a coordinator-driven harvest is sealed as coordinator
149
+ // narration and the evidence gate correctly rejects it. Preserve the
150
+ // actual producer identity while keeping legacy lanes (which have no
151
+ // TurnReservation nonce) on the narrow complete_turn authority.
152
+ evidence_context: {
153
+ channel: 'complete_turn',
154
+ producer_kind: 'slot',
155
+ producer_id: slot.slot_id,
156
+ agent_id: slot.agent_id,
157
+ slot_id: slot.slot_id,
158
+ slot_role: slot.role,
159
+ assignment_id: slot.assignment_id ?? assignment.id,
160
+ claim_id: slot.claim_id,
161
+ turn_id: slot.current_turn_id,
162
+ },
163
+ }, cwd);
141
164
  const advanced = tryAdvance(true);
142
165
  return reportedArtifactType && reportedArtifactType !== expectedArtifactType
143
166
  ? { ...advanced, reason: `reconciled reported artifact type "${reportedArtifactType}" to expected "${expectedArtifactType}"; ${advanced.reason}` }
@@ -257,8 +257,9 @@ function renderUserWorkflow() {
257
257
  '',
258
258
  'Entities: `plan` (intended outcome) · `step` (unit inside a plan) · `sequence` (optional parallel lanes) · `claim` (advisory scope reservation) · `handoff` (stage snapshot) · `candidate` (proposed memory awaiting review) · `decision`/`constraint`/`trap`/`runtime_note` (context captured along the way).',
259
259
  '',
260
- 'Review & Fix Loop: start with `bclaw_coordinate(intent=review, open_loop=true, review_mode=symmetric|asymmetric, targetAgents=[reviewer])` opens the loop AND dispatches the first turn. Drive your turns with `bclaw_loop(intent=turn|complete_turn|advance|close)`. Parallelize a sequence\'s lanes with `bclaw_dispatch(intent=execute)`.',
261
- 'Ideation / Debug / Research loops *planned* (`docs/product/agent-first-model.md` §3).',
260
+ 'Loop Engine: five shipped protocols share one lifecycle — `review` validates a change, `ideation` pressure-tests a proposal, `implementation` drives a bound plan to green, `research` converges an open question, and `debug` drives a reproducible failure back to green.',
261
+ 'Entry points: start review with `bclaw_coordinate(intent=review, open_loop=true, review_mode=symmetric|asymmetric, targetAgents=[reviewer])`; start ideation with `bclaw_coordinate(intent=ideate, targetAgents=[critic])`. For implementation, research, or debug, direct `bclaw_loop(intent=open, kind=<kind>, allow_orphan=true)` is supported only when the caller will explicitly drive or dispatch the loop.',
262
+ 'Drive turns with `bclaw_loop(intent=turn|complete_turn|advance|close)` and follow each response\'s `next_actions`; on a worker phase, trusted `bclaw_loop(intent=turn, dispatch=true)` runs the common AttemptAuthority dispatch path. Parallelize a sequence\'s independent lanes with `bclaw_dispatch(intent=execute)`. Protocol details: `docs/concepts/loop-engine.md` and `docs/loops/`.',
262
263
  ].join('\n');
263
264
  }
264
265
  /**
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Generic production driver for one worker-backed Loop Engine turn.
3
+ *
4
+ * Review and ideation retain their ergonomic coordinate drivers, but every
5
+ * loop kind can enter the same AttemptAuthority lifecycle through this seam.
6
+ * The adapter creates transport projections and starts the worker; it never
7
+ * advances a phase or evaluates a gate.
8
+ */
9
+ import { resolveModel } from './agent-capability.js';
10
+ import { listAgentIdentities } from './agent-registry.js';
11
+ import { transitionAgentRun } from './agentruns.js';
12
+ import { loadAssignment, patchAssignmentMessageId, transitionAssignment } from './assignments.js';
13
+ import { attachAssignmentMessageToClaim, createCoordinatorClaim, ensureClaimAssignmentBinding, } from './claims.js';
14
+ import { generateDispatchBrief } from './dispatcher.js';
15
+ import { search } from './search.js';
16
+ import { attemptExecution } from './execution.js';
17
+ import { resolveExecutionCandidate } from './execution-contract.js';
18
+ import { buildHarnessInvocation, resolveHarnessBinding } from './harness-adapters/index.js';
19
+ import { phasePolicy } from './loops/kind-policies.js';
20
+ import { buildIdeationBrief } from './loops/brief-assembly.js';
21
+ import { getLoop } from './loops/store.js';
22
+ import { prepareTurnExecution } from './loops/turn-execution.js';
23
+ import { sendMessage } from './messaging.js';
24
+ export async function dispatchLoopTurn(input) {
25
+ const loop = getLoop(input.loop_id, input.cwd);
26
+ if (!loop)
27
+ return { loop_id: input.loop_id, slot_id: input.slot_id, error: `unknown loop_id ${input.loop_id}` };
28
+ const slot = loop.slots.find((candidate) => candidate.slot_id === input.slot_id);
29
+ const result = {
30
+ loop_id: loop.id,
31
+ slot_id: input.slot_id,
32
+ kind: loop.kind,
33
+ phase: loop.current_phase,
34
+ agent: slot?.agent,
35
+ };
36
+ if (loop.status !== 'open')
37
+ return { ...result, error: `loop ${loop.id} is ${loop.status}, not open` };
38
+ if (!slot)
39
+ return { ...result, error: `slot ${input.slot_id} not found` };
40
+ const policy = phasePolicy(loop.kind, loop.current_phase);
41
+ if (!policy || policy.execution !== 'worker') {
42
+ return { ...result, error: `${loop.kind}.${loop.current_phase} is ${policy?.execution ?? 'unknown'}, not a worker phase` };
43
+ }
44
+ let agent = slot.agent;
45
+ let agentId = slot.agent_id;
46
+ if (!agent) {
47
+ const registered = listAgentIdentities(input.cwd).filter((identity) => identity.kind !== 'human');
48
+ const allowed = new Set(input.candidate_agents ?? registered.map((identity) => identity.agent_name));
49
+ const candidates = registered
50
+ .filter((identity) => allowed.has(identity.agent_name))
51
+ .map((identity) => ({ agent: identity.agent_name, agent_id: identity.agent_id }));
52
+ const role = loop.kind === 'review' || loop.kind === 'ideation'
53
+ ? 'review'
54
+ : loop.kind === 'research' ? 'consult' : 'execute';
55
+ const selection = resolveExecutionCandidate(candidates, {
56
+ roles: [role], required_surfaces: ['cli_spawn'], execution_surfaces: [], required_tools: [],
57
+ });
58
+ if (selection.kind !== 'selected') {
59
+ return { ...result, error: `slot ${slot.slot_id} has no compatible worker candidate` };
60
+ }
61
+ agent = selection.selected.agent;
62
+ agentId = selection.selected.agent_id;
63
+ result.agent = agent;
64
+ }
65
+ const scope = slot.scope_hint ?? `loop:${loop.kind}:${loop.id}:slot:${slot.slot_id}`;
66
+ const sectionByCategory = {
67
+ traps: 'traps', decisions: 'decisions', constraints: 'constraints', handoffs: 'handoffs',
68
+ plans: 'plans', candidates: 'candidates',
69
+ };
70
+ const provider = {
71
+ fetch(category, query, topK) {
72
+ const section = sectionByCategory[category];
73
+ if (!section)
74
+ return [];
75
+ return search({ query, section, maxResults: topK, cwd: input.cwd, includePending: section === 'candidates' })
76
+ .map((item) => ({
77
+ id: item.id, category, text: item.text, score: item.score, relatedPaths: item.related_paths,
78
+ }));
79
+ },
80
+ };
81
+ const phaseBrief = buildIdeationBrief({
82
+ thread: loop,
83
+ slotRole: slot.role,
84
+ memoryProvider: provider,
85
+ seedText: input.task,
86
+ scopeHints: slot.scope_hint ? slot.scope_hint.split(',').map((value) => value.trim()) : [],
87
+ });
88
+ const laneContext = slot.lane
89
+ ? `Lane: ${slot.lane}\nPlans: ${(slot.plan_ids ?? []).join(', ') || '(none)'}\nSteps: ${(slot.step_ids ?? []).join(', ') || '(whole plan)'}`
90
+ : '';
91
+ const scopedTask = [phaseBrief.text, laneContext].filter(Boolean).join('\n\n');
92
+ const description = `${loop.kind} loop turn for ${loop.id} slot ${slot.slot_id} phase ${loop.current_phase}. ${input.task}`;
93
+ try {
94
+ const claim = createCoordinatorClaim({
95
+ agent,
96
+ scope,
97
+ description,
98
+ dispatcherAgent: input.dispatcher_agent,
99
+ sessionId: input.session_id,
100
+ cwd: input.cwd,
101
+ });
102
+ result.claim_id = claim.claimId;
103
+ result.worktree_path = claim.worktreePath;
104
+ if (claim.scopeConflict) {
105
+ result.error = `scope ${scope} is already claimed by ${claim.conflictAgent ?? 'another agent'}`;
106
+ return result;
107
+ }
108
+ const model = resolveModel(agent, { override: input.model });
109
+ const binding = resolveHarnessBinding(agent, model);
110
+ const prepared = prepareTurnExecution({
111
+ kind: loop.kind,
112
+ loop_id: loop.id,
113
+ slot_id: slot.slot_id,
114
+ phase: loop.current_phase,
115
+ agent,
116
+ agent_id: agentId,
117
+ claim_id: claim.claimId,
118
+ dispatcher_agent: input.dispatcher_agent,
119
+ dispatcher_agent_id: input.dispatcher_agent_id,
120
+ dispatcher_session_id: input.session_id,
121
+ scope,
122
+ description,
123
+ task: scopedTask,
124
+ cwd: input.cwd,
125
+ worktree_path: claim.worktreePath,
126
+ model,
127
+ harness_binding: binding,
128
+ assignment_tags: ['coordinate', loop.kind, 'loop', 'turn-owned'],
129
+ run_tags: ['turn-owned', loop.kind, 'loop'],
130
+ });
131
+ if (prepared.kind !== 'won') {
132
+ result.execution_status = 'inbox_only';
133
+ result.error = prepared.reason;
134
+ return result;
135
+ }
136
+ result.assignment_id = prepared.assignment_id;
137
+ result.run_id = prepared.run_id;
138
+ result.turn_id = prepared.turn_id;
139
+ result.worktree_path = prepared.workspace_path;
140
+ const turnEcho = {
141
+ turn_id: prepared.turn_id,
142
+ run_id: prepared.run_id,
143
+ nonce: prepared.nonce,
144
+ ...(prepared.execution_contract_ref ? {
145
+ contract_hash: prepared.execution_contract_ref.hash,
146
+ capability_snapshot_hash: prepared.execution_contract_ref.snapshot_hash,
147
+ } : {}),
148
+ ...(prepared.attempt_epoch !== undefined ? { attempt_epoch: prepared.attempt_epoch } : {}),
149
+ ...(prepared.workspace_digest ? { workspace_digest: prepared.workspace_digest } : {}),
150
+ };
151
+ const brief = generateDispatchBrief({
152
+ task: scopedTask,
153
+ agent,
154
+ claimId: claim.claimId,
155
+ scope,
156
+ worktreePath: prepared.workspace_path,
157
+ assignmentId: prepared.assignment_id,
158
+ executionContractRef: prepared.execution_contract_ref,
159
+ attemptFence: prepared.attempt_epoch !== undefined && prepared.workspace_digest ? {
160
+ turn_id: prepared.turn_id,
161
+ run_id: prepared.run_id,
162
+ nonce: prepared.nonce,
163
+ attempt_epoch: prepared.attempt_epoch,
164
+ workspace_digest: prepared.workspace_digest,
165
+ } : undefined,
166
+ cwd: input.cwd,
167
+ });
168
+ const message = sendMessage({
169
+ from: input.dispatcher_agent,
170
+ to: agent,
171
+ type: 'assign',
172
+ text: brief,
173
+ ref: loop.id,
174
+ scope,
175
+ requires_ack: true,
176
+ claim_id: claim.claimId,
177
+ assignment_id: prepared.assignment_id,
178
+ tags: ['coordinate', loop.kind, 'loop', 'turn-owned'],
179
+ author_id: input.dispatcher_agent_id,
180
+ session_id: input.session_id,
181
+ payload: {
182
+ intent: 'loop_turn', loop_id: loop.id, slot_id: slot.slot_id,
183
+ phase: loop.current_phase, scope, claim_id: claim.claimId,
184
+ assignment_id: prepared.assignment_id, worktree_path: prepared.workspace_path,
185
+ },
186
+ }, input.cwd);
187
+ result.message_id = message.id;
188
+ attachAssignmentMessageToClaim(claim.claimId, message.id, input.cwd);
189
+ ensureClaimAssignmentBinding(claim.claimId, prepared.assignment_id, input.cwd, {
190
+ worktreePath: prepared.workspace_path,
191
+ });
192
+ const assignment = loadAssignment(prepared.assignment_id, input.cwd);
193
+ if (assignment?.status === 'created' || assignment?.status === 'retrying') {
194
+ transitionAssignment(prepared.assignment_id, 'offered', { actor: input.dispatcher_agent }, input.cwd);
195
+ }
196
+ patchAssignmentMessageId(prepared.assignment_id, message.id, input.cwd);
197
+ const invoke = buildHarnessInvocation(agent, brief, {
198
+ mode: 'worker', model, binding,
199
+ contract: undefined,
200
+ capability_snapshot: prepared.capability_snapshot,
201
+ })?.invoke;
202
+ const execution = await attemptExecution(invoke, {
203
+ agent,
204
+ autoExecute: input.auto_execute ?? true,
205
+ worktreePath: prepared.workspace_path,
206
+ claimId: claim.claimId,
207
+ assignmentId: prepared.assignment_id,
208
+ dispatcherAgent: input.dispatcher_agent,
209
+ dispatcherAgentId: input.dispatcher_agent_id,
210
+ cwd: input.cwd,
211
+ requireWorktree: true,
212
+ turnEcho,
213
+ });
214
+ result.execution_status = execution.execution_status;
215
+ result.command = execution.command;
216
+ result.shell = execution.shell;
217
+ if (execution.error)
218
+ result.error = execution.error;
219
+ if (execution.execution_status === 'delivered_and_started') {
220
+ try {
221
+ transitionAgentRun(prepared.run_id, 'running', {
222
+ actor: input.dispatcher_agent,
223
+ status_reason: `turn-owned ${loop.kind} worker spawned`,
224
+ }, input.cwd);
225
+ }
226
+ catch { /* the reconciler converges transport state */ }
227
+ }
228
+ return result;
229
+ }
230
+ catch (error) {
231
+ result.error = `loop turn dispatch failed: ${error instanceof Error ? error.message : String(error)}`;
232
+ return result;
233
+ }
234
+ }
235
+ //# sourceMappingURL=loop-turn-dispatch.js.map
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ /** Immutable description of one artifact expected from a worker attempt. */
3
+ export const ExpectedArtifactSchema = z.object({
4
+ logical_name: z.string().min(1),
5
+ worker_path: z.string().min(1),
6
+ loop_artifact_type: z.string().min(1),
7
+ schema_id: z.string().optional(),
8
+ completion_policy: z.enum(['required', 'optional']).default('required'),
9
+ sha256: z.string().optional(),
10
+ });
11
+ //# sourceMappingURL=artifact-contract.js.map