@miraland-labs/conduit-bridge 0.11.1 → 0.11.3
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 +1 -1
- package/dist/execution.js +29 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Local Bridge CLI for [Conduit](https://github.com/miralandlabs/conduit). Connects a computer to one organization, claims work, and drives a local agent.
|
|
4
4
|
|
|
5
|
-
**Package version:** `0.11.
|
|
5
|
+
**Package version:** `0.11.3` — a lost connection to the code host during delivery finalize is now reported as **retryable**: that block marked every throw non-retryable, which is right for a malformed delivery report and wrong for a dropped connection, and it reported a GitHub HTTP/2 flake on `git push` as the agent failing its execution contract. Since `0.11.0`: `0.11.1` made a missing stamped class retryable-then-fail-closed, `0.11.2` removed the unread `external_network` spec field and made a driver refusing a class it cannot enforce terminal. **ExecutionClass inversion:** the control plane stamps `execution_class` on the assignment; Bridge launches from that stamp (fail closed if missing), asserts the full class floor after ceilings, and projects permissions locally (“Bridge wins” on tools). Ordinary grants are derived server-side as classMax ∩ project ceiling; landing is package `lands`, not a class axiom. Workspace briefs discover verification commands for Node, Make, Cargo, Go, pytest, and Gradle. Control-plane `instruction` is **additive** evidence/class prose; Bridge always appends local RULES (shell allow-list, verbatim paste, criterion mapping, hard-denied). Prompt is data; permissions stay local — residual risk if the control plane is compromised (sharpest for `observe_network`). Artifact digests still normalize bare 64-hex to `sha256:<hex>`. Experimental Pi lane remains; `observe_network` is refused on Pi. Heartbeat protocol 2, worktrees, ops helpers, multi-driver lanes, and slots **1–4** unchanged. Protocol 1 heartbeats remain compatible for presence but cannot receive work.
|
|
6
6
|
|
|
7
7
|
## Prerequisites
|
|
8
8
|
|
package/dist/execution.js
CHANGED
|
@@ -22,9 +22,21 @@ function changesRequestedFeedback(summary) {
|
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* A network fault reaching the code host. Mirrors FORGE_TRANSPORT_PATTERN in the control plane's
|
|
27
|
+
* failures.ts — Bridge cannot import from there, so test/failure-classification.test.ts pins the two
|
|
28
|
+
* together rather than trusting them to stay in step.
|
|
29
|
+
*/
|
|
30
|
+
export const FORGE_TRANSPORT_PATTERN = /unable to access '?https?:\/\/|error in the http2 framing layer|could not resolve host|connection (?:reset|timed out|refused)|\bcurl\b.*\b(?:52|55|56|92)\b|remote end hung up unexpectedly|\brpc failed\b|tls handshake|network is unreachable|operation timed out/i;
|
|
31
|
+
export function forgeTransportFailure(message) {
|
|
32
|
+
return FORGE_TRANSPORT_PATTERN.test(message);
|
|
33
|
+
}
|
|
25
34
|
/** Failures that require an operator/configuration change must never burn the remaining attempts. */
|
|
26
35
|
export function retryableAgentFailure(message) {
|
|
27
|
-
|
|
36
|
+
// `cannot enforce` is a driver projection refusing a class it structurally cannot bound (Pi and
|
|
37
|
+
// observe_network). No number of retries on that lane changes it, and retrying an immutable input
|
|
38
|
+
// is how one assignment burned ~80 attempts. Fail closed; the message names the drivers that work.
|
|
39
|
+
return !/unknown (?:option|argument)|unrecognized (?:option|argument)|not logged in|no login|not authenticated|login required|\bENOENT\b|could not verify the installed CLI|requires local fuel|No Bridge-mapped|cannot enforce|preflight/i.test(message);
|
|
28
40
|
}
|
|
29
41
|
const assignmentSchema = z.object({
|
|
30
42
|
id: z.string().uuid(),
|
|
@@ -54,7 +66,6 @@ const taskSpecSchema = z.object({
|
|
|
54
66
|
execution_class: z.enum(["observe", "observe_network", "publish_artifact", "verify", "mutate_repo"]).optional(),
|
|
55
67
|
lands: z.boolean().optional(),
|
|
56
68
|
privileged_grants: z.array(z.string()).optional(),
|
|
57
|
-
external_network: z.boolean().optional(),
|
|
58
69
|
instruction: z.string().optional(),
|
|
59
70
|
evidence_standard: z.unknown().optional(),
|
|
60
71
|
working_language: z.enum(["en", "zh"]).optional(),
|
|
@@ -519,9 +530,13 @@ async function runClaimedAssignment(client, config, driver, workspace, brief, ta
|
|
|
519
530
|
}
|
|
520
531
|
catch (error) {
|
|
521
532
|
const message = error instanceof Error ? error.message : "Agent delivery report was invalid";
|
|
522
|
-
// Contract/tooling/credential failures need a human fix
|
|
523
|
-
// state immediately and spend another attempt before the operator can change anything.
|
|
524
|
-
|
|
533
|
+
// Contract/tooling/credential failures need a human fix: automatic retry would repeat the same
|
|
534
|
+
// state immediately and spend another attempt before the operator can change anything. A lost
|
|
535
|
+
// connection to the code host is the opposite — nothing here is wrong and the next attempt can
|
|
536
|
+
// succeed — and calling it non-retryable is what reported a GitHub HTTP/2 flake on `git push`
|
|
537
|
+
// as the agent failing its contract.
|
|
538
|
+
const transport = forgeTransportFailure(message);
|
|
539
|
+
await queueTerminal(client, taskId, { action: "fail", body: { error: message, retryable: transport, idempotency_key: `bridge:invalid-delivery:${active.attemptId}` } });
|
|
525
540
|
console.error(`Assignment ${taskId} could not produce a valid Delivery: ${redactSecrets(message)}`);
|
|
526
541
|
const replyTail = reportText.slice(-8_000);
|
|
527
542
|
console.error(`Assignment ${taskId} agent reply tail (${reportText.length} chars total, redacted): ${redactSecrets(replyTail) || "<empty>"}`);
|
|
@@ -706,10 +721,12 @@ async function prepareDelivery(client, attemptId, taskId, report) {
|
|
|
706
721
|
};
|
|
707
722
|
}
|
|
708
723
|
export function validateDeliveryReport(report, spec, grants = []) {
|
|
709
|
-
validateChangeScope(report, spec.change_scope ?? []);
|
|
710
724
|
// Research findings live in the report. Do not demand a published pack URL for work_role=research
|
|
711
725
|
// even when a planner mis-labeled the package as deliverable=artifact.
|
|
712
726
|
const artifactDelivery = spec.deliverable === "artifact" && spec.work_role !== "research";
|
|
727
|
+
// Artifact packs are not git landings — do not demand head_commit when the planner put
|
|
728
|
+
// `.conduit/artifacts/**` in change_scope as a write hint (path-4 canary incident).
|
|
729
|
+
validateChangeScope(report, spec.change_scope ?? [], { requireHeadCommit: !artifactDelivery });
|
|
713
730
|
if (artifactDelivery) {
|
|
714
731
|
const published = report.evidence.some((item) => {
|
|
715
732
|
if (!["preview", "research", "documentation"].includes(item.kind))
|
|
@@ -776,11 +793,15 @@ function referenceKind(kind) {
|
|
|
776
793
|
return "document";
|
|
777
794
|
return "other";
|
|
778
795
|
}
|
|
779
|
-
export function validateChangeScope(report, changeScope) {
|
|
796
|
+
export function validateChangeScope(report, changeScope, options = {}) {
|
|
780
797
|
if (!changeScope.length)
|
|
781
798
|
return;
|
|
782
|
-
|
|
799
|
+
const requireHeadCommit = options.requireHeadCommit !== false;
|
|
800
|
+
if (requireHeadCommit && !report.head_commit) {
|
|
783
801
|
throw new Error("Repository changes require a delivered head commit");
|
|
802
|
+
}
|
|
803
|
+
if (!report.changes.length)
|
|
804
|
+
return;
|
|
784
805
|
const paths = report.changes.map((change) => change.split(/\s+[—-]\s+/, 1)[0].trim().replaceAll("\\", "/"));
|
|
785
806
|
const outside = paths.filter((path) => !path || path.startsWith("/") || path.split("/").includes("..") || !changeScope.some((scope) => pathMatchesScope(path, scope)));
|
|
786
807
|
if (outside.length)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miraland-labs/conduit-bridge",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "Conduit Bridge CLI — join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity agents for a Conduit organization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|