borgmcp 3.0.0 → 3.0.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/bare-launch-menu.d.ts +5 -5
- package/dist/bare-launch-menu.d.ts.map +1 -1
- package/dist/bare-launch-menu.js +2 -2
- package/dist/bare-launch-menu.js.map +1 -1
- package/dist/claude.js +3 -3
- package/dist/claude.js.map +1 -1
- package/dist/cli-platform.d.ts +4 -0
- package/dist/cli-platform.d.ts.map +1 -1
- package/dist/cli-platform.js +29 -10
- package/dist/cli-platform.js.map +1 -1
- package/dist/cubes.d.ts.map +1 -1
- package/dist/cubes.js +28 -10
- package/dist/cubes.js.map +1 -1
- package/dist/first-run-server.d.ts.map +1 -1
- package/dist/first-run-server.js +4 -1
- package/dist/first-run-server.js.map +1 -1
- package/dist/lifecycle-log-guard.d.ts.map +1 -1
- package/dist/lifecycle-log-guard.js +12 -3
- package/dist/lifecycle-log-guard.js.map +1 -1
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +9 -4
- package/dist/remote-client.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +5 -3
- package/package.json +1 -1
- package/src/bare-launch-menu.ts +6 -6
- package/src/claude.ts +6 -4
- package/src/cli-platform.ts +42 -10
- package/src/cubes.ts +27 -12
- package/src/first-run-server.ts +4 -1
- package/src/lifecycle-log-guard.ts +15 -3
- package/src/remote-client.ts +11 -5
package/src/cubes.ts
CHANGED
|
@@ -84,6 +84,13 @@ interface LaunchFile {
|
|
|
84
84
|
projects: Record<string, { cli: BorgCli }>;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
const UNREADABLE_STATE = Symbol('unreadable-state-file');
|
|
88
|
+
type StateFileRead<T> = T | null | typeof UNREADABLE_STATE;
|
|
89
|
+
|
|
90
|
+
function unreadableStateError(filePath: string): Error {
|
|
91
|
+
return new Error(`Borg state file is unreadable; refusing to overwrite it: ${filePath}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
export interface CodexWakeTargetRecord {
|
|
88
95
|
threadId: string;
|
|
89
96
|
socketPath: string;
|
|
@@ -225,7 +232,7 @@ function isLaunchFile(data: any): data is LaunchFile {
|
|
|
225
232
|
);
|
|
226
233
|
}
|
|
227
234
|
|
|
228
|
-
async function readLaunchFile(): Promise<LaunchFile
|
|
235
|
+
async function readLaunchFile(): Promise<StateFileRead<LaunchFile>> {
|
|
229
236
|
let raw: string;
|
|
230
237
|
try {
|
|
231
238
|
raw = await readFile(LAUNCH_FILE, 'utf8');
|
|
@@ -235,9 +242,9 @@ async function readLaunchFile(): Promise<LaunchFile | null> {
|
|
|
235
242
|
}
|
|
236
243
|
try {
|
|
237
244
|
const parsed = JSON.parse(raw);
|
|
238
|
-
return isLaunchFile(parsed) ? parsed :
|
|
245
|
+
return isLaunchFile(parsed) ? parsed : UNREADABLE_STATE;
|
|
239
246
|
} catch {
|
|
240
|
-
return
|
|
247
|
+
return UNREADABLE_STATE;
|
|
241
248
|
}
|
|
242
249
|
}
|
|
243
250
|
|
|
@@ -263,7 +270,7 @@ function isCodexWakeTargetsFile(data: any): data is CodexWakeTargetsFile {
|
|
|
263
270
|
);
|
|
264
271
|
}
|
|
265
272
|
|
|
266
|
-
async function readCodexWakeTargetsFile(): Promise<CodexWakeTargetsFile
|
|
273
|
+
async function readCodexWakeTargetsFile(): Promise<StateFileRead<CodexWakeTargetsFile>> {
|
|
267
274
|
let raw: string;
|
|
268
275
|
try {
|
|
269
276
|
raw = await readFile(CODEX_WAKE_TARGETS_FILE, 'utf8');
|
|
@@ -273,9 +280,9 @@ async function readCodexWakeTargetsFile(): Promise<CodexWakeTargetsFile | null>
|
|
|
273
280
|
}
|
|
274
281
|
try {
|
|
275
282
|
const parsed = JSON.parse(raw);
|
|
276
|
-
return isCodexWakeTargetsFile(parsed) ? parsed :
|
|
283
|
+
return isCodexWakeTargetsFile(parsed) ? parsed : UNREADABLE_STATE;
|
|
277
284
|
} catch {
|
|
278
|
-
return
|
|
285
|
+
return UNREADABLE_STATE;
|
|
279
286
|
}
|
|
280
287
|
}
|
|
281
288
|
|
|
@@ -574,6 +581,7 @@ export async function refreshActiveCubeMetadata(active: ActiveCubeInput): Promis
|
|
|
574
581
|
|
|
575
582
|
export async function getProjectCliPreference(): Promise<BorgCli | null> {
|
|
576
583
|
const data = await readLaunchFile();
|
|
584
|
+
if (data === UNREADABLE_STATE) throw unreadableStateError(LAUNCH_FILE);
|
|
577
585
|
if (!data) return null;
|
|
578
586
|
const entry = data.projects[findProjectRoot()];
|
|
579
587
|
return entry?.cli === 'claude' || entry?.cli === 'codex' || entry?.cli === 'opencode' ? entry.cli : null;
|
|
@@ -586,6 +594,7 @@ export async function getProjectCliPreference(): Promise<BorgCli | null> {
|
|
|
586
594
|
*/
|
|
587
595
|
export async function getProjectCliPreferenceForPath(dir: string): Promise<BorgCli | null> {
|
|
588
596
|
const data = await readLaunchFile();
|
|
597
|
+
if (data === UNREADABLE_STATE) throw unreadableStateError(LAUNCH_FILE);
|
|
589
598
|
if (!data) return null;
|
|
590
599
|
const entry = data.projects[findProjectRoot(dir)];
|
|
591
600
|
return entry?.cli === 'claude' || entry?.cli === 'codex' || entry?.cli === 'opencode' ? entry.cli : null;
|
|
@@ -617,9 +626,11 @@ export async function readAllProjectIdentities(): Promise<
|
|
|
617
626
|
* sibling worktree but the process still began in the invoking checkout.
|
|
618
627
|
*/
|
|
619
628
|
export async function setProjectCliPreference(cli: BorgCli, dir?: string): Promise<void> {
|
|
620
|
-
const existing =
|
|
621
|
-
existing
|
|
622
|
-
|
|
629
|
+
const existing = await readLaunchFile();
|
|
630
|
+
if (existing === UNREADABLE_STATE) throw unreadableStateError(LAUNCH_FILE);
|
|
631
|
+
const next = existing ?? { projects: {} };
|
|
632
|
+
next.projects[findProjectRoot(dir)] = { cli };
|
|
633
|
+
await writeLaunchFile(next);
|
|
623
634
|
}
|
|
624
635
|
|
|
625
636
|
export async function setCodexWakeTarget(
|
|
@@ -627,12 +638,14 @@ export async function setCodexWakeTarget(
|
|
|
627
638
|
droneId: string,
|
|
628
639
|
target: Omit<CodexWakeTargetRecord, 'updatedAt'>
|
|
629
640
|
): Promise<void> {
|
|
630
|
-
const existing =
|
|
631
|
-
existing
|
|
641
|
+
const existing = await readCodexWakeTargetsFile();
|
|
642
|
+
if (existing === UNREADABLE_STATE) throw unreadableStateError(CODEX_WAKE_TARGETS_FILE);
|
|
643
|
+
const next = existing ?? { targets: {} };
|
|
644
|
+
next.targets[codexWakeTargetKey(cubeId, droneId)] = {
|
|
632
645
|
...target,
|
|
633
646
|
updatedAt: new Date().toISOString(),
|
|
634
647
|
};
|
|
635
|
-
await writeCodexWakeTargetsFile(
|
|
648
|
+
await writeCodexWakeTargetsFile(next);
|
|
636
649
|
}
|
|
637
650
|
|
|
638
651
|
export async function getCodexWakeTarget(
|
|
@@ -640,6 +653,7 @@ export async function getCodexWakeTarget(
|
|
|
640
653
|
droneId: string
|
|
641
654
|
): Promise<CodexWakeTargetRecord | null> {
|
|
642
655
|
const existing = await readCodexWakeTargetsFile();
|
|
656
|
+
if (existing === UNREADABLE_STATE) throw unreadableStateError(CODEX_WAKE_TARGETS_FILE);
|
|
643
657
|
if (!existing) return null;
|
|
644
658
|
const target = existing.targets[codexWakeTargetKey(cubeId, droneId)];
|
|
645
659
|
if (!target || typeof target.threadId !== 'string' || typeof target.socketPath !== 'string') {
|
|
@@ -661,6 +675,7 @@ export async function pruneDeadCodexWakeTargets(
|
|
|
661
675
|
socketLiveness: (socketPath: string) => boolean | null
|
|
662
676
|
): Promise<void> {
|
|
663
677
|
const existing = await readCodexWakeTargetsFile();
|
|
678
|
+
if (existing === UNREADABLE_STATE) throw unreadableStateError(CODEX_WAKE_TARGETS_FILE);
|
|
664
679
|
if (!existing) return;
|
|
665
680
|
const { targets, changed } = pruneDeadWakeTargets(existing.targets, socketLiveness);
|
|
666
681
|
if (changed) await writeCodexWakeTargetsFile({ ...existing, targets });
|
package/src/first-run-server.ts
CHANGED
|
@@ -177,8 +177,11 @@ export async function offerFirstRunServerInstall(
|
|
|
177
177
|
: decision === 'eof'
|
|
178
178
|
? 'Installation was cancelled because confirmation input ended.'
|
|
179
179
|
: 'Installation was declined.';
|
|
180
|
+
const setupRecovery = options.initializeServer
|
|
181
|
+
? ', and no agent configuration was written. Run `borg setup` again to configure your agents.'
|
|
182
|
+
: '.';
|
|
180
183
|
deps.stderr(
|
|
181
|
-
`${reason} No server package or server state was changed
|
|
184
|
+
`${reason} No server package or server state was changed${setupRecovery}\n` +
|
|
182
185
|
`To install it later, run \`${command}\`. Then run \`borg server setup\` and \`borg server start\`.\n`,
|
|
183
186
|
);
|
|
184
187
|
return { kind: 'declined' };
|
|
@@ -28,6 +28,15 @@ interface LifecycleStateFile {
|
|
|
28
28
|
entries: Record<string, LifecycleStateEntry>;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
const UNREADABLE_STATE = Symbol('unreadable-lifecycle-state');
|
|
32
|
+
type LifecycleStateRead = LifecycleStateFile | typeof UNREADABLE_STATE;
|
|
33
|
+
|
|
34
|
+
function unreadableStateError(): Error {
|
|
35
|
+
return new Error(
|
|
36
|
+
`Lifecycle log state is unreadable; refusing to overwrite it: ${STATE_FILE}`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
export function lifecycleSignalForMessage(message: string): LifecycleSignal | null {
|
|
32
41
|
if (message.startsWith('ARRIVAL: ')) return 'arrival';
|
|
33
42
|
if (
|
|
@@ -44,7 +53,7 @@ function stateKey(subject: LifecycleLogSubject): string {
|
|
|
44
53
|
return `${subject.cubeId}:${subject.droneId}`;
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
async function readState(): Promise<
|
|
56
|
+
async function readState(): Promise<LifecycleStateRead> {
|
|
48
57
|
try {
|
|
49
58
|
const raw = await readFile(STATE_FILE, 'utf8');
|
|
50
59
|
const parsed = JSON.parse(raw);
|
|
@@ -58,9 +67,10 @@ async function readState(): Promise<LifecycleStateFile> {
|
|
|
58
67
|
return parsed as LifecycleStateFile;
|
|
59
68
|
}
|
|
60
69
|
} catch (err: any) {
|
|
61
|
-
if (err?.code
|
|
70
|
+
if (err?.code === 'ENOENT') return { entries: {} };
|
|
71
|
+
return UNREADABLE_STATE;
|
|
62
72
|
}
|
|
63
|
-
return
|
|
73
|
+
return UNREADABLE_STATE;
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
async function writeState(state: LifecycleStateFile): Promise<void> {
|
|
@@ -100,6 +110,7 @@ export async function shouldSuppressLifecycleLog(
|
|
|
100
110
|
message: string
|
|
101
111
|
): Promise<{ suppress: boolean; signal: LifecycleSignal | null }> {
|
|
102
112
|
const state = await readState();
|
|
113
|
+
if (state === UNREADABLE_STATE) throw unreadableStateError();
|
|
103
114
|
return shouldSuppressLifecycleLogFromState(
|
|
104
115
|
message,
|
|
105
116
|
state.entries[stateKey(subject)]
|
|
@@ -138,6 +149,7 @@ export async function recordLifecycleLog(
|
|
|
138
149
|
message: string
|
|
139
150
|
): Promise<void> {
|
|
140
151
|
const state = await readState();
|
|
152
|
+
if (state === UNREADABLE_STATE) throw unreadableStateError();
|
|
141
153
|
const key = stateKey(subject);
|
|
142
154
|
state.entries[key] = nextLifecycleStateAfterLog(message, state.entries[key]);
|
|
143
155
|
await writeState(state);
|
package/src/remote-client.ts
CHANGED
|
@@ -95,16 +95,22 @@ export const LOCAL_SERVER_REQUEST_TIMEOUT_MS = 5_000;
|
|
|
95
95
|
const LOCAL_SERVER_RESPONSE_LIMIT_MESSAGE =
|
|
96
96
|
'Local Borg server response exceeded the response limit';
|
|
97
97
|
|
|
98
|
+
export const SERVER_ADVISORY_MAX_CHARS = 512;
|
|
99
|
+
// Keep enough source headroom to remove control sequences that cross the
|
|
100
|
+
// visible 512-character boundary without letting regex work scale with the
|
|
101
|
+
// 32 MiB response-body limit.
|
|
102
|
+
const SERVER_MESSAGE_SANITIZE_MAX_CHARS = SERVER_ADVISORY_MAX_CHARS * 8;
|
|
103
|
+
|
|
98
104
|
function sanitizeServerMessage(message: string): string {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
const bounded = message.slice(0, SERVER_MESSAGE_SANITIZE_MAX_CHARS);
|
|
106
|
+
|
|
107
|
+
return bounded
|
|
108
|
+
.replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\|$)/g, '')
|
|
109
|
+
.replace(/\u001b\[[0-?]*[ -/]*(?:[@-~]|$)/g, '')
|
|
102
110
|
.replace(/[\u0000-\u001f\u007f-\u009f]/g, '')
|
|
103
111
|
.replace(/\\u(?:000[0-9a-f]|001[0-9a-f]|007f|008[0-9a-f]|009[0-9a-f])/gi, '');
|
|
104
112
|
}
|
|
105
113
|
|
|
106
|
-
export const SERVER_ADVISORY_MAX_CHARS = 512;
|
|
107
|
-
|
|
108
114
|
export function sanitizeServerAdvisory(value: unknown): string | undefined {
|
|
109
115
|
if (typeof value !== 'string') return undefined;
|
|
110
116
|
const sanitized = sanitizeServerMessage(value).trim();
|