@borgee/agents-host 0.2.28 → 0.2.31

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.
@@ -5,8 +5,8 @@ import { createConnection, createServer } from 'node:net';
5
5
  import { dirname, join, resolve } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { AgentsHostSupervisor } from './agents-host-supervisor.js';
8
- import { COMPATIBILITY_GATES_ENV, createManagedRuntimeSettingsFingerprint, INTERNAL_POLICY_MODE_ENV, INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV, MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE, MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION, resolveManagedRuntimeSettingsSnapshot, normalizeInternalProviderImplementationOverrides, } from './compatibility-gates.js';
9
- import { loadConfigFromEnv } from './config.js';
8
+ import { CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE, COMPATIBILITY_GATES_ENV, createManagedRuntimeSettingsFingerprint, INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV, INTERNAL_POLICY_MODE_ENV, INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV, MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE, MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION, resolveDisabledDefaultCompatibilityGates, resolveManagedRuntimeSettingsSnapshot, normalizeInternalProviderImplementationOverrides, } from './compatibility-gates.js';
9
+ import { DEFAULT_PROVIDER_COMMAND_CONFIG, hasLegacyClaudeOneShotArgs, loadConfigFromEnv, } from './config.js';
10
10
  import { loadLocalConfigGenerateSpec, materializeLocalConfig, parseGenerateConfigSpec, resolveLocalConfigLayout, } from './local-config.js';
11
11
  import { normalizeManagedRuntimeKey, resolveManagedBootstrapLockPath, resolveManagedDaemonLogPath, resolveManagedDaemonSocketPath, resolveManagedRuntimeRoot, resolveManagedRuntimeSettingsPath, } from './state-paths.js';
12
12
  const MANAGED_ROOT_MODE = 0o700;
@@ -149,8 +149,9 @@ function resolveDesiredManagedRuntimeSettings(env = process.env) {
149
149
  function normalizeManagedRuntimeSettingsSnapshot(snapshot) {
150
150
  const compatibilityGates = [...new Set(snapshot.compatibilityGates.map((gate) => gate.trim()))]
151
151
  .filter((gate) => gate.length > 0)
152
+ .filter((gate) => gate !== CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE)
152
153
  .sort((left, right) => left.localeCompare(right));
153
- const providerImplementationOverrides = normalizeInternalProviderImplementationOverrides(snapshot.providerImplementationOverrides.join(','));
154
+ const providerImplementationOverrides = normalizeInternalProviderImplementationOverrides(snapshot.providerImplementationOverrides.join(',')).filter((assignment) => !assignment.startsWith('claude:'));
154
155
  return {
155
156
  schemaVersion: MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION,
156
157
  compatibilityGates,
@@ -222,15 +223,45 @@ function buildManagedRuntimeSettingsEnv(baseEnv, snapshot) {
222
223
  return {
223
224
  ...baseEnv,
224
225
  [COMPATIBILITY_GATES_ENV]: snapshot.compatibilityGates.join(','),
226
+ [INTERNAL_DISABLED_COMPATIBILITY_GATES_ENV]: resolveDisabledDefaultCompatibilityGates(snapshot.compatibilityGates).join(','),
225
227
  [INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV]: snapshot.providerImplementationOverrides.join(','),
226
228
  [INTERNAL_POLICY_MODE_ENV]: snapshot.internalPolicyMode,
227
229
  };
228
230
  }
229
- function getManagedRuntimeFingerprintMismatch(status, desiredSettings) {
230
- if (!desiredSettings.convergenceEnabled) {
231
- return status.managedRuntimeFingerprint != null;
231
+ function buildDesiredManagedRuntimeSettingsFromSnapshot(snapshot) {
232
+ return {
233
+ snapshot,
234
+ fingerprint: createManagedRuntimeSettingsFingerprint(snapshot),
235
+ convergenceEnabled: snapshot.compatibilityGates.includes(MANAGED_RUNTIME_CONVERGENCE_COMPATIBILITY_GATE),
236
+ };
237
+ }
238
+ async function getManagedRuntimeFingerprintMismatch(rootPath, status, desiredSettings) {
239
+ if (status.managedRuntimeFingerprint != null) {
240
+ return status.managedRuntimeFingerprint !== desiredSettings.fingerprint;
241
+ }
242
+ if (desiredSettings.convergenceEnabled) {
243
+ return true;
244
+ }
245
+ const persistedSettingsSnapshot = await loadManagedRuntimeSettingsSnapshot(rootPath);
246
+ if (!persistedSettingsSnapshot) {
247
+ return true;
248
+ }
249
+ return createManagedRuntimeSettingsFingerprint(persistedSettingsSnapshot) !== desiredSettings.fingerprint;
250
+ }
251
+ async function resolveAuthoritativeManagedRuntimeSettings(rootPath, effectiveProcessEnv) {
252
+ const persistedSettingsSnapshot = await loadManagedRuntimeSettingsSnapshot(rootPath);
253
+ if (!persistedSettingsSnapshot) {
254
+ return {
255
+ desiredSettings: resolveDesiredManagedRuntimeSettings(effectiveProcessEnv),
256
+ processEnv: effectiveProcessEnv,
257
+ persistedSettingsSnapshot: null,
258
+ };
232
259
  }
233
- return status.managedRuntimeFingerprint !== desiredSettings.fingerprint;
260
+ return {
261
+ desiredSettings: buildDesiredManagedRuntimeSettingsFromSnapshot(persistedSettingsSnapshot),
262
+ processEnv: buildManagedRuntimeSettingsEnv(effectiveProcessEnv, persistedSettingsSnapshot),
263
+ persistedSettingsSnapshot,
264
+ };
234
265
  }
235
266
  function cloneLocalAgentConfig(agent) {
236
267
  return {
@@ -311,6 +342,70 @@ export function buildManagedLocalAgentConfig(options) {
311
342
  },
312
343
  };
313
344
  }
345
+ function mergeManagedLocalAgentConfig(hostDefaults, existingAgent, generatedAgent, envOverrides) {
346
+ const overrides = envOverrides ?? {};
347
+ const hasOverride = (key) => Object.prototype.hasOwnProperty.call(overrides, key);
348
+ const effectiveExistingClaudeCommand = existingAgent.claudeCommand ?? hostDefaults?.claudeCommand;
349
+ const effectiveExistingClaudeArgs = existingAgent.claudeArgs ?? hostDefaults?.claudeArgs ?? [];
350
+ const hasLegacyClaudeDefaults = effectiveExistingClaudeCommand === 'claude' && hasLegacyClaudeOneShotArgs(effectiveExistingClaudeArgs);
351
+ const shouldRefreshLegacyClaudeDefaults = hasLegacyClaudeDefaults
352
+ && generatedAgent.claudeCommand === DEFAULT_PROVIDER_COMMAND_CONFIG.claudeCommand
353
+ && JSON.stringify(generatedAgent.claudeArgs ?? []) === JSON.stringify(DEFAULT_PROVIDER_COMMAND_CONFIG.claudeArgs);
354
+ const shouldResetLegacyClaudeArgsForCommandOverride = hasLegacyClaudeDefaults
355
+ && hasOverride('CLAUDE_COMMAND')
356
+ && !hasOverride('CLAUDE_ARGS');
357
+ return {
358
+ ...cloneLocalAgentConfig(existingAgent),
359
+ key: generatedAgent.key,
360
+ apiKey: generatedAgent.apiKey,
361
+ enabled: true,
362
+ name: hasOverride('BORGEE_AGENT_NAME') ? generatedAgent.name : existingAgent.name,
363
+ provider: hasOverride('RUNTIME_PROVIDER') ? generatedAgent.provider : existingAgent.provider,
364
+ claudeCommand: hasOverride('CLAUDE_COMMAND') || shouldRefreshLegacyClaudeDefaults
365
+ ? generatedAgent.claudeCommand
366
+ : existingAgent.claudeCommand,
367
+ claudeArgs: hasOverride('CLAUDE_ARGS')
368
+ || shouldRefreshLegacyClaudeDefaults
369
+ || shouldResetLegacyClaudeArgsForCommandOverride
370
+ ? [...(generatedAgent.claudeArgs ?? [])]
371
+ : [...(existingAgent.claudeArgs ?? [])],
372
+ codexCommand: hasOverride('CODEX_COMMAND') ? generatedAgent.codexCommand : existingAgent.codexCommand,
373
+ codexArgs: hasOverride('CODEX_ARGS')
374
+ ? [...(generatedAgent.codexArgs ?? [])]
375
+ : [...(existingAgent.codexArgs ?? [])],
376
+ copilotCommand: hasOverride('COPILOT_COMMAND')
377
+ ? generatedAgent.copilotCommand
378
+ : existingAgent.copilotCommand,
379
+ copilotArgs: hasOverride('COPILOT_ARGS')
380
+ ? [...(generatedAgent.copilotArgs ?? [])]
381
+ : [...(existingAgent.copilotArgs ?? [])],
382
+ copilotSessionTtlMinutes: hasOverride('COPILOT_SESSION_TTL_MINUTES')
383
+ ? generatedAgent.copilotSessionTtlMinutes
384
+ : existingAgent.copilotSessionTtlMinutes,
385
+ };
386
+ }
387
+ function collectExplicitManagedAgentEnvOverrides(processEnv, optionEnv) {
388
+ const explicitOverrides = {};
389
+ for (const key of [
390
+ 'BORGEE_AGENT_NAME',
391
+ 'RUNTIME_PROVIDER',
392
+ 'CLAUDE_COMMAND',
393
+ 'CLAUDE_ARGS',
394
+ 'CODEX_COMMAND',
395
+ 'CODEX_ARGS',
396
+ 'COPILOT_COMMAND',
397
+ 'COPILOT_ARGS',
398
+ 'COPILOT_SESSION_TTL_MINUTES',
399
+ ]) {
400
+ if (processEnv[key] !== undefined) {
401
+ explicitOverrides[key] = processEnv[key];
402
+ }
403
+ if (optionEnv && Object.prototype.hasOwnProperty.call(optionEnv, key)) {
404
+ explicitOverrides[key] = optionEnv[key];
405
+ }
406
+ }
407
+ return explicitOverrides;
408
+ }
314
409
  function resolveManagedRuntimeLayoutForServerUrl(serverUrl, processEnv = process.env) {
315
410
  const rootPath = resolveManagedRuntimeRoot(serverUrl, processEnv);
316
411
  return {
@@ -884,8 +979,6 @@ export async function bootstrapManagedDaemonStart(options, deps = {}) {
884
979
  ...(options.processEnv ?? process.env),
885
980
  ...options.env,
886
981
  };
887
- const loadSpec = deps.loadSpec
888
- ?? ((hostConfigPath) => loadLocalConfigGenerateSpec(hostConfigPath, { env: effectiveProcessEnv }));
889
982
  const materialize = deps.materialize ?? materializeLocalConfig;
890
983
  const sendRequest = deps.sendRequest ?? sendManagedDaemonRequest;
891
984
  const spawnDaemonProcess = deps.spawnDaemon ?? spawnManagedDaemonProcess;
@@ -895,19 +988,51 @@ export async function bootstrapManagedDaemonStart(options, deps = {}) {
895
988
  ?? (async (rootPath) => {
896
989
  await waitForManagedDaemonShutdown(rootPath, canConnectToSocket);
897
990
  });
898
- const desiredSettings = resolveDesiredManagedRuntimeSettings(effectiveProcessEnv);
899
- const resolvedManagedAgent = options.apiKey === undefined ? null : buildManagedLocalAgentConfig(options);
900
- const resolved = resolvedManagedAgent ??
901
- resolveManagedRuntimeLayoutForServerUrl(options.serverUrl, options.processEnv);
991
+ const requestedSettings = resolveDesiredManagedRuntimeSettings(effectiveProcessEnv);
992
+ const resolved = resolveManagedRuntimeLayoutForServerUrl(options.serverUrl, options.processEnv);
902
993
  const releaseBootstrapLock = await acquireLock(resolved.rootPath);
903
994
  try {
995
+ const currentRuntimeSettings = await resolveAuthoritativeManagedRuntimeSettings(resolved.rootPath, effectiveProcessEnv);
996
+ const loadManagedSpecWithCurrentRuntimeEnv = (hostConfigPath) => deps.loadSpec
997
+ ? deps.loadSpec(hostConfigPath)
998
+ : loadLocalConfigGenerateSpec(hostConfigPath, {
999
+ env: currentRuntimeSettings.processEnv,
1000
+ });
1001
+ if (options.apiKey !== undefined
1002
+ && currentRuntimeSettings.persistedSettingsSnapshot == null
1003
+ && await fs.access(resolved.hostConfigPath).then(() => true).catch(() => false)) {
1004
+ throw new Error(`Managed runtime root ${resolved.rootPath} predates managed-runtime-settings.json; run \`agents-host start-managed ${options.serverUrl}\` once to migrate it before updating an individual agent`);
1005
+ }
1006
+ const explicitManagedAgentOverrides = collectExplicitManagedAgentEnvOverrides(options.processEnv ?? process.env, options.env);
1007
+ const currentManagedSpec = options.apiKey === undefined
1008
+ ? undefined
1009
+ : await loadManagedSpecOrUndefined(resolved.hostConfigPath, loadManagedSpecWithCurrentRuntimeEnv);
1010
+ const resolvedManagedAgent = options.apiKey === undefined
1011
+ ? null
1012
+ : (() => {
1013
+ const generatedManagedAgent = buildManagedLocalAgentConfig({
1014
+ ...options,
1015
+ processEnv: currentRuntimeSettings.processEnv,
1016
+ env: options.env,
1017
+ });
1018
+ const existingAgent = currentManagedSpec?.agents.find((agent) => agent.key === generatedManagedAgent.agent.key);
1019
+ return existingAgent
1020
+ ? {
1021
+ ...generatedManagedAgent,
1022
+ agent: mergeManagedLocalAgentConfig(currentManagedSpec?.host.defaults, existingAgent, generatedManagedAgent.agent, explicitManagedAgentOverrides),
1023
+ }
1024
+ : generatedManagedAgent;
1025
+ })();
1026
+ const desiredBootstrapSettings = options.apiKey === undefined ? requestedSettings : currentRuntimeSettings.desiredSettings;
1027
+ const desiredBootstrapProcessEnv = options.apiKey === undefined ? effectiveProcessEnv : currentRuntimeSettings.processEnv;
904
1028
  let statusResponse = await readReadyManagedDaemonStatus(resolved.rootPath, sendRequest);
905
- if (statusResponse && getManagedRuntimeFingerprintMismatch(statusResponse, desiredSettings)) {
1029
+ if (statusResponse
1030
+ && await getManagedRuntimeFingerprintMismatch(resolved.rootPath, statusResponse, desiredBootstrapSettings)) {
906
1031
  await recycleManagedDaemonWithDesiredSettings({
907
1032
  rootPath: resolved.rootPath,
908
1033
  debug: options.debug === true,
909
- processEnv: effectiveProcessEnv,
910
- desiredSettings,
1034
+ processEnv: desiredBootstrapProcessEnv,
1035
+ desiredSettings: desiredBootstrapSettings,
911
1036
  sendRequest,
912
1037
  waitForShutdown,
913
1038
  spawnDaemonProcess,
@@ -917,11 +1042,17 @@ export async function bootstrapManagedDaemonStart(options, deps = {}) {
917
1042
  }
918
1043
  if (!statusResponse) {
919
1044
  if (options.apiKey === undefined) {
920
- await ensurePersistedManagedAgentsExist(options.serverUrl, resolved.hostConfigPath, loadSpec);
1045
+ await ensurePersistedManagedAgentsExist(options.serverUrl, resolved.hostConfigPath, (hostConfigPath) => options.apiKey === undefined
1046
+ ? (deps.loadSpec
1047
+ ? deps.loadSpec(hostConfigPath)
1048
+ : loadLocalConfigGenerateSpec(hostConfigPath, {
1049
+ env: desiredBootstrapProcessEnv,
1050
+ }))
1051
+ : loadManagedSpecWithCurrentRuntimeEnv(hostConfigPath));
921
1052
  }
922
1053
  else {
923
1054
  try {
924
- await loadSpec(resolved.hostConfigPath);
1055
+ await loadManagedSpecWithCurrentRuntimeEnv(resolved.hostConfigPath);
925
1056
  }
926
1057
  catch (error) {
927
1058
  if (isNotFoundError(error)) {
@@ -938,8 +1069,8 @@ export async function bootstrapManagedDaemonStart(options, deps = {}) {
938
1069
  await startManagedDaemonWithPersistedSettings({
939
1070
  rootPath: resolved.rootPath,
940
1071
  debug: options.debug === true,
941
- processEnv: effectiveProcessEnv,
942
- desiredSettings,
1072
+ processEnv: desiredBootstrapProcessEnv,
1073
+ desiredSettings: desiredBootstrapSettings,
943
1074
  spawnDaemonProcess,
944
1075
  waitForDaemon,
945
1076
  sendRequest,
@@ -948,7 +1079,11 @@ export async function bootstrapManagedDaemonStart(options, deps = {}) {
948
1079
  }
949
1080
  if (options.apiKey === undefined) {
950
1081
  if (!statusResponse) {
951
- await ensurePersistedManagedAgentsExist(options.serverUrl, resolved.hostConfigPath, loadSpec);
1082
+ await ensurePersistedManagedAgentsExist(options.serverUrl, resolved.hostConfigPath, (hostConfigPath) => deps.loadSpec
1083
+ ? deps.loadSpec(hostConfigPath)
1084
+ : loadLocalConfigGenerateSpec(hostConfigPath, {
1085
+ env: desiredBootstrapProcessEnv,
1086
+ }));
952
1087
  }
953
1088
  else {
954
1089
  assertManagedRuntimeBinding(statusResponse.borgeeBaseUrl, options.serverUrl);
@@ -1079,10 +1214,11 @@ async function terminateChildProcessAndWait(child) {
1079
1214
  }
1080
1215
  await waitForChildExit(child, DAEMON_FORCE_KILL_WAIT_MS);
1081
1216
  }
1082
- async function rollbackManagedApplyFailure(rootPath, previousSpec, materialize, clearRuntimeImpl, applyError) {
1217
+ async function rollbackManagedApplyFailure(rootPath, previousSpec, previousSettingsSnapshot, materialize, clearRuntimeImpl, applyError) {
1083
1218
  const original = toManagedErrorPayload(applyError, 'MANAGED_RELOAD_FAILED');
1084
1219
  try {
1085
1220
  if (previousSpec) {
1221
+ await restoreManagedRuntimeSettingsSnapshot(rootPath, previousSettingsSnapshot);
1086
1222
  await materialize(rootPath, cloneLocalConfigGenerateSpec(previousSpec));
1087
1223
  }
1088
1224
  else {
@@ -1139,8 +1275,6 @@ function validateDaemonUpsertAgentInput(currentSpec, serverUrl, agent, env = pro
1139
1275
  }
1140
1276
  export async function describeManagedSpec(options, deps = {}) {
1141
1277
  const effectiveProcessEnv = options.processEnv ?? process.env;
1142
- const loadSpec = deps.loadSpec
1143
- ?? ((hostConfigPath) => loadLocalConfigGenerateSpec(hostConfigPath, { env: effectiveProcessEnv }));
1144
1278
  const sendRequest = deps.sendRequest ?? sendManagedDaemonRequest;
1145
1279
  const resolved = resolveManagedRuntimeLayoutForServerUrl(options.serverUrl, options.processEnv);
1146
1280
  try {
@@ -1167,7 +1301,12 @@ export async function describeManagedSpec(options, deps = {}) {
1167
1301
  }
1168
1302
  }
1169
1303
  try {
1170
- const persistedSpec = await loadManagedSpecOrUndefined(resolved.hostConfigPath, loadSpec);
1304
+ const currentRuntimeSettings = await resolveAuthoritativeManagedRuntimeSettings(resolved.rootPath, effectiveProcessEnv);
1305
+ const persistedSpec = await loadManagedSpecOrUndefined(resolved.hostConfigPath, (hostConfigPath) => deps.loadSpec
1306
+ ? deps.loadSpec(hostConfigPath)
1307
+ : loadLocalConfigGenerateSpec(hostConfigPath, {
1308
+ env: currentRuntimeSettings.processEnv,
1309
+ }));
1171
1310
  if (!persistedSpec) {
1172
1311
  return { ok: true, present: false };
1173
1312
  }
@@ -1207,27 +1346,13 @@ async function applySpecToRunningManagedDaemon(rootPath, borgeeBaseUrl, spec, se
1207
1346
  }
1208
1347
  export async function applyManagedSpec(options, deps = {}) {
1209
1348
  const effectiveProcessEnv = options.processEnv ?? process.env;
1210
- const loadSpec = deps.loadSpec
1211
- ?? ((hostConfigPath) => loadLocalConfigGenerateSpec(hostConfigPath, { env: effectiveProcessEnv }));
1212
- const materialize = deps.materialize
1213
- ?? ((rootPath, spec) => materializeLocalConfig(rootPath, spec, { env: effectiveProcessEnv }));
1349
+ const desiredSettings = resolveDesiredManagedRuntimeSettings(effectiveProcessEnv);
1214
1350
  const sendRequest = deps.sendRequest ?? sendManagedDaemonRequest;
1215
1351
  const spawnDaemonProcess = deps.spawnDaemon ?? spawnManagedDaemonProcess;
1216
1352
  const waitForDaemon = deps.waitForDaemonReady ?? waitForManagedDaemonReady;
1217
1353
  const acquireLock = deps.acquireBootstrapLock ?? acquireBootstrapLock;
1218
1354
  const clearManagedRuntimeImpl = deps.clearManagedRuntime ?? clearManagedRuntime;
1219
1355
  const resolved = resolveManagedRuntimeLayoutForServerUrl(options.serverUrl, options.processEnv);
1220
- let spec;
1221
- try {
1222
- spec = validateApplyManagedInput(options.spec, options.serverUrl, effectiveProcessEnv);
1223
- }
1224
- catch (error) {
1225
- if (isManagedOperationError(error)) {
1226
- return toManagedErrorResponse(error);
1227
- }
1228
- return toManagedErrorResponse(managedOperationError('MANAGED_SPEC_INVALID', `Invalid managed spec: ${toErrorMessage(error)}`));
1229
- }
1230
- const desiredSettings = resolveDesiredManagedRuntimeSettings(effectiveProcessEnv);
1231
1356
  const waitForShutdown = deps.waitForShutdown
1232
1357
  ?? (async (rootPath) => {
1233
1358
  await waitForManagedDaemonShutdown(rootPath, canConnectToSocket);
@@ -1240,6 +1365,32 @@ export async function applyManagedSpec(options, deps = {}) {
1240
1365
  return toApplyManagedColdStartErrorResponse(error);
1241
1366
  }
1242
1367
  try {
1368
+ const currentRuntimeSettings = await resolveAuthoritativeManagedRuntimeSettings(resolved.rootPath, effectiveProcessEnv);
1369
+ const loadSpecWithCurrentRuntimeEnv = (hostConfigPath) => deps.loadSpec
1370
+ ? deps.loadSpec(hostConfigPath)
1371
+ : loadLocalConfigGenerateSpec(hostConfigPath, {
1372
+ env: currentRuntimeSettings.processEnv,
1373
+ });
1374
+ const materializeWithDesiredEnv = (rootPath, nextSpec) => deps.materialize
1375
+ ? deps.materialize(rootPath, nextSpec)
1376
+ : materializeLocalConfig(rootPath, nextSpec, {
1377
+ env: effectiveProcessEnv,
1378
+ });
1379
+ const materializeWithCurrentRuntimeEnv = (rootPath, nextSpec) => deps.materialize
1380
+ ? deps.materialize(rootPath, nextSpec)
1381
+ : materializeLocalConfig(rootPath, nextSpec, {
1382
+ env: currentRuntimeSettings.processEnv,
1383
+ });
1384
+ let spec;
1385
+ try {
1386
+ spec = validateApplyManagedInput(options.spec, options.serverUrl, effectiveProcessEnv);
1387
+ }
1388
+ catch (error) {
1389
+ if (isManagedOperationError(error)) {
1390
+ return toManagedErrorResponse(error);
1391
+ }
1392
+ return toManagedErrorResponse(managedOperationError('MANAGED_SPEC_INVALID', `Invalid managed spec: ${toErrorMessage(error)}`));
1393
+ }
1243
1394
  let statusResponse;
1244
1395
  try {
1245
1396
  statusResponse = await readReadyManagedDaemonStatus(resolved.rootPath, sendRequest);
@@ -1250,7 +1401,8 @@ export async function applyManagedSpec(options, deps = {}) {
1250
1401
  }
1251
1402
  throw error;
1252
1403
  }
1253
- if (statusResponse && getManagedRuntimeFingerprintMismatch(statusResponse, desiredSettings)) {
1404
+ if (statusResponse
1405
+ && await getManagedRuntimeFingerprintMismatch(resolved.rootPath, statusResponse, desiredSettings)) {
1254
1406
  try {
1255
1407
  await recycleManagedDaemonWithDesiredSettings({
1256
1408
  rootPath: resolved.rootPath,
@@ -1273,7 +1425,7 @@ export async function applyManagedSpec(options, deps = {}) {
1273
1425
  }
1274
1426
  let previousSpec;
1275
1427
  try {
1276
- previousSpec = await loadManagedSpecOrUndefined(resolved.hostConfigPath, loadSpec);
1428
+ previousSpec = await loadManagedSpecOrUndefined(resolved.hostConfigPath, loadSpecWithCurrentRuntimeEnv);
1277
1429
  if (previousSpec) {
1278
1430
  assertManagedSpecBinding(previousSpec, options.serverUrl);
1279
1431
  }
@@ -1282,10 +1434,10 @@ export async function applyManagedSpec(options, deps = {}) {
1282
1434
  return toApplyManagedColdStartErrorResponse(error);
1283
1435
  }
1284
1436
  try {
1285
- await materialize(resolved.rootPath, cloneLocalConfigGenerateSpec(spec));
1437
+ await materializeWithDesiredEnv(resolved.rootPath, cloneLocalConfigGenerateSpec(spec));
1286
1438
  }
1287
1439
  catch (error) {
1288
- return rollbackManagedApplyFailure(resolved.rootPath, previousSpec, materialize, clearManagedRuntimeImpl, error);
1440
+ return rollbackManagedApplyFailure(resolved.rootPath, previousSpec, currentRuntimeSettings.persistedSettingsSnapshot ?? undefined, materializeWithCurrentRuntimeEnv, clearManagedRuntimeImpl, error);
1289
1441
  }
1290
1442
  try {
1291
1443
  await startManagedDaemonWithPersistedSettings({
@@ -1300,7 +1452,7 @@ export async function applyManagedSpec(options, deps = {}) {
1300
1452
  const status = await sendRequest(resolved.rootPath, { type: 'status' });
1301
1453
  if (!status.ok) {
1302
1454
  const managedError = getManagedErrorPayload(status);
1303
- return rollbackManagedApplyFailure(resolved.rootPath, previousSpec, materialize, clearManagedRuntimeImpl, managedOperationError(managedError.code, managedError.message));
1455
+ return rollbackManagedApplyFailure(resolved.rootPath, previousSpec, currentRuntimeSettings.persistedSettingsSnapshot ?? undefined, materializeWithCurrentRuntimeEnv, clearManagedRuntimeImpl, managedOperationError(managedError.code, managedError.message));
1304
1456
  }
1305
1457
  if (status.type !== 'status') {
1306
1458
  throw new Error('Unexpected managed daemon response for status');
@@ -1314,7 +1466,7 @@ export async function applyManagedSpec(options, deps = {}) {
1314
1466
  };
1315
1467
  }
1316
1468
  catch (error) {
1317
- return rollbackManagedApplyFailure(resolved.rootPath, previousSpec, materialize, clearManagedRuntimeImpl, error);
1469
+ return rollbackManagedApplyFailure(resolved.rootPath, previousSpec, currentRuntimeSettings.persistedSettingsSnapshot ?? undefined, materializeWithCurrentRuntimeEnv, clearManagedRuntimeImpl, error);
1318
1470
  }
1319
1471
  }
1320
1472
  finally {
@@ -1350,9 +1502,7 @@ export class ManagedAgentsHostDaemon {
1350
1502
  this.loadSpec = deps.loadSpec ?? loadLocalConfigGenerateSpec;
1351
1503
  this.materialize = deps.materialize ?? materializeLocalConfig;
1352
1504
  const managedRuntimeSettings = resolveDesiredManagedRuntimeSettings(process.env);
1353
- this.managedRuntimeFingerprint = managedRuntimeSettings.convergenceEnabled
1354
- ? managedRuntimeSettings.fingerprint
1355
- : null;
1505
+ this.managedRuntimeFingerprint = managedRuntimeSettings.fingerprint;
1356
1506
  this.logger = deps.logger ?? console;
1357
1507
  this.logPath = deps.logPath ?? null;
1358
1508
  this.server = createServer((socket) => {
@@ -1,19 +1,25 @@
1
1
  import spawn from 'cross-spawn';
2
+ import { PROTOCOL_VERSION, client, methods, ndJsonStream } from '@agentclientprotocol/sdk';
2
3
  import { type DebugLogger } from '../../debug.js';
3
4
  import type { PreparedProviderTurnInput, ProviderGenerateOptions } from '../../types.js';
4
5
  import type { ClaudeChannelSessionStore } from './session-store.js';
5
- interface ClaudeCliRuntime {
6
+ interface ClaudeAcpRuntime {
6
7
  spawn: typeof spawn;
8
+ client: typeof client;
9
+ ndJsonStream: typeof ndJsonStream;
10
+ methods: typeof methods;
11
+ protocolVersion: typeof PROTOCOL_VERSION;
7
12
  cwd: string;
13
+ shutdownGracePeriodMs: number;
14
+ shutdownForceKillWaitMs: number;
8
15
  }
9
16
  /**
10
- * CLI client for the Claude Code CLI (`claude`) non-interactive mode, with
11
- * native provider-session continuity.
17
+ * Persistent ACP-backed client for the Claude ACP adapter.
12
18
  *
13
- * Claude session memory remains entirely inside the Claude CLI. This client
14
- * only pins one native session id per routed provider session and serializes
15
- * turns per route so `--resume` is never called concurrently for the same
16
- * native session.
19
+ * The public caller surface intentionally stays stable: callers still create a
20
+ * `ClaudeCliClient`, then call `generateReply()` and `dispose()`. Internally,
21
+ * the shipped runtime now runs one Claude ACP adapter process and reuses one
22
+ * ACP session per routed Borgee channel.
17
23
  */
18
24
  export declare class ClaudeCliClient {
19
25
  private readonly command;
@@ -24,28 +30,61 @@ export declare class ClaudeCliClient {
24
30
  private readonly runtime;
25
31
  private readonly channels;
26
32
  private readonly persistedSessions;
33
+ private readonly closingSessions;
34
+ private readonly pendingSessionStarts;
35
+ private readonly pendingSessionCloses;
36
+ private readonly pendingSessionStoreOperations;
37
+ private readonly fatalPromise;
38
+ private rejectFatalPromise;
39
+ private child?;
40
+ private connection?;
41
+ private startPromise?;
42
+ private shutdownPromise?;
43
+ private childExitPromise?;
44
+ private resolveChildExit?;
45
+ private fatalError;
46
+ private disposing;
47
+ private backendClosed;
27
48
  private loadedSessionStoreAgentId;
28
- private stopped;
29
49
  private sessionStoreLoadPromise;
30
50
  private sessionStoreWriteQueue;
31
- constructor(command: string, args: string[], runtimeOverrides?: Partial<ClaudeCliRuntime>, sessionStore?: ClaudeChannelSessionStore | undefined, resolveSessionStoreAgentId?: () => string | undefined, logger?: DebugLogger);
51
+ private sessionCapabilities;
52
+ private childStderr;
53
+ constructor(command: string, args?: string[], runtimeOverrides?: Partial<ClaudeAcpRuntime>, sessionStore?: ClaudeChannelSessionStore | undefined, resolveSessionStoreAgentId?: () => string | undefined, logger?: DebugLogger);
32
54
  generateReply(turn: PreparedProviderTurnInput, options?: ProviderGenerateOptions): Promise<string>;
33
55
  generateReply(channelId: string, prompt: string, options?: ProviderGenerateOptions): Promise<string>;
34
56
  dispose(): Promise<void>;
57
+ private ensureStarted;
58
+ private startBackend;
35
59
  private getOrCreateChannelState;
36
60
  private processChannelQueue;
61
+ private getOrCreateSession;
62
+ private startFreshSession;
63
+ private restoreOrCreateSession;
64
+ private restoreSession;
65
+ private clearBufferedSessionReplay;
66
+ private resolveSessionCwd;
67
+ private recycleSessionIfScopeChanged;
37
68
  private runTurn;
38
- private runTurnAttempt;
69
+ private raceWithFatal;
70
+ private failAll;
71
+ private handlePermissionRequest;
72
+ private invalidateSession;
73
+ private closeSession;
74
+ private rejectQueuedTurnsAfterSessionTaint;
75
+ private findChannelIdBySessionId;
39
76
  private currentSessionStoreAgentId;
40
77
  private ensureSessionStoreLoaded;
41
- private hydrateChannelState;
78
+ private readPersistedSessionId;
42
79
  private persistSession;
43
80
  private persistSessionBestEffort;
44
- private resetPersistedSession;
45
- private resolveTurnCwd;
46
- private resetSessionIfCwdChanged;
47
- private resetPersistedSessionBestEffort;
81
+ private clearPersistedSession;
82
+ private clearPersistedSessionBestEffort;
48
83
  private enqueueSessionStoreWrite;
49
- private run;
84
+ private trackSessionStoreOperation;
85
+ private shutdownBackend;
86
+ private waitForPendingSessionStarts;
87
+ private waitForPendingSessionCloses;
88
+ private waitForChildExit;
50
89
  }
51
90
  export {};