@miraland-labs/conduit-bridge 0.9.10 → 0.9.12
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/driver.js +35 -6
- package/dist/execution.js +7 -4
- 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.9.
|
|
5
|
+
**Package version:** `0.9.12` — heartbeat protocol 2 requires a clean workspace plus a ready, versioned driver lane before dispatch. Cursor assignments with `external_network` allow-list `WebFetch(*)` and pass `--force` (required for headless fetch); research-only networked runs also deny `Shell(*)` / `Write(**)`. Artifact evidence digests normalize bare 64-hex to `sha256:<hex>`. Attempt worktrees fetch/start from the initiative base when the source tip is behind or history was rewritten, without resetting the source checkout. Also includes **ops** helpers for macOS / Linux / Windows, LaunchAgent/systemd **PATH** for `~/.local/bin`, disconnect/disengage, multi-driver lanes, shared slots **1–4**, worktrees, and optional `--ensure-checkout`. Protocol 1 heartbeats remain compatible for presence but cannot receive work.
|
|
6
6
|
|
|
7
7
|
## Prerequisites
|
|
8
8
|
|
package/dist/driver.js
CHANGED
|
@@ -174,7 +174,7 @@ export function buildAssignmentPrompt(context) {
|
|
|
174
174
|
// `git status` stays clean — which is the point for a content pack, and mandatory for
|
|
175
175
|
// anything binary.
|
|
176
176
|
"- This assignment delivers an artifact, not repository content. Write your output under `.conduit/artifacts/` — that path is excluded from git, so do not commit anything and do not report a head_commit.",
|
|
177
|
-
"- Publish the artifact to its destination and report each output in your evidence with the published URL and
|
|
177
|
+
"- Publish the artifact to its destination and report each output in your evidence with the published HTTPS URL and digest exactly as `sha256:` plus the 64-character hex hash (bare hex from shasum is normalized, but prefer the prefixed form). An artifact nobody can fetch is not a delivery.",
|
|
178
178
|
]
|
|
179
179
|
: ["- You have no repository write authority for this assignment. Do not create, modify, or commit any file; a delivery reporting repository changes will be rejected. Record all findings, verification output, and conclusions in your final report instead."]), "- Never merge, deploy, force-push, push to protected branches, or touch production.", `- Hard-denied commands (all drivers): ${deniedCommands.join("; ")}.`, "- Do not invent evidence. Report unknown when you could not verify a criterion.",
|
|
180
180
|
// A met claim with nothing backing it is rejected server-side ("Met acceptance criteria require
|
|
@@ -222,6 +222,19 @@ export function parseAgentReport(text, acceptance) {
|
|
|
222
222
|
}
|
|
223
223
|
return { ...parsed.data, acceptance_results: parsed.data.acceptance_results.map((item) => ({ ...item, evidence_artifact_ids: [] })) };
|
|
224
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Artifact publication receipts require `sha256:<64 hex>`. Agents often paste bare `shasum` output;
|
|
227
|
+
* accept that and canonicalize. Already-prefixed digests pass through (case-normalized hex).
|
|
228
|
+
*/
|
|
229
|
+
export function normalizeEvidenceDigest(digest) {
|
|
230
|
+
const trimmed = digest.trim();
|
|
231
|
+
const prefixed = /^sha256:([0-9a-f]{64})$/i.exec(trimmed);
|
|
232
|
+
if (prefixed)
|
|
233
|
+
return `sha256:${prefixed[1].toLowerCase()}`;
|
|
234
|
+
if (/^[0-9a-f]{64}$/i.test(trimmed))
|
|
235
|
+
return `sha256:${trimmed.toLowerCase()}`;
|
|
236
|
+
return trimmed;
|
|
237
|
+
}
|
|
225
238
|
/**
|
|
226
239
|
* Tolerate benign agent-output noise before strict validation: drop empty/whitespace-only entries from
|
|
227
240
|
* string arrays and remove empty optional scalars, so a single stray "" does not fail an otherwise-valid
|
|
@@ -251,6 +264,8 @@ function sanitizeReportShape(raw) {
|
|
|
251
264
|
// Agents emit null/"" for optional fields they have no value for — treat as absent.
|
|
252
265
|
if ("digest" in evidence && (typeof evidence.digest !== "string" || evidence.digest.trim() === ""))
|
|
253
266
|
delete evidence.digest;
|
|
267
|
+
else if (typeof evidence.digest === "string")
|
|
268
|
+
evidence.digest = normalizeEvidenceDigest(evidence.digest);
|
|
254
269
|
// Drop non-URL / empty uris so an optional bad uri doesn't fail the whole report.
|
|
255
270
|
if ("uri" in evidence && (typeof evidence.uri !== "string" || !z.string().url().max(4_000).safeParse(evidence.uri).success))
|
|
256
271
|
delete evidence.uri;
|
|
@@ -440,10 +455,12 @@ export const codexDriver = {
|
|
|
440
455
|
* Current Cursor Agent schema accepts only `permissions.{allow,deny}` (no
|
|
441
456
|
* `version` / `approvalMode`). Outside the allow list is denied without --force.
|
|
442
457
|
*
|
|
443
|
-
* `external_network` maps to WebFetch(*)
|
|
444
|
-
*
|
|
445
|
-
* `--force
|
|
446
|
-
*
|
|
458
|
+
* `external_network` maps to WebFetch(*) plus `--force` on the run (see
|
|
459
|
+
* cursorRunArgs). Field evidence: allow-listing WebFetch alone still yields
|
|
460
|
+
* "User Rejected" under headless `-p`; `--force` is required for fetch to
|
|
461
|
+
* succeed. Because `--force` auto-allows unlisted tools, research-only
|
|
462
|
+
* networked assignments also deny Shell(*) and Write(**). Codex's parallel is
|
|
463
|
+
* sandbox_workspace_write.network_access=true.
|
|
447
464
|
*/
|
|
448
465
|
export function cursorPermissionsForGrants(grants, verificationCommands = [], capabilities = []) {
|
|
449
466
|
const allow = [];
|
|
@@ -457,7 +474,16 @@ export function cursorPermissionsForGrants(grants, verificationCommands = [], ca
|
|
|
457
474
|
// Cursor docs: WebFetch(domainOrPattern); WebFetch(*) auto-approves any domain.
|
|
458
475
|
if (capabilities.includes("external_network"))
|
|
459
476
|
allow.push("WebFetch(*)");
|
|
460
|
-
|
|
477
|
+
const deny = deniedCommands.map((command) => `Shell(${command})`);
|
|
478
|
+
// --force opens anything not denied; lock shell/write when the assignment has no mutate/shell grants.
|
|
479
|
+
if (capabilities.includes("external_network")
|
|
480
|
+
&& !grants.includes("repo_write")
|
|
481
|
+
&& !grants.includes("test_run")
|
|
482
|
+
&& !grants.includes("branch_create")
|
|
483
|
+
&& !grants.includes("pr_create")) {
|
|
484
|
+
deny.push("Shell(*)", "Write(**)");
|
|
485
|
+
}
|
|
486
|
+
return { allow, deny };
|
|
461
487
|
}
|
|
462
488
|
/**
|
|
463
489
|
* Cursor CLI arguments. Deliberately no `--mode plan` for read-only assignments: plan mode is not an
|
|
@@ -475,6 +501,9 @@ export function cursorRunArgs(input) {
|
|
|
475
501
|
const args = ["-p", "--output-format", "json", "--workspace", input.workspace];
|
|
476
502
|
if (input.trustWorkspace)
|
|
477
503
|
args.push("--trust");
|
|
504
|
+
// Headless WebFetch requires --force even when WebFetch(*) is allow-listed (Cursor CLI 2026.07+).
|
|
505
|
+
if (input.capabilities?.includes("external_network"))
|
|
506
|
+
args.push("--force");
|
|
478
507
|
if (input.model)
|
|
479
508
|
args.push("--model", input.model);
|
|
480
509
|
if (input.resumeSessionId)
|
package/dist/execution.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { ConduitRequestError } from "./client.js";
|
|
4
4
|
import { redactSecrets } from "./config.js";
|
|
5
|
-
import { DRIVERS, agentReportTemplate, buildAssignmentPrompt, evidenceKinds, fuelEndpoint, parseAgentReport, pickModelCandidate, tierForRisk } from "./driver.js";
|
|
5
|
+
import { DRIVERS, agentReportTemplate, buildAssignmentPrompt, evidenceKinds, fuelEndpoint, normalizeEvidenceDigest, parseAgentReport, pickModelCandidate, tierForRisk } from "./driver.js";
|
|
6
6
|
import { pickDriverForClaim, resolveDriverFuel } from "./drivers.js";
|
|
7
7
|
import { attemptWorktreePath, createAttemptWorktree, proveResumeWorktree, quarantineAttemptWorktree, removeAttemptWorktree, } from "./attempt-worktree.js";
|
|
8
8
|
import { buildWorkspaceBrief, ensureCommitAvailable, normalizeRepositoryUrl, resolveAttemptStartCommit } from "./brief.js";
|
|
@@ -629,9 +629,9 @@ async function prepareDelivery(client, attemptId, taskId, report) {
|
|
|
629
629
|
// Publication receipts require a digest on every acceptance artifact. Textual agent
|
|
630
630
|
// evidence has no file to hash, so default to a canonical content digest of the
|
|
631
631
|
// evidence itself — deterministic and verifiable against the stored metadata.
|
|
632
|
-
const digest = item.digest ?? `sha256:${createHash("sha256")
|
|
632
|
+
const digest = normalizeEvidenceDigest(item.digest ?? `sha256:${createHash("sha256")
|
|
633
633
|
.update(JSON.stringify({ kind: item.kind, name: item.name, details: item.details, acceptance_criteria: item.acceptance_criteria }))
|
|
634
|
-
.digest("hex")}
|
|
634
|
+
.digest("hex")}`);
|
|
635
635
|
const response = await client.attemptRequest(taskId, "artifacts", {
|
|
636
636
|
kind: item.kind, name: item.name, uri, digest,
|
|
637
637
|
metadata: { details: item.details, acceptance_criteria: item.acceptance_criteria },
|
|
@@ -666,7 +666,10 @@ export function validateDeliveryReport(report, spec, grants = []) {
|
|
|
666
666
|
const published = report.evidence.some((item) => {
|
|
667
667
|
if (!["preview", "research", "documentation"].includes(item.kind))
|
|
668
668
|
return false;
|
|
669
|
-
if (!item.uri || !item.digest
|
|
669
|
+
if (!item.uri || !item.digest)
|
|
670
|
+
return false;
|
|
671
|
+
const digest = normalizeEvidenceDigest(item.digest);
|
|
672
|
+
if (!/^sha256:[0-9a-f]{64}$/i.test(digest))
|
|
670
673
|
return false;
|
|
671
674
|
try {
|
|
672
675
|
const protocol = new URL(item.uri).protocol;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miraland-labs/conduit-bridge",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.12",
|
|
4
4
|
"description": "Conduit Bridge CLI — join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Kiro / Antigravity agents for a Conduit organization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|