@miraland-labs/conduit-bridge 0.16.16 → 0.16.18
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/checkout.js +30 -25
- package/dist/driver.js +6 -1
- package/dist/execution.js +33 -4
- package/package.json +1 -1
package/dist/checkout.js
CHANGED
|
@@ -35,16 +35,39 @@ async function gitOriginUrl(workspace, exec) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
/** A control-plane repository fingerprint needs a transport before Git can clone it. */
|
|
38
|
-
function
|
|
38
|
+
function cloneRepositoryUrls(repositoryUrl) {
|
|
39
39
|
const value = repositoryUrl.trim();
|
|
40
40
|
const slash = value.indexOf("/");
|
|
41
41
|
if (slash <= 0 || value.startsWith(".") || value.includes("\\") || /^[^/@\s]+@[^:]+:/.test(value))
|
|
42
|
-
return value;
|
|
42
|
+
return [value];
|
|
43
43
|
const host = value.slice(0, slash);
|
|
44
44
|
const path = value.slice(slash + 1);
|
|
45
45
|
if (!host.includes(".") || /\s/.test(value) || path.split("/").filter(Boolean).length < 2)
|
|
46
|
-
return value;
|
|
47
|
-
return `https://${value}
|
|
46
|
+
return [value];
|
|
47
|
+
return [`https://${value}`, `git@${host}:${path}${path.endsWith(".git") ? "" : ".git"}`];
|
|
48
|
+
}
|
|
49
|
+
async function cloneWorkspace(workspace, repositoryUrls, exec, accessFn, readdirFn) {
|
|
50
|
+
let lastError;
|
|
51
|
+
for (const [index, repositoryUrl] of repositoryUrls.entries()) {
|
|
52
|
+
try {
|
|
53
|
+
await exec("git", ["clone", "--", repositoryUrl, workspace], {
|
|
54
|
+
timeout: 300_000,
|
|
55
|
+
maxBuffer: 2_000_000,
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
lastError = error;
|
|
61
|
+
if (index === repositoryUrls.length - 1)
|
|
62
|
+
break;
|
|
63
|
+
const exists = await pathExists(workspace, accessFn);
|
|
64
|
+
const entries = exists ? await readdirFn(workspace).catch(() => null) : [];
|
|
65
|
+
if (entries === null || entries.length > 0)
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const detail = lastError instanceof Error ? lastError.message : "clone_failed";
|
|
70
|
+
throw new Error(`ensure_checkout_clone_failed:${detail}`);
|
|
48
71
|
}
|
|
49
72
|
/**
|
|
50
73
|
* Ensure `workspace` is a checkout of `repositoryUrl`.
|
|
@@ -61,20 +84,11 @@ export async function ensureCheckout(workspace, repositoryUrl, deps = {}) {
|
|
|
61
84
|
const wanted = normalizeRepositoryUrl(repositoryUrl);
|
|
62
85
|
if (!wanted)
|
|
63
86
|
throw new Error("ensure_checkout_invalid_url");
|
|
64
|
-
const
|
|
87
|
+
const cloneUrls = cloneRepositoryUrls(repositoryUrl);
|
|
65
88
|
const exists = await pathExists(workspace, accessFn);
|
|
66
89
|
if (!exists) {
|
|
67
90
|
await mkdirFn(dirname(workspace), { recursive: true });
|
|
68
|
-
|
|
69
|
-
await exec("git", ["clone", "--", cloneUrl, workspace], {
|
|
70
|
-
timeout: 300_000,
|
|
71
|
-
maxBuffer: 2_000_000,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
const detail = error instanceof Error ? error.message : "clone_failed";
|
|
76
|
-
throw new Error(`ensure_checkout_clone_failed:${detail}`);
|
|
77
|
-
}
|
|
91
|
+
await cloneWorkspace(workspace, cloneUrls, exec, accessFn, readdirFn);
|
|
78
92
|
const origin = await gitOriginUrl(workspace, exec);
|
|
79
93
|
if (!origin || normalizeRepositoryUrl(origin) !== wanted) {
|
|
80
94
|
throw new Error("ensure_checkout_clone_mismatch");
|
|
@@ -85,16 +99,7 @@ export async function ensureCheckout(workspace, repositoryUrl, deps = {}) {
|
|
|
85
99
|
if (entries === null)
|
|
86
100
|
throw new Error("ensure_checkout_path_unreadable");
|
|
87
101
|
if (entries.length === 0) {
|
|
88
|
-
|
|
89
|
-
await exec("git", ["clone", "--", cloneUrl, workspace], {
|
|
90
|
-
timeout: 300_000,
|
|
91
|
-
maxBuffer: 2_000_000,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
const detail = error instanceof Error ? error.message : "clone_failed";
|
|
96
|
-
throw new Error(`ensure_checkout_clone_failed:${detail}`);
|
|
97
|
-
}
|
|
102
|
+
await cloneWorkspace(workspace, cloneUrls, exec, accessFn, readdirFn);
|
|
98
103
|
return "cloned";
|
|
99
104
|
}
|
|
100
105
|
const origin = await gitOriginUrl(workspace, exec);
|
package/dist/driver.js
CHANGED
|
@@ -324,16 +324,21 @@ export function parseAgentReport(text, acceptance) {
|
|
|
324
324
|
throw new Error(`Agent report is invalid${at}: ${issue?.message ?? "unknown validation error"}`);
|
|
325
325
|
}
|
|
326
326
|
const reportedKeys = parsed.data.acceptance_results.map((item) => criterionKey(item.criterion));
|
|
327
|
+
const approvedKeys = new Set(acceptance.map(criterionKey));
|
|
327
328
|
const duplicated = parsed.data.acceptance_results
|
|
328
329
|
.filter((item, index) => reportedKeys.indexOf(criterionKey(item.criterion)) !== index)
|
|
329
330
|
.map((item) => item.criterion);
|
|
330
331
|
const missing = acceptance.filter((criterion) => !reportedKeys.includes(criterionKey(criterion)));
|
|
331
|
-
|
|
332
|
+
const unapproved = parsed.data.acceptance_results
|
|
333
|
+
.filter((item) => !approvedKeys.has(criterionKey(item.criterion)))
|
|
334
|
+
.map((item) => item.criterion);
|
|
335
|
+
if (missing.length || duplicated.length || unapproved.length) {
|
|
332
336
|
// Name the criterion. This text becomes the failure detail and the Conductor repair brief, and
|
|
333
337
|
// "one of them is wrong" gives the next run nothing to act on.
|
|
334
338
|
const detail = [
|
|
335
339
|
missing.length ? `missing: ${missing.map(quoteCriterion).join("; ")}` : "",
|
|
336
340
|
duplicated.length ? `reported more than once: ${duplicated.map(quoteCriterion).join("; ")}` : "",
|
|
341
|
+
unapproved.length ? `not approved: ${unapproved.map(quoteCriterion).join("; ")}` : "",
|
|
337
342
|
].filter(Boolean).join(" | ");
|
|
338
343
|
throw new Error(`Agent report must include every acceptance criterion exactly once (${detail})`);
|
|
339
344
|
}
|
package/dist/execution.js
CHANGED
|
@@ -1406,9 +1406,13 @@ async function runClaimedAssignment(client, config, driver, workspace, brief, ta
|
|
|
1406
1406
|
return;
|
|
1407
1407
|
}
|
|
1408
1408
|
await client.updateAttempt(taskId, { phase: "agent_finished", delivery: { spec, report } });
|
|
1409
|
-
await submitFinishedDelivery(client, taskId);
|
|
1410
|
-
|
|
1411
|
-
|
|
1409
|
+
const response = await submitFinishedDelivery(client, taskId);
|
|
1410
|
+
retainAttemptWorktree = response.retain_worktree === true;
|
|
1411
|
+
deliverySubmitted = response.status !== "failed";
|
|
1412
|
+
if (deliverySubmitted)
|
|
1413
|
+
console.log(`Assignment ${taskId} delivered for review and acceptance.`);
|
|
1414
|
+
else
|
|
1415
|
+
console.error(`Assignment ${taskId} Delivery was rejected and routed to Conductor repair.`);
|
|
1412
1416
|
}
|
|
1413
1417
|
finally {
|
|
1414
1418
|
releaseIdleSleep();
|
|
@@ -1579,7 +1583,7 @@ async function submitFinishedDelivery(client, taskId) {
|
|
|
1579
1583
|
throw new Error("Finished agent run is missing its persisted Delivery data");
|
|
1580
1584
|
const terminal = await prepareDelivery(client, active.attemptId, taskId, active.delivery.report);
|
|
1581
1585
|
await client.attemptRequest(taskId, "progress", { phase: "preparing_delivery", message: "Agent finished; submitting the Delivery.", idempotency_key: `bridge:progress:${active.attemptId}:delivery` });
|
|
1582
|
-
|
|
1586
|
+
return queueTerminal(client, taskId, terminal);
|
|
1583
1587
|
}
|
|
1584
1588
|
export const modelListCache = new Map();
|
|
1585
1589
|
const MODEL_LIST_TTL_MS = 6 * 60 * 60 * 1000;
|
|
@@ -1869,6 +1873,31 @@ export async function flushTerminal(client, taskId) {
|
|
|
1869
1873
|
await client.clearAttempt(taskId);
|
|
1870
1874
|
return { accepted: false, status: "invalid_lease" };
|
|
1871
1875
|
}
|
|
1876
|
+
if (error instanceof ConduitRequestError
|
|
1877
|
+
&& error.code === "criterion_unapproved"
|
|
1878
|
+
&& active.terminal.action === "complete") {
|
|
1879
|
+
const detail = `Delivery report rejected by the Control Plane: ${error.message}`;
|
|
1880
|
+
await client.updateAttempt(taskId, {
|
|
1881
|
+
terminal: {
|
|
1882
|
+
action: "fail",
|
|
1883
|
+
body: {
|
|
1884
|
+
error: detail,
|
|
1885
|
+
retryable: false,
|
|
1886
|
+
failure: {
|
|
1887
|
+
code: "delivery_report_invalid",
|
|
1888
|
+
class: "contract",
|
|
1889
|
+
disposition: "rework",
|
|
1890
|
+
responsible_party: "conduit",
|
|
1891
|
+
message: "The completed delivery report did not match the owner-approved acceptance criteria.",
|
|
1892
|
+
next_action: "Conductor will prepare bounded recovery guidance. You do not need to edit technical constraints.",
|
|
1893
|
+
diagnostic_detail: detail,
|
|
1894
|
+
},
|
|
1895
|
+
idempotency_key: `bridge:delivery-rejected:${active.attemptId}`,
|
|
1896
|
+
},
|
|
1897
|
+
},
|
|
1898
|
+
});
|
|
1899
|
+
return flushTerminal(client, taskId);
|
|
1900
|
+
}
|
|
1872
1901
|
throw error;
|
|
1873
1902
|
}
|
|
1874
1903
|
}
|
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.18",
|
|
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": {
|