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/opencode-drone.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from 'fs';
|
|
15
15
|
import { createHash, randomUUID } from 'crypto';
|
|
16
16
|
import { createServer } from 'node:net';
|
|
17
|
-
import { join } from 'path';
|
|
17
|
+
import { join, resolve } from 'path';
|
|
18
18
|
import { tmpdir } from 'os';
|
|
19
19
|
import {
|
|
20
20
|
borgConfigRoot,
|
|
@@ -44,8 +44,9 @@ const OPEN_CODE_DIAGNOSTIC_LOG_MAX_BYTES = 64 * 1024;
|
|
|
44
44
|
const diagnosticLogPathsForTests = new Set<string>();
|
|
45
45
|
|
|
46
46
|
function stateIdentityDigest(current: OpenCodeDroneState): string {
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// Diagnostic files follow the stable worktree location; launch placeholders
|
|
48
|
+
// must not select a second log before session identity resolves.
|
|
49
|
+
return createHash('sha256').update(resolve(current.directory)).digest('hex').slice(0, 24);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export function openCodeStartupDiagnosticLogPath(): string {
|
|
@@ -166,6 +167,7 @@ interface OpenCodeDroneState {
|
|
|
166
167
|
directory: string;
|
|
167
168
|
droneLabel: string;
|
|
168
169
|
cubeName: string;
|
|
170
|
+
launchIdentity: string;
|
|
169
171
|
connected: boolean;
|
|
170
172
|
totalEntriesInjected: number;
|
|
171
173
|
totalEntriesRetried: number;
|
|
@@ -204,6 +206,7 @@ interface ConnectDeps {
|
|
|
204
206
|
directory: string;
|
|
205
207
|
droneLabel: string;
|
|
206
208
|
cubeName: string;
|
|
209
|
+
launchIdentity: string;
|
|
207
210
|
}
|
|
208
211
|
|
|
209
212
|
interface OCSession {
|
|
@@ -270,7 +273,7 @@ const OPEN_CODE_RECONCILIATION_ATTEMPTS = 20;
|
|
|
270
273
|
const OPEN_CODE_DELIVERY_HISTORY_LIMIT = 256;
|
|
271
274
|
|
|
272
275
|
interface SessionBinding {
|
|
273
|
-
version:
|
|
276
|
+
version: 5;
|
|
274
277
|
sessionId: string;
|
|
275
278
|
sessionCreatedAt: number;
|
|
276
279
|
knownRootSessionIds: string[];
|
|
@@ -278,6 +281,7 @@ interface SessionBinding {
|
|
|
278
281
|
directory: string;
|
|
279
282
|
droneLabel: string;
|
|
280
283
|
cubeName: string;
|
|
284
|
+
launchIdentity: string;
|
|
281
285
|
pendingSubmissions: Array<{
|
|
282
286
|
entryId: string;
|
|
283
287
|
sourceEntryId: string;
|
|
@@ -285,6 +289,13 @@ interface SessionBinding {
|
|
|
285
289
|
}>;
|
|
286
290
|
}
|
|
287
291
|
|
|
292
|
+
interface SessionBindingClaim {
|
|
293
|
+
version: 1;
|
|
294
|
+
directory: string;
|
|
295
|
+
droneLabel: string;
|
|
296
|
+
cubeName: string;
|
|
297
|
+
}
|
|
298
|
+
|
|
288
299
|
export interface OpenCodeLaunchKickoff {
|
|
289
300
|
prompt: string;
|
|
290
301
|
apiPassword: string;
|
|
@@ -323,6 +334,9 @@ export async function connectOpenCodeDrone(deps: ConnectDeps): Promise<void> {
|
|
|
323
334
|
if (!isOpenCode256BitIdentity(deps.apiPassword)) {
|
|
324
335
|
throw new OpenCodeAuthenticationError('OpenCode API password is missing or unverifiable');
|
|
325
336
|
}
|
|
337
|
+
if (!isOpenCode256BitIdentity(deps.launchIdentity)) {
|
|
338
|
+
throw new OpenCodeAuthenticationError('OpenCode launch identity is missing or unverifiable');
|
|
339
|
+
}
|
|
326
340
|
await ensurePrivateBorgConfigRoot(borgConfigRoot());
|
|
327
341
|
abandonOpenCodeDeliveries(state);
|
|
328
342
|
state = {
|
|
@@ -334,6 +348,7 @@ export async function connectOpenCodeDrone(deps: ConnectDeps): Promise<void> {
|
|
|
334
348
|
directory: deps.directory,
|
|
335
349
|
droneLabel: deps.droneLabel,
|
|
336
350
|
cubeName: deps.cubeName,
|
|
351
|
+
launchIdentity: deps.launchIdentity,
|
|
337
352
|
connected: true,
|
|
338
353
|
totalEntriesInjected: 0,
|
|
339
354
|
totalEntriesRetried: 0,
|
|
@@ -546,17 +561,28 @@ async function promptSession(id: string, bodyObj: Record<string, unknown>): Prom
|
|
|
546
561
|
|
|
547
562
|
function bindingPath(): string {
|
|
548
563
|
const current = state!;
|
|
549
|
-
const
|
|
564
|
+
const identity = createHash('sha256').update(current.launchIdentity).digest('hex').slice(0, 24);
|
|
565
|
+
const path = join(tmpdir(), `borg-opencode-session-${identity}.json`);
|
|
550
566
|
bindingPathsForTests.add(path);
|
|
551
567
|
return path;
|
|
552
568
|
}
|
|
553
569
|
|
|
570
|
+
function bindingClaimPath(): string {
|
|
571
|
+
return `${bindingPath()}.claim`;
|
|
572
|
+
}
|
|
573
|
+
|
|
554
574
|
function bindingMatchesState(binding: SessionBinding): boolean {
|
|
555
575
|
const current = state!;
|
|
556
|
-
|
|
576
|
+
const sameResolvedSeat = binding.droneLabel === current.droneLabel
|
|
577
|
+
&& binding.cubeName === current.cubeName;
|
|
578
|
+
// The 256-bit launch identity lets the resolved MCP child claim only its
|
|
579
|
+
// launcher's placeholder binding; resolved ownership still requires labels.
|
|
580
|
+
const unclaimedLaunch = binding.droneLabel === 'opencode'
|
|
581
|
+
&& binding.cubeName === 'borg';
|
|
582
|
+
return binding.version === 5
|
|
557
583
|
&& binding.directory === current.directory
|
|
558
|
-
&& binding.
|
|
559
|
-
&&
|
|
584
|
+
&& binding.launchIdentity === current.launchIdentity
|
|
585
|
+
&& (sameResolvedSeat || unclaimedLaunch)
|
|
560
586
|
&& typeof binding.sessionId === 'string'
|
|
561
587
|
&& typeof binding.sessionCreatedAt === 'number'
|
|
562
588
|
&& Array.isArray(binding.knownRootSessionIds)
|
|
@@ -599,15 +625,14 @@ function clearBinding(): void {
|
|
|
599
625
|
|
|
600
626
|
function writeBinding(binding: SessionBinding): boolean {
|
|
601
627
|
const current = state!;
|
|
602
|
-
current.sessionId = binding.sessionId;
|
|
603
|
-
current.sessionCreatedAt = binding.sessionCreatedAt;
|
|
604
|
-
current.knownRootSessionIds = binding.knownRootSessionIds;
|
|
605
|
-
|
|
606
628
|
try {
|
|
607
629
|
const path = bindingPath();
|
|
608
630
|
const temporary = `${path}.${process.pid}.tmp`;
|
|
609
631
|
writeFileSync(temporary, JSON.stringify(binding), { mode: 0o600 });
|
|
610
632
|
renameSync(temporary, path);
|
|
633
|
+
current.sessionId = binding.sessionId;
|
|
634
|
+
current.sessionCreatedAt = binding.sessionCreatedAt;
|
|
635
|
+
current.knownRootSessionIds = binding.knownRootSessionIds;
|
|
611
636
|
return true;
|
|
612
637
|
} catch (err) {
|
|
613
638
|
log(`session binding write failed: ${err}`);
|
|
@@ -615,10 +640,46 @@ function writeBinding(binding: SessionBinding): boolean {
|
|
|
615
640
|
}
|
|
616
641
|
}
|
|
617
642
|
|
|
643
|
+
function claimPlaceholderBinding(): boolean {
|
|
644
|
+
const current = state!;
|
|
645
|
+
const claim: SessionBindingClaim = {
|
|
646
|
+
version: 1,
|
|
647
|
+
directory: current.directory,
|
|
648
|
+
droneLabel: current.droneLabel,
|
|
649
|
+
cubeName: current.cubeName,
|
|
650
|
+
};
|
|
651
|
+
const path = bindingClaimPath();
|
|
652
|
+
try {
|
|
653
|
+
writeFileSync(path, JSON.stringify(claim), { mode: 0o600, flag: 'wx' });
|
|
654
|
+
return true;
|
|
655
|
+
} catch (error) {
|
|
656
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {
|
|
657
|
+
log(`session binding claim failed: ${error}`);
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
const existing = JSON.parse(readFileSync(path, 'utf8')) as SessionBindingClaim;
|
|
663
|
+
return existing.version === 1
|
|
664
|
+
&& existing.directory === claim.directory
|
|
665
|
+
&& existing.droneLabel === claim.droneLabel
|
|
666
|
+
&& existing.cubeName === claim.cubeName;
|
|
667
|
+
} catch {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function dropCachedBinding(): void {
|
|
673
|
+
if (!state) return;
|
|
674
|
+
state.sessionId = null;
|
|
675
|
+
state.sessionCreatedAt = null;
|
|
676
|
+
state.knownRootSessionIds = [];
|
|
677
|
+
}
|
|
678
|
+
|
|
618
679
|
function saveBinding(session: OCSession, knownRootSessionIds: string[]): void {
|
|
619
680
|
const current = state!;
|
|
620
681
|
const binding: SessionBinding = {
|
|
621
|
-
version:
|
|
682
|
+
version: 5,
|
|
622
683
|
sessionId: session.id,
|
|
623
684
|
sessionCreatedAt: session.time.created,
|
|
624
685
|
knownRootSessionIds,
|
|
@@ -626,6 +687,7 @@ function saveBinding(session: OCSession, knownRootSessionIds: string[]): void {
|
|
|
626
687
|
directory: current.directory,
|
|
627
688
|
droneLabel: current.droneLabel,
|
|
628
689
|
cubeName: current.cubeName,
|
|
690
|
+
launchIdentity: current.launchIdentity,
|
|
629
691
|
pendingSubmissions: [...current.pendingSubmissions].map(([entryId, pending]) => ({
|
|
630
692
|
entryId,
|
|
631
693
|
sourceEntryId: pending.sourceEntryId,
|
|
@@ -639,7 +701,7 @@ function persistCurrentBinding(): boolean {
|
|
|
639
701
|
const current = state;
|
|
640
702
|
if (!current?.sessionId || current.sessionCreatedAt === null) return false;
|
|
641
703
|
return writeBinding({
|
|
642
|
-
version:
|
|
704
|
+
version: 5,
|
|
643
705
|
sessionId: current.sessionId,
|
|
644
706
|
sessionCreatedAt: current.sessionCreatedAt,
|
|
645
707
|
knownRootSessionIds: current.knownRootSessionIds,
|
|
@@ -647,6 +709,7 @@ function persistCurrentBinding(): boolean {
|
|
|
647
709
|
directory: current.directory,
|
|
648
710
|
droneLabel: current.droneLabel,
|
|
649
711
|
cubeName: current.cubeName,
|
|
712
|
+
launchIdentity: current.launchIdentity,
|
|
650
713
|
pendingSubmissions: [...current.pendingSubmissions].map(([entryId, pending]) => ({
|
|
651
714
|
entryId,
|
|
652
715
|
sourceEntryId: pending.sourceEntryId,
|
|
@@ -658,25 +721,34 @@ function persistCurrentBinding(): boolean {
|
|
|
658
721
|
function restoreBinding(): SessionBinding | null {
|
|
659
722
|
if (!state) return null;
|
|
660
723
|
if (state.sessionId && state.sessionCreatedAt !== null) {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
droneLabel: state.droneLabel,
|
|
669
|
-
cubeName: state.cubeName,
|
|
670
|
-
pendingSubmissions: [...state.pendingSubmissions].map(([entryId, pending]) => ({
|
|
671
|
-
entryId,
|
|
672
|
-
sourceEntryId: pending.sourceEntryId,
|
|
673
|
-
sessionId: pending.sessionId,
|
|
674
|
-
})),
|
|
675
|
-
};
|
|
724
|
+
const persisted = readBinding();
|
|
725
|
+
if (
|
|
726
|
+
persisted?.droneLabel === state.droneLabel
|
|
727
|
+
&& persisted.cubeName === state.cubeName
|
|
728
|
+
) return persisted;
|
|
729
|
+
dropCachedBinding();
|
|
730
|
+
return null;
|
|
676
731
|
}
|
|
677
732
|
|
|
678
|
-
|
|
733
|
+
let binding = readBinding();
|
|
679
734
|
if (!binding) return null;
|
|
735
|
+
if (binding.droneLabel === 'opencode' && binding.cubeName === 'borg') {
|
|
736
|
+
if (!claimPlaceholderBinding()) return null;
|
|
737
|
+
if (!writeBinding({
|
|
738
|
+
...binding,
|
|
739
|
+
droneLabel: state.droneLabel,
|
|
740
|
+
cubeName: state.cubeName,
|
|
741
|
+
})) return null;
|
|
742
|
+
binding = readBinding();
|
|
743
|
+
if (
|
|
744
|
+
!binding
|
|
745
|
+
|| binding.droneLabel !== state.droneLabel
|
|
746
|
+
|| binding.cubeName !== state.cubeName
|
|
747
|
+
) {
|
|
748
|
+
dropCachedBinding();
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
680
752
|
state.sessionId = binding.sessionId;
|
|
681
753
|
state.sessionCreatedAt = binding.sessionCreatedAt;
|
|
682
754
|
state.knownRootSessionIds = binding.knownRootSessionIds;
|
|
@@ -1560,6 +1632,11 @@ export function __getOpenCodeDiagnosticLogPathForTests(): string {
|
|
|
1560
1632
|
return diagnosticLogPath(state);
|
|
1561
1633
|
}
|
|
1562
1634
|
|
|
1635
|
+
export function __getOpenCodeBindingPathForTests(): string {
|
|
1636
|
+
if (!state) throw new Error('OpenCode drone is not connected');
|
|
1637
|
+
return bindingPath();
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1563
1640
|
export function __getOpenCodeLastObservationForTests(): OpenCodeLastObservation {
|
|
1564
1641
|
if (!state) throw new Error('OpenCode drone is not connected');
|
|
1565
1642
|
return { ...state.lastObservation };
|
|
@@ -1649,6 +1726,11 @@ export function __resetOpenCodeDroneForTests(): void {
|
|
|
1649
1726
|
} catch {
|
|
1650
1727
|
// Already removed.
|
|
1651
1728
|
}
|
|
1729
|
+
try {
|
|
1730
|
+
unlinkSync(`${path}.claim`);
|
|
1731
|
+
} catch {
|
|
1732
|
+
// Already removed.
|
|
1733
|
+
}
|
|
1652
1734
|
}
|
|
1653
1735
|
bindingPathsForTests.clear();
|
|
1654
1736
|
for (const path of diagnosticLogPathsForTests) {
|
|
@@ -44,3 +44,8 @@ export function openCodeApiPasswordFromEnv(env: NodeJS.ProcessEnv): string | nul
|
|
|
44
44
|
const password = env[OPENCODE_SERVER_PASSWORD_ENV];
|
|
45
45
|
return isOpenCode256BitIdentity(password) ? password : null;
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
export function openCodeLaunchCorrelationFromEnv(env: NodeJS.ProcessEnv): string | null {
|
|
49
|
+
const identity = env[BORG_OPENCODE_LAUNCH_CORRELATION_ENV];
|
|
50
|
+
return isOpenCode256BitIdentity(identity) ? identity : null;
|
|
51
|
+
}
|