@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,4 +1,5 @@
1
1
  import { createBorgeePlugin, } from '../plugin-sdk.js';
2
+ import { ExecutionTelemetryOutbox, ExecutionTelemetryOutboxCapacityError, } from '../execution-telemetry.js';
2
3
  import { normalizeHostedMessageAttachment } from '../hosted-turn-content.js';
3
4
  /**
4
5
  * Thin adapter over `@borgee/plugin-sdk` (the same BPP/`/ws/plugin` SDK used by
@@ -10,11 +11,32 @@ export class SdkChatControlPlane {
10
11
  pendingMessages = [];
11
12
  unsubscribe = null;
12
13
  stateUnsubscribe = null;
14
+ telemetryAckUnsubscribe = null;
15
+ executionTelemetryRetry;
13
16
  activityTransportReady;
17
+ executionTelemetryOutbox;
18
+ onExecutionTelemetryError;
14
19
  connected = false;
20
+ transportOnline = false;
21
+ executionTelemetryCapacityWaitMs;
15
22
  me = null;
16
23
  constructor(baseUrl, apiKey, createClient = createBorgeePlugin, options = {}) {
17
- this.client = createClient({ baseUrl, apiKey, ...options });
24
+ this.client = createClient({
25
+ baseUrl,
26
+ apiKey,
27
+ ...(options.pluginId ? { pluginId: options.pluginId } : {}),
28
+ });
29
+ this.onExecutionTelemetryError =
30
+ options.onExecutionTelemetryError ??
31
+ (() => console.error('[agents-host] execution telemetry persistence failed'));
32
+ this.executionTelemetryCapacityWaitMs =
33
+ options.executionTelemetryCapacityWaitMs ?? 1_000;
34
+ this.executionTelemetryOutbox = new ExecutionTelemetryOutbox(options.executionTelemetryOutboxPath, {
35
+ onRetired: (count) => {
36
+ this.onExecutionTelemetryError(new Error(`Retired ${count} unacknowledged execution telemetry event(s)`));
37
+ },
38
+ policy: options.executionTelemetryOutboxPolicy,
39
+ });
18
40
  }
19
41
  onStopTurn(handler) {
20
42
  this.client.onStopTurn(handler);
@@ -26,6 +48,7 @@ export class SdkChatControlPlane {
26
48
  this.activityTransportReady = handler;
27
49
  }
28
50
  async connect(onMessage) {
51
+ await this.executionTelemetryOutbox.load();
29
52
  this.unsubscribe = this.client.on('message', (event) => {
30
53
  const message = mapInboundToChannelMessage(event);
31
54
  if (!this.connected) {
@@ -35,13 +58,26 @@ export class SdkChatControlPlane {
35
58
  onMessage(message);
36
59
  });
37
60
  this.stateUnsubscribe = this.client.on('connectionState', (state) => {
61
+ this.transportOnline = state.status === 'online';
38
62
  if (state.status === 'online') {
39
63
  this.activityTransportReady?.();
64
+ void this.flushExecutionTelemetry(true);
40
65
  }
66
+ else {
67
+ this.cancelExecutionTelemetryRetry();
68
+ }
69
+ });
70
+ this.telemetryAckUnsubscribe = this.client.on('executionTelemetryAck', (ack) => {
71
+ void this.executionTelemetryOutbox
72
+ .acknowledge(ack.eventId)
73
+ .then(() => this.scheduleExecutionTelemetryRetry())
74
+ .catch(this.onExecutionTelemetryError);
41
75
  });
42
76
  try {
43
77
  await this.client.connect();
44
78
  this.connected = true;
79
+ this.transportOnline =
80
+ this.transportOnline || this.client.connectionState?.status === 'online';
45
81
  if (this.client.agentId) {
46
82
  this.me = { id: this.client.agentId };
47
83
  }
@@ -49,26 +85,36 @@ export class SdkChatControlPlane {
49
85
  onMessage(message);
50
86
  }
51
87
  this.pendingMessages = [];
88
+ await this.flushExecutionTelemetry(true);
52
89
  }
53
90
  catch (error) {
91
+ this.transportOnline = false;
92
+ this.cancelExecutionTelemetryRetry();
54
93
  this.unsubscribe?.();
55
94
  this.unsubscribe = null;
56
95
  this.stateUnsubscribe?.();
57
96
  this.stateUnsubscribe = null;
97
+ this.telemetryAckUnsubscribe?.();
98
+ this.telemetryAckUnsubscribe = null;
58
99
  this.pendingMessages = [];
59
100
  throw error;
60
101
  }
61
102
  }
62
103
  async close() {
63
104
  this.connected = false;
105
+ this.transportOnline = false;
106
+ this.cancelExecutionTelemetryRetry();
64
107
  this.pendingMessages = [];
65
108
  this.unsubscribe?.();
66
109
  this.unsubscribe = null;
67
110
  this.stateUnsubscribe?.();
68
111
  this.stateUnsubscribe = null;
112
+ this.telemetryAckUnsubscribe?.();
113
+ this.telemetryAckUnsubscribe = null;
69
114
  this.activityTransportReady = undefined;
70
115
  this.client.onStopTurn(undefined);
71
116
  this.client.onStopBackgroundRun?.(undefined);
117
+ await this.executionTelemetryOutbox.settle();
72
118
  await this.client.close();
73
119
  }
74
120
  async postMessage(input) {
@@ -82,18 +128,74 @@ export class SdkChatControlPlane {
82
128
  reportActivity(input) {
83
129
  this.client.reportActivity(input);
84
130
  }
131
+ reportTaskFinished(input) {
132
+ this.client.reportTaskFinished?.(input);
133
+ }
85
134
  async editMessage(messageId, content) {
86
135
  await this.client.editMessage({ messageId, body: content });
87
136
  }
88
137
  async deleteMessage(messageId) {
89
138
  await this.client.deleteMessage({ messageId });
90
139
  }
91
- startTyping(channelId) {
92
- return this.client.startTyping(channelId);
140
+ reportTyping(channelId) {
141
+ this.client.reportTyping(channelId);
93
142
  }
94
143
  reportTurnActivity(input) {
95
144
  return this.client.reportTurnActivity(input);
96
145
  }
146
+ async reportExecutionTelemetry(frame) {
147
+ if (!this.client.reportExecutionTelemetry) {
148
+ throw new Error('Plugin SDK execution telemetry is unavailable');
149
+ }
150
+ try {
151
+ await this.executionTelemetryOutbox.enqueue(frame);
152
+ }
153
+ catch (error) {
154
+ if (!(error instanceof ExecutionTelemetryOutboxCapacityError) ||
155
+ !this.transportOnline) {
156
+ throw error;
157
+ }
158
+ await this.flushExecutionTelemetry(true);
159
+ const capacityAvailable = await this.executionTelemetryOutbox.waitForCapacity(this.executionTelemetryCapacityWaitMs);
160
+ if (!capacityAvailable)
161
+ throw error;
162
+ await this.executionTelemetryOutbox.enqueue(frame);
163
+ }
164
+ await this.flushExecutionTelemetry();
165
+ }
166
+ async flushExecutionTelemetry(force = false) {
167
+ if (!this.connected || !this.transportOnline || !this.client.reportExecutionTelemetry)
168
+ return;
169
+ try {
170
+ await this.executionTelemetryOutbox.flush((frame) => {
171
+ this.client.reportExecutionTelemetry?.(frame);
172
+ }, force);
173
+ }
174
+ catch (error) {
175
+ this.onExecutionTelemetryError(error);
176
+ }
177
+ finally {
178
+ this.scheduleExecutionTelemetryRetry();
179
+ }
180
+ }
181
+ scheduleExecutionTelemetryRetry() {
182
+ this.cancelExecutionTelemetryRetry();
183
+ if (!this.connected || !this.transportOnline)
184
+ return;
185
+ const delay = this.executionTelemetryOutbox.nextRetryDelayMs();
186
+ if (delay === undefined)
187
+ return;
188
+ this.executionTelemetryRetry = setTimeout(() => {
189
+ this.executionTelemetryRetry = undefined;
190
+ void this.flushExecutionTelemetry();
191
+ }, Math.max(1, delay));
192
+ }
193
+ cancelExecutionTelemetryRetry() {
194
+ if (!this.executionTelemetryRetry)
195
+ return;
196
+ clearTimeout(this.executionTelemetryRetry);
197
+ this.executionTelemetryRetry = undefined;
198
+ }
97
199
  async getMe() {
98
200
  if (this.me) {
99
201
  return this.me;
@@ -114,6 +216,34 @@ export class SdkChatControlPlane {
114
216
  kind: user.kind,
115
217
  }));
116
218
  }
219
+ async listChannelFiles(input) {
220
+ const files = await this.client.listChannelFiles({ channelId: input.channelId });
221
+ return files.map((file) => ({
222
+ id: file.id,
223
+ ownerUserId: file.ownerUserId,
224
+ channelId: file.channelId,
225
+ path: file.path,
226
+ parentPath: file.parentPath,
227
+ name: file.name,
228
+ isDirectory: file.isDirectory,
229
+ mimeType: file.mimeType,
230
+ sizeBytes: file.sizeBytes,
231
+ source: file.source,
232
+ sourceMessageId: file.sourceMessageId,
233
+ createdAt: file.createdAt,
234
+ updatedAt: file.updatedAt,
235
+ }));
236
+ }
237
+ async readChannelFile(input) {
238
+ return await this.client.readChannelFile(input);
239
+ }
240
+ async publishChannelFile(input) {
241
+ const file = await this.client.publishChannelFile(input);
242
+ return {
243
+ ...file,
244
+ sourceMessageId: file.sourceMessageId,
245
+ };
246
+ }
117
247
  async readChannelHistory(input) {
118
248
  const messages = await this.client.readHistory({
119
249
  channelId: input.channelId,
@@ -175,5 +305,6 @@ export function mapInboundToChannelMessage(event) {
175
305
  content_type: event.message?.contentType,
176
306
  attachments: event.message?.attachments?.map(normalizeHostedMessageAttachment),
177
307
  created_at: event.createdAt,
308
+ ...(event.executionGrant ? { execution_grant: event.executionGrant } : {}),
178
309
  };
179
310
  }
@@ -14,7 +14,7 @@ export function buildSkillManualReadLine(skillRuntime) {
14
14
  */
15
15
  export function buildSkillManualLines(skillRuntime) {
16
16
  return [
17
- "A packaged local CLI reads this Borgee channel and acts on its tasks: channel history, visible participants, this channel's tasks and their properties, and short auxiliary mentions.",
17
+ "A packaged local CLI reads this Borgee channel and acts on its tasks: channel history, channel files, visible participants, this channel's tasks and their properties, and short auxiliary mentions.",
18
18
  buildSkillManualReadLine(skillRuntime),
19
19
  'Every command the manual documents is already authorized on a turn whose prompt names a gateway credential file. Run them directly and do not ask the user for permission first.',
20
20
  ];
@@ -0,0 +1,96 @@
1
+ import type { ExecutionGrant, ExecutionTelemetryFrame } from './plugin-sdk.js';
2
+ import type { ProviderKind, ProviderTokenUsage } from './types.js';
3
+ export interface ExecutionTelemetrySink {
4
+ reportExecutionTelemetry(frame: ExecutionTelemetryFrame): Promise<void>;
5
+ }
6
+ export interface StoredExecutionUsage extends ProviderTokenUsage {
7
+ provider: ProviderKind;
8
+ }
9
+ export type ExecutionDeliveryClaim = 'new' | 'recovering' | 'completed';
10
+ export declare class ExecutionDeliveryJournal {
11
+ private readonly path?;
12
+ private readonly maxCompletedEntries;
13
+ private readonly now;
14
+ private entries;
15
+ private loaded;
16
+ private writeQueue;
17
+ constructor(path?: string | undefined, maxCompletedEntries?: number, now?: () => number);
18
+ load(): Promise<void>;
19
+ claim(executionId: string): Promise<ExecutionDeliveryClaim>;
20
+ complete(executionId: string): Promise<void>;
21
+ recordUsage(executionId: string, usage: StoredExecutionUsage): Promise<void>;
22
+ readUsage(executionId: string): Promise<StoredExecutionUsage | undefined>;
23
+ private mutate;
24
+ private isStoredExecutionDelivery;
25
+ private isStoredExecutionUsage;
26
+ }
27
+ export declare class ExecutionTelemetryReporter {
28
+ private readonly grant;
29
+ private readonly sink;
30
+ private readonly now;
31
+ private readonly persistUsage?;
32
+ private started;
33
+ private finished;
34
+ private attemptCount;
35
+ private attemptsSucceeded;
36
+ private provider;
37
+ private pendingUsage;
38
+ private usageFrameDurable;
39
+ private usagePersistence;
40
+ private readonly continuations;
41
+ private finishing?;
42
+ get startFrameDurable(): boolean;
43
+ get terminalFrameDurable(): boolean;
44
+ get allRequiredFramesDurable(): boolean;
45
+ constructor(grant: ExecutionGrant, sink: ExecutionTelemetrySink, now?: () => number, persistUsage?: ((provider: ProviderKind, usage: ProviderTokenUsage) => Promise<void>) | undefined);
46
+ start(): Promise<void>;
47
+ usage(provider: ProviderKind, usage: ProviderTokenUsage): Promise<void>;
48
+ noteAttempt(succeeded: boolean): void;
49
+ noteFailure(): void;
50
+ hold(completion: Promise<boolean>): void;
51
+ finish(succeeded?: boolean): Promise<void>;
52
+ private finalize;
53
+ }
54
+ export interface ExecutionTelemetryOutboxPolicy {
55
+ maxEntries: number;
56
+ maxAttempts: number;
57
+ retentionMs: number;
58
+ retryIntervalMs: number;
59
+ }
60
+ export declare class ExecutionTelemetryOutboxCapacityError extends Error {
61
+ constructor();
62
+ }
63
+ type ExecutionTelemetryOutboxOptions = {
64
+ now?: () => number;
65
+ onRetired?: (count: number) => void;
66
+ policy?: Partial<ExecutionTelemetryOutboxPolicy>;
67
+ };
68
+ export declare class ExecutionTelemetryOutbox {
69
+ private readonly path?;
70
+ private frames;
71
+ private loaded;
72
+ private writeQueue;
73
+ private flushInFlight?;
74
+ private readonly now;
75
+ private readonly onRetired?;
76
+ private readonly policy;
77
+ private readonly capacityWaiters;
78
+ constructor(path?: string | undefined, options?: ExecutionTelemetryOutboxOptions);
79
+ load(): Promise<void>;
80
+ enqueue(frame: ExecutionTelemetryFrame): Promise<void>;
81
+ acknowledge(eventId: string): Promise<void>;
82
+ flush(send: (frame: ExecutionTelemetryFrame) => void, force?: boolean): Promise<void>;
83
+ private flushOnce;
84
+ settle(): Promise<void>;
85
+ pendingCount(): number;
86
+ nextRetryDelayMs(): number | undefined;
87
+ hasCapacity(): boolean;
88
+ waitForCapacity(timeoutMs: number): Promise<boolean>;
89
+ private retireExpired;
90
+ private mutate;
91
+ private persist;
92
+ private frameTimestamp;
93
+ private isStoredEntry;
94
+ private notifyCapacityAvailable;
95
+ }
96
+ export {};