@borgee/agents-host 0.2.97 → 0.2.101

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.
Files changed (35) hide show
  1. package/dist/agents-host.d.ts +8 -0
  2. package/dist/agents-host.js +258 -64
  3. package/dist/chat/chat-control-plane.d.ts +13 -3
  4. package/dist/chat/sdk-chat-control-plane.d.ts +30 -5
  5. package/dist/chat/sdk-chat-control-plane.js +131 -3
  6. package/dist/context/skill-manual.js +1 -1
  7. package/dist/execution-telemetry.d.ts +96 -0
  8. package/dist/execution-telemetry.js +583 -0
  9. package/dist/gateway/channel-file-workspace.d.ts +15 -0
  10. package/dist/gateway/channel-file-workspace.js +130 -0
  11. package/dist/gateway/localhost-gateway.js +140 -0
  12. package/dist/plugin-sdk.js +72 -3
  13. package/dist/plugin-sdk.js.map +2 -2
  14. package/dist/policy/gateway-authorization.d.ts +1 -1
  15. package/dist/policy/gateway-authorization.js +22 -2
  16. package/dist/providers/claude/adapter.js +1 -0
  17. package/dist/providers/claude/cli-client.js +4 -0
  18. package/dist/providers/codex/adapter.js +1 -0
  19. package/dist/providers/codex/cli-client.js +5 -0
  20. package/dist/providers/copilot/adapter.js +4 -0
  21. package/dist/providers/copilot/cli-client.js +11 -1
  22. package/dist/providers/copilot/sdk-session.d.ts +12 -3
  23. package/dist/providers/copilot/sdk-session.js +222 -5
  24. package/dist/providers/create-provider.js +4 -0
  25. package/dist/providers/prompt-usage.d.ts +8 -0
  26. package/dist/providers/prompt-usage.js +52 -0
  27. package/dist/state-paths.d.ts +2 -0
  28. package/dist/state-paths.js +6 -0
  29. package/dist/types.d.ts +41 -0
  30. package/dist/typing-lease.d.ts +14 -0
  31. package/dist/typing-lease.js +31 -0
  32. package/package.json +2 -2
  33. package/skills/borgee-agent/SKILL.md +12 -4
  34. package/skills/borgee-agent/scripts/borgee-agent.mjs +89 -0
  35. package/skills/borgee-agent/scripts/borgee-agent.py +90 -0
@@ -1,5 +1,6 @@
1
1
  import type { ChatControlPlane } from './chat/chat-control-plane.js';
2
2
  import { type AgentsHostRuntimeOptions } from './debug.js';
3
+ import { ExecutionDeliveryJournal } from './execution-telemetry.js';
3
4
  import { type ProviderAdapter } from './providers/provider-adapter.js';
4
5
  import type { AgentsHostConfig } from './types.js';
5
6
  /**
@@ -13,6 +14,7 @@ export declare class AgentsHost {
13
14
  private readonly borgee;
14
15
  private readonly backgroundRuns;
15
16
  private readonly logger;
17
+ private readonly executionDeliveryJournal;
16
18
  private readonly contextInjectionEnabled;
17
19
  private readonly collaborationEnabled;
18
20
  private readonly collaborationOutcomeModelEnabled;
@@ -20,6 +22,8 @@ export declare class AgentsHost {
20
22
  private readonly taskThreadCollaborationContractEnabled;
21
23
  private readonly collaborationCapabilitiesDiagnosticsEnabled;
22
24
  private readonly activeTurns;
25
+ private readonly executionTelemetryByExecutionId;
26
+ private readonly executionDispatches;
23
27
  private readonly awaitingUserByChannel;
24
28
  private readonly collaborationOutcomeByChannel;
25
29
  /**
@@ -49,6 +53,7 @@ export declare class AgentsHost {
49
53
  private controlPlaneClosed;
50
54
  constructor(config: AgentsHostConfig, deps?: {
51
55
  borgee?: ChatControlPlane;
56
+ executionDeliveryJournal?: ExecutionDeliveryJournal;
52
57
  provider?: ProviderAdapter;
53
58
  runtimeOptions?: AgentsHostRuntimeOptions;
54
59
  });
@@ -56,6 +61,9 @@ export declare class AgentsHost {
56
61
  stop(): Promise<void>;
57
62
  private trackActiveTurn;
58
63
  private dispatchMessage;
64
+ private runClaimedExecution;
65
+ private runMessageWithExecutionTelemetry;
66
+ private createExecutionTelemetryReporter;
59
67
  private dispatchAutonomousReply;
60
68
  private handleLegacyMessage;
61
69
  private handleCollaborationMessage;
@@ -5,6 +5,8 @@ import { SdkChatControlPlane } from './chat/sdk-chat-control-plane.js';
5
5
  import { HostLogger, summarizeError } from './debug.js';
6
6
  import { toReportedActivity } from './progress-to-activity.js';
7
7
  import { BackgroundRunTracker } from './background-runs.js';
8
+ import { ExecutionDeliveryJournal, ExecutionTelemetryReporter, } from './execution-telemetry.js';
9
+ import { startTypingLease } from './typing-lease.js';
8
10
  import { ATTENTION_FOLLOW_SEMANTICS_COMPATIBILITY_GATE, COLLABORATION_CAPABILITIES_DIAGNOSTICS_COMPATIBILITY_GATE, COLLABORATION_OUTCOME_MODEL_COMPATIBILITY_GATE, COLLABORATION_SKILL_FIRST_COMPATIBILITY_GATE, CONTEXT_INJECTION_COMPATIBILITY_GATE, LOCALHOST_GATEWAY_COMPATIBILITY_GATE, POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE, resolveInternalPolicyMode, resolveInternalCompatibilityGates, SKILL_RUNTIME_COMPATIBILITY_GATE, TASK_THREAD_COLLABORATION_CONTRACT_COMPATIBILITY_GATE, TOKEN_BINDING_COMPATIBILITY_GATE, } from './compatibility-gates.js';
9
11
  import { buildNormalizedAttentionSnapshot, parsePersistedAttentionSnapshot, } from './context/attention.js';
10
12
  import { mergeCompactionSnapshot, parsePersistedCompactionSnapshot, } from './context/compaction.js';
@@ -14,7 +16,7 @@ import { extractTaskIdFromTaskAssignmentContent, resolveTaskAssignmentStatePath,
14
16
  import { AuthorizationAuditSink, } from './policy/authorization-audit.js';
15
17
  import { createProvider } from './providers/create-provider.js';
16
18
  import { ProviderTurnCancelledError } from './providers/provider-adapter.js';
17
- import { resolveAttentionStatePath, resolveCompactionStatePath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
19
+ import { resolveAttentionStatePath, resolveCompactionStatePath, resolveExecutionDeliveryJournalPath, resolveExecutionTelemetryOutboxPath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
18
20
  import { appendVisibleMention as appendVisibleBodyMention, extractVisibleMentionIds as extractCanonicalMentionIds, } from './visible-mentions.js';
19
21
  import { buildHostedIncomingContentText, buildHostedTurnContentParts, extractHostedMessageAttachments, normalizeHostedMessageAttachment, } from './hosted-turn-content.js';
20
22
  import { waitForTaskForThread } from './task-thread-resolution.js';
@@ -110,6 +112,12 @@ class DefaultHostRuntime {
110
112
  deps.borgee ??
111
113
  new SdkChatControlPlane(config.borgeeBaseUrl, config.agent.agentApiKey, undefined, {
112
114
  pluginId: `agents-host:${config.agent.provider}`,
115
+ executionTelemetryOutboxPath: resolveExecutionTelemetryOutboxPath(config.stateRootDir),
116
+ onExecutionTelemetryError: (error) => {
117
+ deps.logger.error('execution telemetry transport failed', {
118
+ error: summarizeError(error),
119
+ });
120
+ },
113
121
  });
114
122
  this.gateway = createLocalhostGatewayController({
115
123
  gateEnabled: localhostGatewayGateEnabled,
@@ -291,6 +299,7 @@ export class AgentsHost {
291
299
  borgee;
292
300
  backgroundRuns;
293
301
  logger;
302
+ executionDeliveryJournal;
294
303
  contextInjectionEnabled;
295
304
  collaborationEnabled;
296
305
  collaborationOutcomeModelEnabled;
@@ -298,6 +307,8 @@ export class AgentsHost {
298
307
  taskThreadCollaborationContractEnabled;
299
308
  collaborationCapabilitiesDiagnosticsEnabled;
300
309
  activeTurns = new Set();
310
+ executionTelemetryByExecutionId = new Map();
311
+ executionDispatches = new Map();
301
312
  awaitingUserByChannel = new Map();
302
313
  collaborationOutcomeByChannel = new Map();
303
314
  /**
@@ -328,6 +339,9 @@ export class AgentsHost {
328
339
  constructor(config, deps) {
329
340
  this.config = config;
330
341
  this.logger = new HostLogger(deps?.runtimeOptions);
342
+ this.executionDeliveryJournal =
343
+ deps?.executionDeliveryJournal ??
344
+ new ExecutionDeliveryJournal(resolveExecutionDeliveryJournalPath(config.stateRootDir));
331
345
  const compatibilityGates = resolveInternalCompatibilityGates();
332
346
  const contextInjectionGateEnabled = compatibilityGates.has(CONTEXT_INJECTION_COMPATIBILITY_GATE);
333
347
  this.contextInjectionEnabled = contextInjectionGateEnabled;
@@ -392,6 +406,7 @@ export class AgentsHost {
392
406
  const bufferedMessages = [];
393
407
  let bufferingMessages = true;
394
408
  try {
409
+ await this.executionDeliveryJournal.load();
395
410
  this.provider.onAutonomousReply?.((reply) => this.dispatchAutonomousReply(reply));
396
411
  await this.runtime.start((message) => {
397
412
  if (!this.started) {
@@ -471,16 +486,145 @@ export class AgentsHost {
471
486
  }
472
487
  trackActiveTurn(task) {
473
488
  this.activeTurns.add(task);
474
- void task.finally(() => {
489
+ void task
490
+ .catch((error) => {
491
+ this.logger.error('dispatched turn failed', {
492
+ error: summarizeError(error),
493
+ });
494
+ })
495
+ .finally(() => {
475
496
  this.activeTurns.delete(task);
476
497
  });
477
498
  }
478
499
  dispatchMessage(message) {
479
- const task = this.collaborationEnabled
500
+ const executionId = message.execution_grant?.executionId;
501
+ const existing = executionId
502
+ ? this.executionDispatches.get(executionId)
503
+ : undefined;
504
+ if (existing) {
505
+ this.trackActiveTurn(existing);
506
+ return;
507
+ }
508
+ const run = () => this.collaborationEnabled
480
509
  ? this.handleCollaborationMessage(message)
481
510
  : this.enqueueChannelTurn(message.channel_id, () => this.handleLegacyMessage(message));
511
+ const work = executionId
512
+ ? this.runClaimedExecution(message, run)
513
+ : this.runMessageWithExecutionTelemetry(message, run).then((result) => {
514
+ if (result.error)
515
+ throw result.error;
516
+ });
517
+ const task = executionId
518
+ ? work.finally(() => {
519
+ this.executionDispatches.delete(executionId);
520
+ })
521
+ : work;
522
+ if (executionId)
523
+ this.executionDispatches.set(executionId, task);
482
524
  this.trackActiveTurn(task);
483
525
  }
526
+ async runClaimedExecution(message, run) {
527
+ const grant = message.execution_grant;
528
+ const claim = await this.executionDeliveryJournal.claim(grant.executionId);
529
+ if (claim === 'completed')
530
+ return;
531
+ if (claim === 'recovering') {
532
+ if (this.borgee.reportExecutionTelemetry) {
533
+ let reporter = this.executionTelemetryByExecutionId.get(grant.executionId);
534
+ const restoredReporter = !reporter;
535
+ reporter ??= this.createExecutionTelemetryReporter(grant);
536
+ this.executionTelemetryByExecutionId.set(grant.executionId, reporter);
537
+ await reporter.start();
538
+ if (restoredReporter) {
539
+ const persistedUsage = await this.executionDeliveryJournal.readUsage(grant.executionId);
540
+ if (persistedUsage) {
541
+ const { provider, ...usage } = persistedUsage;
542
+ await reporter.usage(provider, usage);
543
+ }
544
+ }
545
+ reporter.noteFailure();
546
+ let finishError;
547
+ try {
548
+ await reporter.finish(false);
549
+ }
550
+ catch (error) {
551
+ finishError = error;
552
+ }
553
+ if (reporter.allRequiredFramesDurable) {
554
+ await this.executionDeliveryJournal.complete(grant.executionId);
555
+ this.executionTelemetryByExecutionId.delete(grant.executionId);
556
+ }
557
+ if (finishError)
558
+ throw finishError;
559
+ return;
560
+ }
561
+ throw new Error('Execution telemetry is unavailable for interrupted delivery recovery');
562
+ }
563
+ const result = await this.runMessageWithExecutionTelemetry(message, run);
564
+ if (result.allRequiredFramesDurable) {
565
+ await this.executionDeliveryJournal.complete(grant.executionId);
566
+ }
567
+ if (result.error)
568
+ throw result.error;
569
+ }
570
+ async runMessageWithExecutionTelemetry(message, run) {
571
+ const grant = message.execution_grant;
572
+ const reporter = grant && this.borgee.reportExecutionTelemetry
573
+ ? this.executionTelemetryByExecutionId.get(grant.executionId) ??
574
+ this.createExecutionTelemetryReporter(grant)
575
+ : undefined;
576
+ if (grant && !reporter) {
577
+ return {
578
+ allRequiredFramesDurable: false,
579
+ error: new Error('Execution telemetry is unavailable for admitted delivery'),
580
+ };
581
+ }
582
+ if (grant && reporter) {
583
+ this.executionTelemetryByExecutionId.set(grant.executionId, reporter);
584
+ }
585
+ let runError;
586
+ try {
587
+ await run();
588
+ }
589
+ catch (error) {
590
+ reporter?.noteFailure();
591
+ runError = error;
592
+ }
593
+ let finishError;
594
+ if (reporter) {
595
+ try {
596
+ await reporter.finish();
597
+ }
598
+ catch (error) {
599
+ finishError = error;
600
+ this.logger.error('execution telemetry finish failed', {
601
+ error: summarizeError(error),
602
+ });
603
+ }
604
+ finally {
605
+ if (reporter.allRequiredFramesDurable &&
606
+ grant &&
607
+ this.executionTelemetryByExecutionId.get(grant.executionId) === reporter) {
608
+ this.executionTelemetryByExecutionId.delete(grant.executionId);
609
+ }
610
+ }
611
+ }
612
+ const error = runError && finishError
613
+ ? new AggregateError([runError, finishError], 'Execution and telemetry finalization both failed')
614
+ : runError ?? finishError;
615
+ return {
616
+ allRequiredFramesDurable: reporter?.allRequiredFramesDurable ?? grant === undefined,
617
+ ...(error ? { error } : {}),
618
+ };
619
+ }
620
+ createExecutionTelemetryReporter(grant) {
621
+ return new ExecutionTelemetryReporter(grant, {
622
+ reportExecutionTelemetry: (frame) => this.borgee.reportExecutionTelemetry(frame),
623
+ }, Date.now, (provider, usage) => this.executionDeliveryJournal.recordUsage(grant.executionId, {
624
+ provider,
625
+ ...usage,
626
+ }));
627
+ }
484
628
  dispatchAutonomousReply(reply) {
485
629
  if (!this.started
486
630
  || this.controlPlaneClosed
@@ -2322,17 +2466,6 @@ export class AgentsHost {
2322
2466
  ? 'silent-kickoff'
2323
2467
  : undefined;
2324
2468
  const turnExecutionId = activeTurn?.turnExecutionId ?? null;
2325
- const stopTyping = silentTurn ? () => { } : this.borgee.startTyping(channelId);
2326
- const collaborationGrounding = await this.buildCollaborationGrounding(authorId, options?.incomingAuthorKind, activeTurn, options?.kickoff);
2327
- const publishesCollaborationDraft = !silentTurn &&
2328
- !deferDelivery &&
2329
- this.collaborationEnabled &&
2330
- turnExecutionId != null &&
2331
- activeTurn?.protocol === undefined;
2332
- const supportsProviderProgress = this.provider.capabilities?.progressUpdates?.support === 'supported';
2333
- const supportsBackgroundRunLifecycle = this.provider.capabilities?.backgroundRuns?.lifecycle === 'supported';
2334
- const reportsProviderActivity = !silentTurn && supportsProviderProgress;
2335
- const observeProviderProgress = supportsProviderProgress || supportsBackgroundRunLifecycle;
2336
2469
  const turnContext = {
2337
2470
  eventType: msg.type,
2338
2471
  channelId,
@@ -2345,26 +2478,58 @@ export class AgentsHost {
2345
2478
  };
2346
2479
  const turnStartedAt = Date.now();
2347
2480
  this.logger.debug('starting turn', turnContext);
2481
+ const executionTelemetry = msg.execution_grant
2482
+ ? this.executionTelemetryByExecutionId.get(msg.execution_grant.executionId)
2483
+ : undefined;
2484
+ let executionSucceeded = false;
2485
+ let executionTelemetryStartFailed = false;
2348
2486
  let reply;
2349
2487
  let publicReplyText;
2350
2488
  let visibleReplyBody;
2351
- const taskThreadCollaborationContract = this.resolveTaskThreadCollaborationContractForTurn(msg, taskThreadContext);
2352
- const collaborationOutcome = this.collaborationOutcomeModelEnabled
2353
- ? this.collaborationOutcomeByChannel.get(channelId)
2354
- : undefined;
2355
- const missedCollaborationDiagnostic = this.resolveMissedCollaborationDiagnosticForTurn(channelId);
2356
- const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn(taskThreadContext);
2357
- const incomingParts = options?.incomingParts
2358
- ?? buildHostedTurnContentParts({
2359
- text: String(msg.content ?? msg.body ?? content),
2360
- attachments: msg.attachments,
2361
- });
2362
- const activityReporter = silentTurn
2363
- ? undefined
2364
- : this.beginTurnActivity(channelId, activeTurn?.generation);
2365
- await this.ensureCompactionSnapshotLoaded(channelId);
2366
- const compactionSnapshot = this.compactionSnapshotByChannel.get(channelId);
2489
+ let activityReporter;
2490
+ let typingLease;
2367
2491
  try {
2492
+ if (!silentTurn) {
2493
+ typingLease = startTypingLease(() => this.borgee.reportTyping(channelId));
2494
+ }
2495
+ const collaborationGrounding = await this.buildCollaborationGrounding(authorId, options?.incomingAuthorKind, activeTurn, options?.kickoff);
2496
+ const publishesCollaborationDraft = !silentTurn &&
2497
+ !deferDelivery &&
2498
+ this.collaborationEnabled &&
2499
+ turnExecutionId != null &&
2500
+ activeTurn?.protocol === undefined;
2501
+ const supportsProviderProgress = this.provider.capabilities?.progressUpdates?.support === 'supported';
2502
+ const supportsBackgroundRunLifecycle = this.provider.capabilities?.backgroundRuns?.lifecycle === 'supported';
2503
+ const reportsProviderActivity = !silentTurn && supportsProviderProgress;
2504
+ const observeProviderProgress = supportsProviderProgress || supportsBackgroundRunLifecycle;
2505
+ const taskThreadCollaborationContract = this.resolveTaskThreadCollaborationContractForTurn(msg, taskThreadContext);
2506
+ const collaborationOutcome = this.collaborationOutcomeModelEnabled
2507
+ ? this.collaborationOutcomeByChannel.get(channelId)
2508
+ : undefined;
2509
+ const missedCollaborationDiagnostic = this.resolveMissedCollaborationDiagnosticForTurn(channelId);
2510
+ const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn(taskThreadContext);
2511
+ const incomingParts = options?.incomingParts
2512
+ ?? buildHostedTurnContentParts({
2513
+ text: String(msg.content ?? msg.body ?? content),
2514
+ attachments: msg.attachments,
2515
+ });
2516
+ activityReporter = silentTurn
2517
+ ? undefined
2518
+ : this.beginTurnActivity(channelId, activeTurn?.generation);
2519
+ await this.ensureCompactionSnapshotLoaded(channelId);
2520
+ const compactionSnapshot = this.compactionSnapshotByChannel.get(channelId);
2521
+ if (executionTelemetry) {
2522
+ try {
2523
+ await executionTelemetry.start();
2524
+ }
2525
+ catch (error) {
2526
+ executionTelemetryStartFailed = true;
2527
+ this.logger.error('execution telemetry start failed', {
2528
+ error: summarizeError(error),
2529
+ });
2530
+ throw error;
2531
+ }
2532
+ }
2368
2533
  reply = await this.provider.generateReply({
2369
2534
  agentName: this.config.agent.agentName,
2370
2535
  provider: this.config.agent.provider,
@@ -2405,42 +2570,61 @@ export class AgentsHost {
2405
2570
  : { taskAssignmentContextOverridePersistence: 'ephemeral' }),
2406
2571
  }
2407
2572
  : {}),
2408
- }, observeProviderProgress
2573
+ }, observeProviderProgress || executionTelemetry
2409
2574
  ? {
2410
- onProgress: (update) => {
2411
- if (supportsBackgroundRunLifecycle) {
2412
- this.backgroundRuns.observe(channelId, update);
2413
- if (update.type === 'activity' && update.activity.background === true) {
2414
- this.borgee.reportActivity?.({
2415
- channelId,
2416
- activity: { shape: 'activity', ...update.activity },
2417
- });
2418
- return;
2419
- }
2420
- }
2421
- if (!this.canAcceptTurnOutput(channelId, activeTurn?.generation)) {
2422
- return;
2423
- }
2424
- if (supportsProviderProgress && update.type === 'compaction') {
2425
- this.noteCompactionProgress(channelId, update.compaction, {
2426
- turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
2427
- });
2428
- return;
2575
+ ...(executionTelemetry
2576
+ ? {
2577
+ onUsage: (usage) => {
2578
+ void executionTelemetry.usage(this.config.agent.provider, usage);
2579
+ },
2580
+ onExecutionContinuation: (completion) => {
2581
+ executionTelemetry.hold(completion);
2582
+ },
2429
2583
  }
2430
- const reportedActivity = toReportedActivity(update);
2431
- if (reportsProviderActivity && reportedActivity) {
2432
- activityReporter?.progress(reportedActivity);
2433
- }
2434
- if (!supportsProviderProgress
2435
- || update.type !== 'text'
2436
- || update.stream !== 'answer') {
2437
- return;
2438
- }
2439
- if (!publishesCollaborationDraft) {
2440
- return;
2584
+ : {}),
2585
+ ...(observeProviderProgress
2586
+ ? {
2587
+ onProgress: (update) => {
2588
+ if (supportsBackgroundRunLifecycle) {
2589
+ this.backgroundRuns.observe(channelId, update);
2590
+ if (update.type === 'activity' && update.activity.background === true) {
2591
+ this.borgee.reportActivity?.({
2592
+ channelId,
2593
+ activity: { shape: 'activity', ...update.activity },
2594
+ });
2595
+ return;
2596
+ }
2597
+ }
2598
+ if (!this.canAcceptTurnOutput(channelId, activeTurn?.generation)) {
2599
+ return;
2600
+ }
2601
+ if (update.type === 'text'
2602
+ && update.stream === 'answer'
2603
+ && hasVisibleText(update.text)) {
2604
+ typingLease?.renew();
2605
+ }
2606
+ if (supportsProviderProgress && update.type === 'compaction') {
2607
+ this.noteCompactionProgress(channelId, update.compaction, {
2608
+ turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
2609
+ });
2610
+ return;
2611
+ }
2612
+ const reportedActivity = toReportedActivity(update);
2613
+ if (reportsProviderActivity && reportedActivity) {
2614
+ activityReporter?.progress(reportedActivity);
2615
+ }
2616
+ if (!supportsProviderProgress ||
2617
+ update.type !== 'text' ||
2618
+ update.stream !== 'answer') {
2619
+ return;
2620
+ }
2621
+ if (!publishesCollaborationDraft) {
2622
+ return;
2623
+ }
2624
+ this.publishCollaborationDraft(channelId, activeTurn, update.text);
2625
+ },
2441
2626
  }
2442
- this.publishCollaborationDraft(channelId, activeTurn, update.text);
2443
- },
2627
+ : {}),
2444
2628
  }
2445
2629
  : undefined);
2446
2630
  publicReplyText = resolvePublicReplyText(reply);
@@ -2468,6 +2652,7 @@ export class AgentsHost {
2468
2652
  return { reply, stale: true, completed: false };
2469
2653
  }
2470
2654
  if (silentTurn) {
2655
+ executionSucceeded = true;
2471
2656
  this.logger.debug('completed turn', {
2472
2657
  ...turnContext,
2473
2658
  durationMs: Date.now() - turnStartedAt,
@@ -2488,6 +2673,7 @@ export class AgentsHost {
2488
2673
  };
2489
2674
  }
2490
2675
  if (deferDelivery) {
2676
+ executionSucceeded = true;
2491
2677
  this.logger.debug('completed turn', {
2492
2678
  ...turnContext,
2493
2679
  durationMs: Date.now() - turnStartedAt,
@@ -2524,6 +2710,7 @@ export class AgentsHost {
2524
2710
  if (publishesCollaborationDraft) {
2525
2711
  this.finalizeCollaborationDraft(channelId, activeTurn, posted.messageId, visibleReplyBody);
2526
2712
  }
2713
+ executionSucceeded = true;
2527
2714
  return {
2528
2715
  reply,
2529
2716
  deliveredMessageId: posted.messageId,
@@ -2547,6 +2734,7 @@ export class AgentsHost {
2547
2734
  turnExecutionId: activeTurn.turnExecutionId,
2548
2735
  });
2549
2736
  }
2737
+ executionSucceeded = true;
2550
2738
  return {
2551
2739
  reply,
2552
2740
  deliveredMessageId: null,
@@ -2565,6 +2753,9 @@ export class AgentsHost {
2565
2753
  turnExecutionId: activeTurn.turnExecutionId,
2566
2754
  });
2567
2755
  }
2756
+ if (executionTelemetryStartFailed) {
2757
+ throw error;
2758
+ }
2568
2759
  if (error instanceof ProviderTurnCancelledError) {
2569
2760
  const cancelledReplyText = error.publicReplyText?.trim();
2570
2761
  if (!silentTurn &&
@@ -2642,7 +2833,10 @@ export class AgentsHost {
2642
2833
  };
2643
2834
  }
2644
2835
  finally {
2645
- stopTyping();
2836
+ if (executionTelemetry) {
2837
+ executionTelemetry.noteAttempt(executionSucceeded);
2838
+ }
2839
+ typingLease?.stop();
2646
2840
  activityReporter?.end();
2647
2841
  }
2648
2842
  }
@@ -1,5 +1,5 @@
1
- import type { AgentActivity, ReportTaskFinishedInput, ReportTurnActivityInput, StopBackgroundRunHandler, StopTurnHandler, TurnActivityReporter } from '../plugin-sdk.js';
2
- import type { ChannelSummary, ChannelHistoryEntry, ChannelMessageEvent, CreateTaskInput, DirectoryUser, MeResponseUser, PostMessageInput, PostedMessage, ReadChannelHistoryInput, Task, UpdateTaskInput } from '../types.js';
1
+ import type { AgentActivity, ExecutionTelemetryFrame, ReportTaskFinishedInput, ReportTurnActivityInput, StopBackgroundRunHandler, StopTurnHandler, TurnActivityReporter } from '../plugin-sdk.js';
2
+ import type { ChannelSummary, ChannelFileEntry, ChannelFileContent, PublishChannelFileInput, ChannelHistoryEntry, ChannelMessageEvent, CreateTaskInput, DirectoryUser, MeResponseUser, PostMessageInput, PostedMessage, ReadChannelHistoryInput, Task, UpdateTaskInput } from '../types.js';
3
3
  export interface ChatControlPlane {
4
4
  onStopTurn?(handler: StopTurnHandler | undefined): void;
5
5
  onStopBackgroundRun?(handler: StopBackgroundRunHandler | undefined): void;
@@ -9,9 +9,10 @@ export interface ChatControlPlane {
9
9
  postMessage(input: PostMessageInput): Promise<PostedMessage>;
10
10
  editMessage(messageId: string, content: string): Promise<void>;
11
11
  deleteMessage(messageId: string): Promise<void>;
12
- startTyping(channelId: string): () => void;
12
+ reportTyping(channelId: string): void;
13
13
  /** Opens one turn's report on the activity rail. Fire-and-forget: nothing recovers what it sends. */
14
14
  reportTurnActivity(input: ReportTurnActivityInput): TurnActivityReporter;
15
+ reportExecutionTelemetry?(frame: ExecutionTelemetryFrame): Promise<void>;
15
16
  reportTaskFinished?(input: ReportTaskFinishedInput): void;
16
17
  reportActivity?(input: {
17
18
  channelId: string;
@@ -19,6 +20,15 @@ export interface ChatControlPlane {
19
20
  }): void;
20
21
  getMe(): Promise<MeResponseUser>;
21
22
  listUsers(): Promise<DirectoryUser[]>;
23
+ listChannelFiles(input: {
24
+ channelId: string;
25
+ }): Promise<ChannelFileEntry[]>;
26
+ readChannelFile(input: {
27
+ channelId: string;
28
+ path: string;
29
+ textOnly: boolean;
30
+ }): Promise<ChannelFileContent>;
31
+ publishChannelFile(input: PublishChannelFileInput): Promise<ChannelFileEntry>;
22
32
  listChannels(): Promise<ChannelSummary[]>;
23
33
  readChannelHistory(input: ReadChannelHistoryInput): Promise<ChannelHistoryEntry[]>;
24
34
  createTask(input: CreateTaskInput): Promise<Task>;
@@ -1,9 +1,15 @@
1
- import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent, type ReportTaskFinishedInput, type ReportTurnActivityInput, type StopTurnHandler, type StopBackgroundRunHandler, type TurnActivityReporter } from '../plugin-sdk.js';
2
- import type { ChannelSummary, ChannelHistoryEntry, ChannelMessageEvent, CreateTaskInput, DirectoryUser, MeResponseUser, PostMessageInput, PostedMessage, ReadChannelHistoryInput, Task, UpdateTaskInput } from '../types.js';
1
+ import { type BorgeePluginClient, type BorgeePluginOptions, type ExecutionTelemetryFrame, type InboundMessageEvent, type ReportTaskFinishedInput, type ReportTurnActivityInput, type StopTurnHandler, type StopBackgroundRunHandler, type TurnActivityReporter } from '../plugin-sdk.js';
2
+ import { type ExecutionTelemetryOutboxPolicy } from '../execution-telemetry.js';
3
+ import type { ChannelSummary, ChannelFileEntry, ChannelFileContent, PublishChannelFileInput, ChannelHistoryEntry, ChannelMessageEvent, CreateTaskInput, DirectoryUser, MeResponseUser, PostMessageInput, PostedMessage, ReadChannelHistoryInput, Task, UpdateTaskInput } from '../types.js';
3
4
  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' | 'onStopTurn' | 'onStopBackgroundRun' | 'reportActivity' | 'readHistory' | 'reportTurnActivity' | 'sendMessage' | 'startTyping' | 'updateTask' | 'setTaskProperty' | 'deleteTaskProperty'> & Partial<Pick<BorgeePluginClient, 'reportTaskFinished'>>;
5
+ type PluginClientLike = Pick<BorgeePluginClient, 'agentId' | 'close' | 'connect' | 'createTask' | 'deleteMessage' | 'editMessage' | 'getMe' | 'getTask' | 'listChannelFiles' | 'readChannelFile' | 'publishChannelFile' | 'listChannels' | 'listTasks' | 'listUsers' | 'on' | 'onStopTurn' | 'onStopBackgroundRun' | 'reportActivity' | 'readHistory' | 'reportTurnActivity' | 'reportTyping' | 'sendMessage' | 'updateTask' | 'setTaskProperty' | 'deleteTaskProperty'> & Partial<Pick<BorgeePluginClient, 'connectionState' | 'reportExecutionTelemetry' | 'reportTaskFinished'>>;
5
6
  type PluginClientFactory = (options: BorgeePluginOptions) => PluginClientLike;
6
- type SdkChatControlPlaneOptions = Pick<BorgeePluginOptions, 'pluginId'>;
7
+ type SdkChatControlPlaneOptions = Pick<BorgeePluginOptions, 'pluginId'> & {
8
+ executionTelemetryCapacityWaitMs?: number;
9
+ executionTelemetryOutboxPath?: string;
10
+ executionTelemetryOutboxPolicy?: Partial<ExecutionTelemetryOutboxPolicy>;
11
+ onExecutionTelemetryError?: (error: unknown) => void;
12
+ };
7
13
  /**
8
14
  * Thin adapter over `@borgee/plugin-sdk` (the same BPP/`/ws/plugin` SDK used by
9
15
  * the OpenClaw plugin) that implements the minimal `ChatControlPlane` surface
@@ -14,8 +20,14 @@ export declare class SdkChatControlPlane implements ChatControlPlane {
14
20
  private pendingMessages;
15
21
  private unsubscribe;
16
22
  private stateUnsubscribe;
23
+ private telemetryAckUnsubscribe;
24
+ private executionTelemetryRetry?;
17
25
  private activityTransportReady;
26
+ private readonly executionTelemetryOutbox;
27
+ private readonly onExecutionTelemetryError;
18
28
  private connected;
29
+ private transportOnline;
30
+ private readonly executionTelemetryCapacityWaitMs;
19
31
  private me;
20
32
  constructor(baseUrl: string, apiKey: string, createClient?: PluginClientFactory, options?: SdkChatControlPlaneOptions);
21
33
  onStopTurn(handler: StopTurnHandler | undefined): void;
@@ -28,10 +40,23 @@ export declare class SdkChatControlPlane implements ChatControlPlane {
28
40
  reportTaskFinished(input: ReportTaskFinishedInput): void;
29
41
  editMessage(messageId: string, content: string): Promise<void>;
30
42
  deleteMessage(messageId: string): Promise<void>;
31
- startTyping(channelId: string): () => void;
43
+ reportTyping(channelId: string): void;
32
44
  reportTurnActivity(input: ReportTurnActivityInput): TurnActivityReporter;
45
+ reportExecutionTelemetry(frame: ExecutionTelemetryFrame): Promise<void>;
46
+ private flushExecutionTelemetry;
47
+ private scheduleExecutionTelemetryRetry;
48
+ private cancelExecutionTelemetryRetry;
33
49
  getMe(): Promise<MeResponseUser>;
34
50
  listUsers(): Promise<DirectoryUser[]>;
51
+ listChannelFiles(input: {
52
+ channelId: string;
53
+ }): Promise<ChannelFileEntry[]>;
54
+ readChannelFile(input: {
55
+ channelId: string;
56
+ path: string;
57
+ textOnly: boolean;
58
+ }): Promise<ChannelFileContent>;
59
+ publishChannelFile(input: PublishChannelFileInput): Promise<ChannelFileEntry>;
35
60
  readChannelHistory(input: ReadChannelHistoryInput): Promise<ChannelHistoryEntry[]>;
36
61
  listChannels(): Promise<ChannelSummary[]>;
37
62
  createTask(input: CreateTaskInput): Promise<Task>;