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.
- 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 +143 -15
- package/dist/commands/mcp-catalog.js +52 -18
- package/dist/commands/mcp-schemas.generated.js +64 -0
- package/dist/commands/mcp-write-claims.js +128 -1
- package/dist/commands/mcp-write-coordination.js +149 -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 +189 -14
- package/dist/core/execution-contract.js +345 -0
- package/dist/core/execution.js +130 -16
- package/dist/core/facade-schema.js +3 -0
- 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 +235 -0
- package/dist/core/loops/artifact-contract.js +11 -0
- package/dist/core/loops/attempt-authority.js +496 -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/brief-assembly.js +21 -4
- package/dist/core/loops/evidence.js +188 -0
- package/dist/core/loops/facade-schema.js +75 -11
- package/dist/core/loops/gate-policy.js +533 -0
- package/dist/core/loops/impl-bind.js +91 -81
- 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 +237 -18
- package/dist/core/loops/result-reducers.js +113 -10
- package/dist/core/loops/store.js +34 -3
- package/dist/core/loops/turn-execution.js +480 -0
- package/dist/core/loops/types.js +127 -3
- package/dist/core/loops/verbs.js +335 -99
- package/dist/core/loops/verify-command.js +105 -20
- 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 +40 -0
- package/dist/core/spawn-check.js +3 -2
- package/dist/core/upgrades/backup.js +27 -4
- package/dist/facts.js +9 -8
- package/dist/facts.json +8 -7
- 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 +174 -0
- package/docs/loops/research.md +136 -0
- package/docs/loops/review.md +200 -0
- package/docs/mcp-schema-changelog.md +18 -5
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import crypto from 'node:crypto';
|
|
15
15
|
import { buildClaimEnvPrefix } from '../core/execution-profile.js';
|
|
16
16
|
import { resolveProjectCwd } from '../core/cross-project.js';
|
|
17
|
-
import { attachAssignmentMessageToClaim, createCoordinatorClaim, linkClaimToAssignment, listClaims, saveClaim, } from '../core/claims.js';
|
|
17
|
+
import { attachAssignmentMessageToClaim, createCoordinatorClaim, linkClaimToAssignment, listClaims, releaseClaimIfActive, saveClaim, } from '../core/claims.js';
|
|
18
18
|
import { ensureAgentRegisteredForDispatch, findAgentIdentityById, findAgentIdentityByName, } from '../core/agent-registry.js';
|
|
19
19
|
import { appendAuditEntry } from '../core/audit.js';
|
|
20
20
|
import { nowISO } from '../core/ids.js';
|
|
@@ -26,10 +26,13 @@ import { agentValidationFailedWarning, consultAutoExecuteNoOpWarning, planAlread
|
|
|
26
26
|
import { ackMessage, getThread, hasActiveAssignment, sendMessage } from '../core/messaging.js';
|
|
27
27
|
import { dispatch, dispatchReview, generateDispatchBrief } from '../core/dispatcher.js';
|
|
28
28
|
import { CoordinateRequestSchema } from '../core/facade-schema.js';
|
|
29
|
-
import {
|
|
29
|
+
import { getCapabilityProfile, getSpawnableAgents, resolveModel, validateAgentForDispatch, } from '../core/agent-capability.js';
|
|
30
|
+
import { buildHarnessInvocation, resolveHarnessBinding } from '../core/harness-adapters/index.js';
|
|
30
31
|
import { attemptExecution } from '../core/execution.js';
|
|
31
32
|
import { createAgentRun, transitionAgentRun } from '../core/agentruns.js';
|
|
32
33
|
import { prepareTurnOwnedReviewDispatch, turnOwnedReviewEnabled } from '../core/review-loop-turn-dispatch.js';
|
|
34
|
+
import { prepareTurnExecution } from '../core/loops/turn-execution.js';
|
|
35
|
+
import { removeWorktree } from '../core/worktree.js';
|
|
33
36
|
import { createAssignment, generateAssignmentId, patchAssignmentMessageId, transitionAssignment, } from '../core/assignments.js';
|
|
34
37
|
import { createToolErrorResponse, toolResponse, } from './mcp-contract.js';
|
|
35
38
|
import { handleMcpReadToolCall } from './mcp-read-handlers.js';
|
|
@@ -492,16 +495,16 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
492
495
|
if (execResult.error)
|
|
493
496
|
opts.warnings.push(`${entry.agent}: ${execResult.error}`);
|
|
494
497
|
if (turnEcho) {
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
+
// A turn-owned loop run was ALREADY created (`created`) before crossing.
|
|
499
|
+
// Do NOT mint a second run here (double-mint). Transition the
|
|
500
|
+
// deterministic run → running on a real spawn; leave
|
|
498
501
|
// it `created` otherwise so the no-sentinel legacy fallback (turnOwnedLaneEvidence) + the
|
|
499
502
|
// pre-run lease reconciler govern it. Non-turn-owned entries take the unchanged else-branch.
|
|
500
503
|
if (execResult.execution_status === 'delivered_and_started') {
|
|
501
504
|
try {
|
|
502
505
|
transitionAgentRun(turnEcho.run_id, 'running', {
|
|
503
506
|
actor: opts.senderAgent, actor_id: opts.senderAgentId, pid: execResult.pid,
|
|
504
|
-
status_reason: 'turn-owned
|
|
507
|
+
status_reason: 'turn-owned loop worker spawned by coordinator',
|
|
505
508
|
}, opts.cwd);
|
|
506
509
|
}
|
|
507
510
|
catch { /* best-effort — the reconciler converges if this races */ }
|
|
@@ -626,6 +629,8 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
626
629
|
scope: options?.scope,
|
|
627
630
|
worktreePath: options?.worktreePath,
|
|
628
631
|
assignmentId: options?.assignmentId,
|
|
632
|
+
executionContractRef: options?.executionContractRef,
|
|
633
|
+
attemptFence: options?.attemptFence,
|
|
629
634
|
// pln#638 PR-6b — the envelope must read the TARGET project's store: on a
|
|
630
635
|
// cross-project dispatch, defaulting to process.cwd() would inline the
|
|
631
636
|
// WRONG project's constraints/traps into the worker's brief.
|
|
@@ -659,7 +664,7 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
659
664
|
}, dispatchCwd);
|
|
660
665
|
artifacts.push({ type: 'message', id: msgResult.id });
|
|
661
666
|
side_effects.push({ action: 'create', entity: 'message', id: msgResult.id });
|
|
662
|
-
const invoke =
|
|
667
|
+
const invoke = buildHarnessInvocation(input.agent, input.text, {
|
|
663
668
|
mode: input.commandMode ?? 'worker',
|
|
664
669
|
// pln#520/#606 — decouple model from agent identity. req.model is the
|
|
665
670
|
// override link; when unset, resolveModel intentionally falls back to
|
|
@@ -670,7 +675,8 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
670
675
|
// (gpt-5.6-luna review). Flows to both the manual commandHint and the
|
|
671
676
|
// auto-spawn path (runCoordinateExecution reuses this invoke).
|
|
672
677
|
model: resolveModel(input.agent, { override: req.model }),
|
|
673
|
-
|
|
678
|
+
binding: input.harnessBinding,
|
|
679
|
+
})?.invoke;
|
|
674
680
|
// Build env prefix for claim routing — centralised in
|
|
675
681
|
// execution-profile.ts:buildClaimEnvPrefix as of pln#496 step
|
|
676
682
|
// stp_a9afe59d (handles all five shells, not just Windows/POSIX).
|
|
@@ -996,6 +1002,7 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
996
1002
|
created_by: creatorActor,
|
|
997
1003
|
slots,
|
|
998
1004
|
mode: req.review_mode ?? 'asymmetric',
|
|
1005
|
+
linked: req.linked,
|
|
999
1006
|
}, dispatchCwd);
|
|
1000
1007
|
out.loopId = loop.id;
|
|
1001
1008
|
out.artifacts.push({ type: 'loop', id: loop.id });
|
|
@@ -1024,7 +1031,10 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1024
1031
|
// no ids, so a harvest could only match reviewer slots by agent name —
|
|
1025
1032
|
// which completes the WRONG slot in symmetric (multi-reviewer) mode.
|
|
1026
1033
|
try {
|
|
1027
|
-
|
|
1034
|
+
// One claim owns one reviewer slot. A loop-wide claim cannot safely
|
|
1035
|
+
// represent parallel assignments because Claim has one assignment_id
|
|
1036
|
+
// projection; keep the parseable loop prefix and add slot identity.
|
|
1037
|
+
const reviewScope = `review-loop:${loop.id}:slot:${slot.slot_id}`;
|
|
1028
1038
|
const reviewDescription = `Review loop turn for ${loop.id} slot ${slot.slot_id} phase findings. `
|
|
1029
1039
|
+ `Mode: ${advanced.loop.protocol?.review_mode ?? 'asymmetric'}. ${req.task}`;
|
|
1030
1040
|
const claimResult = createCoordinatorClaim({
|
|
@@ -1046,6 +1056,8 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1046
1056
|
});
|
|
1047
1057
|
let reviewAssignmentId;
|
|
1048
1058
|
let reviewTurnEcho;
|
|
1059
|
+
let reviewExecutionContractRef;
|
|
1060
|
+
let reviewWorktreePath = claimResult.worktreePath;
|
|
1049
1061
|
// pln#630 — turn-own the INITIAL reviewer dispatch (same default + kill-switch as the
|
|
1050
1062
|
// fix cycle). Skipped for cross-project reviews (no local worktree/sentinel → they never
|
|
1051
1063
|
// spawn here). WON: prepare minted the DETERMINISTIC assignment + run + turn()-bound the
|
|
@@ -1070,12 +1082,25 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1070
1082
|
dispatcherAgent: senderAgent,
|
|
1071
1083
|
dispatcherAgentId: senderAgentId,
|
|
1072
1084
|
sessionId: connectionSessionId,
|
|
1085
|
+
model: resolveModel(slot.agent ?? '', { override: req.model }),
|
|
1073
1086
|
isReviewer: true,
|
|
1074
1087
|
cwd: dispatchCwd,
|
|
1075
1088
|
});
|
|
1076
1089
|
if (prep.kind === 'won') {
|
|
1077
1090
|
reviewAssignmentId = prep.assignmentId; // deterministic — harvest correlates on it
|
|
1078
|
-
|
|
1091
|
+
reviewExecutionContractRef = prep.executionContractRef;
|
|
1092
|
+
reviewTurnEcho = {
|
|
1093
|
+
turn_id: prep.turnId,
|
|
1094
|
+
run_id: prep.runId,
|
|
1095
|
+
nonce: prep.nonce,
|
|
1096
|
+
...(prep.executionContractRef ? {
|
|
1097
|
+
contract_hash: prep.executionContractRef.hash,
|
|
1098
|
+
capability_snapshot_hash: prep.executionContractRef.snapshot_hash,
|
|
1099
|
+
} : {}),
|
|
1100
|
+
...(prep.attemptEpoch !== undefined ? { attempt_epoch: prep.attemptEpoch } : {}),
|
|
1101
|
+
...(prep.workspaceDigest ? { workspace_digest: prep.workspaceDigest } : {}),
|
|
1102
|
+
};
|
|
1103
|
+
reviewWorktreePath = prep.workspacePath;
|
|
1079
1104
|
out.artifacts.push({ type: 'assignment', id: prep.assignmentId });
|
|
1080
1105
|
usedTurnOwned = true; // prepare already created the assignment + run + bound the slot
|
|
1081
1106
|
}
|
|
@@ -1124,8 +1149,16 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1124
1149
|
const reviewBrief = buildCoordinateBrief(slot.agent ?? '', reviewDescription + reviewVerdictBriefSuffix, {
|
|
1125
1150
|
claimId: claimResult.claimId,
|
|
1126
1151
|
scope: reviewScope,
|
|
1127
|
-
worktreePath:
|
|
1152
|
+
worktreePath: reviewWorktreePath,
|
|
1128
1153
|
assignmentId: reviewAssignmentId,
|
|
1154
|
+
executionContractRef: reviewExecutionContractRef,
|
|
1155
|
+
attemptFence: reviewTurnEcho?.attempt_epoch !== undefined && reviewTurnEcho.workspace_digest ? {
|
|
1156
|
+
turn_id: reviewTurnEcho.turn_id,
|
|
1157
|
+
run_id: reviewTurnEcho.run_id,
|
|
1158
|
+
nonce: reviewTurnEcho.nonce,
|
|
1159
|
+
attempt_epoch: reviewTurnEcho.attempt_epoch,
|
|
1160
|
+
workspace_digest: reviewTurnEcho.workspace_digest,
|
|
1161
|
+
} : undefined,
|
|
1129
1162
|
});
|
|
1130
1163
|
const queued = queueCoordinateMessage({
|
|
1131
1164
|
agent: slot.agent ?? '',
|
|
@@ -1145,7 +1178,7 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1145
1178
|
scope: reviewScope,
|
|
1146
1179
|
claim_id: claimResult.claimId,
|
|
1147
1180
|
...(reviewAssignmentId ? { assignment_id: reviewAssignmentId } : {}),
|
|
1148
|
-
worktree_path:
|
|
1181
|
+
worktree_path: reviewWorktreePath,
|
|
1149
1182
|
},
|
|
1150
1183
|
commandMode: 'worker',
|
|
1151
1184
|
});
|
|
@@ -1164,7 +1197,7 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1164
1197
|
out.preparedReviews.push({
|
|
1165
1198
|
entry: queued.entry,
|
|
1166
1199
|
invoke: queued.invoke,
|
|
1167
|
-
worktreePath:
|
|
1200
|
+
worktreePath: reviewWorktreePath,
|
|
1168
1201
|
turnEcho: reviewTurnEcho,
|
|
1169
1202
|
});
|
|
1170
1203
|
}
|
|
@@ -1441,7 +1474,7 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1441
1474
|
// pln#513 step 2 — labelled block (ideate:) so the bootstrap
|
|
1442
1475
|
// join-or-lock path can break out early after assigning result.
|
|
1443
1476
|
const loopsModuleRef = await import('../core/loops/index.js');
|
|
1444
|
-
const { openLoop, add_artifact, advance,
|
|
1477
|
+
const { openLoop, add_artifact, advance, getLoop, buildIdeationBrief } = loopsModuleRef;
|
|
1445
1478
|
const presetSelected = req.preset
|
|
1446
1479
|
? (await import('../core/loops/presets/index.js')).PRESETS[req.preset]
|
|
1447
1480
|
: undefined;
|
|
@@ -1553,6 +1586,7 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1553
1586
|
goal: req.scope,
|
|
1554
1587
|
created_by: creatorActor,
|
|
1555
1588
|
slots,
|
|
1589
|
+
linked: req.linked,
|
|
1556
1590
|
...(presetSelected
|
|
1557
1591
|
? {
|
|
1558
1592
|
phases: presetSelected.phases,
|
|
@@ -1645,6 +1679,7 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1645
1679
|
category,
|
|
1646
1680
|
text: r.text,
|
|
1647
1681
|
score: r.score,
|
|
1682
|
+
relatedPaths: r.related_paths,
|
|
1648
1683
|
}));
|
|
1649
1684
|
},
|
|
1650
1685
|
};
|
|
@@ -1708,55 +1743,86 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1708
1743
|
entity: 'claim',
|
|
1709
1744
|
id: claimResult.claimId,
|
|
1710
1745
|
});
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
dispatcher_agent: senderAgent,
|
|
1720
|
-
dispatcher_session_id: connectionSessionId,
|
|
1721
|
-
scope: criticScope,
|
|
1722
|
-
description: criticDescription,
|
|
1723
|
-
tags: ['coordinate', 'ideate', 'loop'],
|
|
1724
|
-
}, dispatchCwd);
|
|
1725
|
-
criticAssignmentId = assignment.id;
|
|
1726
|
-
artifacts.push({ type: 'assignment', id: assignment.id });
|
|
1727
|
-
}
|
|
1728
|
-
catch (asgErr) {
|
|
1729
|
-
warnings.push(`ideate assignment creation failed for slot ${slot.slot_id}: ${asgErr instanceof Error ? asgErr.message : String(asgErr)}`);
|
|
1730
|
-
}
|
|
1731
|
-
// pln#629 — bind the slot to its claim/assignment NOW that both
|
|
1732
|
-
// exist (mirrors the review path, pln#628 BLOCKING 2). The turn()
|
|
1733
|
-
// used to fire BEFORE the assignment was created, leaving
|
|
1734
|
-
// slot.assignment_id undefined: bclaw_loop get's reconcile then
|
|
1735
|
-
// skipped the critic slot (loops-handlers.ts `if (!assignmentId)
|
|
1736
|
-
// continue`) and dispatch_status(lop_) resolved no assignment, so
|
|
1737
|
-
// ideate loops could never be reconciled (trp_dfe0b941 /
|
|
1738
|
-
// trp_2187b340 / trp_1de94516). Runs even if assignment creation
|
|
1739
|
-
// failed (undefined id → legacy agent-match fallback, as review).
|
|
1740
|
-
turn({
|
|
1741
|
-
id: loopId,
|
|
1746
|
+
// P0C / dec#171 — ideation crosses the same attempt fence as every
|
|
1747
|
+
// worker-backed LoopKind phase. Assignment, AgentRun, claim binding
|
|
1748
|
+
// and slot binding are all durable before the irreversible crossing.
|
|
1749
|
+
const criticModel = resolveModel(slot.agent, { override: req.model });
|
|
1750
|
+
const criticHarnessBinding = resolveHarnessBinding(slot.agent, criticModel);
|
|
1751
|
+
const attempt = prepareTurnExecution({
|
|
1752
|
+
kind: 'ideation',
|
|
1753
|
+
loop_id: loopId,
|
|
1742
1754
|
slot_id: slot.slot_id,
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1755
|
+
phase: advancedLoop.current_phase,
|
|
1756
|
+
agent: slot.agent,
|
|
1757
|
+
agent_id: slot.agent_id,
|
|
1746
1758
|
claim_id: claimResult.claimId,
|
|
1747
|
-
|
|
1759
|
+
dispatcher_agent: senderAgent,
|
|
1760
|
+
dispatcher_agent_id: senderAgentId,
|
|
1761
|
+
dispatcher_session_id: connectionSessionId,
|
|
1762
|
+
scope: criticScope,
|
|
1763
|
+
description: criticDescription,
|
|
1764
|
+
task: briefResult.text,
|
|
1765
|
+
cwd: dispatchCwd,
|
|
1766
|
+
worktree_path: claimResult.worktreePath,
|
|
1767
|
+
model: criticModel,
|
|
1768
|
+
harness_binding: criticHarnessBinding,
|
|
1769
|
+
assignment_tags: ['coordinate', 'ideate', 'loop', 'turn-owned'],
|
|
1770
|
+
run_tags: ['turn-owned', 'ideate', 'loop'],
|
|
1771
|
+
});
|
|
1772
|
+
if (attempt.kind !== 'won') {
|
|
1773
|
+
warnings.push(`ideate attempt denied for slot ${slot.slot_id}: ${attempt.reason}; no worker spawned`);
|
|
1774
|
+
// A pre-identity refusal or an authority owned by another claim
|
|
1775
|
+
// cannot use this freshly created lane claim. Release only claims
|
|
1776
|
+
// created by THIS call; a reused claim may belong to a concurrent
|
|
1777
|
+
// winner and must remain intact. Repairable/same-claim authority is
|
|
1778
|
+
// also retained so projections can be replayed safely.
|
|
1779
|
+
if (attempt.claim_disposition === 'release' && !claimResult.reusedExisting) {
|
|
1780
|
+
try {
|
|
1781
|
+
const released = releaseClaimIfActive(claimResult.claimId, dispatchCwd);
|
|
1782
|
+
if (released.released) {
|
|
1783
|
+
side_effects.push({ action: 'release', entity: 'claim', id: claimResult.claimId });
|
|
1784
|
+
if (claimResult.worktreePath) {
|
|
1785
|
+
try {
|
|
1786
|
+
removeWorktree(dispatchCwd, claimResult.worktreePath, { force: true });
|
|
1787
|
+
}
|
|
1788
|
+
catch (cleanupError) {
|
|
1789
|
+
warnings.push(`ideate denied-claim worktree cleanup failed for ${claimResult.claimId}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
catch (cleanupError) {
|
|
1795
|
+
warnings.push(`ideate denied-claim cleanup failed for ${claimResult.claimId}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
continue;
|
|
1799
|
+
}
|
|
1800
|
+
const criticAssignmentId = attempt.assignment_id;
|
|
1801
|
+
const criticTurnEcho = {
|
|
1802
|
+
turn_id: attempt.turn_id,
|
|
1803
|
+
run_id: attempt.run_id,
|
|
1804
|
+
nonce: attempt.nonce,
|
|
1805
|
+
...(attempt.execution_contract_ref ? {
|
|
1806
|
+
contract_hash: attempt.execution_contract_ref.hash,
|
|
1807
|
+
capability_snapshot_hash: attempt.execution_contract_ref.snapshot_hash,
|
|
1808
|
+
} : {}),
|
|
1809
|
+
...(attempt.attempt_epoch !== undefined ? { attempt_epoch: attempt.attempt_epoch } : {}),
|
|
1810
|
+
...(attempt.workspace_digest ? { workspace_digest: attempt.workspace_digest } : {}),
|
|
1811
|
+
};
|
|
1812
|
+
artifacts.push({ type: 'assignment', id: criticAssignmentId });
|
|
1748
1813
|
// pln#626 Phase 2 — the critique-only contract must reach the
|
|
1749
1814
|
// DELIVERED brief, not just the claim record: buildCoordinateBrief
|
|
1750
1815
|
// wraps this in a worker envelope, so prepend the constraint + the
|
|
1751
1816
|
// reply path (MCP complete_turn, or LANE-RESULT.json for a sandboxed
|
|
1752
1817
|
// critic without brainclaw MCP) ahead of the ideation brief body.
|
|
1753
1818
|
const criticTaskText = `CRITIQUE-ONLY TASK — do NOT edit code or commit. Read the proposal below and reply with your critique: `
|
|
1754
|
-
+ `call bclaw_loop(intent='complete_turn') if you have brainclaw MCP, otherwise write
|
|
1819
|
+
+ `call bclaw_loop(intent='complete_turn') if you have brainclaw MCP, otherwise write LANE-RESULT.json in your worktree root with `
|
|
1820
|
+
+ `"status":"completed", "artifact_type":"critique", and the full critique in "body" (the coordinator harvests it).\n\n`
|
|
1755
1821
|
+ briefResult.text;
|
|
1756
1822
|
const criticBrief = buildCoordinateBrief(slot.agent, criticTaskText, {
|
|
1757
1823
|
claimId: claimResult.claimId,
|
|
1758
1824
|
scope: criticScope,
|
|
1759
|
-
worktreePath:
|
|
1825
|
+
worktreePath: attempt.workspace_path,
|
|
1760
1826
|
assignmentId: criticAssignmentId,
|
|
1761
1827
|
// The ideation brief above already inlines a BM25-selected,
|
|
1762
1828
|
// budget-managed memory bundle (with its own truncation warning).
|
|
@@ -1764,6 +1830,14 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1764
1830
|
// and break the documented ~48K content cap + dispatch-envelope
|
|
1765
1831
|
// math pinned by ideation-loop-e2e.
|
|
1766
1832
|
contextEnvelope: false,
|
|
1833
|
+
executionContractRef: attempt.execution_contract_ref,
|
|
1834
|
+
attemptFence: attempt.attempt_epoch !== undefined && attempt.workspace_digest ? {
|
|
1835
|
+
turn_id: attempt.turn_id,
|
|
1836
|
+
run_id: attempt.run_id,
|
|
1837
|
+
nonce: attempt.nonce,
|
|
1838
|
+
attempt_epoch: attempt.attempt_epoch,
|
|
1839
|
+
workspace_digest: attempt.workspace_digest,
|
|
1840
|
+
} : undefined,
|
|
1767
1841
|
});
|
|
1768
1842
|
const queued = queueCoordinateMessage({
|
|
1769
1843
|
agent: slot.agent,
|
|
@@ -1783,9 +1857,10 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1783
1857
|
iteration: advancedLoop.iteration_count,
|
|
1784
1858
|
proposal_artifact_id: proposalArtifactId,
|
|
1785
1859
|
...(criticAssignmentId ? { assignment_id: criticAssignmentId } : {}),
|
|
1786
|
-
worktree_path:
|
|
1860
|
+
worktree_path: attempt.workspace_path,
|
|
1787
1861
|
},
|
|
1788
1862
|
commandMode: 'worker',
|
|
1863
|
+
harnessBinding: criticHarnessBinding,
|
|
1789
1864
|
});
|
|
1790
1865
|
if (criticAssignmentId) {
|
|
1791
1866
|
try {
|
|
@@ -1802,7 +1877,8 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1802
1877
|
preparedCritics.push({
|
|
1803
1878
|
entry: queued.entry,
|
|
1804
1879
|
invoke: queued.invoke,
|
|
1805
|
-
worktreePath:
|
|
1880
|
+
worktreePath: attempt.workspace_path,
|
|
1881
|
+
turnEcho: criticTurnEcho,
|
|
1806
1882
|
});
|
|
1807
1883
|
dispatchedCritics += 1;
|
|
1808
1884
|
}
|
|
@@ -1966,34 +2042,31 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1966
2042
|
}
|
|
1967
2043
|
export async function handleBclawLoop(args, ctx) {
|
|
1968
2044
|
const { cwd, connectionSessionId } = ctx;
|
|
1969
|
-
//
|
|
1970
|
-
//
|
|
1971
|
-
//
|
|
1972
|
-
//
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
//
|
|
1982
|
-
// LA BRANCHE `turn && dispatch === true` A ÉTÉ RETIRÉE (pln#626 phase 4). Le drapeau
|
|
1983
|
-
// `TurnInput.dispatch` était déclaré, transporté jusqu'à `turn()` — et JAMAIS LU. Une
|
|
1984
|
-
// porte de confiance sur un no-op est pire qu'absente : elle fait croire qu'un chemin
|
|
1985
|
-
// sensible est gardé, et un lecteur qui la voit conclut à tort que `dispatch: true` a
|
|
1986
|
-
// un effet. Le drapeau lui-même est supprimé dans le même commit ; garder la porte
|
|
1987
|
-
// aurait laissé la fausse impression intacte.
|
|
1988
|
-
if (args?.intent === 'bind') {
|
|
1989
|
-
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'trusted', cwd, connectionSessionId);
|
|
2045
|
+
// Direct open is public for implementation/research/debug orchestration.
|
|
2046
|
+
// The facade schema requires allow_orphan=true, making the caller explicitly
|
|
2047
|
+
// own the subsequent bind/turn/dispatch. Review and ideation still have the
|
|
2048
|
+
// higher-level coordinate shortcuts that open and dispatch together.
|
|
2049
|
+
// `turn(dispatch=true)` is now a REAL generic spawn path (claim + immutable
|
|
2050
|
+
// attempt + inbox + execution adapter). Plain turn remains a pure Loop Engine
|
|
2051
|
+
// mutation. `bind` is also engine-only; takeover and real turn dispatch are
|
|
2052
|
+
// the two trusted authority-changing paths here.
|
|
2053
|
+
const targetCwd = resolveProjectCwd(args?.project, cwd);
|
|
2054
|
+
let effectiveArgs = args;
|
|
2055
|
+
if (args?.intent === 'takeover' || (args?.intent === 'turn' && args?.dispatch === true)) {
|
|
2056
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'trusted', targetCwd, connectionSessionId);
|
|
1990
2057
|
if (resolved.error) {
|
|
1991
2058
|
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
1992
2059
|
}
|
|
2060
|
+
if ((args.intent === 'takeover' || args.intent === 'turn') && resolved.identity) {
|
|
2061
|
+
effectiveArgs = {
|
|
2062
|
+
...args,
|
|
2063
|
+
agent: resolved.identity.agent_name,
|
|
2064
|
+
agentId: resolved.identity.agent_id,
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
1993
2067
|
}
|
|
1994
2068
|
const { handleBclawLoop: runLoopIntent } = await import('./loops-handlers.js');
|
|
1995
|
-
const
|
|
1996
|
-
const result = await runLoopIntent({ args: args, cwd: targetCwd, sessionId: connectionSessionId });
|
|
2069
|
+
const result = await runLoopIntent({ args: effectiveArgs, cwd: targetCwd, sessionId: connectionSessionId });
|
|
1997
2070
|
return {
|
|
1998
2071
|
response: toolResponse({
|
|
1999
2072
|
content: [{ type: 'text', text: result.summary }],
|
|
@@ -166,7 +166,7 @@ const PROFILES = {
|
|
|
166
166
|
// giving Codex the same session-lifecycle wiring as Claude Code.
|
|
167
167
|
hasMcp: true, hasHooks: true, hasAutoApprove: false, hasSkills: true, hasRules: true,
|
|
168
168
|
instructionFile: 'AGENTS.md', sharedInstructionFile: true, mcpConfigScope: 'machine', templateTier: 'A',
|
|
169
|
-
role_capabilities: ['execute', 'review'],
|
|
169
|
+
role_capabilities: ['execute', 'review', 'consult'],
|
|
170
170
|
runtime: { mcp_direct: true, hooks: true, canBeSpawnedCli: true, canSpawnOtherCli: false, inbox: true },
|
|
171
171
|
max_concurrent_tasks: 5,
|
|
172
172
|
// pln#475: prefer stdin_pipe to avoid Windows cmd.exe arg-parsing breaking
|