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
|
@@ -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).
|
|
@@ -1024,7 +1030,10 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1024
1030
|
// no ids, so a harvest could only match reviewer slots by agent name —
|
|
1025
1031
|
// which completes the WRONG slot in symmetric (multi-reviewer) mode.
|
|
1026
1032
|
try {
|
|
1027
|
-
|
|
1033
|
+
// One claim owns one reviewer slot. A loop-wide claim cannot safely
|
|
1034
|
+
// represent parallel assignments because Claim has one assignment_id
|
|
1035
|
+
// projection; keep the parseable loop prefix and add slot identity.
|
|
1036
|
+
const reviewScope = `review-loop:${loop.id}:slot:${slot.slot_id}`;
|
|
1028
1037
|
const reviewDescription = `Review loop turn for ${loop.id} slot ${slot.slot_id} phase findings. `
|
|
1029
1038
|
+ `Mode: ${advanced.loop.protocol?.review_mode ?? 'asymmetric'}. ${req.task}`;
|
|
1030
1039
|
const claimResult = createCoordinatorClaim({
|
|
@@ -1046,6 +1055,8 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1046
1055
|
});
|
|
1047
1056
|
let reviewAssignmentId;
|
|
1048
1057
|
let reviewTurnEcho;
|
|
1058
|
+
let reviewExecutionContractRef;
|
|
1059
|
+
let reviewWorktreePath = claimResult.worktreePath;
|
|
1049
1060
|
// pln#630 — turn-own the INITIAL reviewer dispatch (same default + kill-switch as the
|
|
1050
1061
|
// fix cycle). Skipped for cross-project reviews (no local worktree/sentinel → they never
|
|
1051
1062
|
// spawn here). WON: prepare minted the DETERMINISTIC assignment + run + turn()-bound the
|
|
@@ -1070,12 +1081,25 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1070
1081
|
dispatcherAgent: senderAgent,
|
|
1071
1082
|
dispatcherAgentId: senderAgentId,
|
|
1072
1083
|
sessionId: connectionSessionId,
|
|
1084
|
+
model: resolveModel(slot.agent ?? '', { override: req.model }),
|
|
1073
1085
|
isReviewer: true,
|
|
1074
1086
|
cwd: dispatchCwd,
|
|
1075
1087
|
});
|
|
1076
1088
|
if (prep.kind === 'won') {
|
|
1077
1089
|
reviewAssignmentId = prep.assignmentId; // deterministic — harvest correlates on it
|
|
1078
|
-
|
|
1090
|
+
reviewExecutionContractRef = prep.executionContractRef;
|
|
1091
|
+
reviewTurnEcho = {
|
|
1092
|
+
turn_id: prep.turnId,
|
|
1093
|
+
run_id: prep.runId,
|
|
1094
|
+
nonce: prep.nonce,
|
|
1095
|
+
...(prep.executionContractRef ? {
|
|
1096
|
+
contract_hash: prep.executionContractRef.hash,
|
|
1097
|
+
capability_snapshot_hash: prep.executionContractRef.snapshot_hash,
|
|
1098
|
+
} : {}),
|
|
1099
|
+
...(prep.attemptEpoch !== undefined ? { attempt_epoch: prep.attemptEpoch } : {}),
|
|
1100
|
+
...(prep.workspaceDigest ? { workspace_digest: prep.workspaceDigest } : {}),
|
|
1101
|
+
};
|
|
1102
|
+
reviewWorktreePath = prep.workspacePath;
|
|
1079
1103
|
out.artifacts.push({ type: 'assignment', id: prep.assignmentId });
|
|
1080
1104
|
usedTurnOwned = true; // prepare already created the assignment + run + bound the slot
|
|
1081
1105
|
}
|
|
@@ -1124,8 +1148,16 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1124
1148
|
const reviewBrief = buildCoordinateBrief(slot.agent ?? '', reviewDescription + reviewVerdictBriefSuffix, {
|
|
1125
1149
|
claimId: claimResult.claimId,
|
|
1126
1150
|
scope: reviewScope,
|
|
1127
|
-
worktreePath:
|
|
1151
|
+
worktreePath: reviewWorktreePath,
|
|
1128
1152
|
assignmentId: reviewAssignmentId,
|
|
1153
|
+
executionContractRef: reviewExecutionContractRef,
|
|
1154
|
+
attemptFence: reviewTurnEcho?.attempt_epoch !== undefined && reviewTurnEcho.workspace_digest ? {
|
|
1155
|
+
turn_id: reviewTurnEcho.turn_id,
|
|
1156
|
+
run_id: reviewTurnEcho.run_id,
|
|
1157
|
+
nonce: reviewTurnEcho.nonce,
|
|
1158
|
+
attempt_epoch: reviewTurnEcho.attempt_epoch,
|
|
1159
|
+
workspace_digest: reviewTurnEcho.workspace_digest,
|
|
1160
|
+
} : undefined,
|
|
1129
1161
|
});
|
|
1130
1162
|
const queued = queueCoordinateMessage({
|
|
1131
1163
|
agent: slot.agent ?? '',
|
|
@@ -1145,7 +1177,7 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1145
1177
|
scope: reviewScope,
|
|
1146
1178
|
claim_id: claimResult.claimId,
|
|
1147
1179
|
...(reviewAssignmentId ? { assignment_id: reviewAssignmentId } : {}),
|
|
1148
|
-
worktree_path:
|
|
1180
|
+
worktree_path: reviewWorktreePath,
|
|
1149
1181
|
},
|
|
1150
1182
|
commandMode: 'worker',
|
|
1151
1183
|
});
|
|
@@ -1164,7 +1196,7 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1164
1196
|
out.preparedReviews.push({
|
|
1165
1197
|
entry: queued.entry,
|
|
1166
1198
|
invoke: queued.invoke,
|
|
1167
|
-
worktreePath:
|
|
1199
|
+
worktreePath: reviewWorktreePath,
|
|
1168
1200
|
turnEcho: reviewTurnEcho,
|
|
1169
1201
|
});
|
|
1170
1202
|
}
|
|
@@ -1441,7 +1473,7 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1441
1473
|
// pln#513 step 2 — labelled block (ideate:) so the bootstrap
|
|
1442
1474
|
// join-or-lock path can break out early after assigning result.
|
|
1443
1475
|
const loopsModuleRef = await import('../core/loops/index.js');
|
|
1444
|
-
const { openLoop, add_artifact, advance,
|
|
1476
|
+
const { openLoop, add_artifact, advance, getLoop, buildIdeationBrief } = loopsModuleRef;
|
|
1445
1477
|
const presetSelected = req.preset
|
|
1446
1478
|
? (await import('../core/loops/presets/index.js')).PRESETS[req.preset]
|
|
1447
1479
|
: undefined;
|
|
@@ -1708,55 +1740,86 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1708
1740
|
entity: 'claim',
|
|
1709
1741
|
id: claimResult.claimId,
|
|
1710
1742
|
});
|
|
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,
|
|
1743
|
+
// P0C / dec#171 — ideation crosses the same attempt fence as every
|
|
1744
|
+
// worker-backed LoopKind phase. Assignment, AgentRun, claim binding
|
|
1745
|
+
// and slot binding are all durable before the irreversible crossing.
|
|
1746
|
+
const criticModel = resolveModel(slot.agent, { override: req.model });
|
|
1747
|
+
const criticHarnessBinding = resolveHarnessBinding(slot.agent, criticModel);
|
|
1748
|
+
const attempt = prepareTurnExecution({
|
|
1749
|
+
kind: 'ideation',
|
|
1750
|
+
loop_id: loopId,
|
|
1742
1751
|
slot_id: slot.slot_id,
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1752
|
+
phase: advancedLoop.current_phase,
|
|
1753
|
+
agent: slot.agent,
|
|
1754
|
+
agent_id: slot.agent_id,
|
|
1746
1755
|
claim_id: claimResult.claimId,
|
|
1747
|
-
|
|
1756
|
+
dispatcher_agent: senderAgent,
|
|
1757
|
+
dispatcher_agent_id: senderAgentId,
|
|
1758
|
+
dispatcher_session_id: connectionSessionId,
|
|
1759
|
+
scope: criticScope,
|
|
1760
|
+
description: criticDescription,
|
|
1761
|
+
task: briefResult.text,
|
|
1762
|
+
cwd: dispatchCwd,
|
|
1763
|
+
worktree_path: claimResult.worktreePath,
|
|
1764
|
+
model: criticModel,
|
|
1765
|
+
harness_binding: criticHarnessBinding,
|
|
1766
|
+
assignment_tags: ['coordinate', 'ideate', 'loop', 'turn-owned'],
|
|
1767
|
+
run_tags: ['turn-owned', 'ideate', 'loop'],
|
|
1768
|
+
});
|
|
1769
|
+
if (attempt.kind !== 'won') {
|
|
1770
|
+
warnings.push(`ideate attempt denied for slot ${slot.slot_id}: ${attempt.reason}; no worker spawned`);
|
|
1771
|
+
// A pre-identity refusal or an authority owned by another claim
|
|
1772
|
+
// cannot use this freshly created lane claim. Release only claims
|
|
1773
|
+
// created by THIS call; a reused claim may belong to a concurrent
|
|
1774
|
+
// winner and must remain intact. Repairable/same-claim authority is
|
|
1775
|
+
// also retained so projections can be replayed safely.
|
|
1776
|
+
if (attempt.claim_disposition === 'release' && !claimResult.reusedExisting) {
|
|
1777
|
+
try {
|
|
1778
|
+
const released = releaseClaimIfActive(claimResult.claimId, dispatchCwd);
|
|
1779
|
+
if (released.released) {
|
|
1780
|
+
side_effects.push({ action: 'release', entity: 'claim', id: claimResult.claimId });
|
|
1781
|
+
if (claimResult.worktreePath) {
|
|
1782
|
+
try {
|
|
1783
|
+
removeWorktree(dispatchCwd, claimResult.worktreePath, { force: true });
|
|
1784
|
+
}
|
|
1785
|
+
catch (cleanupError) {
|
|
1786
|
+
warnings.push(`ideate denied-claim worktree cleanup failed for ${claimResult.claimId}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
catch (cleanupError) {
|
|
1792
|
+
warnings.push(`ideate denied-claim cleanup failed for ${claimResult.claimId}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
continue;
|
|
1796
|
+
}
|
|
1797
|
+
const criticAssignmentId = attempt.assignment_id;
|
|
1798
|
+
const criticTurnEcho = {
|
|
1799
|
+
turn_id: attempt.turn_id,
|
|
1800
|
+
run_id: attempt.run_id,
|
|
1801
|
+
nonce: attempt.nonce,
|
|
1802
|
+
...(attempt.execution_contract_ref ? {
|
|
1803
|
+
contract_hash: attempt.execution_contract_ref.hash,
|
|
1804
|
+
capability_snapshot_hash: attempt.execution_contract_ref.snapshot_hash,
|
|
1805
|
+
} : {}),
|
|
1806
|
+
...(attempt.attempt_epoch !== undefined ? { attempt_epoch: attempt.attempt_epoch } : {}),
|
|
1807
|
+
...(attempt.workspace_digest ? { workspace_digest: attempt.workspace_digest } : {}),
|
|
1808
|
+
};
|
|
1809
|
+
artifacts.push({ type: 'assignment', id: criticAssignmentId });
|
|
1748
1810
|
// pln#626 Phase 2 — the critique-only contract must reach the
|
|
1749
1811
|
// DELIVERED brief, not just the claim record: buildCoordinateBrief
|
|
1750
1812
|
// wraps this in a worker envelope, so prepend the constraint + the
|
|
1751
1813
|
// reply path (MCP complete_turn, or LANE-RESULT.json for a sandboxed
|
|
1752
1814
|
// critic without brainclaw MCP) ahead of the ideation brief body.
|
|
1753
1815
|
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
|
|
1816
|
+
+ `call bclaw_loop(intent='complete_turn') if you have brainclaw MCP, otherwise write LANE-RESULT.json in your worktree root with `
|
|
1817
|
+
+ `"status":"completed", "artifact_type":"critique", and the full critique in "body" (the coordinator harvests it).\n\n`
|
|
1755
1818
|
+ briefResult.text;
|
|
1756
1819
|
const criticBrief = buildCoordinateBrief(slot.agent, criticTaskText, {
|
|
1757
1820
|
claimId: claimResult.claimId,
|
|
1758
1821
|
scope: criticScope,
|
|
1759
|
-
worktreePath:
|
|
1822
|
+
worktreePath: attempt.workspace_path,
|
|
1760
1823
|
assignmentId: criticAssignmentId,
|
|
1761
1824
|
// The ideation brief above already inlines a BM25-selected,
|
|
1762
1825
|
// budget-managed memory bundle (with its own truncation warning).
|
|
@@ -1764,6 +1827,14 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1764
1827
|
// and break the documented ~48K content cap + dispatch-envelope
|
|
1765
1828
|
// math pinned by ideation-loop-e2e.
|
|
1766
1829
|
contextEnvelope: false,
|
|
1830
|
+
executionContractRef: attempt.execution_contract_ref,
|
|
1831
|
+
attemptFence: attempt.attempt_epoch !== undefined && attempt.workspace_digest ? {
|
|
1832
|
+
turn_id: attempt.turn_id,
|
|
1833
|
+
run_id: attempt.run_id,
|
|
1834
|
+
nonce: attempt.nonce,
|
|
1835
|
+
attempt_epoch: attempt.attempt_epoch,
|
|
1836
|
+
workspace_digest: attempt.workspace_digest,
|
|
1837
|
+
} : undefined,
|
|
1767
1838
|
});
|
|
1768
1839
|
const queued = queueCoordinateMessage({
|
|
1769
1840
|
agent: slot.agent,
|
|
@@ -1783,9 +1854,10 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1783
1854
|
iteration: advancedLoop.iteration_count,
|
|
1784
1855
|
proposal_artifact_id: proposalArtifactId,
|
|
1785
1856
|
...(criticAssignmentId ? { assignment_id: criticAssignmentId } : {}),
|
|
1786
|
-
worktree_path:
|
|
1857
|
+
worktree_path: attempt.workspace_path,
|
|
1787
1858
|
},
|
|
1788
1859
|
commandMode: 'worker',
|
|
1860
|
+
harnessBinding: criticHarnessBinding,
|
|
1789
1861
|
});
|
|
1790
1862
|
if (criticAssignmentId) {
|
|
1791
1863
|
try {
|
|
@@ -1802,7 +1874,8 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1802
1874
|
preparedCritics.push({
|
|
1803
1875
|
entry: queued.entry,
|
|
1804
1876
|
invoke: queued.invoke,
|
|
1805
|
-
worktreePath:
|
|
1877
|
+
worktreePath: attempt.workspace_path,
|
|
1878
|
+
turnEcho: criticTurnEcho,
|
|
1806
1879
|
});
|
|
1807
1880
|
dispatchedCritics += 1;
|
|
1808
1881
|
}
|
|
@@ -1966,34 +2039,31 @@ export async function handleBclawCoordinate(args, ctx) {
|
|
|
1966
2039
|
}
|
|
1967
2040
|
export async function handleBclawLoop(args, ctx) {
|
|
1968
2041
|
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);
|
|
2042
|
+
// Direct open is public for implementation/research/debug orchestration.
|
|
2043
|
+
// The facade schema requires allow_orphan=true, making the caller explicitly
|
|
2044
|
+
// own the subsequent bind/turn/dispatch. Review and ideation still have the
|
|
2045
|
+
// higher-level coordinate shortcuts that open and dispatch together.
|
|
2046
|
+
// `turn(dispatch=true)` is now a REAL generic spawn path (claim + immutable
|
|
2047
|
+
// attempt + inbox + execution adapter). Plain turn remains a pure Loop Engine
|
|
2048
|
+
// mutation. `bind` is also engine-only; takeover and real turn dispatch are
|
|
2049
|
+
// the two trusted authority-changing paths here.
|
|
2050
|
+
const targetCwd = resolveProjectCwd(args?.project, cwd);
|
|
2051
|
+
let effectiveArgs = args;
|
|
2052
|
+
if (args?.intent === 'takeover' || (args?.intent === 'turn' && args?.dispatch === true)) {
|
|
2053
|
+
const resolved = ensureTrust(args, { nameField: 'agent', idField: 'agentId' }, 'trusted', targetCwd, connectionSessionId);
|
|
1990
2054
|
if (resolved.error) {
|
|
1991
2055
|
return { response: createToolErrorResponse(resolved.error.kind, resolved.error.message, resolved.error.details) };
|
|
1992
2056
|
}
|
|
2057
|
+
if ((args.intent === 'takeover' || args.intent === 'turn') && resolved.identity) {
|
|
2058
|
+
effectiveArgs = {
|
|
2059
|
+
...args,
|
|
2060
|
+
agent: resolved.identity.agent_name,
|
|
2061
|
+
agentId: resolved.identity.agent_id,
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
1993
2064
|
}
|
|
1994
2065
|
const { handleBclawLoop: runLoopIntent } = await import('./loops-handlers.js');
|
|
1995
|
-
const
|
|
1996
|
-
const result = await runLoopIntent({ args: args, cwd: targetCwd, sessionId: connectionSessionId });
|
|
2066
|
+
const result = await runLoopIntent({ args: effectiveArgs, cwd: targetCwd, sessionId: connectionSessionId });
|
|
1997
2067
|
return {
|
|
1998
2068
|
response: toolResponse({
|
|
1999
2069
|
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
|