@miraland-labs/conduit-bridge 0.16.21 → 0.16.23
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 +21 -2
- package/dist/execution.js +11 -3
- package/dist/land-contract.js +0 -5
- package/dist/on-shift-apply.js +1 -1
- package/dist/preflight.js +85 -12
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -520,7 +520,7 @@ async function runner() {
|
|
|
520
520
|
const applied = await maybeApplyOnShiftIntent({
|
|
521
521
|
currentWorkspace: workspace,
|
|
522
522
|
onShift: hb.on_shift,
|
|
523
|
-
runInstall: async (nextWorkspace, repo) => {
|
|
523
|
+
runInstall: async (nextWorkspace, repo, managedRoot) => {
|
|
524
524
|
let env;
|
|
525
525
|
try {
|
|
526
526
|
env = loadOpsEnv();
|
|
@@ -530,7 +530,14 @@ async function runner() {
|
|
|
530
530
|
}
|
|
531
531
|
await runOps("install", [], {
|
|
532
532
|
env: env
|
|
533
|
-
? {
|
|
533
|
+
? {
|
|
534
|
+
...env,
|
|
535
|
+
CONDUIT_WORKSPACE: nextWorkspace,
|
|
536
|
+
CONDUIT_REPO: repo,
|
|
537
|
+
CONDUIT_MANAGED_ROOT: managedRoot === undefined
|
|
538
|
+
? env.CONDUIT_MANAGED_ROOT
|
|
539
|
+
: managedRoot ?? "",
|
|
540
|
+
}
|
|
534
541
|
: undefined,
|
|
535
542
|
});
|
|
536
543
|
},
|
|
@@ -556,6 +563,18 @@ async function runner() {
|
|
|
556
563
|
const preflight = await cachedBridgePreflight({ config, workspace, brief: currentBrief ?? undefined });
|
|
557
564
|
await heartbeat(client, config, workspace, currentBrief, preflight);
|
|
558
565
|
},
|
|
566
|
+
beforeClaim: async (driverId) => {
|
|
567
|
+
// Never let the five-minute heartbeat cache span a CLI upgrade into a certified claim.
|
|
568
|
+
// Publish the fresh probe first; the Control Plane then remains the authority for the
|
|
569
|
+
// exact version frozen onto the claim response and event.
|
|
570
|
+
const currentBrief = await buildWorkspaceBrief(workspace).catch(() => brief);
|
|
571
|
+
const preflight = await cachedBridgePreflight({ config, workspace, brief: currentBrief ?? undefined }, { force: true });
|
|
572
|
+
const hb = await heartbeat(client, config, workspace, currentBrief, preflight);
|
|
573
|
+
if (hb.staleBridge)
|
|
574
|
+
return false;
|
|
575
|
+
const lane = preflight.drivers.find((candidate) => candidate.id === driverId);
|
|
576
|
+
return preflight.ready && lane?.ready === true && typeof lane.version === "string" && lane.version.length > 0;
|
|
577
|
+
},
|
|
559
578
|
heartbeatIntervalMs: intervalMs,
|
|
560
579
|
}, running, {});
|
|
561
580
|
}
|
package/dist/execution.js
CHANGED
|
@@ -538,7 +538,9 @@ export async function pumpExecutionSlots(client, config, workspace, brief, timeo
|
|
|
538
538
|
}
|
|
539
539
|
if (!laneDriver || !driverId)
|
|
540
540
|
break;
|
|
541
|
-
const claimed = await claimNextAssignment(client, config, workspace, brief, driverId, processOnlineIds
|
|
541
|
+
const claimed = await claimNextAssignment(client, config, workspace, brief, driverId, processOnlineIds, {
|
|
542
|
+
beforeClaim: supervision?.beforeClaim,
|
|
543
|
+
});
|
|
542
544
|
if (!claimed)
|
|
543
545
|
break;
|
|
544
546
|
const taskId = claimed.taskId;
|
|
@@ -557,7 +559,7 @@ export async function pumpExecutionSlots(client, config, workspace, brief, timeo
|
|
|
557
559
|
return progressed;
|
|
558
560
|
}
|
|
559
561
|
/** Claim one assignment when under capacity; does not start the agent (multi-slot pump does). */
|
|
560
|
-
export async function claimNextAssignment(client, config, workspace, brief, driverId, processOnlineIds) {
|
|
562
|
+
export async function claimNextAssignment(client, config, workspace, brief, driverId, processOnlineIds, options = {}) {
|
|
561
563
|
if (Object.keys(config.activeAttempts).length >= config.leaseCapacity)
|
|
562
564
|
return null;
|
|
563
565
|
const data = await client.request("/runner/v1/assignments");
|
|
@@ -607,6 +609,10 @@ export async function claimNextAssignment(client, config, workspace, brief, driv
|
|
|
607
609
|
console.error(`Assignment ${assignment.id} rejected before claim: ${rejection}`);
|
|
608
610
|
return null;
|
|
609
611
|
}
|
|
612
|
+
if (selectedDriverId && options.beforeClaim && !await options.beforeClaim(selectedDriverId)) {
|
|
613
|
+
console.warn(`Assignment ${assignment.id} held before claim: driver ${selectedDriverId} preflight is not ready`);
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
610
616
|
console.log(`Claiming ${assignment.execution_kind} assignment ${assignment.id} (attempt ${assignment.attempt_id})`);
|
|
611
617
|
const fuelMode = selectedDriverId ? resolveDriverFuel(config, selectedDriverId) : undefined;
|
|
612
618
|
const fuelProvenance = selectedDriverId ? resolveDriverFuelProvenance(config, selectedDriverId) : undefined;
|
|
@@ -631,7 +637,9 @@ export async function executeNextAssignment(client, config, driver, workspace, b
|
|
|
631
637
|
if (Object.keys(config.activeAttempts).length)
|
|
632
638
|
return recoverActiveAttempt(client, config, driver, workspace, brief, timeoutMs, supervision);
|
|
633
639
|
const preferredDriverId = Object.entries(DRIVERS).find(([, candidate]) => candidate === driver)?.[0] ?? null;
|
|
634
|
-
const claimed = await claimNextAssignment(client, config, workspace, brief, preferredDriverId
|
|
640
|
+
const claimed = await claimNextAssignment(client, config, workspace, brief, preferredDriverId, null, {
|
|
641
|
+
beforeClaim: supervision?.beforeClaim,
|
|
642
|
+
});
|
|
635
643
|
if (!claimed)
|
|
636
644
|
return false;
|
|
637
645
|
const selectedDriver = claimedAssignmentDriver(driver, claimed.driverId);
|
package/dist/land-contract.js
CHANGED
|
@@ -19,11 +19,6 @@ export const AGENT_NO_LAND_COMMIT_PREFIX = "Agent did not land repository change
|
|
|
19
19
|
export function agentNoLandCommitMessage(baseCommit) {
|
|
20
20
|
return `${AGENT_NO_LAND_COMMIT_PREFIX}: the attempt branch has no commit after base ${baseCommit.slice(0, 12)}.`;
|
|
21
21
|
}
|
|
22
|
-
/** Legacy ensureDeliveryPullRequest text — kept recognizable for stored failures. */
|
|
23
|
-
export const LEGACY_NO_LAND_COMMIT_PATTERN = /no commit after base .*; this run produced no change to deliver/i;
|
|
24
|
-
export function isAgentNoLandCommitMessage(message) {
|
|
25
|
-
return message.startsWith(AGENT_NO_LAND_COMMIT_PREFIX) || LEGACY_NO_LAND_COMMIT_PATTERN.test(message);
|
|
26
|
-
}
|
|
27
22
|
export class AgentNoLandCommitError extends Error {
|
|
28
23
|
baseCommit;
|
|
29
24
|
constructor(baseCommit) {
|
package/dist/on-shift-apply.js
CHANGED
|
@@ -47,6 +47,6 @@ export async function maybeApplyOnShiftIntent(input) {
|
|
|
47
47
|
return false;
|
|
48
48
|
}
|
|
49
49
|
console.log(`Applying on-shift workspace intent: ${target}${repo ? ` (${repo})` : ""}`);
|
|
50
|
-
await input.runInstall(target, repo);
|
|
50
|
+
await input.runInstall(target, repo, input.onShift?.managed_workspace_root);
|
|
51
51
|
return true;
|
|
52
52
|
}
|
package/dist/preflight.js
CHANGED
|
@@ -16,9 +16,31 @@ function modelsFingerprint(config) {
|
|
|
16
16
|
}).join(";");
|
|
17
17
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
18
18
|
}
|
|
19
|
-
// Protocol
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
// Protocol 9 adds the Bridge-observed driver interface cohort to every preflight lane.
|
|
20
|
+
export const BRIDGE_PROTOCOL_VERSION = 9;
|
|
21
|
+
/** Keep the version identity within the same bound accepted by the heartbeat schema. */
|
|
22
|
+
export const MAX_DRIVER_VERSION_LENGTH = 200;
|
|
23
|
+
/**
|
|
24
|
+
* Turn one successful CLI version probe into a stable, bounded identity.
|
|
25
|
+
*
|
|
26
|
+
* A probe is deliberately stricter than "some output": warnings, multiple lines, control
|
|
27
|
+
* sequences, and overlong output cannot be assigned to a particular CLI version. Returning null
|
|
28
|
+
* makes the lane unready, so a stale/truncated prefix can never be used for certification.
|
|
29
|
+
*/
|
|
30
|
+
export function parseDriverVersion(result) {
|
|
31
|
+
if (result.code !== 0)
|
|
32
|
+
return null;
|
|
33
|
+
const streams = [result.stdout, result.stderr]
|
|
34
|
+
.filter((value) => typeof value === "string")
|
|
35
|
+
.map((value) => value.trim())
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
if (streams.length !== 1)
|
|
38
|
+
return null;
|
|
39
|
+
const version = streams[0];
|
|
40
|
+
if (version.length > MAX_DRIVER_VERSION_LENGTH || /[\r\n\u0000-\u001f\u007f]/u.test(version))
|
|
41
|
+
return null;
|
|
42
|
+
return version;
|
|
43
|
+
}
|
|
22
44
|
const execFileAsync = promisify(execFile);
|
|
23
45
|
async function defaultCommandRunner(command, args, cwd) {
|
|
24
46
|
try {
|
|
@@ -44,6 +66,55 @@ const DRIVER_COMMAND = {
|
|
|
44
66
|
function executableFor(driver) {
|
|
45
67
|
return driver === "codex" ? resolveCodexExecutable() : DRIVER_COMMAND[driver] ?? driver;
|
|
46
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Primary-driver interface surfaces used by the existing AgentDriver implementations. This is
|
|
71
|
+
* deliberately not a hash of all help prose: vendor copy edits must not manufacture a new cohort.
|
|
72
|
+
*/
|
|
73
|
+
const DRIVER_INTERFACE_CONTRACTS = {
|
|
74
|
+
"claude-code": {
|
|
75
|
+
adapter_revision: 1,
|
|
76
|
+
help_args: ["--help"],
|
|
77
|
+
required: ["outputformat", "allowedtools", "disallowedtools"],
|
|
78
|
+
observed: ["outputformat", "allowedtools", "disallowedtools", "permissionmode", "resume", "model"],
|
|
79
|
+
},
|
|
80
|
+
codex: {
|
|
81
|
+
adapter_revision: 1,
|
|
82
|
+
help_args: ["exec", "--help"],
|
|
83
|
+
required: ["json", "sandbox"],
|
|
84
|
+
observed: ["json", "sandbox", "model", "config"],
|
|
85
|
+
},
|
|
86
|
+
cursor: {
|
|
87
|
+
adapter_revision: 1,
|
|
88
|
+
help_args: ["--help"],
|
|
89
|
+
required: ["outputformat", "workspace"],
|
|
90
|
+
observed: ["outputformat", "workspace", "trust", "force", "resume", "model"],
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
function normalizedHelpSurface(value) {
|
|
94
|
+
return value.toLowerCase().replace(/[-_\s]/g, "");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Fingerprint only what a bounded, non-spending interface probe can prove. It is not a claim that
|
|
98
|
+
* vendor semantics or model quality stayed identical; per-attempt gates and field outcomes remain
|
|
99
|
+
* authoritative for those facts.
|
|
100
|
+
*/
|
|
101
|
+
async function driverCompatibilityFingerprint(driver, workspace, run) {
|
|
102
|
+
const contract = DRIVER_INTERFACE_CONTRACTS[driver];
|
|
103
|
+
if (!contract)
|
|
104
|
+
return null;
|
|
105
|
+
const help = await run(executableFor(driver), contract.help_args, workspace);
|
|
106
|
+
if (help.code !== 0)
|
|
107
|
+
return null;
|
|
108
|
+
const surface = normalizedHelpSurface(`${help.stdout}\n${help.stderr}`);
|
|
109
|
+
if (!contract.required.every((signal) => surface.includes(signal)))
|
|
110
|
+
return null;
|
|
111
|
+
const features = contract.observed.map((signal) => [signal, surface.includes(signal)]);
|
|
112
|
+
return createHash("sha256").update(JSON.stringify({
|
|
113
|
+
driver,
|
|
114
|
+
adapter_revision: contract.adapter_revision,
|
|
115
|
+
features,
|
|
116
|
+
})).digest("hex");
|
|
117
|
+
}
|
|
47
118
|
async function localAuthenticationReady(driver, workspace, run) {
|
|
48
119
|
if (driver === "claude-code")
|
|
49
120
|
return hasClaudeLogin();
|
|
@@ -106,23 +177,25 @@ export async function runBridgePreflight(input, deps = {}) {
|
|
|
106
177
|
const diagnosisReadOnly = supportsReadOnlyDiagnosis(driver);
|
|
107
178
|
const fuel = resolveDriverFuel(input.config, driver);
|
|
108
179
|
if (localFuelOnlyDriver(driver) && fuel !== "local") {
|
|
109
|
-
return { issue: { code: "driver_fuel_mismatch", driver }, snapshot: { id: driver, version: null, ready: false, diagnosis_read_only: diagnosisReadOnly } };
|
|
180
|
+
return { issue: { code: "driver_fuel_mismatch", driver }, snapshot: { id: driver, version: null, ready: false, diagnosis_read_only: diagnosisReadOnly, compatibility_fingerprint: null } };
|
|
110
181
|
}
|
|
111
182
|
const version = await run(executableFor(driver), ["--version"], input.workspace);
|
|
112
|
-
const versionText =
|
|
113
|
-
if (
|
|
114
|
-
return { issue: { code: "driver_missing", driver }, snapshot: { id: driver, version:
|
|
183
|
+
const versionText = parseDriverVersion(version);
|
|
184
|
+
if (!versionText) {
|
|
185
|
+
return { issue: { code: "driver_missing", driver }, snapshot: { id: driver, version: null, ready: false, diagnosis_read_only: diagnosisReadOnly, compatibility_fingerprint: null } };
|
|
115
186
|
}
|
|
116
187
|
if (fuel === "local" && !await localAuthenticationReady(driver, input.workspace, run)) {
|
|
117
|
-
return { issue: { code: "driver_not_authenticated", driver }, snapshot: { id: driver, version: versionText, ready: false, diagnosis_read_only: diagnosisReadOnly } };
|
|
188
|
+
return { issue: { code: "driver_not_authenticated", driver }, snapshot: { id: driver, version: versionText, ready: false, diagnosis_read_only: diagnosisReadOnly, compatibility_fingerprint: null } };
|
|
118
189
|
}
|
|
119
190
|
// Reported, not probed: the window state was learned from the vendor's last refusal and read
|
|
120
191
|
// back through laneQuota, which retires a passed reset. Nothing is executed here, so a lane
|
|
121
192
|
// running dry costs the heartbeat nothing.
|
|
122
193
|
const quota = fuel === "local" ? laneQuota(input.config, driver) ?? undefined : undefined;
|
|
194
|
+
const compatibilityFingerprint = await driverCompatibilityFingerprint(driver, input.workspace, run);
|
|
123
195
|
return {
|
|
124
196
|
issue: null,
|
|
125
|
-
snapshot: { id: driver, version: versionText, ready: quota?.exhausted !== true, diagnosis_read_only: diagnosisReadOnly,
|
|
197
|
+
snapshot: { id: driver, version: versionText, ready: quota?.exhausted !== true, diagnosis_read_only: diagnosisReadOnly,
|
|
198
|
+
compatibility_fingerprint: compatibilityFingerprint, ...(quota ? { quota } : {}) },
|
|
126
199
|
};
|
|
127
200
|
}));
|
|
128
201
|
issues.push(...driverChecks.map((check) => check.issue).filter((issue) => issue !== null));
|
|
@@ -137,10 +210,10 @@ export async function runBridgePreflight(input, deps = {}) {
|
|
|
137
210
|
}
|
|
138
211
|
let cached = null;
|
|
139
212
|
/** Keep auth probes off the 15-second heartbeat hot path while still expiring readiness promptly. */
|
|
140
|
-
export async function cachedBridgePreflight(input) {
|
|
213
|
+
export async function cachedBridgePreflight(input, options = {}) {
|
|
141
214
|
const online = input.processOnlineIds?.length ? input.processOnlineIds : onlineDriverIds(input.config);
|
|
142
215
|
const key = JSON.stringify([input.workspace, input.expectedRepository ?? "", online, input.config.fuelSource, input.config.drivers]);
|
|
143
|
-
if (cached?.key === key && Date.now() - cached.at < 5 * 60_000)
|
|
216
|
+
if (!options.force && cached?.key === key && Date.now() - cached.at < 5 * 60_000)
|
|
144
217
|
return cached.report;
|
|
145
218
|
const report = await runBridgePreflight(input);
|
|
146
219
|
cached = { key, at: Date.now(), report };
|
|
@@ -157,7 +230,7 @@ export function unavailableWorkspacePreflight(input) {
|
|
|
157
230
|
ready: false,
|
|
158
231
|
checked_at: new Date().toISOString(),
|
|
159
232
|
workspace_clean: false,
|
|
160
|
-
drivers: online.map((id) => ({ id, version: null, ready: false, diagnosis_read_only: supportsReadOnlyDiagnosis(id) })),
|
|
233
|
+
drivers: online.map((id) => ({ id, version: null, ready: false, diagnosis_read_only: supportsReadOnlyDiagnosis(id), compatibility_fingerprint: null })),
|
|
161
234
|
models_fingerprint: modelsFingerprint(input.config),
|
|
162
235
|
issues,
|
|
163
236
|
};
|
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.23",
|
|
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": {
|