@miraland-labs/conduit-bridge 0.16.193 → 0.16.195

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/cli.js CHANGED
@@ -805,7 +805,8 @@ function startGateRehearsal(workspace, plan) {
805
805
  .then((rehearsal) => {
806
806
  if (rehearsal === "busy")
807
807
  console.log("Gate rehearsal deferred: another runner on this computer is rehearsing");
808
- else if (abort.signal.aborted && attemptsActiveOnComputer())
808
+ // The attempt-marker watch stops the rehearsal on its own: the runner's abort stays unset.
809
+ else if (rehearsal === null && attemptsActiveOnComputer())
809
810
  console.log("Gate rehearsal stopped: an attempt is in flight on this computer");
810
811
  else if (abort.signal.aborted)
811
812
  console.log("Gate rehearsal stopped: an assignment is waiting");
package/dist/driver.js CHANGED
@@ -45,7 +45,7 @@ function deliveryLanguageRule(language) {
45
45
  return null;
46
46
  }
47
47
  export const evidenceKinds = ["change", "test", "preview", "research", "documentation"];
48
- export const agentReportTemplate = '{"outcome": "one-paragraph summary", "changes": ["path from the repository root — what changed"], "verification": ["command — result"], "acceptance_results": [{"criterion": "criterion key, for example c1", "status": "met|not_met|unknown", "unverified_reason": "omit unless status is unknown", "unverified_detail": "one sentence naming what was missing; omit unless status is 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": ["criterion keys this evidence supports, for example c1"]}], "assumptions": [], "risks": [], "limitations": [], "head_commit": "full sha of your final commit, omit if none"}';
48
+ export const agentReportTemplate = '{"outcome": "one-paragraph summary", "changes": ["path from the repository root — what changed"], "verification": ["command — result"], "acceptance_results": [{"criterion": "criterion key, for example c1", "status": "met|not_met|unknown", "unverified_reason": "omit unless status is unknown", "unverified_detail": "one sentence naming what was missing; omit unless status is 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": ["criterion keys this evidence supports, for example c1"]}], "assumptions": [], "risks": [], "limitations": [], "commit_message": "commit message for scoped changes, omit if none", "head_commit": "full sha of your final commit, omit if none"}';
49
49
  /**
50
50
  * Resolve an operator's ordered tier candidates against the live model list.
51
51
  * Picks the first safe candidate the CLI currently offers; without a live list
@@ -457,6 +457,7 @@ function quoteCriterion(value) {
457
457
  }
458
458
  const reportStringList = z.array(z.string().trim().min(1).max(4_000)).max(100);
459
459
  export const agentReportSchema = z.object({
460
+ commit_message: z.string().optional(),
460
461
  outcome: z.string().trim().min(1).max(20_000),
461
462
  changes: reportStringList,
462
463
  verification: reportStringList,
@@ -474,6 +475,7 @@ export const agentReportSchema = z.object({
474
475
  }).strict();
475
476
  /** Strict generation requires every key; explicit null carries an optional field's absence. */
476
477
  export const agentReportOutputSchema = agentReportSchema.extend({
478
+ commit_message: z.string().nullable().transform((value) => value ?? undefined),
477
479
  acceptance_results: z.array(agentReportSchema.shape.acceptance_results.element.extend({
478
480
  unverified_reason: z.enum(UNVERIFIED_REASONS).nullable().transform((value) => value ?? undefined),
479
481
  unverified_detail: z.string().trim().min(1).max(500).nullable().transform((value) => value ?? undefined),
@@ -485,11 +487,35 @@ export const agentReportOutputSchema = agentReportSchema.extend({
485
487
  head_commit: z.string().regex(/^[0-9a-f]{7,64}$/i).nullable().transform((value) => value ?? undefined),
486
488
  pull_request_url: z.string().url().max(4_000).nullable().transform((value) => value ?? undefined),
487
489
  });
490
+ /** Read only the typed land message before the full delivery report is validated. */
491
+ export function landCommitMessage(text, structuredOutput) {
492
+ const message = (raw) => {
493
+ const parsed = z.object({ commit_message: z.string().nullish() }).safeParse(raw);
494
+ return parsed.success ? parsed.data.commit_message?.trim() || undefined : undefined;
495
+ };
496
+ try {
497
+ const native = message(structuredOutput);
498
+ if (native)
499
+ return native;
500
+ }
501
+ catch {
502
+ // Malformed native output must not prevent the land continuation.
503
+ }
504
+ try {
505
+ return message(JSON.parse(extractAgentReportJsonText(text)));
506
+ }
507
+ catch {
508
+ return undefined;
509
+ }
510
+ }
488
511
  /** Parse the agent's final fenced JSON block or its native structured output into a bounded report. */
489
512
  export function parseAgentReport(text, acceptance, structuredOutput) {
513
+ // Reports from older Bridge schemas omit this optional field.
514
+ const nativeReport = structuredOutput && typeof structuredOutput === "object" && !Array.isArray(structuredOutput)
515
+ ? { commit_message: null, ...structuredOutput } : structuredOutput;
490
516
  const data = structuredOutput === undefined
491
517
  ? parseReplyObject("report_invalid", agentReportSchema, text)
492
- : parseReplyObject("report_invalid", agentReportOutputSchema, text, structuredOutput);
518
+ : parseReplyObject("report_invalid", agentReportOutputSchema, text, nativeReport);
493
519
  // Map every criterion reference — in the results and in the evidence — back to the approved text
494
520
  // before anything else reads it. Downstream (the packet, the reviewer, Today) sees text only; the
495
521
  // key is transport. An unknown key is refused here, because a key the agent invented cannot be
@@ -42,12 +42,13 @@ export async function ensureLandCommit(input) {
42
42
  const ahead = await countAhead(input.workspace, base);
43
43
  const pending = filterPathsInScope(await listPending(input.workspace), input.spec.change_scope ?? []);
44
44
  if (pending.length) {
45
- await commitPaths(input.workspace, pending);
45
+ const message = redactSecrets(input.commitMessage ?? "").trim();
46
+ await commitPaths(input.workspace, pending, message || "Conduit: land scoped agent changes");
46
47
  const afterAhead = await countAhead(input.workspace, base);
47
48
  if (afterAhead === 0) {
48
49
  throw new AgentNoLandCommitError(base);
49
50
  }
50
- return { kind: "committed", headCommit: await readHead(input.workspace), paths: pending };
51
+ return { kind: "committed", headCommit: await readHead(input.workspace), paths: pending, usedAgentMessage: !!message };
51
52
  }
52
53
  if (ahead !== null && ahead > 0) {
53
54
  return { kind: "already_landed", headCommit: await readHead(input.workspace) };
package/dist/execution.js CHANGED
@@ -9,7 +9,7 @@ import { AttemptStalledError, createAttemptWatchdog, withTimeout } from "./attem
9
9
  import { workPackageSchema } from "./work-package-schema.js";
10
10
  import { ConduitRequestError, reportAssignmentPollShapeMismatch } from "./client.js";
11
11
  import { saveDriverOutcome, saveDriverQuota, tierCandidates } from "./config.js";
12
- import { DRIVERS, acceptanceCriteriaLines, acceptanceReportSkeleton, agentReportTemplate, buildAssignmentPrompt, evidenceKinds, fuelEndpoint, normalizeEvidenceDigest, parseAgentReport, parseReplyObject, pickModelCandidate, ReplyInvalid, isSafeModelName, driverExecutable, requireStampedExecutionClass, stopTrackedProcessGroups, tierForRisk } from "./driver.js";
12
+ import { DRIVERS, acceptanceCriteriaLines, acceptanceReportSkeleton, agentReportTemplate, buildAssignmentPrompt, evidenceKinds, fuelEndpoint, landCommitMessage, normalizeEvidenceDigest, parseAgentReport, parseReplyObject, pickModelCandidate, ReplyInvalid, isSafeModelName, driverExecutable, requireStampedExecutionClass, stopTrackedProcessGroups, tierForRisk } from "./driver.js";
13
13
  import { assertClassFloor } from "./execution-class.js";
14
14
  import { pickDriverForClaim, recordDriverQuota, resolveDriverFuel, resolveDriverFuelProvenance, supportsReadOnlyDiagnosis, clearDriverOutcome, recordDriverTimeout } from "./drivers.js";
15
15
  import { attemptWorktreePath, createAttemptWorktree, proveResumeWorktree, removeAttemptWorktree, sweepStaleAttemptWorktrees, } from "./attempt-worktree.js";
@@ -2046,14 +2046,17 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
2046
2046
  repositoryBaseCommit: spec.repository?.base_commit,
2047
2047
  });
2048
2048
  }
2049
+ let reportText = result.resultText ?? "";
2050
+ let reportStructuredOutput = result.structuredOutput;
2049
2051
  watchdog.phase("land");
2050
2052
  if (requiresLandCommit({ grants, spec })) {
2051
2053
  try {
2052
- const land = await ensureLandCommit({ workspace: attemptWorkspace, spec, grants, baseCommit: deliveryBaseCommit });
2054
+ const land = await ensureLandCommit({ workspace: attemptWorkspace, spec, grants, baseCommit: deliveryBaseCommit,
2055
+ commitMessage: landCommitMessage(reportText, reportStructuredOutput) });
2053
2056
  if (land.kind === "committed") {
2054
2057
  await client.attemptRequest(taskId, "progress", {
2055
2058
  phase: "preparing_delivery",
2056
- message: `Bridge committed scoped agent changes that were left uncommitted (${land.paths.join(", ")}).`,
2059
+ message: `Bridge committed scoped agent changes that were left uncommitted (${land.paths.join(", ")}) with ${land.usedAgentMessage ? "the agent's message" : "the default message"}`,
2057
2060
  idempotency_key: `bridge:progress:${active.attemptId}:land-commit`,
2058
2061
  });
2059
2062
  }
@@ -2064,8 +2067,6 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
2064
2067
  }
2065
2068
  }
2066
2069
  let agentSessionId = result.sessionId ?? undefined;
2067
- let reportText = result.resultText ?? "";
2068
- let reportStructuredOutput = result.structuredOutput;
2069
2070
  let landContinuationUsed = false;
2070
2071
  const continueLand = (reason, idempotencyKey) => runLandContinuationTurn({
2071
2072
  config, client, taskId, attemptId: active.attemptId, workspace: attemptWorkspace,
@@ -2638,11 +2639,12 @@ async function finalizeRepositoryLand(input) {
2638
2639
  spec: input.spec,
2639
2640
  grants: input.grants,
2640
2641
  baseCommit: input.landBaseCommit,
2642
+ commitMessage: report.commit_message,
2641
2643
  });
2642
2644
  if (land.kind === "committed") {
2643
2645
  await input.client.attemptRequest(input.taskId, "progress", {
2644
2646
  phase: "preparing_delivery",
2645
- message: `Bridge committed scoped agent changes that were left uncommitted (${land.paths.join(", ")}).`,
2647
+ message: `Bridge committed scoped agent changes that were left uncommitted (${land.paths.join(", ")}) with ${land.usedAgentMessage ? "the agent's message" : "the default message"}`,
2646
2648
  idempotency_key: `bridge:progress:${input.attemptId}:land-commit`,
2647
2649
  });
2648
2650
  }
@@ -8,6 +8,7 @@
8
8
  import { redactSecrets, redactSecretsDeep } from "./secret-redaction.js";
9
9
  import { z } from "zod";
10
10
  import { boundedTail, runBoundedVerificationCommand } from "./ensure-test-evidence.js";
11
+ import { markAttemptActive } from "./preflight.js";
11
12
  import { createAttemptWorktree, removeAttemptWorktree } from "./attempt-worktree.js";
12
13
  import { buildWorkspaceBrief, ensureCommitAvailable, normalizeRepositoryUrl } from "./brief.js";
13
14
  import { headCommitOrNull } from "./git.js";
@@ -831,6 +832,9 @@ export async function executeNextInvestigation(client, config, workspace, brief,
831
832
  }, options.heartbeatIntervalMs ?? 15_000)
832
833
  : null;
833
834
  heartbeatTimer?.unref?.();
835
+ // The investigation runs the delivered head's gates on this computer: hold the attempt marker,
836
+ // so a gate rehearsal on another runner here does not start, or stops, while they run.
837
+ const releaseMarker = markAttemptActive();
834
838
  try {
835
839
  attemptWorkspace = await createAttemptWorktree({
836
840
  sourceWorkspace: workspace,
@@ -1013,6 +1017,7 @@ export async function executeNextInvestigation(client, config, workspace, brief,
1013
1017
  console.error(`Investigation ${assignment.id} failed: ${redactSecrets(message)}`);
1014
1018
  }
1015
1019
  finally {
1020
+ releaseMarker();
1016
1021
  clearInterval(renewTimer);
1017
1022
  if (heartbeatTimer)
1018
1023
  clearInterval(heartbeatTimer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.193",
3
+ "version": "0.16.195",
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": {
@@ -2,6 +2,9 @@
2
2
  "type": "object",
3
3
  "additionalProperties": false,
4
4
  "properties": {
5
+ "commit_message": {
6
+ "type": ["string", "null"]
7
+ },
5
8
  "outcome": {
6
9
  "type": "string"
7
10
  },
@@ -131,6 +134,7 @@
131
134
  }
132
135
  },
133
136
  "required": [
137
+ "commit_message",
134
138
  "outcome",
135
139
  "changes",
136
140
  "verification",