borgmcp 4.6.1 → 4.6.2
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.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +1 -0
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/assimilate-deps.d.ts.map +1 -1
- package/dist/assimilate-deps.js +2 -14
- package/dist/assimilate-deps.js.map +1 -1
- package/dist/claude.d.ts +5 -3
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +82 -30
- package/dist/claude.js.map +1 -1
- package/dist/config-utils.d.ts +3 -0
- package/dist/config-utils.d.ts.map +1 -1
- package/dist/config-utils.js +141 -4
- package/dist/config-utils.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/opencode-drone.d.ts +2 -0
- package/dist/opencode-drone.d.ts.map +1 -1
- package/dist/opencode-drone.js +101 -28
- package/dist/opencode-drone.js.map +1 -1
- package/dist/opencode-launch-trust.d.ts +1 -0
- package/dist/opencode-launch-trust.d.ts.map +1 -1
- package/dist/opencode-launch-trust.js +4 -0
- package/dist/opencode-launch-trust.js.map +1 -1
- package/package.json +1 -1
- package/src/assimilate-cmd.ts +1 -0
- package/src/assimilate-deps.ts +2 -14
- package/src/claude.ts +91 -31
- package/src/config-utils.ts +156 -4
- package/src/index.ts +7 -1
- package/src/opencode-drone.ts +112 -30
- package/src/opencode-launch-trust.ts +5 -0
package/src/claude.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { spawn } from 'child_process';
|
|
19
19
|
import { randomUUID } from 'node:crypto';
|
|
20
20
|
import { realpathSync } from 'node:fs';
|
|
21
|
+
import { homedir } from 'node:os';
|
|
21
22
|
import { basename } from 'node:path';
|
|
22
23
|
import { createInterface } from 'node:readline/promises';
|
|
23
24
|
import { fileURLToPath } from 'node:url';
|
|
@@ -102,6 +103,7 @@ import {
|
|
|
102
103
|
addCodexUserPromptSubmitHook,
|
|
103
104
|
addProjectSessionStartHook,
|
|
104
105
|
addUserPromptSubmitHook,
|
|
106
|
+
provisionLaunchAccess,
|
|
105
107
|
removeSessionStartHook,
|
|
106
108
|
} from './config-utils.js';
|
|
107
109
|
import { ensureCliMcpConfigured } from './ensure-mcp-config.js';
|
|
@@ -118,6 +120,7 @@ import { buildOpenCodeLaunchArgs, defaultApprovalIo, resolveLaunchBorgApprovals
|
|
|
118
120
|
import { isClientOwnedCubeInitArgv, runEarlyServerFacade } from './server-facade.js';
|
|
119
121
|
import { runEarlyUpdate } from './update-cmd.js';
|
|
120
122
|
import { runDoctor, warnIfAgentIntegrationUnhealthy } from './agent-integration-health.js';
|
|
123
|
+
import { scratchRootForSeat } from './launch-access.js';
|
|
121
124
|
|
|
122
125
|
export type AssimilateDepsBuilder = typeof buildDefaultAssimilateDeps;
|
|
123
126
|
|
|
@@ -148,7 +151,7 @@ export function createOpenCodeLaunchPlan(
|
|
|
148
151
|
};
|
|
149
152
|
}
|
|
150
153
|
|
|
151
|
-
export function launchOpenCodeProcess(options: {
|
|
154
|
+
export async function launchOpenCodeProcess(options: {
|
|
152
155
|
cwd: string;
|
|
153
156
|
port: number;
|
|
154
157
|
prompt: string;
|
|
@@ -159,36 +162,76 @@ export function launchOpenCodeProcess(options: {
|
|
|
159
162
|
kickoff: ReturnType<typeof createOpenCodeLaunchKickoff>;
|
|
160
163
|
spawnProcess?: typeof spawn;
|
|
161
164
|
connect?: typeof connectOpenCodeDrone;
|
|
162
|
-
|
|
165
|
+
injectKickoff?: typeof injectInitialKickoff;
|
|
166
|
+
allocatePort?: typeof allocateOpenCodePort;
|
|
167
|
+
}): Promise<{
|
|
163
168
|
launchArgs: string[];
|
|
164
169
|
launchEnv: NodeJS.ProcessEnv;
|
|
165
170
|
process: ReturnType<typeof spawn>;
|
|
166
|
-
} {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
171
|
+
}> {
|
|
172
|
+
let port = options.port;
|
|
173
|
+
let lastExit: { code: number | null; signal: NodeJS.Signals | null } | null = null;
|
|
174
|
+
// Each child gets independent trust so a retry cannot bind a session or
|
|
175
|
+
// durable binding left by an earlier attempt.
|
|
176
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
177
|
+
const attemptKickoff = attempt === 0
|
|
178
|
+
? options.kickoff
|
|
179
|
+
: createOpenCodeLaunchKickoff(options.prompt);
|
|
180
|
+
const plan = createOpenCodeLaunchPlan(options.cwd, port, options.prompt, options.passthroughArgs);
|
|
181
|
+
const launchEnv = {
|
|
182
|
+
...options.env,
|
|
183
|
+
BORG_OPENCODE_PORT: plan.envPort,
|
|
184
|
+
[OPENCODE_SERVER_USERNAME_ENV]: OPENCODE_SERVER_USERNAME,
|
|
185
|
+
[OPENCODE_SERVER_PASSWORD_ENV]: attemptKickoff.apiPassword,
|
|
186
|
+
[BORG_OPENCODE_LAUNCH_CORRELATION_ENV]: attemptKickoff.correlationIdentity,
|
|
187
|
+
};
|
|
188
|
+
const child = (options.spawnProcess ?? spawn)('opencode', plan.launchArgs, {
|
|
189
|
+
stdio: 'inherit',
|
|
190
|
+
shell: false,
|
|
191
|
+
env: launchEnv,
|
|
192
|
+
});
|
|
193
|
+
type Stopped =
|
|
194
|
+
| { kind: 'error'; error: NodeJS.ErrnoException }
|
|
195
|
+
| { kind: 'exit'; code: number | null; signal: NodeJS.Signals | null };
|
|
196
|
+
let markStopped: (outcome: Stopped) => void;
|
|
197
|
+
const stopped = new Promise<Stopped>((resolve) => { markStopped = resolve; });
|
|
198
|
+
const onError = (error: NodeJS.ErrnoException) => markStopped({ kind: 'error', error });
|
|
199
|
+
const onExit = (code: number | null, signal: NodeJS.Signals | null) => {
|
|
200
|
+
markStopped({ kind: 'exit', code, signal });
|
|
201
|
+
};
|
|
202
|
+
child.once('error', onError);
|
|
203
|
+
child.once('exit', onExit);
|
|
204
|
+
await (options.connect ?? connectOpenCodeDrone)({
|
|
205
|
+
serverUrl: plan.serverUrl,
|
|
206
|
+
apiPassword: attemptKickoff.apiPassword,
|
|
207
|
+
directory: options.cwd,
|
|
208
|
+
droneLabel: options.droneLabel,
|
|
209
|
+
cubeName: options.cubeName,
|
|
210
|
+
launchIdentity: attemptKickoff.correlationIdentity,
|
|
211
|
+
});
|
|
212
|
+
const outcome = await Promise.race([
|
|
213
|
+
(options.injectKickoff ?? injectInitialKickoff)(attemptKickoff).then(
|
|
214
|
+
(bound) => bound ? { kind: 'ready' as const } : { kind: 'unbound' as const },
|
|
215
|
+
),
|
|
216
|
+
stopped,
|
|
217
|
+
]);
|
|
218
|
+
child.off('error', onError);
|
|
219
|
+
child.off('exit', onExit);
|
|
220
|
+
if (outcome.kind === 'ready') {
|
|
221
|
+
return { launchArgs: plan.launchArgs, launchEnv, process: child };
|
|
222
|
+
}
|
|
223
|
+
if (outcome.kind === 'unbound') {
|
|
224
|
+
child.kill();
|
|
225
|
+
throw new Error('OpenCode started but its launch session could not be identified');
|
|
226
|
+
}
|
|
227
|
+
if (outcome.kind === 'error') throw outcome.error;
|
|
228
|
+
lastExit = outcome;
|
|
229
|
+
if (attempt < 2) port = await (options.allocatePort ?? allocateOpenCodePort)();
|
|
230
|
+
}
|
|
231
|
+
throw new Error(
|
|
232
|
+
`OpenCode exited before launch readiness after 3 attempts `
|
|
233
|
+
+ `(code=${lastExit?.code ?? 'none'}, signal=${lastExit?.signal ?? 'none'})`,
|
|
234
|
+
);
|
|
192
235
|
}
|
|
193
236
|
|
|
194
237
|
export async function runAssimilateEntry(
|
|
@@ -510,7 +553,19 @@ async function main() {
|
|
|
510
553
|
cli = configureSelectedLaunchCli(
|
|
511
554
|
cli,
|
|
512
555
|
launchAction,
|
|
513
|
-
(selectedCli) =>
|
|
556
|
+
(selectedCli) => {
|
|
557
|
+
ensureResolvedCliConfigured(selectedCli, active);
|
|
558
|
+
if (selectedCli === 'opencode' && active) {
|
|
559
|
+
const worktree = findProjectRoot(process.cwd());
|
|
560
|
+
provisionLaunchAccess(selectedCli, worktree, {
|
|
561
|
+
worktree,
|
|
562
|
+
scratch: scratchRootForSeat(homedir(), active.droneLabel, active.droneId),
|
|
563
|
+
// OpenCode does not grant Git internals; this field is used only by
|
|
564
|
+
// the Codex launch-argument path.
|
|
565
|
+
commonDir: worktree,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
},
|
|
514
569
|
);
|
|
515
570
|
|
|
516
571
|
if (active && !parsedCli.force) {
|
|
@@ -682,7 +737,7 @@ async function main() {
|
|
|
682
737
|
console.error(`${consolePrefix()}${chalk.blue(`◼ Launching ${cliDisplayName}…`)}`);
|
|
683
738
|
|
|
684
739
|
const agentProcess = cli === 'opencode' && openCodeKickoff && openCodePort !== undefined
|
|
685
|
-
? launchOpenCodeProcess({
|
|
740
|
+
? (await launchOpenCodeProcess({
|
|
686
741
|
cwd: process.cwd(),
|
|
687
742
|
port: openCodePort,
|
|
688
743
|
prompt: openCodeKickoff.prompt,
|
|
@@ -691,7 +746,7 @@ async function main() {
|
|
|
691
746
|
droneLabel: active?.droneLabel ?? 'opencode',
|
|
692
747
|
cubeName: active?.name ?? 'borg',
|
|
693
748
|
kickoff: openCodeKickoff,
|
|
694
|
-
}).process
|
|
749
|
+
})).process
|
|
695
750
|
: spawn(cli, launchArgs, { stdio: 'inherit', shell: false, env: launchEnv });
|
|
696
751
|
|
|
697
752
|
// gh#857 WI-2: wake-target recording is codex-only (app-server bridge).
|
|
@@ -807,6 +862,11 @@ if (isEntryInvocation()) {
|
|
|
807
862
|
process.stderr.write(`${error.message}\n`);
|
|
808
863
|
process.exit(1);
|
|
809
864
|
}
|
|
865
|
+
if (error?.code === 'ENOENT' && error?.path === 'opencode') {
|
|
866
|
+
console.error(`${consolePrefix()}${chalk.red('\n◼ Failed to launch opencode')}`);
|
|
867
|
+
console.error(`${consolePrefix()}${chalk.gray('Make sure opencode is installed.\n')}`);
|
|
868
|
+
process.exit(1);
|
|
869
|
+
}
|
|
810
870
|
console.error(`${consolePrefix()}${chalk.red(`\n◼ Error: ${error.message}\n`)}`);
|
|
811
871
|
process.exit(1);
|
|
812
872
|
});
|
package/src/config-utils.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
8
9
|
import fs from 'fs';
|
|
9
10
|
import path from 'path';
|
|
10
11
|
import { fileURLToPath } from 'url';
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
import { shellEscape } from './shell-escape.js';
|
|
24
25
|
import { BORG_STATE_ROOT_ENV, borgAgentConfigEnv, borgHomeRoot } from './private-root.js';
|
|
25
26
|
import type { LaunchAccessPaths } from './launch-access.js';
|
|
26
|
-
import { BORG_LAUNCH_EXPECTED_SEAT_ENV } from './cubes.js';
|
|
27
|
+
import { BORG_LAUNCH_EXPECTED_SEAT_ENV, type BorgCli } from './cubes.js';
|
|
27
28
|
import {
|
|
28
29
|
OPENCODE_SERVER_PASSWORD_ENV,
|
|
29
30
|
OPENCODE_SERVER_PASSWORD_REFERENCE,
|
|
@@ -209,6 +210,132 @@ function writeJsonFile(p: string, data: any): void {
|
|
|
209
210
|
fs.writeFileSync(p, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
210
211
|
}
|
|
211
212
|
|
|
213
|
+
interface OpenCodeProjectConfigSnapshot {
|
|
214
|
+
config: unknown;
|
|
215
|
+
identity: { dev: number; ino: number } | null;
|
|
216
|
+
mode: number;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function unsafeOpenCodeConfigPath(configPath: string, detail: string): Error {
|
|
220
|
+
return new Error(`OpenCode config path ${configPath} is unsafe: ${detail}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function assertOpenCodeProjectConfigDirectory(projectRoot: string, configPath: string): string {
|
|
224
|
+
const root = path.resolve(projectRoot);
|
|
225
|
+
let rootStat: fs.Stats;
|
|
226
|
+
try {
|
|
227
|
+
rootStat = fs.lstatSync(root);
|
|
228
|
+
} catch {
|
|
229
|
+
throw unsafeOpenCodeConfigPath(configPath, 'project root is missing or unreadable');
|
|
230
|
+
}
|
|
231
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory() || fs.realpathSync(root) !== root) {
|
|
232
|
+
throw unsafeOpenCodeConfigPath(configPath, 'project root must be a canonical real directory');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const configDir = path.join(root, '.opencode');
|
|
236
|
+
try {
|
|
237
|
+
fs.mkdirSync(configDir, { mode: 0o700 });
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
240
|
+
}
|
|
241
|
+
let directoryStat: fs.Stats;
|
|
242
|
+
try {
|
|
243
|
+
directoryStat = fs.lstatSync(configDir);
|
|
244
|
+
} catch {
|
|
245
|
+
throw unsafeOpenCodeConfigPath(configPath, '.opencode is missing or unreadable');
|
|
246
|
+
}
|
|
247
|
+
if (
|
|
248
|
+
directoryStat.isSymbolicLink()
|
|
249
|
+
|| !directoryStat.isDirectory()
|
|
250
|
+
|| fs.realpathSync(configDir) !== configDir
|
|
251
|
+
) {
|
|
252
|
+
throw unsafeOpenCodeConfigPath(configPath, '.opencode must be a canonical real directory, not a symlink');
|
|
253
|
+
}
|
|
254
|
+
return configDir;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function inspectOpenCodeProjectConfigFile(configPath: string): fs.Stats | null {
|
|
258
|
+
let metadata: fs.Stats;
|
|
259
|
+
try {
|
|
260
|
+
metadata = fs.lstatSync(configPath);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null;
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
if (metadata.isSymbolicLink() || !metadata.isFile() || metadata.nlink !== 1) {
|
|
266
|
+
throw unsafeOpenCodeConfigPath(configPath, 'final path must be one regular file, not a symlink');
|
|
267
|
+
}
|
|
268
|
+
return metadata;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function readOpenCodeProjectConfig(projectRoot: string, configPath: string): OpenCodeProjectConfigSnapshot {
|
|
272
|
+
assertOpenCodeProjectConfigDirectory(projectRoot, configPath);
|
|
273
|
+
const metadata = inspectOpenCodeProjectConfigFile(configPath);
|
|
274
|
+
if (!metadata) return { config: {}, identity: null, mode: 0o600 };
|
|
275
|
+
|
|
276
|
+
let descriptor: number | null = null;
|
|
277
|
+
try {
|
|
278
|
+
descriptor = fs.openSync(configPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
|
|
279
|
+
const opened = fs.fstatSync(descriptor);
|
|
280
|
+
if (!opened.isFile() || opened.nlink !== 1 || opened.dev !== metadata.dev || opened.ino !== metadata.ino) {
|
|
281
|
+
throw unsafeOpenCodeConfigPath(configPath, 'final file changed while opening');
|
|
282
|
+
}
|
|
283
|
+
const text = fs.readFileSync(descriptor, 'utf8');
|
|
284
|
+
return {
|
|
285
|
+
config: text.trim() ? JSON.parse(text) : {},
|
|
286
|
+
identity: { dev: opened.dev, ino: opened.ino },
|
|
287
|
+
mode: opened.mode & 0o777,
|
|
288
|
+
};
|
|
289
|
+
} finally {
|
|
290
|
+
if (descriptor !== null) fs.closeSync(descriptor);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function writeOpenCodeProjectConfig(
|
|
295
|
+
projectRoot: string,
|
|
296
|
+
configPath: string,
|
|
297
|
+
data: unknown,
|
|
298
|
+
snapshot: OpenCodeProjectConfigSnapshot,
|
|
299
|
+
): void {
|
|
300
|
+
const configDir = assertOpenCodeProjectConfigDirectory(projectRoot, configPath);
|
|
301
|
+
const temporary = path.join(
|
|
302
|
+
configDir,
|
|
303
|
+
`.opencode.json.${process.pid}.${randomBytes(12).toString('hex')}.tmp`,
|
|
304
|
+
);
|
|
305
|
+
let descriptor: number | null = null;
|
|
306
|
+
try {
|
|
307
|
+
descriptor = fs.openSync(
|
|
308
|
+
temporary,
|
|
309
|
+
fs.constants.O_WRONLY
|
|
310
|
+
| fs.constants.O_CREAT
|
|
311
|
+
| fs.constants.O_EXCL
|
|
312
|
+
| (fs.constants.O_NOFOLLOW ?? 0),
|
|
313
|
+
snapshot.mode,
|
|
314
|
+
);
|
|
315
|
+
fs.writeFileSync(descriptor, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
316
|
+
fs.fsyncSync(descriptor);
|
|
317
|
+
fs.closeSync(descriptor);
|
|
318
|
+
descriptor = null;
|
|
319
|
+
|
|
320
|
+
assertOpenCodeProjectConfigDirectory(projectRoot, configPath);
|
|
321
|
+
const current = inspectOpenCodeProjectConfigFile(configPath);
|
|
322
|
+
const unchanged = snapshot.identity === null
|
|
323
|
+
? current === null
|
|
324
|
+
: current !== null
|
|
325
|
+
&& current.dev === snapshot.identity.dev
|
|
326
|
+
&& current.ino === snapshot.identity.ino;
|
|
327
|
+
if (!unchanged) {
|
|
328
|
+
throw unsafeOpenCodeConfigPath(configPath, 'final file changed before replacement');
|
|
329
|
+
}
|
|
330
|
+
fs.renameSync(temporary, configPath);
|
|
331
|
+
} finally {
|
|
332
|
+
if (descriptor !== null) {
|
|
333
|
+
try { fs.closeSync(descriptor); } catch { /* Preserve the primary failure. */ }
|
|
334
|
+
}
|
|
335
|
+
try { fs.unlinkSync(temporary); } catch { /* Renamed or never created. */ }
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
212
339
|
/**
|
|
213
340
|
* Register a Claude Code SessionStart hook that runs `borg-regen` at the
|
|
214
341
|
* start of every session. Idempotent: re-running won't add duplicates.
|
|
@@ -1339,11 +1466,13 @@ export function addOpenCodeLaunchAccess(
|
|
|
1339
1466
|
paths: LaunchAccessPaths,
|
|
1340
1467
|
): boolean {
|
|
1341
1468
|
const configPath = path.join(projectRoot, '.opencode', 'opencode.json');
|
|
1469
|
+
let snapshot: OpenCodeProjectConfigSnapshot;
|
|
1342
1470
|
let config: any;
|
|
1343
1471
|
try {
|
|
1344
|
-
|
|
1472
|
+
snapshot = readOpenCodeProjectConfig(projectRoot, configPath);
|
|
1473
|
+
config = snapshot.config;
|
|
1345
1474
|
} catch (err: any) {
|
|
1346
|
-
throw new Error(`Could not
|
|
1475
|
+
throw new Error(`Could not safely read OpenCode config ${configPath}: ${err.message}`);
|
|
1347
1476
|
}
|
|
1348
1477
|
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
1349
1478
|
throw new Error(`OpenCode config ${configPath} is not an object`);
|
|
@@ -1394,10 +1523,33 @@ export function addOpenCodeLaunchAccess(
|
|
|
1394
1523
|
config.permission = permissionObject;
|
|
1395
1524
|
|
|
1396
1525
|
const changed = JSON.stringify(config) !== before;
|
|
1397
|
-
if (changed)
|
|
1526
|
+
if (changed) {
|
|
1527
|
+
try {
|
|
1528
|
+
writeOpenCodeProjectConfig(projectRoot, configPath, config, snapshot);
|
|
1529
|
+
} catch (err: any) {
|
|
1530
|
+
throw new Error(`Could not safely update OpenCode config ${configPath}: ${err.message}`);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1398
1533
|
return changed;
|
|
1399
1534
|
}
|
|
1400
1535
|
|
|
1536
|
+
/** Provision the project-local path access required by the selected CLI. */
|
|
1537
|
+
export function provisionLaunchAccess(
|
|
1538
|
+
cli: BorgCli,
|
|
1539
|
+
projectRoot: string,
|
|
1540
|
+
paths: LaunchAccessPaths,
|
|
1541
|
+
): void {
|
|
1542
|
+
if (cli === 'claude') {
|
|
1543
|
+
addClaudeLaunchAccess(projectRoot, paths);
|
|
1544
|
+
} else if (cli === 'codex') {
|
|
1545
|
+
// Codex receives path grants on its launch command. Its global hook reads
|
|
1546
|
+
// the scoped launch environment and only supplies the reminder.
|
|
1547
|
+
addCodexForeignPathReminderHook();
|
|
1548
|
+
} else {
|
|
1549
|
+
addOpenCodeLaunchAccess(projectRoot, paths);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1401
1553
|
/**
|
|
1402
1554
|
* Add borg MCP server to OpenCode using `opencode mcp add` CLI.
|
|
1403
1555
|
* Pins activation and agent-kind signals plus OpenCode config substitutions
|
package/src/index.ts
CHANGED
|
@@ -153,7 +153,7 @@ import {
|
|
|
153
153
|
writeOpenCodeStartupDiagnostic,
|
|
154
154
|
} from './opencode-drone.js';
|
|
155
155
|
import { installBorgPlugin } from './opencode-plugin.js';
|
|
156
|
-
import { openCodeApiPasswordFromEnv } from './opencode-launch-trust.js';
|
|
156
|
+
import { openCodeApiPasswordFromEnv, openCodeLaunchCorrelationFromEnv } from './opencode-launch-trust.js';
|
|
157
157
|
import { setModuleInjectOpenCode } from './log-stream.js';
|
|
158
158
|
import {
|
|
159
159
|
lifecycleSignalForMessage,
|
|
@@ -312,6 +312,11 @@ export async function connectOpenCodeRuntime(
|
|
|
312
312
|
console.error('OpenCode API credential is missing or unverifiable; skipping OpenCode entry injection. Relaunch through borg.');
|
|
313
313
|
return false;
|
|
314
314
|
}
|
|
315
|
+
const launchIdentity = openCodeLaunchCorrelationFromEnv(env);
|
|
316
|
+
if (launchIdentity === null) {
|
|
317
|
+
console.error('OpenCode launch identity is missing or unverifiable; skipping OpenCode entry injection. Relaunch through borg.');
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
315
320
|
const binding = openCodeLaunchBinding(configuredPort);
|
|
316
321
|
await (deps.connect ?? connectOpenCodeDrone)({
|
|
317
322
|
serverUrl: binding.serverUrl,
|
|
@@ -319,6 +324,7 @@ export async function connectOpenCodeRuntime(
|
|
|
319
324
|
directory: active.worktree ?? findProjectRoot(),
|
|
320
325
|
droneLabel: active.droneLabel,
|
|
321
326
|
cubeName: active.name,
|
|
327
|
+
launchIdentity,
|
|
322
328
|
});
|
|
323
329
|
return true;
|
|
324
330
|
}
|