@miraland-labs/conduit-bridge 0.16.194 → 0.16.196
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 +11 -14
- package/dist/driver.js +28 -2
- package/dist/ensure-land-commit.js +3 -2
- package/dist/execution.js +8 -6
- package/dist/preflight.js +2 -0
- package/package.json +1 -1
- package/schemas/delivery-report.schema.json +4 -0
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import { BOOTSTRAP_RESULTS, buildSovereignExecutionFacts, workspaceIsClean } fro
|
|
|
20
20
|
import { ensureCheckout } from "./checkout.js";
|
|
21
21
|
import { buildOnShiftIntentProof, managedWorkspacePath, maybeApplyOnShiftIntent } from "./on-shift-apply.js";
|
|
22
22
|
import { expandOpsValue, loadOpsEnv, OPS_VERBS, runOps } from "./ops.js";
|
|
23
|
-
import {
|
|
23
|
+
import { pumpExecutionSlots, renewLeases } from "./execution.js";
|
|
24
24
|
import { createRehearsalWorktree, rehearsalWorktreePath, removeAttemptWorktree } from "./attempt-worktree.js";
|
|
25
25
|
import { applyRunnerToolPath, installRunnerService, uninstallRunnerService } from "./service.js";
|
|
26
26
|
import { attemptsActiveOnComputer, BRIDGE_PROTOCOL_VERSION, cachedBridgePreflight, gateRehearsalLockHeld, planGateRehearsals, PREFLIGHT_CACHE_TTL_MS, runGateRehearsal, unavailableWorkspacePreflight } from "./preflight.js";
|
|
@@ -667,24 +667,21 @@ async function runner() {
|
|
|
667
667
|
await heartbeat(client, config, workspace, currentBrief, preflight, intentProof, bootstrapResult);
|
|
668
668
|
}
|
|
669
669
|
await renewLeases(client, config);
|
|
670
|
-
//
|
|
671
|
-
|
|
672
|
-
// claim on this same cycle. The rehearsal stays due.
|
|
673
|
-
if (gateRehearsalRunning && (attemptsActiveOnComputer() || await assignmentsPending(client))) {
|
|
670
|
+
// Only an active attempt interrupts a rehearsal. Waiting work takes the next cycle.
|
|
671
|
+
if (gateRehearsalRunning && attemptsActiveOnComputer()) {
|
|
674
672
|
gateRehearsalAbort?.abort();
|
|
675
673
|
await gateRehearsalDone;
|
|
676
674
|
}
|
|
677
|
-
// Another runner
|
|
678
|
-
// flight: claim as usual, rehearse on a later cycle. An assignment already waiting is
|
|
679
|
-
// claimed below; this cycle does not start a rehearsal.
|
|
675
|
+
// Another runner with an attempt in flight takes precedence over a new rehearsal.
|
|
680
676
|
if (workspace && onlineDriverIds(config).length && !gateRehearsalRunning && rehearsalPlan?.due
|
|
681
677
|
&& running.size === 0 && Object.keys(config.activeAttempts).length === 0 && !gateRehearsalLockHeld()
|
|
682
|
-
&& !attemptsActiveOnComputer()
|
|
683
|
-
|
|
678
|
+
&& !attemptsActiveOnComputer()) {
|
|
679
|
+
console.log(`Claim deferred for gate rehearsal in ${rehearsalPlan.due.workspace}`);
|
|
684
680
|
startGateRehearsal(rehearsalPlan.due.workspace, rehearsalPlan.due.plan);
|
|
685
681
|
}
|
|
686
|
-
//
|
|
687
|
-
if (workspace && onlineDriverIds(config).length && !gateRehearsalRunning
|
|
682
|
+
// Idle computers rehearse before claiming. Active attempts keep their execution slots pumping.
|
|
683
|
+
if (workspace && onlineDriverIds(config).length && !gateRehearsalRunning
|
|
684
|
+
&& (!rehearsalPlan?.due || running.size > 0 || Object.keys(config.activeAttempts).length > 0 || attemptsActiveOnComputer())) {
|
|
688
685
|
// Read the workspace again, then publish it. The last brief stands if the read fails.
|
|
689
686
|
const publishWorkspaceState = async (options = {}) => {
|
|
690
687
|
// Not while an investigation runs: its observation-only check compares the source checkout
|
|
@@ -740,7 +737,7 @@ async function runner() {
|
|
|
740
737
|
await new Promise((resolveSleep) => setTimeout(resolveSleep, intervalMs));
|
|
741
738
|
}
|
|
742
739
|
}
|
|
743
|
-
/** True while the gate rehearsal runs in the background.
|
|
740
|
+
/** True while the gate rehearsal runs in the background. Waiting assignments do not interrupt it. */
|
|
744
741
|
let gateRehearsalRunning = false;
|
|
745
742
|
let gateRehearsalAbort = null;
|
|
746
743
|
let gateRehearsalDone = Promise.resolve();
|
|
@@ -809,7 +806,7 @@ function startGateRehearsal(workspace, plan) {
|
|
|
809
806
|
else if (rehearsal === null && attemptsActiveOnComputer())
|
|
810
807
|
console.log("Gate rehearsal stopped: an attempt is in flight on this computer");
|
|
811
808
|
else if (abort.signal.aborted)
|
|
812
|
-
console.log("Gate rehearsal stopped: an
|
|
809
|
+
console.log("Gate rehearsal stopped: an attempt is in flight on this computer");
|
|
813
810
|
})
|
|
814
811
|
.catch((error) => console.error(`Gate rehearsal failed: ${redactSecrets(error instanceof Error ? error.message : "unknown error")}`))
|
|
815
812
|
.finally(async () => {
|
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,
|
|
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
|
-
|
|
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
|
}
|
package/dist/preflight.js
CHANGED
|
@@ -611,6 +611,8 @@ async function rehearseGate(workspace, plan, options) {
|
|
|
611
611
|
const seconds = Math.round(elapsed / 1_000);
|
|
612
612
|
if (result.code !== 0 || result.timedOut === true || elapsed <= VERIFICATION_TIMEOUT_MS) {
|
|
613
613
|
commands.push({ command, seconds, exit_code: result.code, timed_out: result.timedOut === true });
|
|
614
|
+
if (result.code !== 0 || result.timedOut === true)
|
|
615
|
+
break;
|
|
614
616
|
continue;
|
|
615
617
|
}
|
|
616
618
|
// The pass fit only the rehearsal's limit: it was a warm-up. Measure once more, in a fresh
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miraland-labs/conduit-bridge",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.196",
|
|
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",
|