@borgee/agents-host 0.2.84 → 0.2.97

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 (54) hide show
  1. package/README.md +1 -1
  2. package/dist/agents-host.js +23 -5
  3. package/dist/background-runs.d.ts +6 -1
  4. package/dist/background-runs.js +56 -21
  5. package/dist/chat/chat-control-plane.d.ts +3 -1
  6. package/dist/chat/sdk-chat-control-plane.d.ts +6 -2
  7. package/dist/chat/sdk-chat-control-plane.js +18 -0
  8. package/dist/plugin-sdk.js +149 -21
  9. package/dist/plugin-sdk.js.map +3 -3
  10. package/dist/progress-to-activity.d.ts +3 -3
  11. package/dist/providers/claude/adapter.d.ts +3 -1
  12. package/dist/providers/claude/adapter.js +41 -3
  13. package/dist/providers/claude/background-run-observer.d.ts +32 -0
  14. package/dist/providers/claude/background-run-observer.js +381 -0
  15. package/dist/providers/claude/cli-client.d.ts +15 -1
  16. package/dist/providers/claude/cli-client.js +699 -69
  17. package/dist/providers/claude/foreground-handoff.d.ts +4 -0
  18. package/dist/providers/claude/foreground-handoff.js +45 -0
  19. package/dist/providers/claude/task-cancellation-protocol.d.ts +14 -0
  20. package/dist/providers/claude/task-cancellation-protocol.js +21 -0
  21. package/dist/providers/copilot/activity-metadata.d.ts +3 -0
  22. package/dist/providers/copilot/activity-metadata.js +19 -0
  23. package/dist/providers/copilot/cli-client.js +5 -0
  24. package/dist/providers/provider-adapter.d.ts +8 -0
  25. package/dist/providers/provider-adapter.js +4 -0
  26. package/dist/types.d.ts +8 -0
  27. package/dist/vendor/claude-agent-acp/LICENSE +191 -0
  28. package/dist/vendor/claude-agent-acp/NOTICE +8 -0
  29. package/dist/vendor/claude-agent-acp/dist/acp-agent.d.ts +1017 -0
  30. package/dist/vendor/claude-agent-acp/dist/acp-agent.d.ts.map +1 -0
  31. package/dist/vendor/claude-agent-acp/dist/acp-agent.js +6305 -0
  32. package/dist/vendor/claude-agent-acp/dist/borgee-foreground-handoff-bridge.js +322 -0
  33. package/dist/vendor/claude-agent-acp/dist/borgee-task-cancellation-bridge.js +101 -0
  34. package/dist/vendor/claude-agent-acp/dist/borgee-task-lifecycle-bridge.js +58 -0
  35. package/dist/vendor/claude-agent-acp/dist/elicitation.d.ts +130 -0
  36. package/dist/vendor/claude-agent-acp/dist/elicitation.d.ts.map +1 -0
  37. package/dist/vendor/claude-agent-acp/dist/elicitation.js +304 -0
  38. package/dist/vendor/claude-agent-acp/dist/index.d.ts +3 -0
  39. package/dist/vendor/claude-agent-acp/dist/index.d.ts.map +1 -0
  40. package/dist/vendor/claude-agent-acp/dist/index.js +75 -0
  41. package/dist/vendor/claude-agent-acp/dist/lib.d.ts +6 -0
  42. package/dist/vendor/claude-agent-acp/dist/lib.d.ts.map +1 -0
  43. package/dist/vendor/claude-agent-acp/dist/lib.js +5 -0
  44. package/dist/vendor/claude-agent-acp/dist/settings.d.ts +68 -0
  45. package/dist/vendor/claude-agent-acp/dist/settings.d.ts.map +1 -0
  46. package/dist/vendor/claude-agent-acp/dist/settings.js +185 -0
  47. package/dist/vendor/claude-agent-acp/dist/tools.d.ts +102 -0
  48. package/dist/vendor/claude-agent-acp/dist/tools.d.ts.map +1 -0
  49. package/dist/vendor/claude-agent-acp/dist/tools.js +1000 -0
  50. package/dist/vendor/claude-agent-acp/dist/utils.d.ts +16 -0
  51. package/dist/vendor/claude-agent-acp/dist/utils.d.ts.map +1 -0
  52. package/dist/vendor/claude-agent-acp/dist/utils.js +81 -0
  53. package/dist/vendor/claude-agent-acp/package.json +85 -0
  54. package/package.json +16 -12
package/README.md CHANGED
@@ -479,7 +479,7 @@ successfully applied snapshot running.
479
479
  Each Borgee channel is mapped 1:1 to a provider-native session while it stays
480
480
  active:
481
481
 
482
- - **Claude**: one persistent `@agentclientprotocol/claude-agent-acp` subprocess is shared by a single `AgentsHost`, with one ACP session per Borgee channel. Same-channel turns are serialized; different channels keep isolated ACP sessions. The real ACP session cwd now comes from the resolved channel working folder `~/.borgee/channels/<encoded-channel-id>/workspace`. Task threads only switch execution onto an explicit existing `execution.local_directory`; otherwise they stay discussion-only on `~/.borgee/channels/<encoded-thread-id>/scratch/workspace`, and ACP permission requests are denied so filesystem and shell tools stay unavailable. The host persists the channel→ACP-session map in its state root, restores same-host continuity with ACP `session/resume` when available, retries stale restores with a fresh session immediately, and injects the per-channel local context paths through Claude ACP `_meta.systemPrompt.append`.
482
+ - **Claude**: one persistent `@agentclientprotocol/claude-agent-acp` subprocess is shared by a single `AgentsHost`, with one ACP session per Borgee channel. Same-channel turns are serialized; different channels keep isolated ACP sessions. The real ACP session cwd now comes from the resolved channel working folder `~/.borgee/channels/<encoded-channel-id>/workspace`. Task threads only switch execution onto an explicit existing `execution.local_directory`; otherwise they stay discussion-only on `~/.borgee/channels/<encoded-thread-id>/scratch/workspace`, and ACP permission requests are denied so filesystem and shell tools stay unavailable. The host persists the channel→ACP-session map in its state root, restores same-host continuity with ACP `session/resume` when available, retries stale restores with a fresh session immediately, and injects the per-channel local context paths through Claude ACP `_meta.systemPrompt.append`. The bundled adapter advertises a versioned targeted task-stop extension backed by the Agent SDK's `Query.stopTask(taskId)`: accepting that request moves only the selected background run to `stopping`, while its provider lifecycle event decides the terminal state and the parent turn plus sibling workers continue unchanged. Custom ACP commands do not receive this capability and remain fail-closed.
483
483
  - **Codex**: one persistent `@agentclientprotocol/codex-acp` subprocess is
484
484
  shared by a single `AgentsHost`, with one ACP session per Borgee channel. When
485
485
  the current turn has a materialized channel context payload, Codex refreshes a
@@ -150,17 +150,19 @@ class DefaultHostRuntime {
150
150
  });
151
151
  this.ensureStateRoot = deps.ensureStateRoot ?? ensurePrivateStateRoot;
152
152
  }
153
- async start(onMessage, onStopTurn, onStopBackgroundRun) {
153
+ async start(onMessage, onStopTurn, onStopBackgroundRun, onActivityTransportReady) {
154
154
  await this.ensureStateRoot(this.config.stateRootDir);
155
155
  await this.gateway.start();
156
156
  try {
157
157
  this.controlPlane.onStopTurn?.(onStopTurn);
158
158
  this.controlPlane.onStopBackgroundRun?.(onStopBackgroundRun);
159
+ this.controlPlane.onActivityTransportReady?.(onActivityTransportReady);
159
160
  await this.controlPlane.connect(onMessage);
160
161
  }
161
162
  catch (error) {
162
163
  this.controlPlane.onStopTurn?.(undefined);
163
164
  this.controlPlane.onStopBackgroundRun?.(undefined);
165
+ this.controlPlane.onActivityTransportReady?.(undefined);
164
166
  await this.gateway.stop().catch(() => { });
165
167
  throw error;
166
168
  }
@@ -170,6 +172,7 @@ class DefaultHostRuntime {
170
172
  try {
171
173
  this.controlPlane.onStopTurn?.(undefined);
172
174
  this.controlPlane.onStopBackgroundRun?.(undefined);
175
+ this.controlPlane.onActivityTransportReady?.(undefined);
173
176
  await this.controlPlane.close();
174
177
  }
175
178
  catch (error) {
@@ -365,6 +368,14 @@ export class AgentsHost {
365
368
  cancel: this.provider.cancelBackgroundRun
366
369
  ? (channelId, providerRunId) => (this.provider.cancelBackgroundRun(channelId, providerRunId))
367
370
  : undefined,
371
+ onFinished: (channelId, run) => {
372
+ this.borgee.reportTaskFinished?.({
373
+ channelId,
374
+ taskId: run.runId,
375
+ outcome: run.state,
376
+ ...(run.state === 'failed' ? { reason: 'unknown' } : {}),
377
+ });
378
+ },
368
379
  });
369
380
  }
370
381
  async start() {
@@ -391,7 +402,7 @@ export class AgentsHost {
391
402
  return;
392
403
  }
393
404
  this.dispatchMessage(message);
394
- }, async (channelId) => this.stopTurn(channelId), async (channelId, runId) => this.backgroundRuns.stop(channelId, runId));
405
+ }, async (channelId) => this.stopTurn(channelId), async (channelId, runId) => this.backgroundRuns.stop(channelId, runId), () => this.backgroundRuns.replay());
395
406
  const agentId = await this.ensureSelfAgentId();
396
407
  bufferingMessages = false;
397
408
  while (bufferedMessages.length > 0) {
@@ -642,10 +653,10 @@ export class AgentsHost {
642
653
  if (this.isHumanWakeFiltered(msg, authorKind, attentionSnapshot)) {
643
654
  return;
644
655
  }
645
- let foregroundInterrupt;
656
+ let foregroundTransition;
646
657
  if (authorKind !== 'agent' && channelState.activeTurn) {
647
658
  this.supersedeActiveTurn(msg.channel_id, channelState.activeTurn);
648
- foregroundInterrupt = (async () => {
659
+ foregroundTransition = (async () => {
649
660
  try {
650
661
  await this.provider.interruptTurn?.(msg.channel_id);
651
662
  }
@@ -669,7 +680,7 @@ export class AgentsHost {
669
680
  attentionSnapshot,
670
681
  taskThreadContext,
671
682
  });
672
- await foregroundInterrupt;
683
+ await foregroundTransition;
673
684
  await this.ensureCollaborationProcessor(msg.channel_id, channelState);
674
685
  }
675
686
  detectProtocolKickoffCandidate(anchorMessageId, content, selfAgentId) {
@@ -2399,6 +2410,13 @@ export class AgentsHost {
2399
2410
  onProgress: (update) => {
2400
2411
  if (supportsBackgroundRunLifecycle) {
2401
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
+ }
2402
2420
  }
2403
2421
  if (!this.canAcceptTurnOutput(channelId, activeTurn?.generation)) {
2404
2422
  return;
@@ -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;
@@ -12,8 +16,9 @@ export declare class BackgroundRunTracker {
12
16
  private readonly mintRunId;
13
17
  private readonly runs;
14
18
  private readonly runsById;
15
- constructor(report: (channelId: string, activity: AgentActivity) => void, options: BackgroundRunTrackerOptions, now?: () => number, mintRunId?: (channelId: string, providerRunId: string, startedAt: number) => string);
19
+ constructor(report: (channelId: string, activity: AgentActivity) => void, options: BackgroundRunTrackerOptions, now?: () => number, mintRunId?: (channelId: string, providerRunId: string, startedAt: number, executionId?: string) => string);
16
20
  observe(channelId: string, update: ProviderProgressUpdate): void;
21
+ replay(): void;
17
22
  stop(channelId: string, runId: string): Promise<{
18
23
  outcome: 'stopped' | 'not_found' | 'unsupported';
19
24
  reason?: string;
@@ -20,15 +20,26 @@ export class BackgroundRunTracker {
20
20
  const key = `${channelId}\0${update.run.providerRunId}`;
21
21
  const held = this.runs.get(key);
22
22
  const state = runState(update.run.state);
23
+ const refinesUnknown = held?.state === 'unknown' && state !== 'unknown';
23
24
  const stopSupported = update.run.stopSupported &&
24
25
  this.options.targetedCancellationSupported &&
25
26
  this.options.cancel !== undefined;
26
27
  const executionChanged = held !== undefined &&
27
- update.run.startedAt !== undefined &&
28
- update.run.startedAt !== held.startedAt;
29
- const startsNewExecution = !held || executionChanged || (isTerminal(held.state) && !isTerminal(state));
30
- if (held && isTerminal(held.state) && isTerminal(state) && !startsNewExecution)
28
+ ((update.run.executionId !== undefined &&
29
+ update.run.executionId !== held.executionId) ||
30
+ (update.run.startedAt !== undefined &&
31
+ update.run.startedAt !== held.startedAt));
32
+ const startsNewExecution = !held ||
33
+ executionChanged ||
34
+ (held.state !== 'unknown' && isTerminal(held.state) && !isTerminal(state));
35
+ if (held &&
36
+ isTerminal(held.state) &&
37
+ isTerminal(state) &&
38
+ !startsNewExecution &&
39
+ held.state !== state &&
40
+ held.state !== 'unknown') {
31
41
  return;
42
+ }
32
43
  const startedAt = startsNewExecution
33
44
  ? (update.run.startedAt ?? this.now())
34
45
  : (update.run.startedAt ?? held.startedAt);
@@ -36,9 +47,11 @@ export class BackgroundRunTracker {
36
47
  ? {
37
48
  channelId,
38
49
  providerRunId: update.run.providerRunId,
39
- runId: this.mintRunId(channelId, update.run.providerRunId, startedAt),
50
+ runId: this.mintRunId(channelId, update.run.providerRunId, startedAt, update.run.executionId),
51
+ executionId: update.run.executionId,
40
52
  label: update.run.label,
41
53
  state,
54
+ parentActivityId: update.run.parentActivityId,
42
55
  startedAt,
43
56
  finishedAt: update.run.finishedAt,
44
57
  reason: update.run.reason,
@@ -48,32 +61,53 @@ export class BackgroundRunTracker {
48
61
  ...held,
49
62
  label: update.run.label,
50
63
  state,
64
+ parentActivityId: update.run.parentActivityId ?? held.parentActivityId,
51
65
  startedAt,
52
- finishedAt: update.run.finishedAt,
53
- reason: update.run.reason,
66
+ finishedAt: refinesUnknown
67
+ ? update.run.finishedAt
68
+ : (update.run.finishedAt ?? held.finishedAt),
69
+ reason: refinesUnknown ? update.run.reason : (update.run.reason ?? held.reason),
54
70
  stopSupported,
55
71
  };
56
72
  if (isTerminal(state) && run.finishedAt === undefined) {
57
73
  run.finishedAt = this.now();
58
74
  }
75
+ if (held &&
76
+ held.runId === run.runId &&
77
+ held.label === run.label &&
78
+ held.state === run.state &&
79
+ held.parentActivityId === run.parentActivityId &&
80
+ held.finishedAt === run.finishedAt &&
81
+ held.reason === run.reason &&
82
+ held.stopSupported === run.stopSupported) {
83
+ return;
84
+ }
59
85
  if (held && held.runId !== run.runId) {
60
86
  this.runsById.delete(held.runId);
61
87
  }
62
88
  this.runs.delete(key);
63
89
  this.runs.set(key, run);
64
90
  this.runsById.set(run.runId, run);
65
- while (this.runs.size > MAX_TRACKED_RUNS) {
66
- const oldestKey = this.runs.keys().next().value;
67
- if (typeof oldestKey !== 'string') {
91
+ let historySize = [...this.runs.values()].filter((candidate) => isTerminal(candidate.state)).length;
92
+ while (historySize > MAX_TRACKED_RUNS) {
93
+ const oldest = [...this.runs].find(([, candidate]) => isTerminal(candidate.state));
94
+ if (!oldest) {
68
95
  break;
69
96
  }
70
- const oldest = this.runs.get(oldestKey);
71
- this.runs.delete(oldestKey);
72
- if (oldest) {
73
- this.runsById.delete(oldest.runId);
74
- }
97
+ this.runs.delete(oldest[0]);
98
+ this.runsById.delete(oldest[1].runId);
99
+ historySize -= 1;
75
100
  }
76
101
  this.publish(run);
102
+ if (isConcreteTerminal(state) &&
103
+ (startsNewExecution || !held || !isConcreteTerminal(held.state))) {
104
+ this.options.onFinished?.(channelId, { runId: run.runId, state });
105
+ }
106
+ }
107
+ replay() {
108
+ for (const run of this.runs.values()) {
109
+ this.publish(run);
110
+ }
77
111
  }
78
112
  async stop(channelId, runId) {
79
113
  if (!this.options.lifecycleSupported ||
@@ -110,6 +144,7 @@ export class BackgroundRunTracker {
110
144
  runId: run.runId,
111
145
  label: run.label,
112
146
  state: run.state,
147
+ ...(run.parentActivityId ? { parentId: run.parentActivityId } : {}),
113
148
  startedAt: run.startedAt,
114
149
  ...(run.finishedAt !== undefined ? { finishedAt: run.finishedAt } : {}),
115
150
  ...(run.reason ? { reason: run.reason } : {}),
@@ -135,18 +170,18 @@ function runState(status) {
135
170
  }
136
171
  }
137
172
  function isTerminal(state) {
138
- return (state === 'completed' ||
139
- state === 'failed' ||
140
- state === 'cancelled' ||
141
- state === 'unknown');
173
+ return (isConcreteTerminal(state) || state === 'unknown');
174
+ }
175
+ function isConcreteTerminal(state) {
176
+ return state === 'completed' || state === 'failed' || state === 'cancelled';
142
177
  }
143
- function stableRunId(channelId, providerRunId, startedAt) {
178
+ function stableRunId(channelId, providerRunId, startedAt, executionId) {
144
179
  const digest = createHash('sha256')
145
180
  .update(channelId)
146
181
  .update('\0')
147
182
  .update(providerRunId)
148
183
  .update('\0')
149
- .update(String(startedAt))
184
+ .update(executionId ?? String(startedAt))
150
185
  .digest('hex')
151
186
  .slice(0, 32);
152
187
  return `run_${digest}`;
@@ -1,8 +1,9 @@
1
- import type { AgentActivity, ReportTurnActivityInput, StopBackgroundRunHandler, StopTurnHandler, TurnActivityReporter } from '../plugin-sdk.js';
1
+ import type { AgentActivity, ReportTaskFinishedInput, ReportTurnActivityInput, StopBackgroundRunHandler, 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
4
  onStopTurn?(handler: StopTurnHandler | undefined): void;
5
5
  onStopBackgroundRun?(handler: StopBackgroundRunHandler | undefined): void;
6
+ onActivityTransportReady?(handler: (() => void) | undefined): void;
6
7
  connect(onMessage: (message: ChannelMessageEvent) => void): Promise<void>;
7
8
  close(): Promise<void>;
8
9
  postMessage(input: PostMessageInput): Promise<PostedMessage>;
@@ -11,6 +12,7 @@ export interface ChatControlPlane {
11
12
  startTyping(channelId: string): () => void;
12
13
  /** Opens one turn's report on the activity rail. Fire-and-forget: nothing recovers what it sends. */
13
14
  reportTurnActivity(input: ReportTurnActivityInput): TurnActivityReporter;
15
+ reportTaskFinished?(input: ReportTaskFinishedInput): void;
14
16
  reportActivity?(input: {
15
17
  channelId: string;
16
18
  activity: AgentActivity;
@@ -1,7 +1,7 @@
1
- import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent, type ReportTurnActivityInput, type StopTurnHandler, type StopBackgroundRunHandler, type TurnActivityReporter } from '../plugin-sdk.js';
1
+ import { type BorgeePluginClient, type BorgeePluginOptions, type InboundMessageEvent, type ReportTaskFinishedInput, type ReportTurnActivityInput, type StopTurnHandler, type StopBackgroundRunHandler, 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' | 'onStopTurn' | 'onStopBackgroundRun' | 'reportActivity' | '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' | 'onStopBackgroundRun' | 'reportActivity' | 'readHistory' | 'reportTurnActivity' | 'sendMessage' | 'startTyping' | 'updateTask' | 'setTaskProperty' | 'deleteTaskProperty'> & Partial<Pick<BorgeePluginClient, 'reportTaskFinished'>>;
5
5
  type PluginClientFactory = (options: BorgeePluginOptions) => PluginClientLike;
6
6
  type SdkChatControlPlaneOptions = Pick<BorgeePluginOptions, 'pluginId'>;
7
7
  /**
@@ -13,15 +13,19 @@ export declare class SdkChatControlPlane implements ChatControlPlane {
13
13
  private readonly client;
14
14
  private pendingMessages;
15
15
  private unsubscribe;
16
+ private stateUnsubscribe;
17
+ private activityTransportReady;
16
18
  private connected;
17
19
  private me;
18
20
  constructor(baseUrl: string, apiKey: string, createClient?: PluginClientFactory, options?: SdkChatControlPlaneOptions);
19
21
  onStopTurn(handler: StopTurnHandler | undefined): void;
20
22
  onStopBackgroundRun(handler: StopBackgroundRunHandler | undefined): void;
23
+ onActivityTransportReady(handler: (() => void) | undefined): void;
21
24
  connect(onMessage: (message: ChannelMessageEvent) => void): Promise<void>;
22
25
  close(): Promise<void>;
23
26
  postMessage(input: PostMessageInput): Promise<PostedMessage>;
24
27
  reportActivity(input: Parameters<BorgeePluginClient['reportActivity']>[0]): void;
28
+ reportTaskFinished(input: ReportTaskFinishedInput): void;
25
29
  editMessage(messageId: string, content: string): Promise<void>;
26
30
  deleteMessage(messageId: string): Promise<void>;
27
31
  startTyping(channelId: string): () => void;
@@ -9,6 +9,8 @@ export class SdkChatControlPlane {
9
9
  client;
10
10
  pendingMessages = [];
11
11
  unsubscribe = null;
12
+ stateUnsubscribe = null;
13
+ activityTransportReady;
12
14
  connected = false;
13
15
  me = null;
14
16
  constructor(baseUrl, apiKey, createClient = createBorgeePlugin, options = {}) {
@@ -20,6 +22,9 @@ export class SdkChatControlPlane {
20
22
  onStopBackgroundRun(handler) {
21
23
  this.client.onStopBackgroundRun?.(handler);
22
24
  }
25
+ onActivityTransportReady(handler) {
26
+ this.activityTransportReady = handler;
27
+ }
23
28
  async connect(onMessage) {
24
29
  this.unsubscribe = this.client.on('message', (event) => {
25
30
  const message = mapInboundToChannelMessage(event);
@@ -29,6 +34,11 @@ export class SdkChatControlPlane {
29
34
  }
30
35
  onMessage(message);
31
36
  });
37
+ this.stateUnsubscribe = this.client.on('connectionState', (state) => {
38
+ if (state.status === 'online') {
39
+ this.activityTransportReady?.();
40
+ }
41
+ });
32
42
  try {
33
43
  await this.client.connect();
34
44
  this.connected = true;
@@ -43,6 +53,8 @@ export class SdkChatControlPlane {
43
53
  catch (error) {
44
54
  this.unsubscribe?.();
45
55
  this.unsubscribe = null;
56
+ this.stateUnsubscribe?.();
57
+ this.stateUnsubscribe = null;
46
58
  this.pendingMessages = [];
47
59
  throw error;
48
60
  }
@@ -52,6 +64,9 @@ export class SdkChatControlPlane {
52
64
  this.pendingMessages = [];
53
65
  this.unsubscribe?.();
54
66
  this.unsubscribe = null;
67
+ this.stateUnsubscribe?.();
68
+ this.stateUnsubscribe = null;
69
+ this.activityTransportReady = undefined;
55
70
  this.client.onStopTurn(undefined);
56
71
  this.client.onStopBackgroundRun?.(undefined);
57
72
  await this.client.close();
@@ -67,6 +82,9 @@ export class SdkChatControlPlane {
67
82
  reportActivity(input) {
68
83
  this.client.reportActivity(input);
69
84
  }
85
+ reportTaskFinished(input) {
86
+ this.client.reportTaskFinished?.(input);
87
+ }
70
88
  async editMessage(messageId, content) {
71
89
  await this.client.editMessage({ messageId, body: content });
72
90
  }
@@ -3722,8 +3722,8 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
3722
3722
  // ../sdk/plugin-ts/dist/errors.js
3723
3723
  var BorgeeError = class extends Error {
3724
3724
  code;
3725
- constructor(message, code) {
3726
- super(message);
3725
+ constructor(message, code, options) {
3726
+ super(message, options);
3727
3727
  this.name = "BorgeeError";
3728
3728
  this.code = code;
3729
3729
  }
@@ -3743,6 +3743,95 @@ var PermissionDeniedError = class extends BorgeeError {
3743
3743
  }
3744
3744
  };
3745
3745
 
3746
+ // ../sdk/plugin-ts/dist/channel-contracts.js
3747
+ var isZoneId = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(value);
3748
+ var validateChannelPlacement = (parentId) => {
3749
+ if (parentId !== null && (!isZoneId(parentId) || parentId === "null")) {
3750
+ throw new BorgeeError("parentId is required: pass null for Default or a Zone ID", "channel.placement_invalid");
3751
+ }
3752
+ };
3753
+ var rejectChannelPlacementUpdate = (input) => {
3754
+ for (const field of ["parentId", "parent_id", "channelType", "channel_type", "kind"]) {
3755
+ if (Object.hasOwn(input, field)) {
3756
+ throw new BorgeeError(`${field} cannot be changed after channel creation`, "channel.field_immutable");
3757
+ }
3758
+ }
3759
+ };
3760
+ var validateListZonesInput = (input) => {
3761
+ if (!isZoneId(input.guildId)) {
3762
+ throw new BorgeeError("guildId must be a nonempty opaque ID", "zone.input_invalid");
3763
+ }
3764
+ if (input.limit !== void 0 && (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 50)) {
3765
+ throw new BorgeeError("limit must be an integer between 1 and 50", "zone.input_invalid");
3766
+ }
3767
+ if (input.after !== void 0 && !isZoneId(input.after)) {
3768
+ throw new BorgeeError("after must be a nonempty opaque Zone ID", "zone.input_invalid");
3769
+ }
3770
+ };
3771
+
3772
+ // ../sdk/plugin-ts/dist/bpp/channel-results.js
3773
+ var invalidResult = (field) => new BorgeeError(`Invalid channel result: ${field}`, "bpp.invalid_result");
3774
+ var record = (value, field) => {
3775
+ if (!value || typeof value !== "object" || Array.isArray(value))
3776
+ throw invalidResult(field);
3777
+ return value;
3778
+ };
3779
+ var requiredString = (value, field) => {
3780
+ if (typeof value !== "string")
3781
+ throw invalidResult(field);
3782
+ return value;
3783
+ };
3784
+ var decodeChannel = (value) => {
3785
+ const channel = record(value, "channel");
3786
+ if (!isZoneId(channel.id))
3787
+ throw invalidResult("id");
3788
+ if (channel.parent_id !== null && !isZoneId(channel.parent_id))
3789
+ throw invalidResult("parent_id");
3790
+ return {
3791
+ id: channel.id,
3792
+ name: requiredString(channel.name, "name"),
3793
+ kind: requiredString(channel.kind, "kind"),
3794
+ parentId: channel.parent_id
3795
+ };
3796
+ };
3797
+ var decodeChannels = (value) => {
3798
+ if (!Array.isArray(value))
3799
+ throw invalidResult("channels");
3800
+ return value.map(decodeChannel);
3801
+ };
3802
+ var decodeZone = (value) => {
3803
+ const zone = record(value, "zone");
3804
+ if (!isZoneId(zone.id))
3805
+ throw invalidResult("zone.id");
3806
+ const name = requiredString(zone.name, "zone.name");
3807
+ if (new RegExp("^\\p{White_Space}|\\p{White_Space}$", "u").test(name) || [...name].length < 1 || [...name].length > 100 || /[\p{Cc}\p{Cs}]/u.test(name)) {
3808
+ throw invalidResult("zone.name");
3809
+ }
3810
+ return { id: zone.id, name };
3811
+ };
3812
+ var decodeZonePage = (value, input) => {
3813
+ const page = record(value, "Zone page");
3814
+ if (!Array.isArray(page.zones))
3815
+ throw invalidResult("zones");
3816
+ const limit = input?.limit ?? 20;
3817
+ if (page.zones.length > limit)
3818
+ throw invalidResult("zones exceed the requested limit");
3819
+ const zones = page.zones.map(decodeZone);
3820
+ let previous = input?.after;
3821
+ for (const zone of zones) {
3822
+ if (previous !== void 0 && zone.id <= previous)
3823
+ throw invalidResult("Zone IDs must follow the cursor in ascending order");
3824
+ previous = zone.id;
3825
+ }
3826
+ const nextAfter = page.next_after;
3827
+ if (nextAfter !== null) {
3828
+ if (!isZoneId(nextAfter) || zones.length !== limit || nextAfter !== zones.at(-1)?.id) {
3829
+ throw invalidResult("next_after must be null or the final ID of a full page");
3830
+ }
3831
+ }
3832
+ return { zones, nextAfter };
3833
+ };
3834
+
3746
3835
  // ../sdk/plugin-ts/dist/bpp/bpp-transport.js
3747
3836
  var PROTOCOL_VERSION = "bpp-1";
3748
3837
  var CONFIG_ACK_REASONS = /* @__PURE__ */ new Set([
@@ -3929,7 +4018,13 @@ var BppTransport = class {
3929
4018
  this.pending.delete(nonce);
3930
4019
  reject(new BorgeeError(`semantic_action ${action.op} timed out`, "bpp.action_timeout"));
3931
4020
  }, remaining);
3932
- this.pending.set(nonce, { op: action.op, resolve, reject, timer });
4021
+ this.pending.set(nonce, {
4022
+ op: action.op,
4023
+ ...action.op === "list_zones" ? { zoneInput: { guildId: action.guildId, limit: action.limit, after: action.after } } : {},
4024
+ resolve,
4025
+ reject,
4026
+ timer
4027
+ });
3933
4028
  this.send(frame);
3934
4029
  });
3935
4030
  }
@@ -4159,7 +4254,13 @@ var BppTransport = class {
4159
4254
  this.pending.delete(frame.nonce);
4160
4255
  clearTimeout(p.timer);
4161
4256
  if (frame.status === RESULT_STATUS_OK) {
4162
- const result = decodeActionResult(p.op, frame.payload);
4257
+ let result;
4258
+ try {
4259
+ result = decodeActionResult(p.op, frame.payload, p.zoneInput);
4260
+ } catch (error) {
4261
+ p.reject(error);
4262
+ return;
4263
+ }
4163
4264
  if (p.op === "get_me" && result && typeof result === "object") {
4164
4265
  const id = result.id;
4165
4266
  if (id)
@@ -4401,7 +4502,7 @@ var BppTransport = class {
4401
4502
  };
4402
4503
  this.send(frame);
4403
4504
  }
4404
- // sendActivity flattens the four-shape union onto one frame. The union is
4505
+ // sendActivity flattens the activity union onto one frame. The union is
4405
4506
  // how a producer says which shape it is reporting; the wire puts the
4406
4507
  // discriminator beside its payload with no wrapper object, matching both the
4407
4508
  // protocol this relays and inbound_message's own `kind`.
@@ -4443,6 +4544,9 @@ var BppTransport = class {
4443
4544
  frame.detail = action.activity.detail ?? "";
4444
4545
  frame.parent_id = action.activity.parentId ?? "";
4445
4546
  frame.subagent = action.activity.subagent ?? false;
4547
+ frame.background = action.activity.background;
4548
+ frame.background_state = action.activity.backgroundState;
4549
+ frame.follow_up_control = action.activity.followUpControl;
4446
4550
  frame.reason = action.activity.reason ?? "";
4447
4551
  if (action.activity.paths)
4448
4552
  frame.paths = action.activity.paths;
@@ -4464,12 +4568,25 @@ var BppTransport = class {
4464
4568
  frame.run_id = action.activity.runId;
4465
4569
  frame.label = action.activity.label;
4466
4570
  frame.run_state = action.activity.state;
4571
+ frame.parent_id = action.activity.parentId ?? "";
4467
4572
  frame.started_at = action.activity.startedAt;
4468
4573
  frame.finished_at = action.activity.finishedAt ?? 0;
4469
4574
  frame.reason = action.activity.reason ?? "";
4470
4575
  frame.stop_supported = action.activity.stopSupported;
4471
4576
  frame.stop_unsupported_reason = action.activity.stopUnsupportedReason ?? "";
4472
4577
  break;
4578
+ case "normalized":
4579
+ frame.protocol_version = action.activity.protocolVersion;
4580
+ frame.task_id = action.activity.taskId;
4581
+ frame.event = action.activity.event;
4582
+ break;
4583
+ }
4584
+ if (action.activity.shape === "normalized") {
4585
+ const ids = [frame.channel_id, frame.task_id];
4586
+ if (ids.some((value) => new TextEncoder().encode(value).byteLength > 256))
4587
+ return;
4588
+ if (new TextEncoder().encode(JSON.stringify(frame)).byteLength > 32768)
4589
+ return;
4473
4590
  }
4474
4591
  this.send(frame);
4475
4592
  }
@@ -4603,15 +4720,21 @@ function encodeActionPayload(action) {
4603
4720
  case "list_users":
4604
4721
  case "list_channels":
4605
4722
  return {};
4723
+ case "list_zones":
4724
+ validateListZonesInput(action);
4725
+ return { guild_id: action.guildId, limit: action.limit, after: action.after };
4606
4726
  case "create_channel":
4727
+ validateChannelPlacement(action.parentId);
4607
4728
  return {
4608
4729
  guild_id: action.guildId,
4609
4730
  name: action.name,
4731
+ parent_id: action.parentId,
4610
4732
  topic: action.topic,
4611
4733
  visibility: action.visibility,
4612
4734
  member_ids: action.memberIds
4613
4735
  };
4614
4736
  case "update_channel":
4737
+ rejectChannelPlacementUpdate(action);
4615
4738
  return {
4616
4739
  channel_id: action.channelId,
4617
4740
  name: action.name,
@@ -4655,7 +4778,20 @@ function encodeActionPayload(action) {
4655
4778
  return {};
4656
4779
  }
4657
4780
  }
4658
- function decodeActionResult(op, payloadJSON) {
4781
+ function decodeActionResult(op, payloadJSON, zoneInput) {
4782
+ if (op === "list_zones" || op === "list_channels" || op === "create_channel" || op === "update_channel") {
4783
+ let value;
4784
+ try {
4785
+ value = JSON.parse(payloadJSON);
4786
+ } catch (cause) {
4787
+ throw new BorgeeError("Invalid JSON in channel result", "bpp.invalid_result", { cause });
4788
+ }
4789
+ if (op === "list_zones")
4790
+ return decodeZonePage(value, zoneInput);
4791
+ if (op === "list_channels")
4792
+ return decodeChannels(value);
4793
+ return decodeChannel(value);
4794
+ }
4659
4795
  const p = safeParseJSON(payloadJSON);
4660
4796
  switch (op) {
4661
4797
  case "send_message":
@@ -4695,21 +4831,6 @@ function decodeActionResult(op, payloadJSON) {
4695
4831
  kind: u.kind === "agent" ? "agent" : "user"
4696
4832
  }));
4697
4833
  }
4698
- case "list_channels": {
4699
- const arr = Array.isArray(p) ? p : [];
4700
- return arr.map((c) => ({
4701
- id: String(c.id ?? ""),
4702
- name: String(c.name ?? ""),
4703
- kind: String(c.kind ?? "")
4704
- }));
4705
- }
4706
- case "create_channel":
4707
- case "update_channel":
4708
- return {
4709
- id: String(p.id ?? ""),
4710
- name: String(p.name ?? ""),
4711
- kind: String(p.kind ?? "")
4712
- };
4713
4834
  case "set_topic":
4714
4835
  case "delete_channel":
4715
4836
  return void 0;
@@ -5050,16 +5171,19 @@ var Client = class {
5050
5171
  return await this.t.perform({ op: "list_channels" });
5051
5172
  }
5052
5173
  async createChannel(input) {
5174
+ validateChannelPlacement(input.parentId);
5053
5175
  return await this.t.perform({
5054
5176
  op: "create_channel",
5055
5177
  guildId: input.guildId,
5056
5178
  name: input.name,
5179
+ parentId: input.parentId,
5057
5180
  topic: input.topic,
5058
5181
  visibility: input.visibility,
5059
5182
  memberIds: input.memberIds
5060
5183
  });
5061
5184
  }
5062
5185
  async updateChannel(input) {
5186
+ rejectChannelPlacementUpdate(input);
5063
5187
  return await this.t.perform({
5064
5188
  op: "update_channel",
5065
5189
  channelId: input.channelId,
@@ -5068,6 +5192,10 @@ var Client = class {
5068
5192
  archived: input.archived
5069
5193
  });
5070
5194
  }
5195
+ async listZones(input) {
5196
+ validateListZonesInput(input);
5197
+ return await this.t.perform({ op: "list_zones", ...input });
5198
+ }
5071
5199
  async setTopic(input) {
5072
5200
  await this.t.perform({ op: "set_topic", channelId: input.channelId, topic: input.topic });
5073
5201
  }