@miraland-labs/conduit-bridge 0.16.132 → 0.16.135

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/config.js CHANGED
@@ -29,22 +29,6 @@ const runtimePath = join(directory, "runtime.json");
29
29
  const lockPath = join(directory, "config.lock");
30
30
  const pendingPath = join(directory, "pending-connect.json");
31
31
  const installationPath = join(directory, "installation.json");
32
- /**
33
- * Prefer runtime.json; when absent, adopt legacy activeAttempts/sessions still on config.json
34
- * so one Bridge upgrade does not strand in-flight claims.
35
- */
36
- export function adoptLegacyRuntimeClaims(input) {
37
- if (input.runtime) {
38
- return {
39
- activeAttempts: input.runtime.activeAttempts ?? {},
40
- sessions: input.runtime.sessions,
41
- };
42
- }
43
- return {
44
- activeAttempts: input.legacyAttempts,
45
- sessions: input.legacySessions,
46
- };
47
- }
48
32
  export async function loadConfig() {
49
33
  const config = await loadConfigIfPresent();
50
34
  if (!config)
@@ -71,13 +55,9 @@ export async function loadConfigIfPresent() {
71
55
  }
72
56
  }
73
57
  const runtime = await loadRuntime();
74
- const adopted = adoptLegacyRuntimeClaims({
75
- runtime,
76
- legacyAttempts: config.activeAttempts && typeof config.activeAttempts === "object" ? config.activeAttempts : {},
77
- legacySessions: config.sessions && typeof config.sessions === "object" ? config.sessions : undefined,
78
- });
79
- config.activeAttempts = adopted.activeAttempts;
80
- config.sessions = adopted.sessions;
58
+ // Claims live only in runtime.json; a fresh install has none.
59
+ config.activeAttempts = runtime?.activeAttempts ?? {};
60
+ config.sessions = runtime?.sessions;
81
61
  for (const active of Object.values(config.activeAttempts))
82
62
  active.phase ??= "agent_running";
83
63
  if (config.fuelSource !== "local" && config.fuelSource !== "conduit")
@@ -264,24 +244,10 @@ export async function saveConfig(config, options = {}) {
264
244
  diskPrefs = JSON.parse(await readFile(path, "utf8"));
265
245
  }
266
246
  catch { /* no prior config */ }
267
- // One-time migrate: if runtime.json is missing, preserve legacy activeAttempts from config.json.
268
- const existingRuntime = await loadRuntime();
269
- if (!existingRuntime && diskPrefs) {
270
- const legacyAttempts = diskPrefs.activeAttempts && typeof diskPrefs.activeAttempts === "object"
271
- ? diskPrefs.activeAttempts
272
- : {};
273
- const legacySessions = diskPrefs.sessions && typeof diskPrefs.sessions === "object"
274
- ? diskPrefs.sessions
275
- : undefined;
276
- if (Object.keys(legacyAttempts).length || legacySessions) {
277
- await writeJsonAtomic(runtimePath, { activeAttempts: legacyAttempts, sessions: legacySessions });
278
- }
279
- }
280
247
  const prefs = { ...snapshot, drivers: mergeCurrentDriverQuota(diskPrefs, snapshot.drivers) };
281
248
  delete prefs.activeAttempts;
282
249
  delete prefs.sessions;
283
- const prefsForDisk = { ...prefs, activeAttempts: {}, sessions: undefined };
284
- await writeJsonAtomic(path, prefsForDisk);
250
+ await writeJsonAtomic(path, prefs);
285
251
  if (!options.prefsOnly) {
286
252
  const disk = await loadRuntime();
287
253
  await writeJsonAtomic(runtimePath, mergeRuntimeState(disk, {
@@ -342,7 +308,7 @@ async function updateDriverLane(driverId, update) {
342
308
  if (!lane || typeof lane !== "object")
343
309
  return;
344
310
  update(lane);
345
- await writeJsonAtomic(path, { ...prefs, activeAttempts: {}, sessions: undefined });
311
+ await writeJsonAtomic(path, prefs);
346
312
  });
347
313
  };
348
314
  const next = saveChain.then(run, run);
package/dist/driver.js CHANGED
@@ -787,15 +787,6 @@ export function claudeCodeFuelEnv(fuelSource, model) {
787
787
  return undefined;
788
788
  return { ANTHROPIC_MODEL: model, ANTHROPIC_SMALL_FAST_MODEL: model, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" };
789
789
  }
790
- /** Map Conduit grants → Codex sandbox (privileged grants never widen the sandbox). */
791
- export function codexSandboxForGrants(grants) {
792
- if (grants.includes("repo_write") || grants.includes("branch_create") || grants.includes("pr_create") || grants.includes("test_run")) {
793
- return "workspace-write";
794
- }
795
- if (grants.includes("repo_read"))
796
- return "read-only";
797
- return null;
798
- }
799
790
  /**
800
791
  * Codex exec arguments. The sandbox is the enforcement boundary: network stays
801
792
  * off inside workspace-write unless the class/capabilities require it.
@@ -2075,12 +2066,6 @@ export function hasGrokLogin(env = process.env) {
2075
2066
  // `~/.grok` exists after install; auth.json is the `grok login` / cached_token file.
2076
2067
  return Boolean(home && existsSync(join(home, ".grok", "auth.json")));
2077
2068
  }
2078
- /** True when local fuel can use a host vendor login for at least one supported agent. */
2079
- export function hasLocalVendorLogin(env = process.env) {
2080
- return hasClaudeLogin(env) || hasOpenAiLogin(env) || hasCursorLogin(env)
2081
- || hasOpenCodeLogin(env) || hasKiroLogin(env) || hasAntigravityLogin(env)
2082
- || hasGrokLogin(env);
2083
- }
2084
2069
  /** Exported for tests — builds the stripped process env with optional Conduit fuel. */
2085
2070
  export function agentProcessEnv(fuel, fuelSource = "conduit", extraEnv) {
2086
2071
  return boundedEnvironment(fuel, fuelSource, extraEnv);
package/dist/execution.js CHANGED
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
3
3
  import { z } from "zod";
4
4
  import { resolveAgentTimeout } from "./execution-budget.js";
5
5
  import { AttemptStalledError, createAttemptWatchdog, withTimeout } from "./attempt-watchdog.js";
6
+ import { workPackageSchema } from "./work-package-schema.js";
6
7
  import { ConduitRequestError, reportAssignmentPollShapeMismatch } from "./client.js";
7
8
  import { redactSecrets, saveDriverOutcome, saveDriverQuota, tierCandidates } from "./config.js";
8
9
  import { DRIVERS, acceptanceCriteriaLines, acceptanceReportSkeleton, agentReportTemplate, buildAssignmentPrompt, evidenceKinds, extractAgentReportJsonText, fuelEndpoint, normalizeEvidenceDigest, parseAgentReport, parseJsonObjectCandidate, pickModelCandidate, isSafeModelName, driverExecutable, requireStampedExecutionClass, tierForRisk } from "./driver.js";
@@ -412,30 +413,6 @@ const executionContractSchema = z.object({
412
413
  prior_pull_request_url: z.string().nullable().optional().default(null),
413
414
  prior_head_commit: z.string().nullable().optional().default(null),
414
415
  });
415
- const workPackageSchema = z.object({
416
- work_role: z.string().nullable().optional(),
417
- goal: z.string().nullable().optional(),
418
- scope: z.array(z.string()).optional(),
419
- boundaries: z.array(z.string()).optional(),
420
- acceptance: z.array(z.string()).optional(),
421
- change_scope: z.array(z.string()).optional(),
422
- required_evidence: z.array(z.string()).optional(),
423
- deliverable: z.enum(["repository", "artifact"]).optional().default("repository"),
424
- initiative: z.object({
425
- title: z.string().nullable().optional(),
426
- desired_outcome: z.string().nullable().optional(),
427
- boundaries: z.array(z.string()).optional(),
428
- context: z.string().max(20_000).nullable().optional(),
429
- }).optional(),
430
- decisions: z.array(z.object({ question: z.string(), decision: z.string() })).optional(),
431
- rework_feedback: z.string().nullable().optional(),
432
- diagnostic_context: z.object({
433
- source_attempt_id: z.string().uuid(),
434
- failure_output: z.string().min(1).max(12_000),
435
- }).optional(),
436
- working_language: z.enum(["en", "zh"]).optional(),
437
- max_duration_ms: z.number().int().min(60_000).max(4 * 60 * 60_000).optional(),
438
- }).nullable().optional();
439
416
  const diagnosticRepairBriefSchema = z.object({
440
417
  root_cause: z.string().trim().min(1).max(2_000),
441
418
  wrong_approach: z.string().trim().max(2_000).default(""),
@@ -1052,7 +1029,15 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
1052
1029
  const detail = await client.request(`/runner/v1/tasks/${taskId}`);
1053
1030
  const task = taskDetailSchema.parse(detail.task);
1054
1031
  const executionContract = executionContractSchema.parse(detail.execution_contract ?? {});
1055
- const workPackage = workPackageSchema.parse(detail.work_package) ?? null;
1032
+ const parsedPackage = workPackageSchema.safeParse(detail.work_package);
1033
+ if (!parsedPackage.success) {
1034
+ // A strict refusal before any worktree or agent spend: a key this Bridge does not know would
1035
+ // otherwise be dropped with no error, and the agent would run without it.
1036
+ const issues = parsedPackage.error.issues.map((issue) => `${issue.path.join(".") || "work_package"}: ${issue.message}`).join("; ");
1037
+ await queueTerminal(client, taskId, { action: "fail", body: { error: `Work package refused: ${issues}`, retryable: false, failure: bridgeCause("work_package_invalid", issues), idempotency_key: `bridge:work-package-invalid:${active.attemptId}` } });
1038
+ return;
1039
+ }
1040
+ const workPackage = parsedPackage.data;
1056
1041
  const agentTimeout = resolveAgentTimeout({
1057
1042
  machineCeilingMs: timeoutMs,
1058
1043
  packageBudgetMs: workPackage?.max_duration_ms,
@@ -128,6 +128,7 @@ export const BRIDGE_CAUSES = {
128
128
  agent_execution_failed: { class: "transient", disposition: "retry", responsible_party: "conduit", message: "The agent run failed before delivery.", next_action: "Conduit will retry with bounded backoff while the authoring budget remains. No technical constraints are required from the owner." },
129
129
  verification_failed_after_run: { class: "transient", disposition: "retry", responsible_party: "conduit", message: "The agent run failed its own verification before delivery.", next_action: "Conduit will diagnose the retained failed run and brief the next authoring attempt." },
130
130
  bridge_finalize_failed: { class: "platform", disposition: "stop", responsible_party: "conduit", message: "Bridge stopped while finishing the delivery report.", next_action: "Inspect Bridge logs for the finalize fault. Fix Bridge or open a revision if the plan is wrong, then Retry." },
131
+ work_package_invalid: { class: "platform", disposition: "stop", responsible_party: "conduit", message: "Bridge refused this work package: this Bridge release does not know its shape.", next_action: "Update Bridge and Conduit to the same release, then Retry. The work package itself needs no change from you." },
131
132
  bridge_preflight_missing: { class: "environment", disposition: "hold", responsible_party: "computer_operator", message: "This computer has not reported Bridge readiness.", next_action: "Run Bridge Doctor and reinstall or restart the background service, then recheck readiness." },
132
133
  bridge_preflight_stale: { class: "environment", disposition: "hold", responsible_party: "computer_operator", message: "This computer's Bridge readiness report is stale.", next_action: "Wake or restart the Bridge and wait for a fresh heartbeat, then recheck readiness." },
133
134
  hosted_computer: { class: "environment", disposition: "hold", responsible_party: "conduit", message: "This hosted computer is Conduit's responsibility.", next_action: "Conduit fixes the hosted computer; Recheck runs the work package again." },
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The work package: what the control plane sends with a claimed attempt, and what Bridge runs.
4
+ *
5
+ * The Bridge source is packages/conduit-bridge/src/work-package-schema.ts. The control-plane copy at
6
+ * src/conductor/work-package-schema.ts is generated by `npm run sync:twins`. Do not edit that copy.
7
+ * Bridge cannot import from the control plane, and the other way round.
8
+ *
9
+ * One declaration for both ends. The control plane's builder returns `WorkPackage`, so a key it
10
+ * emits and this schema does not name is a type error; Bridge parses the package strictly, so a key
11
+ * it does not know is a refusal, never a silent drop. Every change to this schema raises
12
+ * `MINIMUM_BRIDGE_VERSION` in the same change, so the two copies cannot disagree at runtime.
13
+ */
14
+ export const workPackageSchema = z.object({
15
+ organization_id: z.string(),
16
+ project_id: z.string(),
17
+ /** implement | review | research | diagnose — guides agent behavior; matching still uses capabilities. */
18
+ work_role: z.enum(["implement", "review", "research", "diagnose"]).nullable(),
19
+ objective: z.string(),
20
+ goal: z.string().nullable(),
21
+ scope: z.array(z.string()),
22
+ boundaries: z.array(z.string()),
23
+ acceptance: z.array(z.string()),
24
+ change_scope: z.array(z.string()),
25
+ required_evidence: z.array(z.string()),
26
+ normative_refs: z.array(z.object({
27
+ label: z.string(),
28
+ repository: z.string(),
29
+ paths: z.array(z.string()),
30
+ revision: z.string().optional(),
31
+ markers: z.array(z.string()).optional(),
32
+ }).strict()),
33
+ /** repository | artifact — artifact output is produced and published, never committed. */
34
+ deliverable: z.enum(["repository", "artifact"]),
35
+ risk_level: z.string().nullable(),
36
+ /** Server-derived per-agent-turn request; Bridge clamps it to the machine ceiling. */
37
+ max_duration_ms: z.number().int().min(60_000).max(4 * 60 * 60_000).optional(),
38
+ grants: z.array(z.string()),
39
+ repository: z.object({ url: z.string().optional(), base_commit: z.string().optional() }).strict().nullable(),
40
+ initiative: z.object({
41
+ id: z.string().nullable(),
42
+ objective: z.string().nullable(),
43
+ title: z.string().nullable(),
44
+ desired_outcome: z.string().nullable(),
45
+ boundaries: z.array(z.string()),
46
+ /**
47
+ * The brief's own context, verbatim. The planner distils the brief into the package, and a brief
48
+ * that is mostly data (release notes, a catalog diff) lost that data on the way to the agent.
49
+ */
50
+ context: z.string().max(20_000).nullable(),
51
+ }).strict(),
52
+ /** Explicit human decisions for this project — bounded, never auto-promoted chat. */
53
+ decisions: z.array(z.object({ question: z.string(), decision: z.string() }).strict()),
54
+ rework_feedback: z.string().nullable(),
55
+ /** Present only for Conductor's internal, read-only Option C assignment. */
56
+ diagnostic_context: z.object({
57
+ source_attempt_id: z.string().uuid(),
58
+ failure_output: z.string().min(1).max(12_000),
59
+ }).strict().optional(),
60
+ /** Working/Delivery language for Bridge assignment prose. */
61
+ working_language: z.enum(["en", "zh"]).optional(),
62
+ }).strict();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.132",
3
+ "version": "0.16.135",
4
4
  "description": "Conduit Bridge CLI \u2014 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": {