@borgee/agents-host 0.2.62 → 0.2.68

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.
@@ -1,6 +1,6 @@
1
1
  import type { ChatControlPlane } from './chat/chat-control-plane.js';
2
2
  import { type AgentsHostRuntimeOptions } from './debug.js';
3
- import type { ProviderAdapter } from './providers/provider-adapter.js';
3
+ import { type ProviderAdapter } from './providers/provider-adapter.js';
4
4
  import type { AgentsHostConfig } from './types.js';
5
5
  /**
6
6
  * Minimal single-agent agents host: connects one local Claude/Copilot CLI
@@ -160,6 +160,8 @@ export declare class AgentsHost {
160
160
  private readCollaborationDraft;
161
161
  private supersedeActiveTurn;
162
162
  private invalidateChannelTurn;
163
+ private stopTurn;
164
+ private responseStateForExecuted;
163
165
  private rememberRecentTurn;
164
166
  private resolveAuthorizedCollaborationTurn;
165
167
  private updateAwaitingUserState;
@@ -12,6 +12,7 @@ import { createLocalhostGatewayController, LOCALHOST_GATEWAY_COLLABORATION_TARGE
12
12
  import { extractTaskIdFromTaskAssignmentContent, resolveTaskAssignmentStatePath, } from './context/injection.js';
13
13
  import { AuthorizationAuditSink, } from './policy/authorization-audit.js';
14
14
  import { createProvider } from './providers/create-provider.js';
15
+ import { ProviderTurnCancelledError } from './providers/provider-adapter.js';
15
16
  import { resolveAttentionStatePath, resolveCompactionStatePath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
16
17
  import { appendVisibleMention as appendVisibleBodyMention, extractVisibleMentionIds as extractCanonicalMentionIds, } from './visible-mentions.js';
17
18
  import { buildHostedIncomingContentText, buildHostedTurnContentParts, extractHostedMessageAttachments, normalizeHostedMessageAttachment, } from './hosted-turn-content.js';
@@ -24,6 +25,9 @@ const PROTOCOL_KICKOFF_DECISION_POLL_INTERVAL_MS = 50;
24
25
  const PROTOCOL_KICKOFF_ROUND_LIMIT = 20;
25
26
  const COLLABORATION_LATE_SEND_GRACE_MS = 5_000;
26
27
  const ORDINARY_HUMAN_EVENT_KINDS = new Set(['', 'message']);
28
+ const TASK_EXECUTION_LOCAL_DIRECTORY_PROPERTY_KEY = 'execution.local_directory';
29
+ const TASK_EXECUTION_LOCAL_DIRECTORY_REMINDER = 'This task has no Execution Local Directory yet. Set it before asking me to work in a real local project directory.';
30
+ const TASK_EXECUTION_LOCAL_DIRECTORY_IN_USE_PREFIX = 'Using Execution Local Directory:';
27
31
  async function ensurePrivateStateRoot(path) {
28
32
  await mkdir(path, { recursive: true, mode: PRIVATE_STATE_ROOT_MODE });
29
33
  await chmod(path, PRIVATE_STATE_ROOT_MODE);
@@ -145,13 +149,15 @@ class DefaultHostRuntime {
145
149
  });
146
150
  this.ensureStateRoot = deps.ensureStateRoot ?? ensurePrivateStateRoot;
147
151
  }
148
- async start(onMessage) {
152
+ async start(onMessage, onStopTurn) {
149
153
  await this.ensureStateRoot(this.config.stateRootDir);
150
154
  await this.gateway.start();
151
155
  try {
156
+ this.controlPlane.onStopTurn?.(onStopTurn);
152
157
  await this.controlPlane.connect(onMessage);
153
158
  }
154
159
  catch (error) {
160
+ this.controlPlane.onStopTurn?.(undefined);
155
161
  await this.gateway.stop().catch(() => { });
156
162
  throw error;
157
163
  }
@@ -159,6 +165,7 @@ class DefaultHostRuntime {
159
165
  async stop() {
160
166
  let thrown;
161
167
  try {
168
+ this.controlPlane.onStopTurn?.(undefined);
162
169
  await this.controlPlane.close();
163
170
  }
164
171
  catch (error) {
@@ -366,7 +373,7 @@ export class AgentsHost {
366
373
  return;
367
374
  }
368
375
  this.dispatchMessage(message);
369
- });
376
+ }, async (channelId) => this.stopTurn(channelId));
370
377
  const agentId = await this.ensureSelfAgentId();
371
378
  bufferingMessages = false;
372
379
  while (bufferedMessages.length > 0) {
@@ -675,7 +682,7 @@ export class AgentsHost {
675
682
  return null;
676
683
  }
677
684
  this.recordCollaborationOutcome(msg.channel_id, {
678
- responseState: executed.completed ? 'responded' : 'failed',
685
+ responseState: this.responseStateForExecuted(executed),
679
686
  deliveryState: executed.deliveryState ?? 'not-delivered',
680
687
  });
681
688
  this.updateMissedCollaborationDiagnostic(msg.channel_id, priorMissedCollaborationDiagnostic);
@@ -1110,7 +1117,7 @@ export class AgentsHost {
1110
1117
  channelState.blocked = undefined;
1111
1118
  this.awaitingUserByChannel.delete(channelId);
1112
1119
  this.recordCollaborationOutcome(channelId, {
1113
- responseState: executed.completed ? 'responded' : 'failed',
1120
+ responseState: this.responseStateForExecuted(executed),
1114
1121
  deliveryState: executed.deliveryState ?? 'not-delivered',
1115
1122
  ...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
1116
1123
  turnExecutionId: activeTurn.turnExecutionId ?? undefined,
@@ -1174,7 +1181,7 @@ export class AgentsHost {
1174
1181
  const visibleMessageId = deliveredMessageId ?? protocolState.lastProtocolMessageId;
1175
1182
  if (executed.reply?.controlMalformed) {
1176
1183
  this.recordCollaborationOutcome(channelId, {
1177
- responseState: executed.completed ? 'responded' : 'failed',
1184
+ responseState: this.responseStateForExecuted(executed),
1178
1185
  deliveryState: executed.deliveryState ?? 'not-delivered',
1179
1186
  ...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
1180
1187
  turnExecutionId: activeTurn.turnExecutionId ?? undefined,
@@ -1184,7 +1191,7 @@ export class AgentsHost {
1184
1191
  }
1185
1192
  if (!executed.reply?.control) {
1186
1193
  this.recordCollaborationOutcome(channelId, {
1187
- responseState: executed.completed ? 'responded' : 'failed',
1194
+ responseState: this.responseStateForExecuted(executed),
1188
1195
  deliveryState: executed.deliveryState ?? 'not-delivered',
1189
1196
  ...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
1190
1197
  turnExecutionId: activeTurn.turnExecutionId ?? undefined,
@@ -1218,7 +1225,7 @@ export class AgentsHost {
1218
1225
  }
1219
1226
  if (executed.reply.control.kind === 'start-protocol') {
1220
1227
  this.recordCollaborationOutcome(channelId, {
1221
- responseState: executed.completed ? 'responded' : 'failed',
1228
+ responseState: this.responseStateForExecuted(executed),
1222
1229
  deliveryState: executed.deliveryState ?? 'not-delivered',
1223
1230
  ...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
1224
1231
  turnExecutionId: activeTurn.turnExecutionId ?? undefined,
@@ -1229,7 +1236,7 @@ export class AgentsHost {
1229
1236
  }
1230
1237
  if (executed.reply.control.kind === 'attention-only') {
1231
1238
  this.recordCollaborationOutcome(channelId, {
1232
- responseState: executed.completed ? 'responded' : 'failed',
1239
+ responseState: this.responseStateForExecuted(executed),
1233
1240
  deliveryState: executed.deliveryState ?? 'not-delivered',
1234
1241
  ...(message.wakeBlockedState ? { wakeState: 'resumed-human' } : {}),
1235
1242
  turnExecutionId: activeTurn.turnExecutionId ?? undefined,
@@ -2188,9 +2195,7 @@ export class AgentsHost {
2188
2195
  this.recordCollaborationOutcome(msg.channel_id, {
2189
2196
  responseState: executed.completed && executed.reply?.awaitingUser
2190
2197
  ? 'blocked'
2191
- : executed.completed
2192
- ? 'responded'
2193
- : 'failed',
2198
+ : this.responseStateForExecuted(executed),
2194
2199
  deliveryState: executed.deliveryState ?? 'not-delivered',
2195
2200
  ...(executed.completed && executed.reply?.awaitingUser
2196
2201
  ? {
@@ -2444,6 +2449,65 @@ export class AgentsHost {
2444
2449
  turnExecutionId: activeTurn.turnExecutionId,
2445
2450
  });
2446
2451
  }
2452
+ if (error instanceof ProviderTurnCancelledError) {
2453
+ const cancelledReplyText = error.publicReplyText?.trim();
2454
+ if (!silentTurn &&
2455
+ !deferDelivery &&
2456
+ typeof cancelledReplyText === 'string' &&
2457
+ cancelledReplyText.length > 0 &&
2458
+ this.started &&
2459
+ !this.controlPlaneClosed) {
2460
+ reply = { text: cancelledReplyText };
2461
+ publicReplyText = cancelledReplyText;
2462
+ visibleReplyBody = this.resolveVisibleTurnBody(activeTurn, reply, publicReplyText);
2463
+ try {
2464
+ const posted = await this.borgee.postMessage({
2465
+ channelId,
2466
+ body: visibleReplyBody,
2467
+ replyToId: activeTurn?.delivery?.replyToId,
2468
+ });
2469
+ this.logger.debug('posted cancelled turn feedback', {
2470
+ ...turnContext,
2471
+ durationMs: Date.now() - turnStartedAt,
2472
+ replyLength: publicReplyText.length,
2473
+ delivery: 'post',
2474
+ });
2475
+ return {
2476
+ reply,
2477
+ deliveredMessageId: posted.messageId,
2478
+ publicReplyText,
2479
+ visibleReplyBody,
2480
+ deliveryState: 'posted',
2481
+ stale: activeTurn?.superseded === true,
2482
+ completed: false,
2483
+ cancelled: true,
2484
+ };
2485
+ }
2486
+ catch (postError) {
2487
+ this.logger.debugError('failed to post cancelled turn feedback', {
2488
+ ...turnContext,
2489
+ error: postError,
2490
+ });
2491
+ this.logger.error('failed to post cancelled turn feedback', {
2492
+ ...turnContext,
2493
+ error: summarizeError(postError),
2494
+ });
2495
+ }
2496
+ }
2497
+ this.logger.debug('cancelled turn', {
2498
+ ...turnContext,
2499
+ durationMs: Date.now() - turnStartedAt,
2500
+ });
2501
+ return {
2502
+ reply,
2503
+ publicReplyText,
2504
+ visibleReplyBody,
2505
+ deliveryState: 'not-delivered',
2506
+ stale: activeTurn?.superseded === true,
2507
+ completed: false,
2508
+ cancelled: true,
2509
+ };
2510
+ }
2447
2511
  this.logger.debugError('failed to generate or send reply', {
2448
2512
  ...turnContext,
2449
2513
  error,
@@ -2655,6 +2719,29 @@ export class AgentsHost {
2655
2719
  activeTurn.turnExecutionId = null;
2656
2720
  }
2657
2721
  }
2722
+ async stopTurn(channelId) {
2723
+ const activeTurn = this.collaborationChannels.get(channelId)?.activeTurn;
2724
+ const aborted = await this.provider.cancelTurn?.(channelId) ?? false;
2725
+ if (!aborted) {
2726
+ return { aborted: false };
2727
+ }
2728
+ if (activeTurn) {
2729
+ this.supersedeActiveTurn(channelId, activeTurn);
2730
+ }
2731
+ else {
2732
+ this.recordCollaborationOutcome(channelId, {
2733
+ responseState: 'superseded',
2734
+ deliveryState: 'not-delivered',
2735
+ });
2736
+ }
2737
+ return { aborted: true };
2738
+ }
2739
+ responseStateForExecuted(executed) {
2740
+ if (executed.cancelled) {
2741
+ return 'superseded';
2742
+ }
2743
+ return executed.completed ? 'responded' : 'failed';
2744
+ }
2658
2745
  rememberRecentTurn(channelState, activeTurn) {
2659
2746
  if (!activeTurn.turnExecutionId) {
2660
2747
  channelState.recentTurn = undefined;
@@ -2853,6 +2940,34 @@ export class AgentsHost {
2853
2940
  task = null;
2854
2941
  }
2855
2942
  if (task && task.assigneeId === selfAgentId) {
2943
+ const executionLocalDirectory = task.properties?.[TASK_EXECUTION_LOCAL_DIRECTORY_PROPERTY_KEY];
2944
+ if (typeof executionLocalDirectory !== 'string' || executionLocalDirectory.trim().length === 0) {
2945
+ try {
2946
+ await this.borgee.postMessage({
2947
+ channelId: msg.channel_id,
2948
+ body: TASK_EXECUTION_LOCAL_DIRECTORY_REMINDER,
2949
+ });
2950
+ }
2951
+ catch (error) {
2952
+ this.logger.debugError('task local directory reminder post failed', {
2953
+ channelId: msg.channel_id,
2954
+ error,
2955
+ });
2956
+ }
2957
+ return null;
2958
+ }
2959
+ try {
2960
+ await this.borgee.postMessage({
2961
+ channelId: msg.channel_id,
2962
+ body: `${TASK_EXECUTION_LOCAL_DIRECTORY_IN_USE_PREFIX} ${executionLocalDirectory}`,
2963
+ });
2964
+ }
2965
+ catch (error) {
2966
+ this.logger.debugError('task local directory notice post failed', {
2967
+ channelId: msg.channel_id,
2968
+ error,
2969
+ });
2970
+ }
2856
2971
  if (task.status === 'open') {
2857
2972
  try {
2858
2973
  await this.borgee.updateTask({
@@ -1,6 +1,7 @@
1
- import type { ReportTurnActivityInput, TurnActivityReporter } from '../plugin-sdk.js';
1
+ import type { ReportTurnActivityInput, StopTurnHandler, TurnActivityReporter } from '../plugin-sdk.js';
2
2
  import type { ChannelSummary, ChannelHistoryEntry, ChannelMessageEvent, CreateTaskInput, DirectoryUser, MeResponseUser, PostMessageInput, PostedMessage, ReadChannelHistoryInput, Task, UpdateTaskInput } from '../types.js';
3
3
  export interface ChatControlPlane {
4
+ onStopTurn?(handler: StopTurnHandler | undefined): void;
4
5
  connect(onMessage: (message: ChannelMessageEvent) => void): Promise<void>;
5
6
  close(): Promise<void>;
6
7
  postMessage(input: PostMessageInput): Promise<PostedMessage>;
@@ -1,7 +1,7 @@
1
- import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent, type ReportTurnActivityInput, type TurnActivityReporter } from '../plugin-sdk.js';
1
+ import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent, type ReportTurnActivityInput, type StopTurnHandler, type TurnActivityReporter } from '../plugin-sdk.js';
2
2
  import type { ChannelSummary, ChannelHistoryEntry, ChannelMessageEvent, CreateTaskInput, DirectoryUser, MeResponseUser, PostMessageInput, PostedMessage, ReadChannelHistoryInput, Task, UpdateTaskInput } from '../types.js';
3
3
  import type { ChatControlPlane } from './chat-control-plane.js';
4
- type PluginClientLike = Pick<BorgeePluginClient, 'agentId' | 'close' | 'connect' | 'createTask' | 'deleteMessage' | 'editMessage' | 'getMe' | 'getTask' | 'listChannels' | 'listTasks' | 'listUsers' | 'on' | 'readHistory' | 'reportTurnActivity' | 'sendMessage' | 'startTyping' | 'updateTask' | 'setTaskProperty' | 'deleteTaskProperty'>;
4
+ type PluginClientLike = Pick<BorgeePluginClient, 'agentId' | 'close' | 'connect' | 'createTask' | 'deleteMessage' | 'editMessage' | 'getMe' | 'getTask' | 'listChannels' | 'listTasks' | 'listUsers' | 'on' | 'onStopTurn' | 'readHistory' | 'reportTurnActivity' | 'sendMessage' | 'startTyping' | 'updateTask' | 'setTaskProperty' | 'deleteTaskProperty'>;
5
5
  type PluginClientFactory = (options: BorgeePluginOptions) => PluginClientLike;
6
6
  type SdkChatControlPlaneOptions = Pick<BorgeePluginOptions, 'pluginId'>;
7
7
  /**
@@ -16,6 +16,7 @@ export declare class SdkChatControlPlane implements ChatControlPlane {
16
16
  private connected;
17
17
  private me;
18
18
  constructor(baseUrl: string, apiKey: string, createClient?: PluginClientFactory, options?: SdkChatControlPlaneOptions);
19
+ onStopTurn(handler: StopTurnHandler | undefined): void;
19
20
  connect(onMessage: (message: ChannelMessageEvent) => void): Promise<void>;
20
21
  close(): Promise<void>;
21
22
  postMessage(input: PostMessageInput): Promise<PostedMessage>;
@@ -14,6 +14,9 @@ export class SdkChatControlPlane {
14
14
  constructor(baseUrl, apiKey, createClient = createBorgeePlugin, options = {}) {
15
15
  this.client = createClient({ baseUrl, apiKey, ...options });
16
16
  }
17
+ onStopTurn(handler) {
18
+ this.client.onStopTurn(handler);
19
+ }
17
20
  async connect(onMessage) {
18
21
  this.unsubscribe = this.client.on('message', (event) => {
19
22
  const message = mapInboundToChannelMessage(event);
@@ -46,6 +49,7 @@ export class SdkChatControlPlane {
46
49
  this.pendingMessages = [];
47
50
  this.unsubscribe?.();
48
51
  this.unsubscribe = null;
52
+ this.client.onStopTurn(undefined);
49
53
  await this.client.close();
50
54
  }
51
55
  async postMessage(input) {
@@ -25,12 +25,14 @@ export type InternalPolicyMode = 'audit-only' | 'enforce';
25
25
  export type InternalProviderImplementationPath = 'v1' | 'v2';
26
26
  export type InternalProviderImplementationKey = 'claude' | 'copilot';
27
27
  export type InternalProviderImplementationOverrides = Partial<Record<InternalProviderImplementationKey, InternalProviderImplementationPath>>;
28
+ export declare const CURRENT_AGENTS_HOST_PACKAGE_VERSION: string;
28
29
  export interface ManagedRuntimeSettingsSnapshot {
29
30
  schemaVersion: number;
30
31
  compatibilityGates: string[];
31
32
  providerImplementationOverrides: string[];
32
33
  internalPolicyMode: InternalPolicyMode;
33
34
  projectionStrategy: ProjectionStrategy;
35
+ packageVersion: string;
34
36
  }
35
37
  export declare const DEFAULT_INTERNAL_COMPATIBILITY_GATES: readonly string[];
36
38
  export declare function resolveInternalProjectionStrategy(env?: NodeJS.ProcessEnv): ProjectionStrategy;
@@ -1,4 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
2
5
  import { DEFAULT_PROJECTION_STRATEGY, normalizeProjectionStrategy, } from './projection-strategy-values.js';
3
6
  export const CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE = 'claude-provider-v2';
4
7
  export const COPILOT_PROVIDER_V2_COMPATIBILITY_GATE = 'copilot-provider-v2';
@@ -22,6 +25,15 @@ export const INTERNAL_POLICY_MODE_ENV = 'AGENTS_HOST_INTERNAL_POLICY_MODE';
22
25
  export const INTERNAL_PROVIDER_IMPLEMENTATIONS_ENV = 'AGENTS_HOST_INTERNAL_PROVIDER_IMPLEMENTATIONS';
23
26
  export const INTERNAL_CLAUDE_PROMPT_STRATEGY_ENV = 'AGENTS_HOST_INTERNAL_CLAUDE_PROMPT_STRATEGY';
24
27
  export const MANAGED_RUNTIME_SETTINGS_SCHEMA_VERSION = 1;
28
+ function readCurrentAgentsHostPackageVersion() {
29
+ const packageManifestPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
30
+ const manifest = JSON.parse(readFileSync(packageManifestPath, 'utf8'));
31
+ if (typeof manifest.version !== 'string' || manifest.version.length === 0) {
32
+ throw new Error(`Unable to resolve agents-host package version from ${packageManifestPath}`);
33
+ }
34
+ return manifest.version;
35
+ }
36
+ export const CURRENT_AGENTS_HOST_PACKAGE_VERSION = readCurrentAgentsHostPackageVersion();
25
37
  const VALID_PROVIDER_IMPLEMENTATION_ENTRIES = '"claude:v1", "claude:v2", "copilot:v1", or "copilot:v2"';
26
38
  export const DEFAULT_INTERNAL_COMPATIBILITY_GATES = Object.freeze([
27
39
  ATTENTION_FOLLOW_SEMANTICS_COMPATIBILITY_GATE,
@@ -176,6 +188,7 @@ export function resolveManagedRuntimeSettingsSnapshot(env = process.env) {
176
188
  providerImplementationOverrides: resolveManagedRuntimeProviderImplementationOverrides(env, compatibilityGates),
177
189
  internalPolicyMode: resolveInternalPolicyMode(compatibilityGates.includes(POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE), env),
178
190
  projectionStrategy: resolveInternalProjectionStrategy(env),
191
+ packageVersion: CURRENT_AGENTS_HOST_PACKAGE_VERSION,
179
192
  };
180
193
  }
181
194
  export function serializeManagedRuntimeSettingsSnapshot(snapshot) {
@@ -565,9 +565,7 @@ function buildClaudeZeroTurnPrompt(params) {
565
565
  }
566
566
  const gatewayTaskLines = buildClaudeOrdinaryGatewayTaskLines(params.promptContext);
567
567
  return [
568
- 'Do not claim to have performed actions you did not actually perform.',
569
568
  ...(gatewayTaskLines.length > 0 ? [...gatewayTaskLines, ''] : []),
570
- `New message from ${params.incomingAuthorId}:`,
571
569
  params.incomingContent,
572
570
  ].join('\n');
573
571
  }
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url';
7
7
  import { AgentsHostSupervisor } from './agents-host-supervisor.js';
8
8
  import { DEFAULT_PROJECTION_STRATEGY, normalizeProjectionStrategy, } from './projection-strategy-values.js';
9
9
  import { resolveChannelWorkspaceDirectory, resolveChannelWorkspaceRootDirectory, resolveManagedWorkspaceCollectionRoot, resolveTaskThreadScratchWorkspaceDirectory, } from './context/injection.js';
10
- import { CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE, COMPATIBILITY_GATES_ENV, createManagedRuntimeSettingsFingerprint, INTERNAL_CLAUDE_PROMPT_STRATEGY_ENV, 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, serializeManagedRuntimeSettingsSnapshot, normalizeInternalProviderImplementationOverrides, } from './compatibility-gates.js';
10
+ import { CLAUDE_PROVIDER_V2_COMPATIBILITY_GATE, COMPATIBILITY_GATES_ENV, CURRENT_AGENTS_HOST_PACKAGE_VERSION, createManagedRuntimeSettingsFingerprint, INTERNAL_CLAUDE_PROMPT_STRATEGY_ENV, 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, serializeManagedRuntimeSettingsSnapshot, normalizeInternalProviderImplementationOverrides, } from './compatibility-gates.js';
11
11
  import { DEFAULT_PROVIDER_COMMAND_CONFIG, hasLegacyClaudeOneShotArgs, loadConfigFromEnv, } from './config.js';
12
12
  import { loadLocalConfigGenerateSpec, materializeLocalConfig, parseGenerateConfigSpec, resolveLocalConfigLayout, } from './local-config.js';
13
13
  import { normalizeManagedRuntimeKey, resolveManagedBootstrapLockPath, resolveManagedDaemonEndpoint, resolveManagedDaemonLogPath, resolveManagedRuntimeRoot, resolveManagedStateRoot, resolveManagedRuntimeSettingsPath, } from './state-paths.js';
@@ -163,6 +163,7 @@ function normalizeManagedRuntimeSettingsSnapshot(snapshot) {
163
163
  providerImplementationOverrides,
164
164
  internalPolicyMode: snapshot.internalPolicyMode,
165
165
  projectionStrategy: snapshot.projectionStrategy ?? DEFAULT_PROJECTION_STRATEGY,
166
+ packageVersion: CURRENT_AGENTS_HOST_PACKAGE_VERSION,
166
167
  };
167
168
  }
168
169
  function parseManagedRuntimeSettingsSnapshotRecord(value, sourceLabel) {
@@ -197,6 +198,9 @@ function parseManagedRuntimeSettingsSnapshotRecord(value, sourceLabel) {
197
198
  providerImplementationOverrides: [...record.providerImplementationOverrides],
198
199
  internalPolicyMode: record.internalPolicyMode,
199
200
  projectionStrategy: projectionStrategy ?? DEFAULT_PROJECTION_STRATEGY,
201
+ packageVersion: typeof record.packageVersion === 'string' && record.packageVersion.trim().length > 0
202
+ ? record.packageVersion
203
+ : CURRENT_AGENTS_HOST_PACKAGE_VERSION,
200
204
  });
201
205
  }
202
206
  async function loadManagedRuntimeSettingsSnapshot(rootPath) {
@@ -3777,7 +3777,6 @@ var BppTransport = class {
3777
3777
  // send issued during a reconnect never races ahead of the handshake.
3778
3778
  online = false;
3779
3779
  terminal = false;
3780
- inboundFailed = false;
3781
3780
  inboundChain = Promise.resolve();
3782
3781
  configChain = Promise.resolve();
3783
3782
  configAcks = /* @__PURE__ */ new Map();
@@ -3941,8 +3940,15 @@ var BppTransport = class {
3941
3940
  }
3942
3941
  });
3943
3942
  ws.on("close", (code, reason) => {
3943
+ const closeReason = reason?.toString("utf8") ?? "";
3944
3944
  connectionAbort.abort();
3945
3945
  const wasCurrent = this.ws === ws;
3946
+ const closeSummary = `code=${code} reason=${closeReason || "<none>"} current=${wasCurrent}`;
3947
+ if (code === 1e3 || this.closed) {
3948
+ this.logger?.info("bpp.ws_closed", closeSummary);
3949
+ } else {
3950
+ this.logger?.warn("bpp.ws_closed", closeSummary);
3951
+ }
3946
3952
  if (wasCurrent) {
3947
3953
  this.online = false;
3948
3954
  this.stopHeartbeat();
@@ -3952,7 +3958,7 @@ var BppTransport = class {
3952
3958
  if (authFailed && wasCurrent) {
3953
3959
  this.closed = true;
3954
3960
  this.terminal = true;
3955
- this.logger?.error("bpp.auth_failed", `server closed with ${WS_CLOSE_AUTH_FAILED} (authentication failed); not reconnecting. reason=${reason?.toString("utf8") ?? ""}`);
3961
+ this.logger?.error("bpp.auth_failed", `server closed with ${WS_CLOSE_AUTH_FAILED} (authentication failed); not reconnecting. reason=${closeReason}`);
3956
3962
  this.handlers?.onStateChange({ status: "error", reason: "api_key_invalid" });
3957
3963
  }
3958
3964
  if (wasCurrent) {
@@ -3998,6 +4004,7 @@ var BppTransport = class {
3998
4004
  }
3999
4005
  this.startHeartbeat();
4000
4006
  this.goOnline(ws);
4007
+ this.logger?.info("bpp.connected");
4001
4008
  resolve();
4002
4009
  } catch (error) {
4003
4010
  const failure = error instanceof BorgeeError ? error : new BorgeeError(`agent identity handshake failed: ${String(error)}`, "bpp.identity_unresolved");
@@ -4011,6 +4018,7 @@ var BppTransport = class {
4011
4018
  this.reconnectAttempt += 1;
4012
4019
  const delay = Math.min(this.reconnectBaseMs * 2 ** (this.reconnectAttempt - 1), this.reconnectMaxMs);
4013
4020
  this.handlers?.onStateChange({ status: "reconnecting", attempt: this.reconnectAttempt });
4021
+ this.logger?.info("bpp.reconnect_scheduled", `attempt=${this.reconnectAttempt} delay_ms=${delay}`);
4014
4022
  this.reconnectTimer = setTimeout(() => {
4015
4023
  this.reconnectTimer = void 0;
4016
4024
  if (this.closed)
@@ -4080,12 +4088,11 @@ var BppTransport = class {
4080
4088
  }
4081
4089
  handleInbound(frame) {
4082
4090
  this.inboundChain = this.inboundChain.then(() => {
4083
- if (this.inboundFailed)
4084
- return;
4085
- this.handlers?.onInbound(mapInbound(frame));
4086
- }).catch((error) => {
4087
- this.inboundFailed = true;
4088
- this.failTerminal(this.inboundFailure(error), this.ws);
4091
+ try {
4092
+ this.handlers?.onInbound(mapInbound(frame));
4093
+ } catch (error) {
4094
+ this.logger?.error("bpp.inbound_dispatch_failed", this.inboundFailure(error));
4095
+ }
4089
4096
  });
4090
4097
  }
4091
4098
  handleActionResult(frame) {
@@ -4380,6 +4387,7 @@ var BppTransport = class {
4380
4387
  failTerminal(error, ws) {
4381
4388
  if (this.terminal)
4382
4389
  return;
4390
+ this.logger?.error("bpp.terminal_failure", error);
4383
4391
  this.terminal = true;
4384
4392
  this.online = false;
4385
4393
  this.handlers?.onStateChange({ status: "error", reason: "unknown", code: error.code });
@@ -5051,8 +5059,13 @@ var Client = class {
5051
5059
  this.t.setStopTurnHandler?.(handler);
5052
5060
  }
5053
5061
  emit(event, payload) {
5054
- for (const h of this.listeners[event])
5055
- h(payload);
5062
+ for (const h of this.listeners[event]) {
5063
+ try {
5064
+ h(payload);
5065
+ } catch (error) {
5066
+ this.logger.error("client.observer_failed", { event, error });
5067
+ }
5068
+ }
5056
5069
  }
5057
5070
  handlers() {
5058
5071
  return {