@miraland-labs/conduit-bridge 0.16.113 → 0.16.114
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/dist/cli.js +9 -7
- package/dist/investigation.js +105 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -655,13 +655,6 @@ async function runner() {
|
|
|
655
655
|
await heartbeat(client, config, workspace, currentBrief, preflight, intentProof, bootstrapResult);
|
|
656
656
|
}
|
|
657
657
|
await renewLeases(client, config);
|
|
658
|
-
if (workspace && onlineDriverIds(config).length) {
|
|
659
|
-
// Observation runs before authoring: an owner waiting on an answer should not queue behind
|
|
660
|
-
// a long delivery, and the control plane already counted this slot as busy.
|
|
661
|
-
progressed = await executeNextInvestigation(client, config, workspace, brief, timeoutMs, {
|
|
662
|
-
managedRoot: hb.on_shift?.managed_workspace_root ?? null,
|
|
663
|
-
}) || progressed;
|
|
664
|
-
}
|
|
665
658
|
if (workspace && onlineDriverIds(config).length) {
|
|
666
659
|
// Read the workspace again, then publish it. The last brief stands if the read fails.
|
|
667
660
|
const publishWorkspaceState = async (options = {}) => {
|
|
@@ -671,6 +664,15 @@ async function runner() {
|
|
|
671
664
|
const hb = await heartbeat(client, config, workspace, currentBrief, preflight, undefined, bootstrapResult);
|
|
672
665
|
return { preflight, hb };
|
|
673
666
|
};
|
|
667
|
+
// Observation runs before authoring: an owner waiting on an answer should not queue behind
|
|
668
|
+
// a long delivery, and the control plane already counted this slot as busy.
|
|
669
|
+
progressed = await executeNextInvestigation(client, config, workspace, brief, timeoutMs, {
|
|
670
|
+
managedRoot: hb.on_shift?.managed_workspace_root ?? null,
|
|
671
|
+
heartbeat: async () => {
|
|
672
|
+
await publishWorkspaceState();
|
|
673
|
+
},
|
|
674
|
+
heartbeatIntervalMs: intervalMs,
|
|
675
|
+
}) || progressed;
|
|
674
676
|
progressed = await pumpExecutionSlots(client, config, workspace, brief, timeoutMs, {
|
|
675
677
|
heartbeat: async () => {
|
|
676
678
|
await publishWorkspaceState();
|
package/dist/investigation.js
CHANGED
|
@@ -100,6 +100,8 @@ export const DELIVERY_VERIFICATION_HEADER = "DELIVERY VERIFICATION";
|
|
|
100
100
|
export const DELIVERY_VERIFICATION_PROBE_INSTRUCTION = 'For every BRIEF KEY that states behaviour, add one entry to probes: either {"key":"[sN]","test":"<registered command that asserts it>","checks":"<one line: what it proves>"} or {"key":"[sN]","lang":"ts|py|sh","source":"<a short script that exits 0 only when the key holds at this commit and non-zero otherwise; it may import repository modules by absolute path from the working directory>","checks":"<one line>"}. You do not run probes; Bridge runs them after you finish. Keys that state no behaviour (wording, scope, docs) get no probe.';
|
|
101
101
|
const BRIEF_JSON_EXAMPLE = '{"summary":"one paragraph: what the change does and whether it meets the criteria","findings":["grounded fact with file/symbol"],"verification":["command — result"],"limitations":["what you could not settle"],"criteria":[{"criterion":"c1","assessment":"supported|unsupported|unclear","reason":"what you saw"}],"command_failures":["command that exited non-zero — the decisive output line"],"files_read":0,"bytes_read":0,"commands_run":["command you actually ran"]}';
|
|
102
102
|
const BRIEF_JSON_EXAMPLE_WITH_PROBES = '{"summary":"one paragraph: what the change does and whether it meets the criteria","findings":["grounded fact with file/symbol"],"verification":["command — result"],"limitations":["what you could not settle"],"criteria":[{"criterion":"c1","assessment":"supported|unsupported|unclear","reason":"what you saw"}],"command_failures":["command that exited non-zero — the decisive output line"],"probes":[],"files_read":0,"bytes_read":0,"commands_run":["command you actually ran"]}';
|
|
103
|
+
/** Named in both prompts so the lane knows each list's maximum before it writes. */
|
|
104
|
+
export const BRIEF_LIMITS_SENTENCE = "Limits: findings at most 12, verification at most 8, limitations at most 8, criteria at most 40, command_failures at most 10, commands_run at most 10, probes at most 20. Keep the most decisive entries.";
|
|
103
105
|
export function isDeliveryVerification(assignment) {
|
|
104
106
|
return assignment.question.trimStart().startsWith(DELIVERY_VERIFICATION_HEADER);
|
|
105
107
|
}
|
|
@@ -121,6 +123,7 @@ export function buildInvestigationPrompt(assignment, commit, context) {
|
|
|
121
123
|
"",
|
|
122
124
|
"Return only one fenced ```json object with exactly this shape:",
|
|
123
125
|
'{"summary":"direct answer to the question","findings":["grounded fact with file/symbol"],"verification":["command you actually ran"],"limitations":["what you could not settle"],"files_read":0,"bytes_read":0,"commands_run":["command you actually ran"]}',
|
|
126
|
+
BRIEF_LIMITS_SENTENCE,
|
|
124
127
|
"summary is a string; the other list fields are arrays of strings. Close the fence and add no prose after it.",
|
|
125
128
|
].join("\n");
|
|
126
129
|
}
|
|
@@ -164,6 +167,7 @@ export function buildDeliveryVerificationPrompt(assignment, commit, context) {
|
|
|
164
167
|
"",
|
|
165
168
|
"Return only one fenced ```json object with exactly this shape:",
|
|
166
169
|
keys.length ? BRIEF_JSON_EXAMPLE_WITH_PROBES : BRIEF_JSON_EXAMPLE,
|
|
170
|
+
BRIEF_LIMITS_SENTENCE,
|
|
167
171
|
keys.length
|
|
168
172
|
? "criteria has one entry per ACCEPTANCE key. probes has at most 20 entries, one per BRIEF KEY that states behaviour. Close the fence and add no prose after it."
|
|
169
173
|
: "criteria has one entry per ACCEPTANCE key. Close the fence and add no prose after it.",
|
|
@@ -208,6 +212,83 @@ function settleCommandsRun(gates, reported) {
|
|
|
208
212
|
export const UNUSABLE_BRIEF = "Investigation returned no usable brief";
|
|
209
213
|
/** T130: how much of an unusable reply travels with the failure, so the next run can be diagnosed. */
|
|
210
214
|
const UNUSABLE_BRIEF_TAIL_CHARS = 800;
|
|
215
|
+
const BRIEF_SUMMARY_MAX = 4_000;
|
|
216
|
+
const BRIEF_ITEM_MAX = 1_000;
|
|
217
|
+
const BRIEF_FILES_READ_MAX = 24;
|
|
218
|
+
const BRIEF_BYTES_READ_MAX = 512 * 1024;
|
|
219
|
+
const BRIEF_COMMAND_RUN_MAX = 200;
|
|
220
|
+
const BRIEF_LIST_MAX = {
|
|
221
|
+
findings: 12,
|
|
222
|
+
verification: 8,
|
|
223
|
+
limitations: 8,
|
|
224
|
+
criteria: 40,
|
|
225
|
+
command_failures: 10,
|
|
226
|
+
commands_run: 10,
|
|
227
|
+
probes: 20,
|
|
228
|
+
};
|
|
229
|
+
function clipBriefText(value, max) {
|
|
230
|
+
return value.length > max ? value.slice(0, max) : value;
|
|
231
|
+
}
|
|
232
|
+
function clipBriefStringField(record, key, max) {
|
|
233
|
+
const value = record[key];
|
|
234
|
+
if (typeof value === "string")
|
|
235
|
+
record[key] = clipBriefText(value, max);
|
|
236
|
+
}
|
|
237
|
+
function fitBriefStrings(items, max) {
|
|
238
|
+
return items.map((item) => typeof item === "string" ? clipBriefText(item, max) : item);
|
|
239
|
+
}
|
|
240
|
+
function fitCriteriaItems(items) {
|
|
241
|
+
return items.map((item) => {
|
|
242
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
243
|
+
return item;
|
|
244
|
+
const row = { ...item };
|
|
245
|
+
clipBriefStringField(row, "criterion", BRIEF_ITEM_MAX);
|
|
246
|
+
clipBriefStringField(row, "reason", BRIEF_ITEM_MAX);
|
|
247
|
+
return row;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Cut an over-full brief to the contract before schema parse. Only size is fitted; a wrong type,
|
|
252
|
+
* a missing summary, or an unknown assessment still fails as today. Probe entries are kept as
|
|
253
|
+
* written — clipping them would change what Bridge later executes.
|
|
254
|
+
*/
|
|
255
|
+
export function fitBriefToContract(candidate) {
|
|
256
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
257
|
+
return { fitted: candidate, cuts: [] };
|
|
258
|
+
}
|
|
259
|
+
const brief = { ...candidate };
|
|
260
|
+
const cuts = [];
|
|
261
|
+
const fitNamedList = (key, fitItems) => {
|
|
262
|
+
const value = brief[key];
|
|
263
|
+
if (!Array.isArray(value))
|
|
264
|
+
return;
|
|
265
|
+
const max = BRIEF_LIST_MAX[key];
|
|
266
|
+
if (value.length > max)
|
|
267
|
+
cuts.push({ list: key, kept: max, total: value.length });
|
|
268
|
+
brief[key] = fitItems(value.slice(0, max));
|
|
269
|
+
};
|
|
270
|
+
fitNamedList("findings", (items) => fitBriefStrings(items, BRIEF_ITEM_MAX));
|
|
271
|
+
fitNamedList("verification", (items) => fitBriefStrings(items, BRIEF_ITEM_MAX));
|
|
272
|
+
fitNamedList("limitations", (items) => fitBriefStrings(items, BRIEF_ITEM_MAX));
|
|
273
|
+
fitNamedList("criteria", fitCriteriaItems);
|
|
274
|
+
fitNamedList("command_failures", (items) => fitBriefStrings(items, BRIEF_ITEM_MAX));
|
|
275
|
+
fitNamedList("commands_run", (items) => fitBriefStrings(items, BRIEF_COMMAND_RUN_MAX));
|
|
276
|
+
fitNamedList("probes", (items) => items);
|
|
277
|
+
clipBriefStringField(brief, "summary", BRIEF_SUMMARY_MAX);
|
|
278
|
+
if (typeof brief.files_read === "number" && brief.files_read > BRIEF_FILES_READ_MAX) {
|
|
279
|
+
brief.files_read = BRIEF_FILES_READ_MAX;
|
|
280
|
+
}
|
|
281
|
+
if (typeof brief.bytes_read === "number" && brief.bytes_read > BRIEF_BYTES_READ_MAX) {
|
|
282
|
+
brief.bytes_read = BRIEF_BYTES_READ_MAX;
|
|
283
|
+
}
|
|
284
|
+
if (cuts.length) {
|
|
285
|
+
const notes = cuts.map((cut) => clipBriefText(`Bridge kept the first ${cut.kept} of ${cut.total} ${cut.list}`, BRIEF_ITEM_MAX));
|
|
286
|
+
const existing = Array.isArray(brief.limitations) ? [...brief.limitations] : [];
|
|
287
|
+
const room = Math.max(0, BRIEF_LIST_MAX.limitations - notes.length);
|
|
288
|
+
brief.limitations = [...existing.slice(0, room), ...notes];
|
|
289
|
+
}
|
|
290
|
+
return { fitted: brief, cuts };
|
|
291
|
+
}
|
|
211
292
|
/**
|
|
212
293
|
* T130: an unusable brief used to fail with five words. The first production verification retry
|
|
213
294
|
* (2026-09-09, acme-sim / forge R20) returned "no usable brief" twice and nobody could say whether the
|
|
@@ -222,7 +303,7 @@ export function parseInvestigationBrief(text) {
|
|
|
222
303
|
catch {
|
|
223
304
|
throw new Error(`${UNUSABLE_BRIEF}: no JSON object in the reply. Reply tail: ${redactSecrets(boundedTail(text, UNUSABLE_BRIEF_TAIL_CHARS))}`);
|
|
224
305
|
}
|
|
225
|
-
const parsed = investigationBriefSchema.safeParse(candidate);
|
|
306
|
+
const parsed = investigationBriefSchema.safeParse(fitBriefToContract(candidate).fitted);
|
|
226
307
|
if (!parsed.success) {
|
|
227
308
|
const issues = parsed.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
228
309
|
throw new Error(`${UNUSABLE_BRIEF}: the JSON does not match the brief contract (${issues}). Reply tail: ${redactSecrets(boundedTail(text, UNUSABLE_BRIEF_TAIL_CHARS))}`);
|
|
@@ -495,11 +576,12 @@ const deliveredWitnessSchema = z.object({
|
|
|
495
576
|
function stampInstrument(witnesses, instrument) {
|
|
496
577
|
return witnesses.map((witness) => ({ ...witness, instrument }));
|
|
497
578
|
}
|
|
498
|
-
function markSelfCheckBroken(witnesses) {
|
|
579
|
+
function markSelfCheckBroken(witnesses, reason) {
|
|
580
|
+
const tail = boundedTail(`${DELIVERED_PROBE_SELF_CHECK_TAIL}: ${reason}`, PROBE_TAIL_CHARS);
|
|
499
581
|
return stampInstrument(witnesses.map((witness) => ({
|
|
500
582
|
...witness,
|
|
501
583
|
verdict: "broken",
|
|
502
|
-
tail
|
|
584
|
+
tail,
|
|
503
585
|
})), "running");
|
|
504
586
|
}
|
|
505
587
|
function toStepWitness(witness) {
|
|
@@ -569,16 +651,18 @@ export async function runDeliveryVerificationProbesForAttempt(input) {
|
|
|
569
651
|
return stampInstrument(await runRunning(), "running");
|
|
570
652
|
}
|
|
571
653
|
const runDelivered = input.runDeliveredProbes ?? ((args) => runDeliveredProbeInstrument(args));
|
|
654
|
+
let selfCheckDetail = null;
|
|
572
655
|
try {
|
|
573
656
|
const check = await runDelivered({ workspace: input.workspace, probes: KNOWN_ANSWER_PROBES });
|
|
574
657
|
if (knownAnswerProbesPassed(check)) {
|
|
575
658
|
return stampInstrument(await runDelivered({ workspace: input.workspace, probes: input.probes }), "delivered");
|
|
576
659
|
}
|
|
660
|
+
selfCheckDetail = `gave ${check.map((witness) => witness.verdict).join(", ")}`;
|
|
577
661
|
}
|
|
578
|
-
catch {
|
|
579
|
-
|
|
662
|
+
catch (error) {
|
|
663
|
+
selfCheckDetail = redactSecrets(error instanceof Error ? error.message : String(error));
|
|
580
664
|
}
|
|
581
|
-
return markSelfCheckBroken(await runRunning());
|
|
665
|
+
return markSelfCheckBroken(await runRunning(), selfCheckDetail ?? "");
|
|
582
666
|
}
|
|
583
667
|
async function settle(client, investigationId, body) {
|
|
584
668
|
await client.request(`/runner/v1/investigations/${investigationId}/settle`, {
|
|
@@ -675,6 +759,19 @@ export async function executeNextInvestigation(client, config, workspace, brief,
|
|
|
675
759
|
}).catch((error) => console.error(`Investigation ${assignment.id} lease renewal failed: ${redactSecrets(error instanceof Error ? error.message : "unknown")}`));
|
|
676
760
|
}, options.renewIntervalMs ?? INVESTIGATION_RENEW_INTERVAL_MS);
|
|
677
761
|
renewTimer.unref?.();
|
|
762
|
+
let heartbeatRunning = false;
|
|
763
|
+
const heartbeat = options.heartbeat;
|
|
764
|
+
const heartbeatTimer = heartbeat
|
|
765
|
+
? setInterval(() => {
|
|
766
|
+
if (heartbeatRunning)
|
|
767
|
+
return;
|
|
768
|
+
heartbeatRunning = true;
|
|
769
|
+
void heartbeat()
|
|
770
|
+
.catch((error) => console.error(`Heartbeat failed during investigation: ${redactSecrets(error instanceof Error ? error.message : "unknown")}`))
|
|
771
|
+
.finally(() => { heartbeatRunning = false; });
|
|
772
|
+
}, options.heartbeatIntervalMs ?? 15_000)
|
|
773
|
+
: null;
|
|
774
|
+
heartbeatTimer?.unref?.();
|
|
678
775
|
try {
|
|
679
776
|
attemptWorkspace = await createAttemptWorktree({
|
|
680
777
|
sourceWorkspace: workspace,
|
|
@@ -795,6 +892,8 @@ export async function executeNextInvestigation(client, config, workspace, brief,
|
|
|
795
892
|
}
|
|
796
893
|
finally {
|
|
797
894
|
clearInterval(renewTimer);
|
|
895
|
+
if (heartbeatTimer)
|
|
896
|
+
clearInterval(heartbeatTimer);
|
|
798
897
|
if (attemptWorkspace) {
|
|
799
898
|
await removeAttemptWorktree(workspace, attemptWorkspace)
|
|
800
899
|
.catch((error) => console.error(`Investigation worktree release failed: ${redactSecrets(error instanceof Error ? error.message : "unknown")}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miraland-labs/conduit-bridge",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.114",
|
|
4
4
|
"description": "Conduit Bridge CLI \u2014 join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity / Grok Build agents for a Conduit organization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|