borgmcp 1.0.6 → 1.0.7
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/assimilate-cmd.js +39 -511
- package/dist/assimilate-deps.js +3 -177
- package/dist/assimilate-welcome.js +2 -24
- package/dist/auth-env.js +1 -107
- package/dist/auth.js +23 -612
- package/dist/claude.js +11 -281
- package/dist/cli-help.js +29 -50
- package/dist/cli-platform.js +4 -94
- package/dist/codex-app-server.js +4 -228
- package/dist/codex-app-wake.js +2 -122
- package/dist/codex-launch.js +1 -81
- package/dist/codex-remote.js +1 -250
- package/dist/config-utils.js +3 -385
- package/dist/config.js +1 -190
- package/dist/console-prefix.js +1 -86
- package/dist/cube-name.js +1 -65
- package/dist/cubes.js +4 -269
- package/dist/debug.js +1 -71
- package/dist/device-auth.js +1 -167
- package/dist/direct-log.js +1 -11
- package/dist/health-beat.js +1 -168
- package/dist/inbox-monitor.js +1 -129
- package/dist/index.js +26 -1378
- package/dist/lifecycle-log-guard.js +2 -93
- package/dist/list-roles-render.js +6 -39
- package/dist/log-audit.js +3 -186
- package/dist/log-stream.js +9 -848
- package/dist/name-validator.js +1 -22
- package/dist/parse-assimilate-args.js +1 -82
- package/dist/postinstall.js +8 -22
- package/dist/regen-format.js +11 -337
- package/dist/regen.js +5 -83
- package/dist/remote-client.js +1 -695
- package/dist/role-resolver.js +1 -36
- package/dist/role-section.js +8 -208
- package/dist/roster-render.js +3 -96
- package/dist/setup.js +36 -251
- package/dist/shell-escape.js +1 -22
- package/dist/spawn.js +10 -29
- package/dist/stale-version-check.js +1 -102
- package/dist/stream-owner.js +2 -202
- package/dist/stream-status.js +3 -211
- package/dist/subscription-retry.js +1 -23
- package/dist/sync-roles-render.js +3 -118
- package/dist/sync.js +22 -286
- package/dist/templates.js +120 -626
- package/dist/terminal-title.js +1 -68
- package/dist/token-crypto.js +1 -91
- package/dist/token-store.js +1 -222
- package/dist/types.js +0 -5
- package/dist/version.js +2 -78
- package/dist/worktree-lifecycle.js +2 -173
- package/package.json +11 -2
- package/dist/assimilate-cmd.d.ts.map +0 -1
- package/dist/assimilate-cmd.js.map +0 -1
- package/dist/assimilate-deps.d.ts.map +0 -1
- package/dist/assimilate-deps.js.map +0 -1
- package/dist/assimilate-welcome.d.ts.map +0 -1
- package/dist/assimilate-welcome.js.map +0 -1
- package/dist/auth-env.d.ts.map +0 -1
- package/dist/auth-env.js.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/claude.d.ts.map +0 -1
- package/dist/claude.js.map +0 -1
- package/dist/cli-help.d.ts.map +0 -1
- package/dist/cli-help.js.map +0 -1
- package/dist/cli-platform.d.ts.map +0 -1
- package/dist/cli-platform.js.map +0 -1
- package/dist/codex-app-server.d.ts.map +0 -1
- package/dist/codex-app-server.js.map +0 -1
- package/dist/codex-app-wake.d.ts.map +0 -1
- package/dist/codex-app-wake.js.map +0 -1
- package/dist/codex-launch.d.ts.map +0 -1
- package/dist/codex-launch.js.map +0 -1
- package/dist/codex-remote.d.ts.map +0 -1
- package/dist/codex-remote.js.map +0 -1
- package/dist/config-utils.d.ts.map +0 -1
- package/dist/config-utils.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/console-prefix.d.ts.map +0 -1
- package/dist/console-prefix.js.map +0 -1
- package/dist/cube-name.d.ts.map +0 -1
- package/dist/cube-name.js.map +0 -1
- package/dist/cubes.d.ts.map +0 -1
- package/dist/cubes.js.map +0 -1
- package/dist/debug.d.ts.map +0 -1
- package/dist/debug.js.map +0 -1
- package/dist/device-auth.d.ts.map +0 -1
- package/dist/device-auth.js.map +0 -1
- package/dist/direct-log.d.ts.map +0 -1
- package/dist/direct-log.js.map +0 -1
- package/dist/health-beat.d.ts.map +0 -1
- package/dist/health-beat.js.map +0 -1
- package/dist/inbox-monitor.d.ts.map +0 -1
- package/dist/inbox-monitor.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lifecycle-log-guard.d.ts.map +0 -1
- package/dist/lifecycle-log-guard.js.map +0 -1
- package/dist/list-roles-render.d.ts.map +0 -1
- package/dist/list-roles-render.js.map +0 -1
- package/dist/log-audit.d.ts.map +0 -1
- package/dist/log-audit.js.map +0 -1
- package/dist/log-stream.d.ts.map +0 -1
- package/dist/log-stream.js.map +0 -1
- package/dist/name-validator.d.ts.map +0 -1
- package/dist/name-validator.js.map +0 -1
- package/dist/parse-assimilate-args.d.ts.map +0 -1
- package/dist/parse-assimilate-args.js.map +0 -1
- package/dist/postinstall.d.ts.map +0 -1
- package/dist/postinstall.js.map +0 -1
- package/dist/regen-format.d.ts.map +0 -1
- package/dist/regen-format.js.map +0 -1
- package/dist/regen.d.ts.map +0 -1
- package/dist/regen.js.map +0 -1
- package/dist/remote-client.d.ts.map +0 -1
- package/dist/remote-client.js.map +0 -1
- package/dist/role-resolver.d.ts.map +0 -1
- package/dist/role-resolver.js.map +0 -1
- package/dist/role-section.d.ts.map +0 -1
- package/dist/role-section.js.map +0 -1
- package/dist/roster-render.d.ts.map +0 -1
- package/dist/roster-render.js.map +0 -1
- package/dist/setup.d.ts.map +0 -1
- package/dist/setup.js.map +0 -1
- package/dist/shell-escape.d.ts.map +0 -1
- package/dist/shell-escape.js.map +0 -1
- package/dist/spawn.d.ts.map +0 -1
- package/dist/spawn.js.map +0 -1
- package/dist/stale-version-check.d.ts.map +0 -1
- package/dist/stale-version-check.js.map +0 -1
- package/dist/stream-owner.d.ts.map +0 -1
- package/dist/stream-owner.js.map +0 -1
- package/dist/stream-status.d.ts.map +0 -1
- package/dist/stream-status.js.map +0 -1
- package/dist/subscription-retry.d.ts.map +0 -1
- package/dist/subscription-retry.js.map +0 -1
- package/dist/sync-roles-render.d.ts.map +0 -1
- package/dist/sync-roles-render.js.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js.map +0 -1
- package/dist/templates.d.ts.map +0 -1
- package/dist/templates.js.map +0 -1
- package/dist/terminal-title.d.ts.map +0 -1
- package/dist/terminal-title.js.map +0 -1
- package/dist/token-crypto.d.ts.map +0 -1
- package/dist/token-crypto.js.map +0 -1
- package/dist/token-store.d.ts.map +0 -1
- package/dist/token-store.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
- package/dist/worktree-lifecycle.d.ts.map +0 -1
- package/dist/worktree-lifecycle.js.map +0 -1
package/dist/assimilate-cmd.js
CHANGED
|
@@ -1,511 +1,39 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
cubeName = args.flags.cubeName;
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
const remoteResult = deps.runSync('git', ['remote', 'get-url', 'origin'], projectRoot);
|
|
44
|
-
const remoteUrl = remoteResult.status === 0 ? remoteResult.stdout : null;
|
|
45
|
-
cubeName = deriveCubeName(projectRoot, remoteUrl);
|
|
46
|
-
// UX-F4 (drone-7 Phase E review): emit a stderr nudge when the
|
|
47
|
-
// remote was readable but didn't parse, so the user has a signal
|
|
48
|
-
// that auto-derivation diverged from their git remote.
|
|
49
|
-
if (remoteUrl) {
|
|
50
|
-
const sanitized = sanitizeRemoteUrl(remoteUrl);
|
|
51
|
-
const parsedRepo = sanitized ? parseGitRemote(sanitized) : null;
|
|
52
|
-
if (sanitized && !parsedRepo && cubeName) {
|
|
53
|
-
deps.stderr(`couldn't parse git remote '${sanitized}' — using directory name '${cubeName}' as cube name\n`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// gh#293: detect cross-account cube reference (owner-email:cube-name format).
|
|
58
|
-
let crossAccountRef = null;
|
|
59
|
-
if (cubeName && cubeName.includes('@') && cubeName.includes(':')) {
|
|
60
|
-
const colonIdx = cubeName.lastIndexOf(':');
|
|
61
|
-
crossAccountRef = {
|
|
62
|
-
ownerEmail: cubeName.substring(0, colonIdx),
|
|
63
|
-
cubeName: cubeName.substring(colonIdx + 1),
|
|
64
|
-
};
|
|
65
|
-
cubeName = crossAccountRef.cubeName;
|
|
66
|
-
}
|
|
67
|
-
// ----- Sprint 19 (gh#184): Reorder for strict-rollback semantics. -----
|
|
68
|
-
// The previous flow created a sibling worktree (FS state) BEFORE
|
|
69
|
-
// role resolution + API assimilate. Any early-return between
|
|
70
|
-
// worktree-spawn and API success orphaned the worktree (gh#184
|
|
71
|
-
// canonical case: unknown role arg). The new flow defers all FS
|
|
72
|
-
// state until AFTER the API assimilate succeeds — early-return at
|
|
73
|
-
// role resolution / listCubes / createCube / template-prompt /
|
|
74
|
-
// template-invalid-choice is now structurally clean (no orphan
|
|
75
|
-
// class possible). Worktree rollback narrows to the single
|
|
76
|
-
// setActiveCube failure path post-worktree-creation.
|
|
77
|
-
// Sprint 18: capture pre-chdir cwd for the post-exit shell-cd hint
|
|
78
|
-
// (no chdir has happened yet; this is a stable starting point).
|
|
79
|
-
const originalCwd = deps.cwd();
|
|
80
|
-
// ----- Step 3: Cube existence check (with auto-refresh on auth failure) -----
|
|
81
|
-
let allCubes;
|
|
82
|
-
try {
|
|
83
|
-
allCubes = await deps.listCubes(auth.apiUrl, auth.token);
|
|
84
|
-
}
|
|
85
|
-
catch (err) {
|
|
86
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
87
|
-
if (msg.includes('Authentication required') || msg.includes('Authentication expired')) {
|
|
88
|
-
deps.stderr('Re-authenticating...\n');
|
|
89
|
-
auth = await deps.runSetup();
|
|
90
|
-
allCubes = await deps.listCubes(auth.apiUrl, auth.token);
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
throw err;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const existingCube = allCubes.find((c) => c.name === cubeName);
|
|
97
|
-
// gh#312: cross-account typo guard. If the user typed owner@example.com:cube-name
|
|
98
|
-
// and the cube isn't found, error out — don't silently create a new cube.
|
|
99
|
-
if (!existingCube && crossAccountRef) {
|
|
100
|
-
deps.stderr(`No cube named '${crossAccountRef.cubeName}' accessible to you owned by '${crossAccountRef.ownerEmail}'. Did you accept their invite? See borgmcp.ai/dashboard.\n`);
|
|
101
|
-
return 1;
|
|
102
|
-
}
|
|
103
|
-
// ----- Step 4: Fetch detail OR create cube -----
|
|
104
|
-
let cubeDetail;
|
|
105
|
-
let isFirstDrone;
|
|
106
|
-
if (existingCube) {
|
|
107
|
-
cubeDetail = await deps.getCube(auth.apiUrl, auth.token, existingCube.id);
|
|
108
|
-
isFirstDrone = false;
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
// ----- Step 4a: First-drone bootstrap (template selection) -----
|
|
112
|
-
let chosenTemplate;
|
|
113
|
-
if (args.flags.template) {
|
|
114
|
-
chosenTemplate = args.flags.template;
|
|
115
|
-
}
|
|
116
|
-
else if (args.flags.noTemplate) {
|
|
117
|
-
chosenTemplate = undefined;
|
|
118
|
-
}
|
|
119
|
-
else if (!deps.isTTY()) {
|
|
120
|
-
if (!args.flags.yes) {
|
|
121
|
-
deps.stderr('cube creation needs a template choice but stdin is non-interactive.\n' +
|
|
122
|
-
'Pass --template <name>, --no-template, or --yes (defaults to starter).\n');
|
|
123
|
-
return 1;
|
|
124
|
-
}
|
|
125
|
-
chosenTemplate = 'starter';
|
|
126
|
-
}
|
|
127
|
-
else if (args.flags.yes) {
|
|
128
|
-
chosenTemplate = 'starter';
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
const templates = await deps.listTemplates(auth.apiUrl, auth.token);
|
|
132
|
-
const lines = ['First drone joining a new cube. Apply a template?'];
|
|
133
|
-
templates.forEach((t, i) => {
|
|
134
|
-
const tag = i === 0 ? ' (default)' : '';
|
|
135
|
-
lines.push(` ${i + 1}) ${t.name}${tag} — ${t.description}`);
|
|
136
|
-
});
|
|
137
|
-
lines.push(` ${templates.length + 1}) skip — no template`);
|
|
138
|
-
const answer = (await deps.prompt(lines.join('\n') + '\n[1]: ')).trim();
|
|
139
|
-
const choice = answer === '' ? 1 : parseInt(answer, 10);
|
|
140
|
-
if (Number.isNaN(choice) || choice < 1 || choice > templates.length + 1) {
|
|
141
|
-
deps.stderr(`invalid choice "${answer}"\n`);
|
|
142
|
-
return 1;
|
|
143
|
-
}
|
|
144
|
-
chosenTemplate = choice <= templates.length ? templates[choice - 1].name : undefined;
|
|
145
|
-
}
|
|
146
|
-
cubeDetail = await deps.createCube(auth.apiUrl, auth.token, chosenTemplate ? { name: cubeName ?? undefined, template: chosenTemplate } : { name: cubeName ?? undefined });
|
|
147
|
-
isFirstDrone = true;
|
|
148
|
-
}
|
|
149
|
-
// ----- Step 5: Role resolution -----
|
|
150
|
-
let resolvedRole;
|
|
151
|
-
if (args.role !== undefined) {
|
|
152
|
-
resolvedRole = matchRoleByName(cubeDetail.roles, args.role);
|
|
153
|
-
if (!resolvedRole) {
|
|
154
|
-
// Sprint 19 (gh#184) + drone-7 metaphor argument: include a
|
|
155
|
-
// fuzzy-match "did you mean ...?" suggestion to serve Queen's
|
|
156
|
-
// "more user-friendly" intent without violating the
|
|
157
|
-
// Borg-collective metaphor (collective defines roles; drones
|
|
158
|
-
// slot in). Levenshtein distance ≤2 on the cube's role names.
|
|
159
|
-
const available = cubeDetail.roles.map((r) => r.name).join(', ');
|
|
160
|
-
const suggestion = suggestRoleName(args.role, cubeDetail.roles.map((r) => r.name));
|
|
161
|
-
const suggestionLine = suggestion ? ` Did you mean "${suggestion}"?` : '';
|
|
162
|
-
deps.stderr(`no role matching "${args.role}" in cube "${cubeDetail.name}". Available: ${available}.${suggestionLine}\n` +
|
|
163
|
-
`(Use --template <name> on first-drone setup or run \`borg:create-role\` from inside Claude.)\n`);
|
|
164
|
-
return 1;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
resolvedRole = pickDefaultRole(cubeDetail.roles, { isFirstDrone });
|
|
169
|
-
if (!resolvedRole) {
|
|
170
|
-
deps.stderr(`cube "${cubeDetail.name}" has no default or human-seat role; cannot infer a role. ` +
|
|
171
|
-
`Either pass a role argument explicitly (e.g. \`borg assimilate builder\`) or ` +
|
|
172
|
-
`run \`borg:create-role\` from inside Claude to set up roles.\n`);
|
|
173
|
-
return 1;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// ----- Step 6: API assimilate (no FS state yet — clean exit on failure) -----
|
|
177
|
-
let result;
|
|
178
|
-
try {
|
|
179
|
-
result = await deps.assimilate(auth.apiUrl, auth.token, {
|
|
180
|
-
cube_id: cubeDetail.id,
|
|
181
|
-
role_id: resolvedRole.id,
|
|
182
|
-
hostname: deps.getHostname(),
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
catch (err) {
|
|
186
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
187
|
-
deps.stderr(`assimilate failed: ${message}\n`);
|
|
188
|
-
return 1;
|
|
189
|
-
}
|
|
190
|
-
// The server may assimilate a member into a DIFFERENT role than the client's
|
|
191
|
-
// auto-picked default (gh#700 fallback: when the member's invite doesn't
|
|
192
|
-
// grant the default role, the server picks one of their GRANTED roles).
|
|
193
|
-
// Resolve the role the SERVER ACTUALLY assigned (result.role_id) and use it
|
|
194
|
-
// for all human-facing display + naming below — not the client's pre-pick.
|
|
195
|
-
// The drone label / session token are already server-truth; this aligns the
|
|
196
|
-
// displayed role name + worktree slug with what was actually assigned.
|
|
197
|
-
const assignedRole = cubeDetail.roles.find((r) => r.id === result.role_id) ?? resolvedRole;
|
|
198
|
-
if (assignedRole.id !== resolvedRole.id) {
|
|
199
|
-
deps.stderr(`Note: your invite didn't grant the "${resolvedRole.name}" role — ` +
|
|
200
|
-
`assimilated as "${assignedRole.name}" instead.\n`);
|
|
201
|
-
}
|
|
202
|
-
// ----- Step 7: Worktree decision (FS state ONLY after API success) -----
|
|
203
|
-
const existing = await deps.getActiveCube();
|
|
204
|
-
const wantSibling = args.flags.worktree !== undefined || existing !== null;
|
|
205
|
-
let spawnedWorktreePath = null;
|
|
206
|
-
if (wantSibling) {
|
|
207
|
-
if (existing && args.flags.here) {
|
|
208
|
-
deps.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree\n`);
|
|
209
|
-
return 1;
|
|
210
|
-
}
|
|
211
|
-
// BUG-4 / gh#150 fix (v0.9.5): `git worktree add --detach <path>`
|
|
212
|
-
// fails with "fatal: not a valid object name: 'HEAD'" when the
|
|
213
|
-
// repo has no commits yet (unborn HEAD). Detect explicitly via
|
|
214
|
-
// `git rev-parse --verify HEAD` so we surface an actionable
|
|
215
|
-
// prerequisite error rather than git's cryptic internal message.
|
|
216
|
-
const headProbe = deps.runSync('git', ['rev-parse', '--verify', 'HEAD'], projectRoot);
|
|
217
|
-
if (headProbe.status !== 0) {
|
|
218
|
-
deps.stderr(`sibling worktree spawn requires HEAD pointing at a commit.\n` +
|
|
219
|
-
` Fix: create at least one commit (\`git commit --allow-empty -m "initial"\`)\n` +
|
|
220
|
-
` OR: pass --here to skip the sibling spawn and use the current directory\n`);
|
|
221
|
-
return 1;
|
|
222
|
-
}
|
|
223
|
-
// gh#238: fetch origin so the new worktree starts on the latest
|
|
224
|
-
// remote HEAD, not the (possibly stale) local HEAD.
|
|
225
|
-
deps.runSync('git', ['fetch', 'origin'], projectRoot);
|
|
226
|
-
// Resolve the default branch on origin (main or master).
|
|
227
|
-
let startRef = 'origin/main';
|
|
228
|
-
const mainProbe = deps.runSync('git', ['rev-parse', '--verify', 'origin/main'], projectRoot);
|
|
229
|
-
if (mainProbe.status !== 0) {
|
|
230
|
-
const masterProbe = deps.runSync('git', ['rev-parse', '--verify', 'origin/master'], projectRoot);
|
|
231
|
-
if (masterProbe.status === 0) {
|
|
232
|
-
startRef = 'origin/master';
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// Warn if local HEAD diverges from the remote default branch.
|
|
236
|
-
const localHead = headProbe.stdout.trim();
|
|
237
|
-
const remoteHead = deps.runSync('git', ['rev-parse', startRef], projectRoot).stdout.trim();
|
|
238
|
-
if (localHead !== remoteHead) {
|
|
239
|
-
deps.stderr(`note: local HEAD (${localHead.slice(0, 7)}) differs from ${startRef} (${remoteHead.slice(0, 7)}); ` +
|
|
240
|
-
`new worktree will start on ${startRef}\n`);
|
|
241
|
-
}
|
|
242
|
-
const parent = dirname(projectRoot);
|
|
243
|
-
const repoBase = basename(projectRoot);
|
|
244
|
-
const suffix = args.flags.worktree ?? roleSlug(assignedRole.name);
|
|
245
|
-
let candidate = join(parent, `${repoBase}-${suffix}`);
|
|
246
|
-
let n = 2;
|
|
247
|
-
while (deps.pathExists(candidate) || worktreeRegistered(deps, projectRoot, candidate)) {
|
|
248
|
-
candidate = join(parent, `${repoBase}-${suffix}-${n}`);
|
|
249
|
-
n++;
|
|
250
|
-
}
|
|
251
|
-
// gh#33 (Q1/Q4): spawn on a named per-worktree branch (wt-<suffix>),
|
|
252
|
-
// NOT detached HEAD. The named branch is current with startRef and is
|
|
253
|
-
// where the drone's feature branches get cut from. Uniform for every
|
|
254
|
-
// role incl. coordinator — main is never a working branch (Q4).
|
|
255
|
-
const wtBranch = perWorktreeBranchName(basename(candidate), repoBase);
|
|
256
|
-
const wt = deps.runSync('git', ['worktree', 'add', '-b', wtBranch, candidate, startRef], projectRoot);
|
|
257
|
-
if (wt.status !== 0) {
|
|
258
|
-
deps.stderr(`git worktree add failed: ${safeStderr(wt.stderr)}\n`);
|
|
259
|
-
return 1;
|
|
260
|
-
}
|
|
261
|
-
deps.stderr(`spawned sibling worktree at ${candidate} on branch ${wtBranch} (${startRef}); ` +
|
|
262
|
-
`original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).\n`);
|
|
263
|
-
deps.chdir(candidate);
|
|
264
|
-
deps.stderr(renderWorktreeSteeringNote(candidate, wtBranch, projectRoot));
|
|
265
|
-
spawnedWorktreePath = deps.cwd();
|
|
266
|
-
}
|
|
267
|
-
// ----- Step 8: setActiveCube (narrow rollback — worktree exists if spawned) -----
|
|
268
|
-
try {
|
|
269
|
-
await deps.setActiveCube({
|
|
270
|
-
cubeId: result.cube_id,
|
|
271
|
-
droneId: result.drone_id,
|
|
272
|
-
name: cubeDetail.name,
|
|
273
|
-
sessionToken: result.session_token,
|
|
274
|
-
droneLabel: result.drone_label,
|
|
275
|
-
apiUrl: auth.apiUrl,
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
catch (err) {
|
|
279
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
280
|
-
deps.stderr(`setActiveCube failed: ${message}\n`);
|
|
281
|
-
if (spawnedWorktreePath) {
|
|
282
|
-
const rm = deps.runSync('git', ['worktree', 'remove', '--force', spawnedWorktreePath], projectRoot);
|
|
283
|
-
if (rm.status === 0) {
|
|
284
|
-
deps.stderr(`rolled back spawned worktree at ${spawnedWorktreePath}\n`);
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
deps.stderr(`manual cleanup needed: \`git worktree remove --force ${spawnedWorktreePath}\` ` +
|
|
288
|
-
`(rollback attempt failed: ${safeStderr(rm.stderr).trim() || 'unknown'})\n`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return 1;
|
|
292
|
-
}
|
|
293
|
-
// ----- Step 8: Launch selected agent CLI -----
|
|
294
|
-
// Mirrors the kickoff invocation from claude.ts (no-args path): the agent
|
|
295
|
-
// picks up the newly-persisted ActiveCube via the MCP stdio server on
|
|
296
|
-
// startup. The kickoff prompt re-enters /loop borg:regen so the new
|
|
297
|
-
// drone bootstraps into the cube cleanly. The monitor clause (CR-PE-F1)
|
|
298
|
-
// arms the inbox tail so the new drone wakes on peer log entries in
|
|
299
|
-
// real time — without this, drones miss real-time wake events during
|
|
300
|
-
// the bootstrap window and only self-heal at the /loop heartbeat.
|
|
301
|
-
deps.setTerminalTitle(result.drone_label, cubeDetail.name);
|
|
302
|
-
// Pedagogical hint to stdout before Claude takes over the terminal.
|
|
303
|
-
// Ink does not enter alt-screen-buffer (verified empirically via PTY
|
|
304
|
-
// probe 2026-05-19), so lines printed here remain visible in the
|
|
305
|
-
// user's terminal scrollback above Claude's interactive UI. Color is
|
|
306
|
-
// gated on TTY + NO_COLOR/CI env-var conventions; the welcome shape
|
|
307
|
-
// itself is cube-agnostic so non-default templates render identically.
|
|
308
|
-
const useColor = deps.isTTY() && !process.env.NO_COLOR && !process.env.CI;
|
|
309
|
-
deps.stdout(renderAssimilationWelcome(assignedRole.name, cubeDetail.name, useColor));
|
|
310
|
-
const cli = await deps.resolveCli(args.flags.cli);
|
|
311
|
-
const agentCwd = deps.cwd(); // post-chdir if step 3 spawned a worktree
|
|
312
|
-
// gh#33 (Q2/Q4/Q6): in-place wt- adoption. A freshly-spawned worktree is
|
|
313
|
-
// already at origin/main on a fresh wt- branch, so only the in-place /
|
|
314
|
-
// --here path (running in an existing checkout) needs handling. ADOPT
|
|
315
|
-
// the per-worktree branch — switch the checkout onto wt-<suffix> at
|
|
316
|
-
// origin/main when clean + merged. This both moves the drone off main
|
|
317
|
-
// (Q4: main is never a working branch) AND brings the branch current,
|
|
318
|
-
// which a bare ff-sync would not do (it would leave a main checkout on
|
|
319
|
-
// main — the gap two-of-four-CR 27af1001 + QA c7a0c615 caught). Dirty ->
|
|
320
|
-
// skip + surface; unmerged HEAD -> block + surface; NEVER discards.
|
|
321
|
-
// Using adoptWorktree (HEAD-merged check + explicit `switch -C wtBranch
|
|
322
|
-
// ref`) also closes one-of-four-CR's compute-name-vs-current-branch NIT.
|
|
323
|
-
// Best-effort: a skip/block surfaces but never blocks the launch.
|
|
324
|
-
if (!spawnedWorktreePath) {
|
|
325
|
-
deps.runSync('git', ['fetch', 'origin', '--prune'], agentCwd);
|
|
326
|
-
const wtBranch = perWorktreeBranchName(basename(agentCwd), basename(projectRoot));
|
|
327
|
-
const adopt = adoptWorktree(deps.runSync, agentCwd, wtBranch, 'origin/main');
|
|
328
|
-
if (adopt.action === 'adopted') {
|
|
329
|
-
deps.stderr(`worktree: adopted branch ${wtBranch} at origin/main\n`);
|
|
330
|
-
deps.stderr(renderInPlaceWorktreeNote(agentCwd, wtBranch));
|
|
331
|
-
}
|
|
332
|
-
else if (adopt.message) {
|
|
333
|
-
deps.stderr(`worktree sync: ${adopt.message}\n`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
// BUG-5 / v0.9.3: probe MCP readiness before launching claude so
|
|
337
|
-
// the launched session sees tools at startup. Non-blocking: probe
|
|
338
|
-
// failure surfaces a stderr warning but the launch proceeds (the
|
|
339
|
-
// kickoff text's ToolSearch recovery clause is the second line of
|
|
340
|
-
// defense).
|
|
341
|
-
const mcpReady = await deps.probeMcpReady();
|
|
342
|
-
if (!mcpReady) {
|
|
343
|
-
deps.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; ` +
|
|
344
|
-
`launching ${cli} anyway — the kickoff prompt's ToolSearch fallback ` +
|
|
345
|
-
`will recover if the MCP server takes longer to start.\n`);
|
|
346
|
-
}
|
|
347
|
-
const inboxPath = deps.getInboxPath(result.cube_id, result.drone_id);
|
|
348
|
-
const codexWakeNonce = cli === 'codex' ? `borg-wake-${randomUUID()}` : null;
|
|
349
|
-
const monitorClause = cli === 'claude'
|
|
350
|
-
? `If you haven't yet, arm a persistent Monitor running the command ` +
|
|
351
|
-
`\`borg-inbox-monitor ${inboxPath}\` ` +
|
|
352
|
-
`so each event's task-notification title summarizes the new cube log entry ` +
|
|
353
|
-
`(drone label, role, and first ~80 chars of the message body) — letting you ` +
|
|
354
|
-
`triage events without reading the full body. `
|
|
355
|
-
: '';
|
|
356
|
-
let codexWakePathClause;
|
|
357
|
-
let remoteArgs = [];
|
|
358
|
-
let launchArgs;
|
|
359
|
-
let launchEnv;
|
|
360
|
-
let codexSocketPath = null;
|
|
361
|
-
let codexServerCleanup = null;
|
|
362
|
-
if (cli === 'codex') {
|
|
363
|
-
const remote = await deps.prepareCodexRemoteLaunch();
|
|
364
|
-
if (remote.warning) {
|
|
365
|
-
deps.stderr(`warning: ${remote.warning}\n`);
|
|
366
|
-
codexWakePathClause =
|
|
367
|
-
`⚠ Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg:regen manually whenever you return, and expect only fallback wakeups until relaunch.`;
|
|
368
|
-
}
|
|
369
|
-
else {
|
|
370
|
-
codexWakePathClause =
|
|
371
|
-
`Codex wake-path capability check passed: remote-control socket established for this session.`;
|
|
372
|
-
}
|
|
373
|
-
remoteArgs = remote.args;
|
|
374
|
-
launchEnv = Object.keys(remote.env).length > 0 ? remote.env : undefined;
|
|
375
|
-
codexSocketPath = socketPathFromRemoteArgs(remote.args);
|
|
376
|
-
codexServerCleanup = remote.server?.cleanup ?? null;
|
|
377
|
-
}
|
|
378
|
-
const kickoff = buildAgentKickoffPrompt({
|
|
379
|
-
cli,
|
|
380
|
-
codexWakeNonce,
|
|
381
|
-
monitorClause,
|
|
382
|
-
codexWakePathClause,
|
|
383
|
-
});
|
|
384
|
-
launchArgs = [kickoff];
|
|
385
|
-
if (cli === 'codex') {
|
|
386
|
-
launchArgs = [...remoteArgs, ...withCodexCwdArg(launchArgs, agentCwd)];
|
|
387
|
-
}
|
|
388
|
-
const exitPromise = deps.exec(cli, launchArgs, agentCwd, launchEnv);
|
|
389
|
-
if (cli === 'codex' && codexSocketPath && codexWakeNonce) {
|
|
390
|
-
void recordCodexWakeTarget({
|
|
391
|
-
deps,
|
|
392
|
-
cubeId: result.cube_id,
|
|
393
|
-
droneId: result.drone_id,
|
|
394
|
-
socketPath: codexSocketPath,
|
|
395
|
-
cwd: agentCwd,
|
|
396
|
-
previewNeedle: codexWakeNonce,
|
|
397
|
-
launchedAtSeconds: Math.floor(Date.now() / 1000),
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
const exitCode = await exitPromise;
|
|
401
|
-
// gh#528: kill the borg-owned Codex app-server when the assimilate-launched
|
|
402
|
-
// session exits, so it isn't left orphaned (live → not pruned by pid liveness).
|
|
403
|
-
if (codexServerCleanup) {
|
|
404
|
-
try {
|
|
405
|
-
codexServerCleanup();
|
|
406
|
-
}
|
|
407
|
-
catch {
|
|
408
|
-
// best-effort
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
// Sprint 18: when a sibling worktree was spawned, the user's shell
|
|
412
|
-
// returns to their original cwd after Claude exits (process.chdir
|
|
413
|
-
// doesn't propagate to the parent). Emit a stderr hint so they know
|
|
414
|
-
// how to get back into the worktree. shellEscape defangs any shell
|
|
415
|
-
// metachars in the path against paste-injection (drone-11 SR-LANE).
|
|
416
|
-
// Skip the hint when no worktree was spawned (--here / no-worktree
|
|
417
|
-
// flow) or when originalCwd already matches the worktree path
|
|
418
|
-
// (defensive against the no-op edge case drone-9 UX-LANE flagged).
|
|
419
|
-
if (spawnedWorktreePath && originalCwd !== spawnedWorktreePath) {
|
|
420
|
-
deps.stderr(`\nAgent exited. You were working in ${spawnedWorktreePath}; your shell is back in ${originalCwd}.\n` +
|
|
421
|
-
`To return:\n` +
|
|
422
|
-
` cd ${shellEscape(spawnedWorktreePath)}\n`);
|
|
423
|
-
}
|
|
424
|
-
return exitCode;
|
|
425
|
-
}
|
|
426
|
-
function renderWorktreeSteeringNote(worktreePath, wtBranch, primaryPath) {
|
|
427
|
-
return (`\nWORKTREE STEERING: You are in worktree ${worktreePath} on branch ${wtBranch}. ` +
|
|
428
|
-
`Do ALL work HERE — cut your feature branch (fix/.../feat/...) off ${wtBranch} in THIS worktree, ` +
|
|
429
|
-
`use relative paths / your cwd. NEVER \`git -C ${primaryPath}\` or operate on the primary checkout ${primaryPath}: ` +
|
|
430
|
-
`the same branch can't be checked out in two worktrees, so work created in the primary won't reach your wt-branch ` +
|
|
431
|
-
`without manual surgery (cherry-pick/merge).\n`);
|
|
432
|
-
}
|
|
433
|
-
function renderInPlaceWorktreeNote(worktreePath, wtBranch) {
|
|
434
|
-
return (`\nWORKTREE STEERING: This checkout is now on branch ${wtBranch}. ` +
|
|
435
|
-
`Do ALL work HERE in ${worktreePath} — cut your feature branch (fix/.../feat/...) off ${wtBranch}, ` +
|
|
436
|
-
`use relative paths / your cwd.\n`);
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Sprint 4 / gh#147 (drone-8 SR-PE-FINDING-1): strip ASCII control
|
|
440
|
-
* characters before interpolating subprocess stderr into operator-
|
|
441
|
-
* facing messages. Defense-in-depth against a local attacker editing
|
|
442
|
-
* `.git/config` to embed ANSI escapes (e.g. `\x1b[2J` cursor moves,
|
|
443
|
-
* `\x1b]0;...\x07` title injection) — git command stderr then carries
|
|
444
|
-
* them, and unfiltered orchestrator output corrupts the terminal.
|
|
445
|
-
*
|
|
446
|
-
* Strips `[\x00-\x1F\x7F]` (NUL, all C0 controls, DEL). ASCII
|
|
447
|
-
* whitespace inside C0 (tab, newline, CR) gets stripped too — the
|
|
448
|
-
* orchestrator only ever interpolates short status fragments where
|
|
449
|
-
* preserving multi-line layout isn't load-bearing; over-strip
|
|
450
|
-
* trade-off accepted for shape simplicity.
|
|
451
|
-
*/
|
|
452
|
-
export function safeStderr(msg) {
|
|
453
|
-
return msg.replace(/[\x00-\x1F\x7F]/g, '');
|
|
454
|
-
}
|
|
455
|
-
function worktreeRegistered(deps, projectRoot, candidate) {
|
|
456
|
-
const res = deps.runSync('git', ['worktree', 'list', '--porcelain'], projectRoot);
|
|
457
|
-
if (res.status !== 0)
|
|
458
|
-
return false;
|
|
459
|
-
return res.stdout.split('\n').some((line) => line === `worktree ${candidate}`);
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* Sprint 19 (gh#184): suggest the closest cube-role name for a misspelled
|
|
463
|
-
* CLI role argument. Levenshtein distance ≤2 against the cube's role
|
|
464
|
-
* names; case-insensitive. Returns null when no close match exists.
|
|
465
|
-
*
|
|
466
|
-
* Serves Queen's "more user-friendly" intent without violating the
|
|
467
|
-
* Borg-collective metaphor (collective defines roles; drones slot in).
|
|
468
|
-
* The original strict-failure semantic is preserved; the suggestion
|
|
469
|
-
* is an additive nudge in the error message, not a fallback path.
|
|
470
|
-
*/
|
|
471
|
-
export function suggestRoleName(input, candidates) {
|
|
472
|
-
if (candidates.length === 0)
|
|
473
|
-
return null;
|
|
474
|
-
const inputLower = input.toLowerCase();
|
|
475
|
-
let best = null;
|
|
476
|
-
for (const candidate of candidates) {
|
|
477
|
-
const distance = levenshtein(inputLower, candidate.toLowerCase());
|
|
478
|
-
if (distance <= 2 && (best === null || distance < best.distance)) {
|
|
479
|
-
best = { name: candidate, distance };
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
return best ? best.name : null;
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* Minimal Levenshtein distance implementation. Used only by
|
|
486
|
-
* `suggestRoleName` for the fuzzy-match nudge; intentionally
|
|
487
|
-
* unexported and not a general-purpose helper.
|
|
488
|
-
*/
|
|
489
|
-
function levenshtein(a, b) {
|
|
490
|
-
if (a === b)
|
|
491
|
-
return 0;
|
|
492
|
-
if (a.length === 0)
|
|
493
|
-
return b.length;
|
|
494
|
-
if (b.length === 0)
|
|
495
|
-
return a.length;
|
|
496
|
-
const prev = new Array(b.length + 1);
|
|
497
|
-
const curr = new Array(b.length + 1);
|
|
498
|
-
for (let j = 0; j <= b.length; j++)
|
|
499
|
-
prev[j] = j;
|
|
500
|
-
for (let i = 1; i <= a.length; i++) {
|
|
501
|
-
curr[0] = i;
|
|
502
|
-
for (let j = 1; j <= b.length; j++) {
|
|
503
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
504
|
-
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
505
|
-
}
|
|
506
|
-
for (let j = 0; j <= b.length; j++)
|
|
507
|
-
prev[j] = curr[j];
|
|
508
|
-
}
|
|
509
|
-
return prev[b.length];
|
|
510
|
-
}
|
|
511
|
-
//# sourceMappingURL=assimilate-cmd.js.map
|
|
1
|
+
import{dirname as K,basename as C,join as W}from"node:path";import{randomUUID as V}from"node:crypto";import{roleSlug as J,matchRoleByName as Q,pickDefaultRole as X}from"./role-resolver.js";import{deriveCubeName as Z,parseGitRemote as ee,sanitizeRemoteUrl as te}from"./cube-name.js";import{validateName as H}from"./name-validator.js";import{renderAssimilationWelcome as re}from"./assimilate-welcome.js";import{shellEscape as ne}from"./shell-escape.js";import{withCodexCwdArg as oe}from"./codex-remote.js";import{buildAgentKickoffPrompt as ie,recordCodexWakeTarget as ae,socketPathFromRemoteArgs as se}from"./codex-launch.js";import{perWorktreeBranchName as O,adoptWorktree as le}from"./worktree-lifecycle.js";async function Ee(r,e){if(r.role!==void 0){const t=H(r.role);if(!t.ok)return e.stderr(t.error+`
|
|
2
|
+
`),1}if(r.flags.worktree!==void 0){const t=H(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
|
|
3
|
+
`),1}let o=await e.getCachedAuth();if(!o){if(!e.isTTY()&&!r.flags.yes)return e.stderr("borg setup required and stdin is non-interactive. Run `borg setup` first in an interactive terminal, then `borg assimilate`.\n"),1;o=await e.runSetup()}const a=e.findProjectRoot(e.cwd());let i;if(r.flags.cubeName)i=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],a),n=t.status===0?t.stdout:null;if(i=Z(a,n),n){const l=te(n),u=l?ee(l):null;l&&!u&&i&&e.stderr(`couldn't parse git remote '${l}' \u2014 using directory name '${i}' as cube name
|
|
4
|
+
`)}}let s=null;if(i&&i.includes("@")&&i.includes(":")){const t=i.lastIndexOf(":");s={ownerEmail:i.substring(0,t),cubeName:i.substring(t+1)},i=s.cubeName}const p=e.cwd();let R;try{R=await e.listCubes(o.apiUrl,o.token)}catch(t){const n=t instanceof Error?t.message:String(t);if(n.includes("Authentication required")||n.includes("Authentication expired"))e.stderr(`Re-authenticating...
|
|
5
|
+
`),o=await e.runSetup(),R=await e.listCubes(o.apiUrl,o.token);else throw t}const S=R.find(t=>t.name===i);if(!S&&s)return e.stderr(`No cube named '${s.cubeName}' accessible to you owned by '${s.ownerEmail}'. Did you accept their invite? See borgmcp.ai/dashboard.
|
|
6
|
+
`),1;let c,E;if(S)c=await e.getCube(o.apiUrl,o.token,S.id),E=!1;else{let t;if(r.flags.template)t=r.flags.template;else if(r.flags.noTemplate)t=void 0;else if(e.isTTY())if(r.flags.yes)t="starter";else{const n=await e.listTemplates(o.apiUrl,o.token),l=["First drone joining a new cube. Apply a template?"];n.forEach((y,k)=>{const x=k===0?" (default)":"";l.push(` ${k+1}) ${y.name}${x} \u2014 ${y.description}`)}),l.push(` ${n.length+1}) skip \u2014 no template`);const u=(await e.prompt(l.join(`
|
|
7
|
+
`)+`
|
|
8
|
+
[1]: `)).trim(),h=u===""?1:parseInt(u,10);if(Number.isNaN(h)||h<1||h>n.length+1)return e.stderr(`invalid choice "${u}"
|
|
9
|
+
`),1;t=h<=n.length?n[h-1].name:void 0}else{if(!r.flags.yes)return e.stderr(`cube creation needs a template choice but stdin is non-interactive.
|
|
10
|
+
Pass --template <name>, --no-template, or --yes (defaults to starter).
|
|
11
|
+
`),1;t="starter"}c=await e.createCube(o.apiUrl,o.token,t?{name:i??void 0,template:t}:{name:i??void 0}),E=!0}let d;if(r.role!==void 0){if(d=Q(c.roles,r.role),!d){const t=c.roles.map(u=>u.name).join(", "),n=fe(r.role,c.roles.map(u=>u.name)),l=n?` Did you mean "${n}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${c.name}". Available: ${t}.${l}
|
|
12
|
+
(Use --template <name> on first-drone setup or run \`borg:create-role\` from inside Claude.)
|
|
13
|
+
`),1}}else if(d=X(c.roles,{isFirstDrone:E}),!d)return e.stderr(`cube "${c.name}" has no default or human-seat role; cannot infer a role. Either pass a role argument explicitly (e.g. \`borg assimilate builder\`) or run \`borg:create-role\` from inside Claude to set up roles.
|
|
14
|
+
`),1;let m;try{m=await e.assimilate(o.apiUrl,o.token,{cube_id:c.id,role_id:d.id,hostname:e.getHostname()})}catch(t){const n=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${n}
|
|
15
|
+
`),1}const $=c.roles.find(t=>t.id===m.role_id)??d;$.id!==d.id&&e.stderr(`Note: your invite didn't grant the "${d.name}" role \u2014 assimilated as "${$.name}" instead.
|
|
16
|
+
`);const U=await e.getActiveCube(),M=r.flags.worktree!==void 0||U!==null;let f=null;if(M){if(U&&r.flags.here)return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
|
|
17
|
+
`),1;const t=e.runSync("git",["rev-parse","--verify","HEAD"],a);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
|
|
18
|
+
Fix: create at least one commit (\`git commit --allow-empty -m "initial"\`)
|
|
19
|
+
OR: pass --here to skip the sibling spawn and use the current directory
|
|
20
|
+
`),1;e.runSync("git",["fetch","origin"],a);let n="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],a).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],a).status===0&&(n="origin/master");const u=t.stdout.trim(),h=e.runSync("git",["rev-parse",n],a).stdout.trim();u!==h&&e.stderr(`note: local HEAD (${u.slice(0,7)}) differs from ${n} (${h.slice(0,7)}); new worktree will start on ${n}
|
|
21
|
+
`);const y=K(a),k=C(a),x=r.flags.worktree??J($.name);let w=W(y,`${k}-${x}`),j=2;for(;e.pathExists(w)||me(e,a,w);)w=W(y,`${k}-${x}-${j}`),j++;const I=O(C(w),k),L=e.runSync("git",["worktree","add","-b",I,w,n],a);if(L.status!==0)return e.stderr(`git worktree add failed: ${F(L.stderr)}
|
|
22
|
+
`),1;e.stderr(`spawned sibling worktree at ${w} on branch ${I} (${n}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
|
|
23
|
+
`),e.chdir(w),e.stderr(ce(w,I,a)),f=e.cwd()}try{await e.setActiveCube({cubeId:m.cube_id,droneId:m.drone_id,name:c.name,sessionToken:m.session_token,droneLabel:m.drone_label,apiUrl:o.apiUrl})}catch(t){const n=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${n}
|
|
24
|
+
`),f){const l=e.runSync("git",["worktree","remove","--force",f],a);l.status===0?e.stderr(`rolled back spawned worktree at ${f}
|
|
25
|
+
`):e.stderr(`manual cleanup needed: \`git worktree remove --force ${f}\` (rollback attempt failed: ${F(l.stderr).trim()||"unknown"})
|
|
26
|
+
`)}return 1}e.setTerminalTitle(m.drone_label,c.name);const Y=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(re($.name,c.name,Y));const g=await e.resolveCli(r.flags.cli),b=e.cwd();if(!f){e.runSync("git",["fetch","origin","--prune"],b);const t=O(C(b),C(a)),n=le(e.runSync,b,t,"origin/main");n.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
|
|
27
|
+
`),e.stderr(ue(b,t))):n.message&&e.stderr(`worktree sync: ${n.message}
|
|
28
|
+
`)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${g} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
|
|
29
|
+
`);const q=e.getInboxPath(m.cube_id,m.drone_id),A=g==="codex"?`borg-wake-${V()}`:null,z=g==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${q}\` so each event's task-notification title summarizes the new cube log entry (drone label, role, and first ~80 chars of the message body) \u2014 letting you triage events without reading the full body. `:"";let N,_=[],v,D,T=null,P=null;if(g==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
|
|
30
|
+
`),N="\u26A0 Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg:regen manually whenever you return, and expect only fallback wakeups until relaunch."):N="Codex wake-path capability check passed: remote-control socket established for this session.",_=t.args,D=Object.keys(t.env).length>0?t.env:void 0,T=se(t.args),P=t.server?.cleanup??null}v=[ie({cli:g,codexWakeNonce:A,monitorClause:z,codexWakePathClause:N})],g==="codex"&&(v=[..._,...oe(v,b)]);const B=e.exec(g,v,b,D);g==="codex"&&T&&A&&ae({deps:e,cubeId:m.cube_id,droneId:m.drone_id,socketPath:T,cwd:b,previewNeedle:A,launchedAtSeconds:Math.floor(Date.now()/1e3)});const G=await B;if(P)try{P()}catch{}return f&&p!==f&&e.stderr(`
|
|
31
|
+
Agent exited. You were working in ${f}; your shell is back in ${p}.
|
|
32
|
+
To return:
|
|
33
|
+
cd ${ne(f)}
|
|
34
|
+
`),G}function ce(r,e,o){return`
|
|
35
|
+
WORKTREE STEERING: You are in worktree ${r} on branch ${e}. Do ALL work HERE \u2014 cut your feature branch (fix/.../feat/...) off ${e} in THIS worktree, use relative paths / your cwd. NEVER \`git -C ${o}\` or operate on the primary checkout ${o}: the same branch can't be checked out in two worktrees, so work created in the primary won't reach your wt-branch without manual surgery (cherry-pick/merge).
|
|
36
|
+
`}function ue(r,e){return`
|
|
37
|
+
WORKTREE STEERING: This checkout is now on branch ${e}. Do ALL work HERE in ${r} \u2014 cut your feature branch (fix/.../feat/...) off ${e}, use relative paths / your cwd.
|
|
38
|
+
`}function F(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function me(r,e,o){const a=r.runSync("git",["worktree","list","--porcelain"],e);return a.status!==0?!1:a.stdout.split(`
|
|
39
|
+
`).some(i=>i===`worktree ${o}`)}function fe(r,e){if(e.length===0)return null;const o=r.toLowerCase();let a=null;for(const i of e){const s=de(o,i.toLowerCase());s<=2&&(a===null||s<a.distance)&&(a={name:i,distance:s})}return a?a.name:null}function de(r,e){if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;const o=new Array(e.length+1),a=new Array(e.length+1);for(let i=0;i<=e.length;i++)o[i]=i;for(let i=1;i<=r.length;i++){a[0]=i;for(let s=1;s<=e.length;s++){const p=r[i-1]===e[s-1]?0:1;a[s]=Math.min(a[s-1]+1,o[s]+1,o[s-1]+p)}for(let s=0;s<=e.length;s++)o[s]=a[s]}return o[e.length]}export{Ee as runAssimilate,F as safeStderr,fe as suggestRoleName};
|