@miraland-labs/conduit-bridge 0.4.0 → 0.4.1

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 CHANGED
@@ -46,7 +46,7 @@ Every driver enforces the assignment's granted actions with its CLI's native mec
46
46
  | --- | --- | --- |
47
47
  | `claude-code` | pump or local | `--allowedTools` / `--disallowedTools` per grant; bounded verification commands |
48
48
  | `codex` | pump or local | sandbox `read-only` / `workspace-write`; network enabled only with `pr_create`; resumes by thread id |
49
- | `opencode` | pump or local | `plan` agent (no edit/bash) for read-only, `build` for write; `--session` resume; `--format json` |
49
+ | `opencode` | pump or local | `plan`/`build` agents; per-run `opencode.json` bash deny list (shared `deniedCommands`); `--session` resume; `--format json` |
50
50
  | `cursor` | local only | per-run `.cursor/cli.json` allowlist (`approvalMode: allowlist`, shared deny list, no `--force`); read-only runs in `--mode plan` |
51
51
  | `kiro` | local only | `--trust-tools=fs_read,fs_write,execute_bash` allowlist; `--no-interactive`; `--resume-id`. Tool-level only — no per-command scoping, so a bash grant trusts `execute_bash` wholesale (Antigravity-tier) |
52
52
  | `antigravity` | local only | `-p --mode plan` (read-only) / `--mode accept-edits --sandbox` (write). Mode-level, coarser than per-tool; plain-text output; resume not surfaced |
package/dist/client.js CHANGED
@@ -52,13 +52,21 @@ export class ConduitClient {
52
52
  delete this.config.activeAttempts[taskId];
53
53
  await this.persist(this.config);
54
54
  }
55
- /** Rotate and cache a project-scoped gateway fuel key for agent /v1 calls. */
55
+ /**
56
+ * Ensure a project-scoped gateway fuel key for agent /v1 calls.
57
+ * GET creates once (returns secret) or reports provisioned; cache miss after
58
+ * that uses POST …/rotate so heartbeats never rotate on every claim.
59
+ */
56
60
  async ensureFuel(projectId) {
57
61
  const cached = this.config.fuel?.[projectId]?.gatewayKey;
58
62
  if (cached)
59
63
  return cached;
60
- const data = await this.request(`/runner/v1/fuel/${projectId}`);
61
- const gatewayKey = String(data.gateway_secret);
64
+ const peeked = await this.request(`/runner/v1/fuel/${projectId}`);
65
+ let gatewayKey = typeof peeked.gateway_secret === "string" ? peeked.gateway_secret : "";
66
+ if (!gatewayKey) {
67
+ const rotated = await this.request(`/runner/v1/fuel/${projectId}/rotate`, { method: "POST", body: "{}" });
68
+ gatewayKey = String(rotated.gateway_secret);
69
+ }
62
70
  this.config.fuel = { ...this.config.fuel, [projectId]: { gatewayKey } };
63
71
  await this.persist(this.config);
64
72
  return gatewayKey;
package/dist/driver.js CHANGED
@@ -73,7 +73,7 @@ export function buildAssignmentPrompt(context) {
73
73
  }
74
74
  if (rework)
75
75
  lines.push("", `REWORK FEEDBACK — an independent review returned this delivery; address every point\n${rework}`);
76
- lines.push("", "RULES", "- Stay within the change scope and boundaries.", "- Run the relevant verification commands if your permissions allow it, and report their real results.", "- Never merge, deploy, push to protected branches, or touch production.", "- Do not invent evidence. Report unknown when you could not verify a criterion.", "", "When the work is finished, end your reply with exactly one fenced ```json block:", '{"outcome": "one-paragraph summary", "changes": ["path — what changed"], "verification": ["command — result"], "acceptance_results": [{"criterion": "exact criterion text", "status": "met|not_met|unknown"}], "evidence": [{"kind": "change|test|preview|research|documentation", "name": "concise evidence name", "uri": "external URL if one exists", "digest": "optional digest", "details": ["observable result"], "acceptance_criteria": ["exact criterion text supported by this evidence"]}], "assumptions": [], "risks": [], "limitations": [], "head_commit": "full sha of your final commit, omit if none"}');
76
+ lines.push("", "RULES", "- Stay within the change scope and boundaries.", "- Run the relevant verification commands if your permissions allow it, and report their real results.", "- Never merge, deploy, 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.", "", "When the work is finished, end your reply with exactly one fenced ```json block:", '{"outcome": "one-paragraph summary", "changes": ["path — what changed"], "verification": ["command — result"], "acceptance_results": [{"criterion": "exact criterion text", "status": "met|not_met|unknown"}], "evidence": [{"kind": "change|test|preview|research|documentation", "name": "concise evidence name", "uri": "external URL if one exists", "digest": "optional digest", "details": ["observable result"], "acceptance_criteria": ["exact criterion text supported by this evidence"]}], "assumptions": [], "risks": [], "limitations": [], "head_commit": "full sha of your final commit, omit if none"}');
77
77
  return lines.join("\n");
78
78
  }
79
79
  /** Parse the agent's final fenced JSON block into a bounded report. */
@@ -383,6 +383,54 @@ export function openCodeRunArgs(input, agent) {
383
383
  args.push("--session", input.resumeSessionId);
384
384
  return args;
385
385
  }
386
+ /** Shared deny list as OpenCode `permission.bash` rules (enforced even with `--auto`). */
387
+ export function openCodePermissionConfig() {
388
+ const bash = {};
389
+ for (const command of deniedCommands)
390
+ bash[`${command} *`] = "deny";
391
+ return { permission: { bash } };
392
+ }
393
+ /** Write opencode.json permission deny rules for the run, restoring any prior file. */
394
+ async function withOpenCodePermissions(workspace, run) {
395
+ const configPath = join(workspace, "opencode.json");
396
+ let previous = null;
397
+ try {
398
+ previous = await readFile(configPath, "utf8");
399
+ }
400
+ catch {
401
+ previous = null;
402
+ }
403
+ let config = {};
404
+ if (previous) {
405
+ try {
406
+ config = { ...JSON.parse(previous) };
407
+ }
408
+ catch {
409
+ config = {};
410
+ }
411
+ }
412
+ const deny = openCodePermissionConfig();
413
+ const existingPermission = (config.permission && typeof config.permission === "object" && !Array.isArray(config.permission))
414
+ ? { ...config.permission }
415
+ : {};
416
+ const existingBash = (existingPermission.bash && typeof existingPermission.bash === "object" && !Array.isArray(existingPermission.bash))
417
+ ? { ...existingPermission.bash }
418
+ : {};
419
+ config.permission = {
420
+ ...existingPermission,
421
+ bash: { ...existingBash, ...deny.permission.bash },
422
+ };
423
+ await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
424
+ try {
425
+ return await run();
426
+ }
427
+ finally {
428
+ if (previous === null)
429
+ await rm(configPath, { force: true });
430
+ else
431
+ await writeFile(configPath, previous, "utf8");
432
+ }
433
+ }
386
434
  /**
387
435
  * OpenCode `--format json` streams JSONL events. Verified against opencode 1.18:
388
436
  * assistant text arrives as `{type:"text", part:{type:"text", text:"…"}}`, the
@@ -434,7 +482,7 @@ export const openCodeDriver = {
434
482
  }
435
483
  const args = openCodeRunArgs(input, agent);
436
484
  args.push(input.prompt);
437
- const { code, stdout, stderr } = await execute(input.executable ?? "opencode", args, input.workspace, input.timeoutMs ?? 20 * 60_000, fuelSource === "conduit" ? input.fuel : undefined, fuelSource);
485
+ const { code, stdout, stderr } = await withOpenCodePermissions(input.workspace, () => execute(input.executable ?? "opencode", args, input.workspace, input.timeoutMs ?? 20 * 60_000, fuelSource === "conduit" ? input.fuel : undefined, fuelSource));
438
486
  const parsed = parseOpenCodeOutput(stdout);
439
487
  if (code !== 0 || parsed.isError) {
440
488
  return { status: "failed", resultText: parsed.resultText, sessionId: parsed.sessionId, error: (stderr || parsed.resultText || `opencode exited with code ${code}`).slice(0, 20_000) };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Conduit Bridge CLI — join, connect, and run Claude Code / Codex / Cursor / OpenCode / Kiro / Antigravity agents for a Conduit organization",
5
5
  "type": "module",
6
6
  "bin": {