@borgee/agents-host 0.2.94 → 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 (40) hide show
  1. package/dist/agents-host.d.ts +8 -0
  2. package/dist/agents-host.js +266 -64
  3. package/dist/background-runs.d.ts +4 -0
  4. package/dist/background-runs.js +8 -4
  5. package/dist/chat/chat-control-plane.d.ts +14 -3
  6. package/dist/chat/sdk-chat-control-plane.d.ts +31 -5
  7. package/dist/chat/sdk-chat-control-plane.js +134 -3
  8. package/dist/context/skill-manual.js +1 -1
  9. package/dist/execution-telemetry.d.ts +96 -0
  10. package/dist/execution-telemetry.js +583 -0
  11. package/dist/gateway/channel-file-workspace.d.ts +15 -0
  12. package/dist/gateway/channel-file-workspace.js +130 -0
  13. package/dist/gateway/localhost-gateway.js +140 -0
  14. package/dist/plugin-sdk.js +85 -4
  15. package/dist/plugin-sdk.js.map +2 -2
  16. package/dist/policy/gateway-authorization.d.ts +1 -1
  17. package/dist/policy/gateway-authorization.js +22 -2
  18. package/dist/progress-to-activity.d.ts +3 -3
  19. package/dist/providers/claude/adapter.d.ts +2 -1
  20. package/dist/providers/claude/adapter.js +11 -0
  21. package/dist/providers/claude/cli-client.d.ts +3 -1
  22. package/dist/providers/claude/cli-client.js +267 -13
  23. package/dist/providers/codex/adapter.js +1 -0
  24. package/dist/providers/codex/cli-client.js +5 -0
  25. package/dist/providers/copilot/adapter.js +4 -0
  26. package/dist/providers/copilot/cli-client.js +11 -1
  27. package/dist/providers/copilot/sdk-session.d.ts +12 -3
  28. package/dist/providers/copilot/sdk-session.js +222 -5
  29. package/dist/providers/create-provider.js +4 -0
  30. package/dist/providers/prompt-usage.d.ts +8 -0
  31. package/dist/providers/prompt-usage.js +52 -0
  32. package/dist/state-paths.d.ts +2 -0
  33. package/dist/state-paths.js +6 -0
  34. package/dist/types.d.ts +41 -0
  35. package/dist/typing-lease.d.ts +14 -0
  36. package/dist/typing-lease.js +31 -0
  37. package/package.json +9 -9
  38. package/skills/borgee-agent/SKILL.md +12 -4
  39. package/skills/borgee-agent/scripts/borgee-agent.mjs +89 -0
  40. 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;
@@ -368,6 +382,14 @@ export class AgentsHost {
368
382
  cancel: this.provider.cancelBackgroundRun
369
383
  ? (channelId, providerRunId) => (this.provider.cancelBackgroundRun(channelId, providerRunId))
370
384
  : undefined,
385
+ onFinished: (channelId, run) => {
386
+ this.borgee.reportTaskFinished?.({
387
+ channelId,
388
+ taskId: run.runId,
389
+ outcome: run.state,
390
+ ...(run.state === 'failed' ? { reason: 'unknown' } : {}),
391
+ });
392
+ },
371
393
  });
372
394
  }
373
395
  async start() {
@@ -384,6 +406,7 @@ export class AgentsHost {
384
406
  const bufferedMessages = [];
385
407
  let bufferingMessages = true;
386
408
  try {
409
+ await this.executionDeliveryJournal.load();
387
410
  this.provider.onAutonomousReply?.((reply) => this.dispatchAutonomousReply(reply));
388
411
  await this.runtime.start((message) => {
389
412
  if (!this.started) {
@@ -463,16 +486,145 @@ export class AgentsHost {
463
486
  }
464
487
  trackActiveTurn(task) {
465
488
  this.activeTurns.add(task);
466
- void task.finally(() => {
489
+ void task
490
+ .catch((error) => {
491
+ this.logger.error('dispatched turn failed', {
492
+ error: summarizeError(error),
493
+ });
494
+ })
495
+ .finally(() => {
467
496
  this.activeTurns.delete(task);
468
497
  });
469
498
  }
470
499
  dispatchMessage(message) {
471
- 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
472
509
  ? this.handleCollaborationMessage(message)
473
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);
474
524
  this.trackActiveTurn(task);
475
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
+ }
476
628
  dispatchAutonomousReply(reply) {
477
629
  if (!this.started
478
630
  || this.controlPlaneClosed
@@ -2314,17 +2466,6 @@ export class AgentsHost {
2314
2466
  ? 'silent-kickoff'
2315
2467
  : undefined;
2316
2468
  const turnExecutionId = activeTurn?.turnExecutionId ?? null;
2317
- const stopTyping = silentTurn ? () => { } : this.borgee.startTyping(channelId);
2318
- const collaborationGrounding = await this.buildCollaborationGrounding(authorId, options?.incomingAuthorKind, activeTurn, options?.kickoff);
2319
- const publishesCollaborationDraft = !silentTurn &&
2320
- !deferDelivery &&
2321
- this.collaborationEnabled &&
2322
- turnExecutionId != null &&
2323
- activeTurn?.protocol === undefined;
2324
- const supportsProviderProgress = this.provider.capabilities?.progressUpdates?.support === 'supported';
2325
- const supportsBackgroundRunLifecycle = this.provider.capabilities?.backgroundRuns?.lifecycle === 'supported';
2326
- const reportsProviderActivity = !silentTurn && supportsProviderProgress;
2327
- const observeProviderProgress = supportsProviderProgress || supportsBackgroundRunLifecycle;
2328
2469
  const turnContext = {
2329
2470
  eventType: msg.type,
2330
2471
  channelId,
@@ -2337,26 +2478,58 @@ export class AgentsHost {
2337
2478
  };
2338
2479
  const turnStartedAt = Date.now();
2339
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;
2340
2486
  let reply;
2341
2487
  let publicReplyText;
2342
2488
  let visibleReplyBody;
2343
- const taskThreadCollaborationContract = this.resolveTaskThreadCollaborationContractForTurn(msg, taskThreadContext);
2344
- const collaborationOutcome = this.collaborationOutcomeModelEnabled
2345
- ? this.collaborationOutcomeByChannel.get(channelId)
2346
- : undefined;
2347
- const missedCollaborationDiagnostic = this.resolveMissedCollaborationDiagnosticForTurn(channelId);
2348
- const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn(taskThreadContext);
2349
- const incomingParts = options?.incomingParts
2350
- ?? buildHostedTurnContentParts({
2351
- text: String(msg.content ?? msg.body ?? content),
2352
- attachments: msg.attachments,
2353
- });
2354
- const activityReporter = silentTurn
2355
- ? undefined
2356
- : this.beginTurnActivity(channelId, activeTurn?.generation);
2357
- await this.ensureCompactionSnapshotLoaded(channelId);
2358
- const compactionSnapshot = this.compactionSnapshotByChannel.get(channelId);
2489
+ let activityReporter;
2490
+ let typingLease;
2359
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
+ }
2360
2533
  reply = await this.provider.generateReply({
2361
2534
  agentName: this.config.agent.agentName,
2362
2535
  provider: this.config.agent.provider,
@@ -2397,42 +2570,61 @@ export class AgentsHost {
2397
2570
  : { taskAssignmentContextOverridePersistence: 'ephemeral' }),
2398
2571
  }
2399
2572
  : {}),
2400
- }, observeProviderProgress
2573
+ }, observeProviderProgress || executionTelemetry
2401
2574
  ? {
2402
- onProgress: (update) => {
2403
- if (supportsBackgroundRunLifecycle) {
2404
- this.backgroundRuns.observe(channelId, update);
2405
- if (update.type === 'activity' && update.activity.background === true) {
2406
- this.borgee.reportActivity?.({
2407
- channelId,
2408
- activity: { shape: 'activity', ...update.activity },
2409
- });
2410
- return;
2411
- }
2412
- }
2413
- if (!this.canAcceptTurnOutput(channelId, activeTurn?.generation)) {
2414
- 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
+ },
2415
2583
  }
2416
- if (supportsProviderProgress && update.type === 'compaction') {
2417
- this.noteCompactionProgress(channelId, update.compaction, {
2418
- turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
2419
- });
2420
- return;
2421
- }
2422
- const reportedActivity = toReportedActivity(update);
2423
- if (reportsProviderActivity && reportedActivity) {
2424
- activityReporter?.progress(reportedActivity);
2425
- }
2426
- if (!supportsProviderProgress
2427
- || update.type !== 'text'
2428
- || update.stream !== 'answer') {
2429
- return;
2430
- }
2431
- if (!publishesCollaborationDraft) {
2432
- 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
+ },
2433
2626
  }
2434
- this.publishCollaborationDraft(channelId, activeTurn, update.text);
2435
- },
2627
+ : {}),
2436
2628
  }
2437
2629
  : undefined);
2438
2630
  publicReplyText = resolvePublicReplyText(reply);
@@ -2460,6 +2652,7 @@ export class AgentsHost {
2460
2652
  return { reply, stale: true, completed: false };
2461
2653
  }
2462
2654
  if (silentTurn) {
2655
+ executionSucceeded = true;
2463
2656
  this.logger.debug('completed turn', {
2464
2657
  ...turnContext,
2465
2658
  durationMs: Date.now() - turnStartedAt,
@@ -2480,6 +2673,7 @@ export class AgentsHost {
2480
2673
  };
2481
2674
  }
2482
2675
  if (deferDelivery) {
2676
+ executionSucceeded = true;
2483
2677
  this.logger.debug('completed turn', {
2484
2678
  ...turnContext,
2485
2679
  durationMs: Date.now() - turnStartedAt,
@@ -2516,6 +2710,7 @@ export class AgentsHost {
2516
2710
  if (publishesCollaborationDraft) {
2517
2711
  this.finalizeCollaborationDraft(channelId, activeTurn, posted.messageId, visibleReplyBody);
2518
2712
  }
2713
+ executionSucceeded = true;
2519
2714
  return {
2520
2715
  reply,
2521
2716
  deliveredMessageId: posted.messageId,
@@ -2539,6 +2734,7 @@ export class AgentsHost {
2539
2734
  turnExecutionId: activeTurn.turnExecutionId,
2540
2735
  });
2541
2736
  }
2737
+ executionSucceeded = true;
2542
2738
  return {
2543
2739
  reply,
2544
2740
  deliveredMessageId: null,
@@ -2557,6 +2753,9 @@ export class AgentsHost {
2557
2753
  turnExecutionId: activeTurn.turnExecutionId,
2558
2754
  });
2559
2755
  }
2756
+ if (executionTelemetryStartFailed) {
2757
+ throw error;
2758
+ }
2560
2759
  if (error instanceof ProviderTurnCancelledError) {
2561
2760
  const cancelledReplyText = error.publicReplyText?.trim();
2562
2761
  if (!silentTurn &&
@@ -2634,7 +2833,10 @@ export class AgentsHost {
2634
2833
  };
2635
2834
  }
2636
2835
  finally {
2637
- stopTyping();
2836
+ if (executionTelemetry) {
2837
+ executionTelemetry.noteAttempt(executionSucceeded);
2838
+ }
2839
+ typingLease?.stop();
2638
2840
  activityReporter?.end();
2639
2841
  }
2640
2842
  }
@@ -4,6 +4,10 @@ interface BackgroundRunTrackerOptions {
4
4
  lifecycleSupported: boolean;
5
5
  targetedCancellationSupported: boolean;
6
6
  cancel?: (channelId: string, providerRunId: string) => Promise<boolean>;
7
+ onFinished?: (channelId: string, run: {
8
+ runId: string;
9
+ state: 'completed' | 'failed' | 'cancelled';
10
+ }) => void;
7
11
  }
8
12
  export declare class BackgroundRunTracker {
9
13
  private readonly report;
@@ -99,6 +99,10 @@ export class BackgroundRunTracker {
99
99
  historySize -= 1;
100
100
  }
101
101
  this.publish(run);
102
+ if (isConcreteTerminal(state) &&
103
+ (startsNewExecution || !held || !isConcreteTerminal(held.state))) {
104
+ this.options.onFinished?.(channelId, { runId: run.runId, state });
105
+ }
102
106
  }
103
107
  replay() {
104
108
  for (const run of this.runs.values()) {
@@ -166,10 +170,10 @@ function runState(status) {
166
170
  }
167
171
  }
168
172
  function isTerminal(state) {
169
- return (state === 'completed' ||
170
- state === 'failed' ||
171
- state === 'cancelled' ||
172
- state === 'unknown');
173
+ return (isConcreteTerminal(state) || state === 'unknown');
174
+ }
175
+ function isConcreteTerminal(state) {
176
+ return state === 'completed' || state === 'failed' || state === 'cancelled';
173
177
  }
174
178
  function stableRunId(channelId, providerRunId, startedAt, executionId) {
175
179
  const digest = createHash('sha256')
@@ -1,5 +1,5 @@
1
- import type { AgentActivity, 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,15 +9,26 @@ 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>;
16
+ reportTaskFinished?(input: ReportTaskFinishedInput): void;
15
17
  reportActivity?(input: {
16
18
  channelId: string;
17
19
  activity: AgentActivity;
18
20
  }): void;
19
21
  getMe(): Promise<MeResponseUser>;
20
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>;
21
32
  listChannels(): Promise<ChannelSummary[]>;
22
33
  readChannelHistory(input: ReadChannelHistoryInput): Promise<ChannelHistoryEntry[]>;
23
34
  createTask(input: CreateTaskInput): Promise<Task>;
@@ -1,9 +1,15 @@
1
- import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent, 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'>;
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;
@@ -25,12 +37,26 @@ export declare class SdkChatControlPlane implements ChatControlPlane {
25
37
  close(): Promise<void>;
26
38
  postMessage(input: PostMessageInput): Promise<PostedMessage>;
27
39
  reportActivity(input: Parameters<BorgeePluginClient['reportActivity']>[0]): void;
40
+ reportTaskFinished(input: ReportTaskFinishedInput): void;
28
41
  editMessage(messageId: string, content: string): Promise<void>;
29
42
  deleteMessage(messageId: string): Promise<void>;
30
- startTyping(channelId: string): () => void;
43
+ reportTyping(channelId: string): void;
31
44
  reportTurnActivity(input: ReportTurnActivityInput): TurnActivityReporter;
45
+ reportExecutionTelemetry(frame: ExecutionTelemetryFrame): Promise<void>;
46
+ private flushExecutionTelemetry;
47
+ private scheduleExecutionTelemetryRetry;
48
+ private cancelExecutionTelemetryRetry;
32
49
  getMe(): Promise<MeResponseUser>;
33
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>;
34
60
  readChannelHistory(input: ReadChannelHistoryInput): Promise<ChannelHistoryEntry[]>;
35
61
  listChannels(): Promise<ChannelSummary[]>;
36
62
  createTask(input: CreateTaskInput): Promise<Task>;