borgmcp 3.2.0 → 3.4.0
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 +18 -3
- package/dist/agent-integration-health.d.ts +44 -0
- package/dist/agent-integration-health.d.ts.map +1 -0
- package/dist/agent-integration-health.js +284 -0
- package/dist/agent-integration-health.js.map +1 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +13 -0
- package/dist/claude.js.map +1 -1
- package/dist/cli-help.d.ts +1 -0
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +11 -3
- package/dist/cli-help.js.map +1 -1
- package/dist/config-utils.d.ts +21 -0
- package/dist/config-utils.d.ts.map +1 -1
- package/dist/config-utils.js +186 -20
- package/dist/config-utils.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/dist/log-audit-core.d.ts +7 -0
- package/dist/log-audit-core.d.ts.map +1 -0
- package/dist/log-audit-core.js +80 -0
- package/dist/log-audit-core.js.map +1 -0
- package/dist/log-audit.d.ts +3 -3
- package/dist/log-audit.js +7 -110
- package/dist/log-audit.js.map +1 -1
- package/dist/opencode-drone.d.ts.map +1 -1
- package/dist/opencode-drone.js +6 -1
- package/dist/opencode-drone.js.map +1 -1
- package/dist/opencode-plugin.d.ts +47 -7
- package/dist/opencode-plugin.d.ts.map +1 -1
- package/dist/opencode-plugin.js +187 -23
- package/dist/opencode-plugin.js.map +1 -1
- package/dist/regen-format.d.ts.map +1 -1
- package/dist/regen-format.js +5 -6
- package/dist/regen-format.js.map +1 -1
- package/dist/resolved-cli-config.d.ts +1 -0
- package/dist/resolved-cli-config.d.ts.map +1 -1
- package/dist/resolved-cli-config.js +1 -0
- package/dist/resolved-cli-config.js.map +1 -1
- package/dist/startup-services.d.ts +0 -1
- package/dist/startup-services.d.ts.map +1 -1
- package/dist/startup-services.js +0 -2
- package/dist/startup-services.js.map +1 -1
- package/dist/unknown-subcommand.d.ts +1 -1
- package/dist/unknown-subcommand.d.ts.map +1 -1
- package/dist/unknown-subcommand.js +1 -0
- package/dist/unknown-subcommand.js.map +1 -1
- package/dist/update-cmd.d.ts +1 -1
- package/dist/update-cmd.d.ts.map +1 -1
- package/dist/update-cmd.js +15 -8
- package/dist/update-cmd.js.map +1 -1
- package/docs/LOCAL_SERVER.md +28 -7
- package/package.json +1 -1
- package/src/agent-integration-health.ts +347 -0
- package/src/claude.ts +14 -0
- package/src/cli-help.ts +14 -3
- package/src/config-utils.ts +185 -20
- package/src/index.ts +1 -7
- package/src/log-audit-core.ts +81 -0
- package/src/log-audit.ts +6 -117
- package/src/opencode-drone.ts +6 -1
- package/src/opencode-plugin.ts +216 -22
- package/src/regen-format.ts +5 -6
- package/src/resolved-cli-config.ts +2 -0
- package/src/startup-services.ts +0 -3
- package/src/unknown-subcommand.ts +1 -0
- package/src/update-cmd.ts +18 -11
package/src/opencode-plugin.ts
CHANGED
|
@@ -1,37 +1,231 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
import { evaluateLogAudit } from './log-audit-core.js';
|
|
4
|
+
import { borgHomeRoot, isCanonicalPath } from './private-root.js';
|
|
5
|
+
import { getPackageVersion } from './version.js';
|
|
6
|
+
|
|
7
|
+
export const OPENCODE_COMPATIBILITY = {
|
|
8
|
+
// Empirically pinned on 2026-08-09. OpenCode 1.18.15 loads the default
|
|
9
|
+
// function -> Hooks object shape while the installed SDK uses the 1.17.18
|
|
10
|
+
// path/query/body client call shape. TextPart.metadata exists, persists
|
|
11
|
+
// through history/compaction/reload, and is hidden from the TUI and model.
|
|
12
|
+
// Do not replace either contract without a new live compatibility measurement.
|
|
13
|
+
opencode: '1.18.15',
|
|
14
|
+
sdk: '1.17.18',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
const COMPACT_FALLBACK =
|
|
18
|
+
'## Borg Cube\nYou are in a Borg MCP multi-agent coordination cube. ' +
|
|
19
|
+
'Use MCP tool borg_regen to get full context and recent activity.';
|
|
20
|
+
export const OPENCODE_INJECTED_ENTRY_METADATA_KEY = 'borgOpenCodeInjectedEntry';
|
|
21
|
+
export const OPENCODE_RECOVERY_METADATA_KEY = 'borgOpenCodeSessionOrientation';
|
|
22
|
+
const PLUGIN_REL_PATH = path.join('.config', 'opencode', 'plugins', 'borg-orient.js');
|
|
23
|
+
|
|
24
|
+
export interface OpenCodePluginCoreDeps {
|
|
25
|
+
defer(task: () => Promise<void>): void;
|
|
26
|
+
wait(milliseconds: number): Promise<void>;
|
|
27
|
+
listMessages(sessionID: string): Promise<any[]>;
|
|
28
|
+
renderOrientation(source: 'clear' | 'compact'): Promise<string>;
|
|
29
|
+
submitPrompt(
|
|
30
|
+
sessionID: string,
|
|
31
|
+
text: string,
|
|
32
|
+
recoveryVersion: string,
|
|
33
|
+
shouldSubmit: () => boolean,
|
|
34
|
+
): Promise<boolean>;
|
|
35
|
+
audit(messages: readonly any[]): string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface OpenCodePluginCoreOptions {
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
pluginVersion: string;
|
|
41
|
+
recoveryMetadataKey: string;
|
|
42
|
+
injectedEntryMetadataKey: string;
|
|
43
|
+
kickoffPollAttempts: number;
|
|
44
|
+
confirmationPollAttempts: number;
|
|
45
|
+
pollDelayMs: number;
|
|
46
|
+
compactFallback: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Pure, dependency-injected behavior core. Its emitted JavaScript function
|
|
50
|
+
* body is also embedded in the installed self-contained plugin. */
|
|
51
|
+
export function createOpenCodePluginCore(
|
|
52
|
+
deps: OpenCodePluginCoreDeps,
|
|
53
|
+
options: OpenCodePluginCoreOptions,
|
|
54
|
+
) {
|
|
55
|
+
const claimedSessions = new Set<string>();
|
|
56
|
+
const humanPromptSessions = new Set<string>();
|
|
57
|
+
const textParts = (message: any): any[] => Array.isArray(message?.parts)
|
|
58
|
+
? message.parts.filter((part: any) => part?.type === 'text' && typeof part.text === 'string')
|
|
59
|
+
: [];
|
|
60
|
+
const isInjectedEntry = (message: any): boolean => {
|
|
61
|
+
const parts = textParts(message);
|
|
62
|
+
return message?.info?.role === 'user' &&
|
|
63
|
+
parts[0]?.metadata?.[options.injectedEntryMetadataKey] === true;
|
|
64
|
+
};
|
|
65
|
+
const isOwnedRecovery = (message: any): boolean => {
|
|
66
|
+
const parts = textParts(message);
|
|
67
|
+
return message?.info?.role === 'user' &&
|
|
68
|
+
parts[0]?.metadata?.[options.recoveryMetadataKey] === options.pluginVersion;
|
|
69
|
+
};
|
|
70
|
+
const hasRecoveryBlocker = (sessionID: string, messages: readonly any[]): boolean =>
|
|
71
|
+
humanPromptSessions.has(sessionID) || messages.some((message) =>
|
|
72
|
+
isOwnedRecovery(message) ||
|
|
73
|
+
(message?.info?.role === 'user' && !isInjectedEntry(message)));
|
|
74
|
+
|
|
75
|
+
const recoverNewSession = async (sessionID: string): Promise<void> => {
|
|
76
|
+
try {
|
|
77
|
+
for (let attempt = 0; attempt < options.kickoffPollAttempts; attempt++) {
|
|
78
|
+
const messages = await deps.listMessages(sessionID);
|
|
79
|
+
if (hasRecoveryBlocker(sessionID, messages)) return;
|
|
80
|
+
if (attempt + 1 < options.kickoffPollAttempts) await deps.wait(options.pollDelayMs);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const orientation = (await deps.renderOrientation('clear')).trim();
|
|
84
|
+
if (!orientation) return;
|
|
85
|
+
const beforeSubmit = await deps.listMessages(sessionID);
|
|
86
|
+
if (hasRecoveryBlocker(sessionID, beforeSubmit)) return;
|
|
87
|
+
|
|
88
|
+
const submitted = await deps.submitPrompt(
|
|
89
|
+
sessionID,
|
|
90
|
+
orientation,
|
|
91
|
+
options.pluginVersion,
|
|
92
|
+
() => !hasRecoveryBlocker(sessionID, []),
|
|
18
93
|
);
|
|
94
|
+
if (!submitted) return;
|
|
95
|
+
// promptAsync is not idempotent. Confirmation may retry, submission may not.
|
|
96
|
+
for (let attempt = 0; attempt < options.confirmationPollAttempts; attempt++) {
|
|
97
|
+
const messages = await deps.listMessages(sessionID);
|
|
98
|
+
if (messages.some(isOwnedRecovery)) return;
|
|
99
|
+
if (attempt + 1 < options.confirmationPollAttempts) await deps.wait(options.pollDelayMs);
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// The plugin is best-effort. Never block OpenCode session creation.
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
event: async ({ event }: any): Promise<void> => {
|
|
108
|
+
if (!options.enabled || event?.type !== 'session.created') return;
|
|
109
|
+
const sessionID = event?.properties?.info?.id;
|
|
110
|
+
if (typeof sessionID !== 'string' || claimedSessions.has(sessionID)) return;
|
|
111
|
+
claimedSessions.add(sessionID);
|
|
112
|
+
deps.defer(() => recoverNewSession(sessionID));
|
|
113
|
+
},
|
|
114
|
+
'experimental.session.compacting': async (
|
|
115
|
+
_input: { sessionID: string },
|
|
116
|
+
output: { context: string[] },
|
|
117
|
+
): Promise<void> => {
|
|
118
|
+
if (!options.enabled) return;
|
|
119
|
+
try {
|
|
120
|
+
const orientation = (await deps.renderOrientation('compact')).trim();
|
|
121
|
+
output.context.push(orientation || options.compactFallback);
|
|
122
|
+
} catch {
|
|
123
|
+
output.context.push(options.compactFallback);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
'chat.message': async (
|
|
127
|
+
input: { sessionID: string },
|
|
128
|
+
output: { message: unknown; parts: any[] },
|
|
129
|
+
): Promise<void> => {
|
|
130
|
+
if (!options.enabled) return;
|
|
131
|
+
const current = { info: { role: 'user' }, parts: [...output.parts] };
|
|
132
|
+
if (!isInjectedEntry(current) && !isOwnedRecovery(current)) {
|
|
133
|
+
// chat.message fires before the user message is persisted. Record the
|
|
134
|
+
// human turn synchronously so recovery cannot race that short gap.
|
|
135
|
+
humanPromptSessions.add(input.sessionID);
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const history = await deps.listMessages(input.sessionID);
|
|
139
|
+
const nudge = deps.audit([...history, current]);
|
|
140
|
+
if (nudge) output.parts.push({ type: 'text', text: nudge });
|
|
141
|
+
} catch {
|
|
142
|
+
// Audit is advisory and must never block a prompt.
|
|
143
|
+
}
|
|
19
144
|
},
|
|
20
145
|
};
|
|
21
146
|
}
|
|
147
|
+
|
|
148
|
+
export function buildBorgPluginSource(version: string): string {
|
|
149
|
+
const marker = `borgmcp-opencode-plugin:${version};opencode=${OPENCODE_COMPATIBILITY.opencode};sdk=${OPENCODE_COMPATIBILITY.sdk};textpart-metadata=exists+persisted+tui-hidden+model-hidden`;
|
|
150
|
+
return `// ${marker}
|
|
151
|
+
// Generated by borgmcp. Self-contained; do not edit.
|
|
152
|
+
const createCore = ${createOpenCodePluginCore.toString()};
|
|
153
|
+
const evaluateAudit = ${evaluateLogAudit.toString()};
|
|
154
|
+
export default async function (ctx) {
|
|
155
|
+
const runRegen = async (source) => {
|
|
156
|
+
const input = JSON.stringify({ source });
|
|
157
|
+
const result = await ctx.$\`printf '%s' \${input} | borg-regen\`.quiet().nothrow();
|
|
158
|
+
return result.exitCode === 0 ? result.stdout.toString('utf8') : '';
|
|
159
|
+
};
|
|
160
|
+
return createCore({
|
|
161
|
+
defer: (task) => { setTimeout(() => { void task(); }, 0); },
|
|
162
|
+
wait: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
163
|
+
listMessages: async (sessionID) => {
|
|
164
|
+
const result = await ctx.client.session.messages({
|
|
165
|
+
path: { id: sessionID },
|
|
166
|
+
query: { directory: ctx.directory },
|
|
167
|
+
});
|
|
168
|
+
return Array.isArray(result.data) ? result.data : [];
|
|
169
|
+
},
|
|
170
|
+
renderOrientation: runRegen,
|
|
171
|
+
submitPrompt: async (sessionID, text, recoveryVersion, shouldSubmit) => {
|
|
172
|
+
if (!shouldSubmit()) return false;
|
|
173
|
+
await ctx.client.session.promptAsync({
|
|
174
|
+
path: { id: sessionID },
|
|
175
|
+
query: { directory: ctx.directory },
|
|
176
|
+
body: { parts: [{
|
|
177
|
+
type: 'text',
|
|
178
|
+
text,
|
|
179
|
+
metadata: { [${JSON.stringify(OPENCODE_RECOVERY_METADATA_KEY)}]: recoveryVersion },
|
|
180
|
+
}] },
|
|
181
|
+
});
|
|
182
|
+
return true;
|
|
183
|
+
},
|
|
184
|
+
audit: (messages) => evaluateAudit(messages),
|
|
185
|
+
}, {
|
|
186
|
+
...${JSON.stringify({
|
|
187
|
+
pluginVersion: version,
|
|
188
|
+
recoveryMetadataKey: OPENCODE_RECOVERY_METADATA_KEY,
|
|
189
|
+
injectedEntryMetadataKey: OPENCODE_INJECTED_ENTRY_METADATA_KEY,
|
|
190
|
+
kickoffPollAttempts: 6,
|
|
191
|
+
confirmationPollAttempts: 6,
|
|
192
|
+
pollDelayMs: 200,
|
|
193
|
+
compactFallback: COMPACT_FALLBACK,
|
|
194
|
+
})},
|
|
195
|
+
enabled: process.env.BORG_SESSION === '1',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
22
198
|
`;
|
|
199
|
+
}
|
|
23
200
|
|
|
24
|
-
const
|
|
201
|
+
export const BORG_PLUGIN_SOURCE = buildBorgPluginSource(getPackageVersion());
|
|
25
202
|
|
|
26
|
-
export function
|
|
27
|
-
|
|
203
|
+
export function openCodePluginPath(homeDir: string = borgHomeRoot()): string {
|
|
204
|
+
return path.join(homeDir, PLUGIN_REL_PATH);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function installBorgPlugin(options: {
|
|
208
|
+
homeDir?: string;
|
|
209
|
+
version?: string;
|
|
210
|
+
} = {}): void {
|
|
211
|
+
const pluginPath = openCodePluginPath(options.homeDir);
|
|
212
|
+
const source = buildBorgPluginSource(options.version ?? getPackageVersion());
|
|
28
213
|
try {
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
if (
|
|
214
|
+
if (!isCanonicalPath(pluginPath)) return;
|
|
215
|
+
try {
|
|
216
|
+
if (fs.lstatSync(pluginPath).isSymbolicLink()) return;
|
|
217
|
+
if (fs.readFileSync(pluginPath, 'utf-8') === source) return;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) return;
|
|
32
220
|
}
|
|
33
221
|
fs.mkdirSync(path.dirname(pluginPath), { recursive: true });
|
|
34
|
-
|
|
222
|
+
if (!isCanonicalPath(pluginPath)) return;
|
|
223
|
+
try {
|
|
224
|
+
if (fs.lstatSync(pluginPath).isSymbolicLink()) return;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) return;
|
|
227
|
+
}
|
|
228
|
+
fs.writeFileSync(pluginPath, source, 'utf-8');
|
|
35
229
|
} catch {
|
|
36
230
|
// Best-effort — plugin is an optimization, not a requirement.
|
|
37
231
|
}
|
package/src/regen-format.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
renderRuntimeMetadataLines,
|
|
19
19
|
} from './roster-render.js';
|
|
20
20
|
import { shellEscape } from './shell-escape.js';
|
|
21
|
-
import { resolveInboxMonitorPath } from './self-path.js';
|
|
22
21
|
import { OPENCODE_WAKE_PATH_GUIDANCE } from './opencode-wake-copy.js';
|
|
23
22
|
import { isBorgSession } from './launch-gate.js';
|
|
24
23
|
|
|
@@ -112,10 +111,10 @@ export function wakePathArming(
|
|
|
112
111
|
if (agentKind === 'opencode') {
|
|
113
112
|
return OPENCODE_WAKE_PATH_GUIDANCE;
|
|
114
113
|
}
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
const monitorBin =
|
|
114
|
+
// client#394: the stable npm bin survives Node/nvm install-path rotation.
|
|
115
|
+
// Launch-time health checks make a missing or version-skewed PATH target
|
|
116
|
+
// visible instead of silently embedding a stale installation path here.
|
|
117
|
+
const monitorBin = 'borg-inbox-monitor';
|
|
119
118
|
const monitorCommand = monitorStateRoot
|
|
120
119
|
? `${monitorBin} --state-root ${shellEscape(monitorStateRoot)} ${shellEscape(inboxPath)}`
|
|
121
120
|
: `${monitorBin} ${shellEscape(inboxPath)}`;
|
|
@@ -191,7 +190,7 @@ export function formatLeanOrientation(args: {
|
|
|
191
190
|
'\n_(`/clear` cleared Claude\'s conversation — re-arm the inbox Monitor now.)_',
|
|
192
191
|
'_Quiet-clear fallback: if a later turn follows silence, inspect `borg_stream-status` + `borg_roster`; call `borg_regen mode="full"`, drain `borg_read-log unread_only=true`, then re-arm the Monitor._\n',
|
|
193
192
|
].join('\n')
|
|
194
|
-
: ''
|
|
193
|
+
: '\n_(OpenCode started a new session; Borg supplied this orientation automatically.)_\n'
|
|
195
194
|
: '';
|
|
196
195
|
return [
|
|
197
196
|
`# Cube: ${cubeName} — ${droneLabel}`,
|
|
@@ -7,6 +7,7 @@ export interface ResolvedCliConfigDeps {
|
|
|
7
7
|
addClaudeUserPromptSubmitHook(): void;
|
|
8
8
|
addCodexSessionStartHook(): void;
|
|
9
9
|
addCodexUserPromptSubmitHook(): void;
|
|
10
|
+
installOpenCodePlugin(): void;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* Apply only the integration writes for the CLI the launcher resolved.
|
|
@@ -37,4 +38,5 @@ export function configureResolvedCli(
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
deps.ensureMcp('opencode');
|
|
41
|
+
deps.installOpenCodePlugin();
|
|
40
42
|
}
|
package/src/startup-services.ts
CHANGED
|
@@ -2,7 +2,6 @@ export type McpStartupTask = () => void | Promise<void>;
|
|
|
2
2
|
|
|
3
3
|
/** Required named services: omission from index wiring is a type error. */
|
|
4
4
|
export interface McpStartupServices {
|
|
5
|
-
sessionStartHook: McpStartupTask;
|
|
6
5
|
auditHook: McpStartupTask;
|
|
7
6
|
sseStream: McpStartupTask;
|
|
8
7
|
openCode: McpStartupTask;
|
|
@@ -21,12 +20,10 @@ export async function runMcpStartupServices(
|
|
|
21
20
|
): Promise<void> {
|
|
22
21
|
if (readinessProbe) return;
|
|
23
22
|
const tasks = options.openCodeFirst ? [
|
|
24
|
-
services.sessionStartHook,
|
|
25
23
|
services.auditHook,
|
|
26
24
|
services.openCode,
|
|
27
25
|
services.sseStream,
|
|
28
26
|
] : [
|
|
29
|
-
services.sessionStartHook,
|
|
30
27
|
services.auditHook,
|
|
31
28
|
services.sseStream,
|
|
32
29
|
services.openCode,
|
package/src/update-cmd.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { updateHelpText } from './cli-help.js';
|
|
|
8
8
|
import { preflightBorgServerTag } from './server-handshake.js';
|
|
9
9
|
import { loadBorgServerTrust } from './server-trust.js';
|
|
10
10
|
import { shellEscape } from './shell-escape.js';
|
|
11
|
-
import {
|
|
11
|
+
import { refreshAndVerifyManagedAgentIntegrations } from './agent-integration-health.js';
|
|
12
12
|
|
|
13
13
|
const CLIENT_PACKAGE = 'borgmcp';
|
|
14
14
|
const SERVER_PACKAGE = 'borgmcp-server';
|
|
@@ -65,7 +65,7 @@ export interface UpdateDeps {
|
|
|
65
65
|
reenter(binPath: string, args: readonly string[]): Promise<number>;
|
|
66
66
|
serverJson(binPath: string, command: 'update' | 'status'): Promise<unknown>;
|
|
67
67
|
verifyRunningProtocol(origin: string): Promise<void>;
|
|
68
|
-
|
|
68
|
+
refreshAgentIntegrations(): Promise<void>;
|
|
69
69
|
confirm(message: string): Promise<'yes' | 'no' | 'eof' | 'interrupted'>;
|
|
70
70
|
isTTY(): boolean;
|
|
71
71
|
stdout(text: string): void;
|
|
@@ -115,8 +115,7 @@ type ServerUpdateFailureStage =
|
|
|
115
115
|
| 'final server state verification'
|
|
116
116
|
| 'final package verification'
|
|
117
117
|
| 'managed service continuity check'
|
|
118
|
-
| 'running server protocol verification'
|
|
119
|
-
| 'agent MCP config refresh';
|
|
118
|
+
| 'running server protocol verification';
|
|
120
119
|
|
|
121
120
|
interface NpmContext {
|
|
122
121
|
commandPath: string;
|
|
@@ -699,11 +698,10 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
|
|
|
699
698
|
|
|
700
699
|
if (!serverWasPresent) {
|
|
701
700
|
try {
|
|
702
|
-
await deps.
|
|
701
|
+
await deps.refreshAgentIntegrations();
|
|
703
702
|
} catch (error) {
|
|
704
703
|
deps.stderr(
|
|
705
|
-
`Client updated, but agent
|
|
706
|
-
`Run borg setup to repair Borg-written agent registrations. Configurations that use another command are not changed.\n`,
|
|
704
|
+
`Client updated, but agent integration refresh and health check failed: ${errorMessage(error, 'unknown failure')}.\n`,
|
|
707
705
|
);
|
|
708
706
|
return 1;
|
|
709
707
|
}
|
|
@@ -821,9 +819,18 @@ export async function runUpdate(options: UpdateOptions, deps: UpdateDeps): Promi
|
|
|
821
819
|
retryCommand = 'borg server status';
|
|
822
820
|
await deps.verifyRunningProtocol(status.endpoint!);
|
|
823
821
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
822
|
+
try {
|
|
823
|
+
await deps.refreshAgentIntegrations();
|
|
824
|
+
} catch (error) {
|
|
825
|
+
const verifiedOutcome = state === 'stopped'
|
|
826
|
+
? 'prepared runtime verified; server remains stopped'
|
|
827
|
+
: 'running identities and protocol verified';
|
|
828
|
+
deps.stderr(
|
|
829
|
+
`Updated ${CLIENT_PACKAGE}@${pair.client.version} and ${SERVER_PACKAGE}@${pair.server.version}; ${verifiedOutcome}.\n` +
|
|
830
|
+
`Agent integration refresh and health check failed: ${errorMessage(error, 'unknown failure')}.\n`,
|
|
831
|
+
);
|
|
832
|
+
return signalExitCode(error) ?? 1;
|
|
833
|
+
}
|
|
827
834
|
deps.stdout(
|
|
828
835
|
state === 'stopped'
|
|
829
836
|
? (
|
|
@@ -1225,7 +1232,7 @@ export function buildDefaultUpdateDeps(): UpdateDeps {
|
|
|
1225
1232
|
const trust = await loadBorgServerTrust(origin);
|
|
1226
1233
|
await preflightBorgServerTag(origin, trust.fetchImpl);
|
|
1227
1234
|
},
|
|
1228
|
-
|
|
1235
|
+
refreshAgentIntegrations: async () => refreshAndVerifyManagedAgentIntegrations(),
|
|
1229
1236
|
confirm: defaultConfirm,
|
|
1230
1237
|
isTTY: () => process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
1231
1238
|
stdout: (text) => process.stdout.write(text),
|