@miraland-labs/conduit-bridge 0.16.24 → 0.16.25
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/client.js +24 -0
- package/dist/execution.js +108 -2
- package/dist/workspace-bootstrap.js +56 -9
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -80,6 +80,30 @@ export class ConduitClient {
|
|
|
80
80
|
const active = this.attempt(taskId);
|
|
81
81
|
return this.request(`/runner/v1/tasks/${taskId}/${action}`, { method: "POST", body: JSON.stringify({ ...body, attempt_id: active.attemptId, lease_token: active.leaseToken }) });
|
|
82
82
|
}
|
|
83
|
+
async listInstructions(taskId, after = "") {
|
|
84
|
+
const active = this.attempt(taskId);
|
|
85
|
+
const query = new URLSearchParams({ attempt_id: active.attemptId, ...(after ? { after } : {}) });
|
|
86
|
+
const data = await this.request(`/runner/v1/tasks/${taskId}/instructions?${query}`);
|
|
87
|
+
return Array.isArray(data.instructions)
|
|
88
|
+
? data.instructions.flatMap((row) => {
|
|
89
|
+
if (!row || typeof row !== "object")
|
|
90
|
+
return [];
|
|
91
|
+
const instruction = row;
|
|
92
|
+
return typeof instruction.id === "string"
|
|
93
|
+
&& typeof instruction.payload_json === "string"
|
|
94
|
+
&& typeof instruction.created_at === "string"
|
|
95
|
+
? [{ id: instruction.id, payload_json: instruction.payload_json, created_at: instruction.created_at }]
|
|
96
|
+
: [];
|
|
97
|
+
})
|
|
98
|
+
: [];
|
|
99
|
+
}
|
|
100
|
+
async acknowledgeInstruction(taskId, instructionId) {
|
|
101
|
+
const active = this.attempt(taskId);
|
|
102
|
+
await this.attemptRequest(taskId, `instructions/${instructionId}/ack`, {
|
|
103
|
+
instruction_id: instructionId,
|
|
104
|
+
idempotency_key: `bridge:instruction-ack:${active.attemptId}:${instructionId}`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
83
107
|
async updateAttempt(taskId, patch) {
|
|
84
108
|
const active = this.attempt(taskId);
|
|
85
109
|
for (const key of Object.keys(patch)) {
|
package/dist/execution.js
CHANGED
|
@@ -411,6 +411,79 @@ export async function renewLeases(client, config) {
|
|
|
411
411
|
}
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Poll while the headless CLI owns the worktree, then hand owner guidance to its next session turn.
|
|
416
|
+
* Driver stdin is intentionally closed after the initial prompt, so pretending a mid-process write
|
|
417
|
+
* is portable would silently lose guidance on most lanes. The first safe common checkpoint is the
|
|
418
|
+
* driver's completed turn, before Bridge validates or submits its delivery.
|
|
419
|
+
*/
|
|
420
|
+
function pollCheckpointInstructions(client, taskId, state, intervalMs = 15_000) {
|
|
421
|
+
let stopped = false;
|
|
422
|
+
let inFlight = null;
|
|
423
|
+
const pending = new Map();
|
|
424
|
+
const poll = async () => {
|
|
425
|
+
const rows = await client.listInstructions(taskId, state.cursor);
|
|
426
|
+
for (const row of rows) {
|
|
427
|
+
state.cursor = `${row.created_at}|${row.id}`;
|
|
428
|
+
if (state.seen.has(row.id))
|
|
429
|
+
continue;
|
|
430
|
+
state.seen.add(row.id);
|
|
431
|
+
const parsed = parseCheckpointInstruction(row);
|
|
432
|
+
if (parsed && state.acceptedCount < 10 && state.acceptedCharacters + parsed.message.length <= 40_000) {
|
|
433
|
+
pending.set(parsed.id, parsed);
|
|
434
|
+
state.acceptedCount += 1;
|
|
435
|
+
state.acceptedCharacters += parsed.message.length;
|
|
436
|
+
}
|
|
437
|
+
else if (parsed) {
|
|
438
|
+
console.error(`Instruction ${parsed.id} exceeded the checkpoint guidance bound and was not acknowledged.`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
const runPoll = () => {
|
|
443
|
+
if (stopped || inFlight)
|
|
444
|
+
return;
|
|
445
|
+
inFlight = poll()
|
|
446
|
+
.catch((error) => console.error(`Instruction poll failed: ${redactSecrets(error instanceof Error ? error.message : "unknown")}`))
|
|
447
|
+
.finally(() => { inFlight = null; });
|
|
448
|
+
};
|
|
449
|
+
runPoll();
|
|
450
|
+
const timer = setInterval(runPoll, intervalMs);
|
|
451
|
+
timer.unref?.();
|
|
452
|
+
return {
|
|
453
|
+
stop: async () => {
|
|
454
|
+
stopped = true;
|
|
455
|
+
clearInterval(timer);
|
|
456
|
+
await inFlight;
|
|
457
|
+
await poll().catch((error) => console.error(`Final instruction poll failed: ${redactSecrets(error instanceof Error ? error.message : "unknown")}`));
|
|
458
|
+
return [...pending.values()];
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function parseCheckpointInstruction(row) {
|
|
463
|
+
try {
|
|
464
|
+
const payload = JSON.parse(row.payload_json);
|
|
465
|
+
if (payload.changes_scope === true) {
|
|
466
|
+
console.error(`Refusing scope-changing instruction ${row.id}; revise the approved initiative contract instead.`);
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
const message = typeof payload.message === "string" ? payload.message.trim() : "";
|
|
470
|
+
return message ? { id: row.id, message: message.slice(0, 20_000), createdAt: row.created_at } : null;
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function checkpointInstructionPrompt(instructions) {
|
|
477
|
+
return [
|
|
478
|
+
"OWNER INSTRUCTIONS AT CHECKPOINT",
|
|
479
|
+
"These messages refine the current run only. They do not amend approved scope, boundaries, acceptance criteria, or grants.",
|
|
480
|
+
...instructions.map((instruction) => `- ${instruction.message}`),
|
|
481
|
+
"",
|
|
482
|
+
"Apply the guidance within the approved contract, re-check the resulting work, then return a complete final report.",
|
|
483
|
+
"Return only one fenced ```json object with exactly this shape:",
|
|
484
|
+
agentReportTemplate,
|
|
485
|
+
].join("\n");
|
|
486
|
+
}
|
|
414
487
|
function resolveAttemptDriver(config, active, fallback) {
|
|
415
488
|
if (active.driverId && DRIVERS[active.driverId])
|
|
416
489
|
return DRIVERS[active.driverId];
|
|
@@ -983,7 +1056,7 @@ async function runClaimedAssignment(client, config, driver, workspace, brief, ta
|
|
|
983
1056
|
// turn already "finished" makes the agent reply conversationally without the report block.
|
|
984
1057
|
const resumeSessionId = options.forceResumeSessionId
|
|
985
1058
|
?? (reworkFeedback && task.repair_mode !== "briefed" ? config.sessions?.[taskId] : undefined);
|
|
986
|
-
const
|
|
1059
|
+
const runInput = {
|
|
987
1060
|
prompt,
|
|
988
1061
|
workspace: attemptWorkspace,
|
|
989
1062
|
grants,
|
|
@@ -997,7 +1070,40 @@ async function runClaimedAssignment(client, config, driver, workspace, brief, ta
|
|
|
997
1070
|
model: selection.model,
|
|
998
1071
|
fuel,
|
|
999
1072
|
fuelSource,
|
|
1000
|
-
}
|
|
1073
|
+
};
|
|
1074
|
+
let result;
|
|
1075
|
+
let currentRunInput = runInput;
|
|
1076
|
+
const instructionPollState = {
|
|
1077
|
+
cursor: "",
|
|
1078
|
+
seen: new Set(),
|
|
1079
|
+
acceptedCount: 0,
|
|
1080
|
+
acceptedCharacters: 0,
|
|
1081
|
+
};
|
|
1082
|
+
let instructionsInPrompt = [];
|
|
1083
|
+
for (let round = 0;; round += 1) {
|
|
1084
|
+
const instructionPoll = diagnosis ? null : pollCheckpointInstructions(client, taskId, instructionPollState);
|
|
1085
|
+
let polled = [];
|
|
1086
|
+
try {
|
|
1087
|
+
result = await driver.run(currentRunInput);
|
|
1088
|
+
}
|
|
1089
|
+
finally {
|
|
1090
|
+
polled = await instructionPoll?.stop() ?? polled;
|
|
1091
|
+
}
|
|
1092
|
+
if (instructionsInPrompt.length > 0) {
|
|
1093
|
+
await Promise.all(instructionsInPrompt.map((instruction) => client.acknowledgeInstruction(taskId, instruction.id).catch((error) => console.error(`Instruction acknowledgement failed: ${redactSecrets(error instanceof Error ? error.message : "unknown")}`))));
|
|
1094
|
+
}
|
|
1095
|
+
// One initial turn plus at most two bounded checkpoint turns. Guidance arriving after that
|
|
1096
|
+
// remains unacknowledged rather than creating an unbounded owner-driven agent loop.
|
|
1097
|
+
if (diagnosis || result.status !== "completed" || polled.length === 0 || round >= 2)
|
|
1098
|
+
break;
|
|
1099
|
+
instructionsInPrompt = polled;
|
|
1100
|
+
const followup = checkpointInstructionPrompt(instructionsInPrompt);
|
|
1101
|
+
currentRunInput = {
|
|
1102
|
+
...runInput,
|
|
1103
|
+
prompt: result.sessionId ? followup : `${prompt}\n\n${followup}`,
|
|
1104
|
+
resumeSessionId: result.sessionId ?? undefined,
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1001
1107
|
if (result.sessionId)
|
|
1002
1108
|
config.sessions = { ...config.sessions, [taskId]: result.sessionId };
|
|
1003
1109
|
await learnDriverFuel(config, driver.name, fuelSource, result);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
function defaultRunner(command, args, workspace) {
|
|
5
5
|
const result = spawnSync(command, args, {
|
|
@@ -10,17 +10,64 @@ function defaultRunner(command, args, workspace) {
|
|
|
10
10
|
});
|
|
11
11
|
return { status: result.status, ...(result.error ? { error: result.error } : {}) };
|
|
12
12
|
}
|
|
13
|
-
/** Install
|
|
13
|
+
/** Install dependencies with the repository's own committed package-manager identity. */
|
|
14
14
|
export function bootstrapManagedWorkspace(workspace, run = defaultRunner) {
|
|
15
|
-
|
|
15
|
+
const packagePath = join(workspace, "package.json");
|
|
16
|
+
if (!existsSync(packagePath))
|
|
16
17
|
return "not_applicable";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
const result = run("npm", ["ci", "--include=optional", "--no-audit", "--no-fund"], workspace);
|
|
18
|
+
const install = lockedInstall(workspace, packagePath);
|
|
19
|
+
const result = run(install.command, install.args, workspace);
|
|
21
20
|
if (result.error)
|
|
22
|
-
throw new Error(`managed_workspace_bootstrap_failed:${result.error.message}`);
|
|
21
|
+
throw new Error(`managed_workspace_bootstrap_failed:${install.id}:${result.error.message}`);
|
|
23
22
|
if (result.status !== 0)
|
|
24
|
-
throw new Error(`managed_workspace_bootstrap_failed:exit_${result.status ?? "unknown"}`);
|
|
23
|
+
throw new Error(`managed_workspace_bootstrap_failed:${install.id}:exit_${result.status ?? "unknown"}`);
|
|
25
24
|
return "installed";
|
|
26
25
|
}
|
|
26
|
+
function lockedInstall(workspace, packagePath) {
|
|
27
|
+
const declared = declaredPackageManager(packagePath);
|
|
28
|
+
const managers = [
|
|
29
|
+
existsSync(join(workspace, "package-lock.json")) || existsSync(join(workspace, "npm-shrinkwrap.json")) ? "npm" : null,
|
|
30
|
+
existsSync(join(workspace, "pnpm-lock.yaml")) ? "pnpm" : null,
|
|
31
|
+
existsSync(join(workspace, "yarn.lock")) ? "yarn" : null,
|
|
32
|
+
existsSync(join(workspace, "bun.lock")) || existsSync(join(workspace, "bun.lockb")) ? "bun" : null,
|
|
33
|
+
].filter((manager) => manager !== null);
|
|
34
|
+
if (managers.length === 0)
|
|
35
|
+
throw new Error("managed_workspace_bootstrap_lockfile_required");
|
|
36
|
+
if (declared && !managers.includes(declared)) {
|
|
37
|
+
throw new Error(`managed_workspace_bootstrap_package_manager_lockfile_mismatch:${declared}:${managers.join(",")}`);
|
|
38
|
+
}
|
|
39
|
+
const selected = declared ?? (managers.length === 1 ? managers[0] : null);
|
|
40
|
+
if (!selected)
|
|
41
|
+
throw new Error(`managed_workspace_bootstrap_lockfile_ambiguous:${managers.join(",")}`);
|
|
42
|
+
if (selected === "npm")
|
|
43
|
+
return { id: selected, command: "npm", args: ["ci", "--include=optional", "--no-audit", "--no-fund"] };
|
|
44
|
+
if (selected === "pnpm")
|
|
45
|
+
return { id: selected, command: "pnpm", args: ["install", "--frozen-lockfile"] };
|
|
46
|
+
if (selected === "bun")
|
|
47
|
+
return { id: selected, command: "bun", args: ["install", "--frozen-lockfile"] };
|
|
48
|
+
const modernYarn = existsSync(join(workspace, ".yarnrc.yml")) || declaredPackageManagerMajor(packagePath, "yarn") >= 2;
|
|
49
|
+
return { id: selected, command: "yarn", args: ["install", modernYarn ? "--immutable" : "--frozen-lockfile"] };
|
|
50
|
+
}
|
|
51
|
+
function declaredPackageManager(packagePath) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
54
|
+
const match = typeof parsed.packageManager === "string" ? /^(npm|pnpm|yarn|bun)@/.exec(parsed.packageManager.trim()) : null;
|
|
55
|
+
const id = match?.[1];
|
|
56
|
+
return id === "npm" || id === "pnpm" || id === "yarn" || id === "bun" ? id : null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function declaredPackageManagerMajor(packagePath, manager) {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
65
|
+
const match = typeof parsed.packageManager === "string"
|
|
66
|
+
? new RegExp(`^${manager}@(\\d+)`).exec(parsed.packageManager.trim())
|
|
67
|
+
: null;
|
|
68
|
+
return match ? Number(match[1]) : 0;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
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.25",
|
|
4
4
|
"description": "Conduit Bridge CLI — 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": {
|