@miraland-labs/conduit-bridge 0.9.8 → 0.9.9
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/brief.js +62 -4
- package/dist/execution.js +3 -22
- 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.9` — heartbeat protocol 2 requires a clean workspace plus a ready, versioned driver lane before dispatch. 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/brief.js
CHANGED
|
@@ -43,6 +43,26 @@ export async function isBaseCommitAncestor(workspace, baseCommit, headCommit) {
|
|
|
43
43
|
throw error;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
async function fetchOrigin(workspace) {
|
|
47
|
+
await execFileAsync("git", ["-C", workspace, "fetch", "--no-tags", "origin"], {
|
|
48
|
+
timeout: 120_000,
|
|
49
|
+
windowsHide: true,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** Null when commits do not share history (e.g. orphan / force-pushed replacement tip). */
|
|
53
|
+
async function gitMergeBase(workspace, left, right) {
|
|
54
|
+
try {
|
|
55
|
+
const { stdout } = await execFileAsync("git", ["-C", workspace, "merge-base", left, right], {
|
|
56
|
+
timeout: 10_000,
|
|
57
|
+
windowsHide: true,
|
|
58
|
+
});
|
|
59
|
+
const sha = stdout.trim().toLowerCase();
|
|
60
|
+
return /^[0-9a-f]{40,64}$/.test(sha) ? sha : null;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
46
66
|
/** True when `sha` is a local commit object, optionally after fetching it from origin. */
|
|
47
67
|
export async function ensureCommitAvailable(workspace, sha) {
|
|
48
68
|
const present = async () => {
|
|
@@ -67,10 +87,7 @@ export async function ensureCommitAvailable(workspace, sha) {
|
|
|
67
87
|
}
|
|
68
88
|
catch {
|
|
69
89
|
try {
|
|
70
|
-
await
|
|
71
|
-
timeout: 120_000,
|
|
72
|
-
windowsHide: true,
|
|
73
|
-
});
|
|
90
|
+
await fetchOrigin(workspace);
|
|
74
91
|
}
|
|
75
92
|
catch {
|
|
76
93
|
return false;
|
|
@@ -78,6 +95,47 @@ export async function ensureCommitAvailable(workspace, sha) {
|
|
|
78
95
|
}
|
|
79
96
|
return present();
|
|
80
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Choose the commit an attempt worktree starts from for a required initiative base.
|
|
100
|
+
*
|
|
101
|
+
* Never rewrites the source checkout HEAD. Fetches missing objects when needed, prefers the claimed
|
|
102
|
+
* head when it already contains the base (rework), advances to the base when the workspace is merely
|
|
103
|
+
* behind, and falls back to the contract base when history was rewritten (force-push) so the owner
|
|
104
|
+
* does not have to manually reset before Recheck.
|
|
105
|
+
*/
|
|
106
|
+
export async function resolveAttemptStartCommit(workspace, requestedBase, claimedHead) {
|
|
107
|
+
if (!requestedBase)
|
|
108
|
+
return claimedHead;
|
|
109
|
+
const base = requestedBase.toLowerCase();
|
|
110
|
+
const claimed = claimedHead.toLowerCase();
|
|
111
|
+
if (await isBaseCommitAncestor(workspace, base, claimed).catch(() => false))
|
|
112
|
+
return claimed;
|
|
113
|
+
if (!(await ensureCommitAvailable(workspace, base).catch(() => false)))
|
|
114
|
+
return null;
|
|
115
|
+
await ensureCommitAvailable(workspace, claimed).catch(() => false);
|
|
116
|
+
if (await isBaseCommitAncestor(workspace, base, claimed).catch(() => false))
|
|
117
|
+
return claimed;
|
|
118
|
+
if (await isBaseCommitAncestor(workspace, claimed, base).catch(() => false))
|
|
119
|
+
return base;
|
|
120
|
+
// Shallow tip fetches can hide ancestry; deepen once before treating history as rewritten.
|
|
121
|
+
try {
|
|
122
|
+
await fetchOrigin(workspace);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
/* offline / no origin — continue with objects already present */
|
|
126
|
+
}
|
|
127
|
+
if (await isBaseCommitAncestor(workspace, base, claimed).catch(() => false))
|
|
128
|
+
return claimed;
|
|
129
|
+
if (await isBaseCommitAncestor(workspace, claimed, base).catch(() => false))
|
|
130
|
+
return base;
|
|
131
|
+
// Unrelated histories (orphan / force-pushed default branch): honor the contract base for a fresh
|
|
132
|
+
// attempt worktree. Related-but-diverged tips can carry rework commits — refuse rather than drop them.
|
|
133
|
+
if (!(await ensureCommitAvailable(workspace, base).catch(() => false)))
|
|
134
|
+
return null;
|
|
135
|
+
if (await gitMergeBase(workspace, base, claimed) === null)
|
|
136
|
+
return base;
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
81
139
|
async function gitRemoteUrl(workspace) {
|
|
82
140
|
try {
|
|
83
141
|
const { common } = await gitDirectories(workspace);
|
package/dist/execution.js
CHANGED
|
@@ -5,7 +5,7 @@ import { redactSecrets } from "./config.js";
|
|
|
5
5
|
import { DRIVERS, agentReportTemplate, buildAssignmentPrompt, evidenceKinds, fuelEndpoint, 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
|
-
import { buildWorkspaceBrief, ensureCommitAvailable,
|
|
8
|
+
import { buildWorkspaceBrief, ensureCommitAvailable, normalizeRepositoryUrl, resolveAttemptStartCommit } from "./brief.js";
|
|
9
9
|
import { ensureDeliveryPullRequest } from "./ensure-pull-request.js";
|
|
10
10
|
/** Feedback text for changes_requested summaries (plain string or `{ feedback }`). */
|
|
11
11
|
function changesRequestedFeedback(summary) {
|
|
@@ -230,7 +230,7 @@ export async function claimNextAssignment(client, config, workspace, brief, driv
|
|
|
230
230
|
rejection = "workspace_head_changed";
|
|
231
231
|
}
|
|
232
232
|
if (!rejection && assignment.requested_base_commit && assignment.claimed_head) {
|
|
233
|
-
const startHead = await
|
|
233
|
+
const startHead = await resolveAttemptStartCommit(workspace, assignment.requested_base_commit, assignment.claimed_head);
|
|
234
234
|
if (!startHead)
|
|
235
235
|
rejection = "base_not_ancestor";
|
|
236
236
|
}
|
|
@@ -246,25 +246,6 @@ export async function claimNextAssignment(client, config, workspace, brief, driv
|
|
|
246
246
|
await client.claim(assignment.id, assignment.attempt_id, driverId ? { driverId } : undefined);
|
|
247
247
|
return assignment.id;
|
|
248
248
|
}
|
|
249
|
-
/**
|
|
250
|
-
* When a sibling merge advances task.base_commit past the machine checkout, the required base is a
|
|
251
|
-
* descendant of claimed_head — not an ancestor. Fetch it and start from the advanced base instead of looping.
|
|
252
|
-
*/
|
|
253
|
-
async function resolveStartHead(workspace, requestedBase, claimedHead) {
|
|
254
|
-
if (await isBaseCommitAncestor(workspace, requestedBase, claimedHead).catch(() => false))
|
|
255
|
-
return claimedHead;
|
|
256
|
-
await ensureCommitAvailable(workspace, requestedBase).catch(() => false);
|
|
257
|
-
if (await isBaseCommitAncestor(workspace, requestedBase, claimedHead).catch(() => false))
|
|
258
|
-
return claimedHead;
|
|
259
|
-
// Workspace is behind the required base (dependent package after merge).
|
|
260
|
-
if (await isBaseCommitAncestor(workspace, claimedHead, requestedBase).catch(() => false))
|
|
261
|
-
return requestedBase;
|
|
262
|
-
if (await ensureCommitAvailable(workspace, requestedBase).catch(() => false)
|
|
263
|
-
&& await isBaseCommitAncestor(workspace, claimedHead, requestedBase).catch(() => false)) {
|
|
264
|
-
return requestedBase;
|
|
265
|
-
}
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
249
|
export async function executeNextAssignment(client, config, driver, workspace, brief, timeoutMs, supervision) {
|
|
269
250
|
if (Object.keys(config.activeAttempts).length)
|
|
270
251
|
return recoverActiveAttempt(client, config, driver, workspace, brief, timeoutMs, supervision);
|
|
@@ -304,7 +285,7 @@ async function runClaimedAssignment(client, config, driver, workspace, brief, ta
|
|
|
304
285
|
}
|
|
305
286
|
let worktreeStart = startCommit;
|
|
306
287
|
if (executionContract.requested_base_commit) {
|
|
307
|
-
const resolved = await
|
|
288
|
+
const resolved = await resolveAttemptStartCommit(workspace, executionContract.requested_base_commit, startCommit);
|
|
308
289
|
if (!resolved) {
|
|
309
290
|
await queueTerminal(client, taskId, { action: "fail", body: { error: "Required base commit is not available in this workspace", retryable: true, idempotency_key: `bridge:base-not-ancestor:${active.attemptId}` } });
|
|
310
291
|
return;
|
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.9",
|
|
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": {
|