@cuylabs/agent-server 0.10.0

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.
package/dist/index.js ADDED
@@ -0,0 +1,2288 @@
1
+ // src/protocol/capabilities.ts
2
+ function createDefaultAgentServerCapabilities() {
3
+ return {
4
+ protocol: {
5
+ version: 1,
6
+ transport: "in-process",
7
+ reconnectable: false,
8
+ multiClient: true
9
+ },
10
+ sessions: {
11
+ persistent: true,
12
+ list: true,
13
+ create: true,
14
+ read: true,
15
+ delete: true,
16
+ branch: true
17
+ },
18
+ turns: {
19
+ start: true,
20
+ wait: true,
21
+ interrupt: true,
22
+ steer: true,
23
+ followUp: true,
24
+ followUpManagement: true,
25
+ eventStreaming: true,
26
+ concurrentTurnsPerSession: false
27
+ },
28
+ interactive: {
29
+ approvalRequests: true,
30
+ humanRequests: true
31
+ },
32
+ runtime: {
33
+ execution: "local",
34
+ orchestration: "direct",
35
+ durability: "process",
36
+ dapr: {
37
+ available: false,
38
+ delegatedTurns: false,
39
+ workflowBacked: false
40
+ }
41
+ },
42
+ agent: {
43
+ workspaceSummary: true,
44
+ toolInventory: false,
45
+ skillInspection: false,
46
+ subAgentInspection: false,
47
+ modelInspection: false,
48
+ modelSwitch: false,
49
+ runtimeStatus: false,
50
+ contextCompaction: false,
51
+ turnUndo: false,
52
+ turnDiff: false
53
+ },
54
+ plugins: {
55
+ headlessOnly: true,
56
+ commandMetadata: true,
57
+ commandDiscovery: false,
58
+ commandExecution: false,
59
+ clientExtensions: false
60
+ },
61
+ events: {
62
+ eventBus: false,
63
+ pubSubBridge: false,
64
+ sseStreaming: false,
65
+ eventReplay: false
66
+ }
67
+ };
68
+ }
69
+ function mergeAgentServerCapabilities(base, override) {
70
+ if (!override) {
71
+ return base;
72
+ }
73
+ const runtime = override.runtime ? {
74
+ ...base.runtime,
75
+ ...override.runtime,
76
+ dapr: {
77
+ ...base.runtime.dapr,
78
+ ...override.runtime.dapr ?? {}
79
+ }
80
+ } : base.runtime;
81
+ return {
82
+ ...base,
83
+ ...override.protocol ? { protocol: { ...base.protocol, ...override.protocol } } : {},
84
+ ...override.sessions ? { sessions: { ...base.sessions, ...override.sessions } } : {},
85
+ ...override.turns ? { turns: { ...base.turns, ...override.turns } } : {},
86
+ ...override.interactive ? { interactive: { ...base.interactive, ...override.interactive } } : {},
87
+ runtime,
88
+ ...override.agent ? { agent: { ...base.agent, ...override.agent } } : {},
89
+ ...override.plugins ? { plugins: { ...base.plugins, ...override.plugins } } : {},
90
+ ...override.events ? { events: { ...base.events, ...override.events } } : {}
91
+ };
92
+ }
93
+
94
+ // src/protocol/notifications.ts
95
+ function matchesNotificationSubscription(notification, options) {
96
+ if (!options) {
97
+ return true;
98
+ }
99
+ if (options.turnId) {
100
+ const notificationTurnId = notification.type === "turn/started" || notification.type === "turn/completed" ? notification.turn.id : notification.type === "turn/event" ? notification.turnId : notification.type === "input/request" ? notification.request.turnId : notification.type === "input/resolved" ? notification.turnId : void 0;
101
+ if (notificationTurnId !== options.turnId) {
102
+ return false;
103
+ }
104
+ }
105
+ if (options.teamId) {
106
+ const notificationTeamId = notification.type === "team/notification" ? notification.teamId : void 0;
107
+ if (notificationTeamId !== options.teamId) {
108
+ return false;
109
+ }
110
+ }
111
+ if (options.sessionId) {
112
+ const notificationSessionId = notification.type === "turn/started" || notification.type === "turn/completed" ? notification.turn.sessionId : notification.type === "turn/event" ? notification.sessionId : notification.type === "input/request" ? notification.request.sessionId : notification.type === "input/resolved" ? notification.sessionId : notification.type === "team/notification" ? void 0 : notification.type === "session/deleted" ? notification.sessionId : notification.type === "session/created" ? notification.session.id : notification.sessionId;
113
+ if (notificationSessionId !== options.sessionId) {
114
+ return false;
115
+ }
116
+ }
117
+ return true;
118
+ }
119
+
120
+ // src/protocol/adapter.ts
121
+ import { extractModelId, extractProvider, ensureSessionLoaded } from "@cuylabs/agent-core";
122
+ function createAgentServerAdapter(agent, options = {}) {
123
+ const interactiveAgent = agent;
124
+ const pluginCommands = options.pluginCommands ?? [];
125
+ const pluginCommandInfos = pluginCommands.map((command) => ({
126
+ pluginId: command.pluginId,
127
+ name: command.name,
128
+ alias: [...command.alias],
129
+ summary: command.summary,
130
+ ...command.metadata ? { metadata: command.metadata } : {}
131
+ }));
132
+ const pluginCommandTokens = /* @__PURE__ */ new Map();
133
+ for (const command of pluginCommands) {
134
+ pluginCommandTokens.set(normalizePluginCommandToken(command.name), command);
135
+ for (const alias of command.alias) {
136
+ pluginCommandTokens.set(normalizePluginCommandToken(alias), command);
137
+ }
138
+ }
139
+ let currentModelLabel = options.currentModelLabel?.trim() || null;
140
+ let lastTrackedTurnSessionId = null;
141
+ const humanInputController = interactiveAgent.getHumanInputController();
142
+ const hasHumanInput = Boolean(
143
+ humanInputController && interactiveAgent.hasHumanInputTools()
144
+ );
145
+ const getModelState = currentModelLabel !== null ? async () => ({
146
+ label: currentModelLabel ?? "server-managed",
147
+ switchable: Boolean(options.switchModel)
148
+ }) : void 0;
149
+ const switchModel = options.switchModel ? async (spec) => {
150
+ const result = await options.switchModel(spec);
151
+ if (result.ok) {
152
+ currentModelLabel = result.label;
153
+ }
154
+ return result;
155
+ } : void 0;
156
+ const listTools = async () => agent.getToolIds().map((id) => ({ id })).sort((left, right) => left.id.localeCompare(right.id));
157
+ const listSkills = options.listSkills ? async () => [...await options.listSkills()].sort(
158
+ (left, right) => left.name.localeCompare(right.name)
159
+ ) : void 0;
160
+ const listSubAgents = options.listSubAgents ? async () => [...await options.listSubAgents()].sort(
161
+ (left, right) => left.name.localeCompare(right.name)
162
+ ) : void 0;
163
+ async function ensureActionSessionLoaded(sessionId) {
164
+ await ensureSessionLoaded({
165
+ sessionId,
166
+ sessions: agent.getSessionManager(),
167
+ cwd: agent.cwd
168
+ });
169
+ }
170
+ const compactSessionContext = async (sessionId) => {
171
+ await ensureActionSessionLoaded(sessionId);
172
+ return await agent.compactContext();
173
+ };
174
+ const undoSessionTurn = async (sessionId) => {
175
+ if (lastTrackedTurnSessionId !== sessionId) {
176
+ return { restored: [], failed: [] };
177
+ }
178
+ return await agent.undoTurn();
179
+ };
180
+ const getSessionTurnDiff = async (sessionId) => {
181
+ if (lastTrackedTurnSessionId !== sessionId) {
182
+ return null;
183
+ }
184
+ return await agent.getTurnDiff();
185
+ };
186
+ return {
187
+ cwd: agent.cwd,
188
+ getCapabilities: () => mergeAgentServerCapabilities(
189
+ mergeAgentServerCapabilities(createDefaultAgentServerCapabilities(), {
190
+ protocol: { transport: "in-process", reconnectable: false, multiClient: true },
191
+ turns: { steer: true, followUp: true, followUpManagement: true },
192
+ interactive: { approvalRequests: true, humanRequests: hasHumanInput },
193
+ runtime: {
194
+ execution: "local",
195
+ orchestration: "direct",
196
+ durability: "process",
197
+ dapr: {
198
+ available: false,
199
+ delegatedTurns: false,
200
+ workflowBacked: false
201
+ }
202
+ },
203
+ agent: {
204
+ workspaceSummary: true,
205
+ toolInventory: true,
206
+ skillInspection: Boolean(listSkills),
207
+ subAgentInspection: Boolean(listSubAgents),
208
+ modelInspection: Boolean(getModelState),
209
+ modelSwitch: Boolean(switchModel),
210
+ runtimeStatus: true,
211
+ contextCompaction: true,
212
+ turnUndo: true,
213
+ turnDiff: true
214
+ },
215
+ plugins: {
216
+ headlessOnly: true,
217
+ commandMetadata: true,
218
+ commandDiscovery: true,
219
+ commandExecution: true,
220
+ clientExtensions: false
221
+ }
222
+ }),
223
+ options.capabilities
224
+ ),
225
+ getModelState,
226
+ switchModel,
227
+ async getSessionRuntimeStatus(sessionId) {
228
+ await ensureActionSessionLoaded(sessionId);
229
+ const manager = agent.getSessionManager();
230
+ const loadedId = manager.getSessionId() ?? void 0;
231
+ const leafId = manager.getLeafId() ?? void 0;
232
+ const header = manager.getHeader();
233
+ const modelId = extractModelId(agent.model);
234
+ const provider = extractProvider(agent.model);
235
+ return {
236
+ session: {
237
+ selectedId: sessionId,
238
+ ...loadedId ? { loadedId } : {},
239
+ messageCount: agent.getMessages().length,
240
+ ...leafId ? { leafId } : {},
241
+ ...header?.cwd ? { cwd: header.cwd } : {},
242
+ ...header?.title ? { title: header.title } : {}
243
+ },
244
+ model: {
245
+ id: modelId,
246
+ ...provider ? { provider } : {}
247
+ },
248
+ context: agent.getContextStats()
249
+ };
250
+ },
251
+ listTools,
252
+ listSkills,
253
+ listSubAgents,
254
+ ...humanInputController ? {
255
+ async respondToInputRequest(requestId, payload) {
256
+ if (payload.kind !== "human") {
257
+ return;
258
+ }
259
+ await humanInputController.respondToRequest(
260
+ requestId,
261
+ payload.response
262
+ );
263
+ }
264
+ } : {},
265
+ ...options.listTeamNotifications ? {
266
+ listTeamNotifications(optionsArg) {
267
+ return options.listTeamNotifications(optionsArg);
268
+ }
269
+ } : {},
270
+ ...options.subscribeTeamNotifications ? {
271
+ subscribeTeamNotifications(listener) {
272
+ return options.subscribeTeamNotifications(listener);
273
+ }
274
+ } : {},
275
+ compactSessionContext,
276
+ undoSessionTurn,
277
+ getSessionTurnDiff,
278
+ async *chat(sessionId, message, chatOptions) {
279
+ lastTrackedTurnSessionId = sessionId;
280
+ for await (const event of agent.chat(sessionId, message, {
281
+ ...chatOptions?.abort ? { abort: chatOptions.abort } : {},
282
+ ...chatOptions?.system ? { system: chatOptions.system } : {}
283
+ })) {
284
+ yield event;
285
+ }
286
+ },
287
+ steer: ({ message }) => {
288
+ const id = agent.intervene(message);
289
+ return { id, accepted: true };
290
+ },
291
+ followUp: ({ message }) => {
292
+ const id = agent.queueNext(message);
293
+ return { id, accepted: true };
294
+ },
295
+ drainQueuedFollowUps: () => agent.drainQueuedNext(),
296
+ listSessions: () => agent.listSessions(),
297
+ deleteSession: (sessionId) => agent.deleteSession(sessionId),
298
+ getSessionStorage: () => agent.getSessionManager().getStorage(),
299
+ listPluginCommands: () => pluginCommandInfos,
300
+ executePluginCommand: async (name, args) => {
301
+ const command = pluginCommandTokens.get(normalizePluginCommandToken(name));
302
+ if (!command) {
303
+ throw new Error(`Plugin command not found: ${name}`);
304
+ }
305
+ return await command.execute(args);
306
+ }
307
+ };
308
+ }
309
+ function normalizePluginCommandToken(token) {
310
+ return token.trim().replace(/^\//, "").toLowerCase();
311
+ }
312
+
313
+ // src/protocol/wire.ts
314
+ function isAgentServerWireEnvelope(value) {
315
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
316
+ return false;
317
+ }
318
+ const record = value;
319
+ return record.kind === "request" || record.kind === "response" || record.kind === "notification";
320
+ }
321
+
322
+ // src/runtime/in-process-server.ts
323
+ import {
324
+ SessionManager,
325
+ canSeedQueuedFollowUp,
326
+ createQueuedFollowUpRecord,
327
+ markQueuedFollowUpApplied,
328
+ resolveQueuedFollowUp,
329
+ shouldCascadeApprovalDecision
330
+ } from "@cuylabs/agent-core";
331
+
332
+ // src/runtime/helpers.ts
333
+ function nowIso() {
334
+ return (/* @__PURE__ */ new Date()).toISOString();
335
+ }
336
+ function isTerminalTurnStatus(status) {
337
+ return status === "completed" || status === "failed" || status === "interrupted";
338
+ }
339
+ function isAbortError(error, signal) {
340
+ if (signal.aborted) {
341
+ return true;
342
+ }
343
+ if (error instanceof DOMException) {
344
+ return error.name === "AbortError";
345
+ }
346
+ if (error instanceof Error) {
347
+ return error.name === "AbortError" || /aborted/i.test(error.message);
348
+ }
349
+ return false;
350
+ }
351
+ function getDerivedParentSessionId(sessionId) {
352
+ const marker = ":sub:";
353
+ const index = sessionId.lastIndexOf(marker);
354
+ if (index <= 0) {
355
+ return void 0;
356
+ }
357
+ return sessionId.slice(0, index);
358
+ }
359
+ function toSessionSummary(info, runtime) {
360
+ return {
361
+ id: info.id,
362
+ cwd: info.cwd,
363
+ createdAt: info.createdAt.toISOString(),
364
+ updatedAt: info.updatedAt.toISOString(),
365
+ messageCount: info.messageCount,
366
+ ...info.title ? { title: info.title } : {},
367
+ ...info.name ? { name: info.name } : {},
368
+ ...info.parentSessionId ? { parentSessionId: info.parentSessionId } : {},
369
+ ...info.firstMessage ? { preview: info.firstMessage } : {},
370
+ runtime
371
+ };
372
+ }
373
+ function toSessionSummaryFromDetail(detail) {
374
+ return {
375
+ id: detail.id,
376
+ cwd: detail.cwd,
377
+ createdAt: detail.createdAt,
378
+ updatedAt: detail.updatedAt,
379
+ messageCount: detail.messageCount,
380
+ ...detail.title ? { title: detail.title } : {},
381
+ ...detail.name ? { name: detail.name } : {},
382
+ ...detail.parentSessionId ? { parentSessionId: detail.parentSessionId } : {},
383
+ ...detail.preview ? { preview: detail.preview } : {},
384
+ runtime: detail.runtime
385
+ };
386
+ }
387
+ function getNotificationChannel(notification) {
388
+ switch (notification.type) {
389
+ case "session/created":
390
+ return `session:${notification.session.id}`;
391
+ case "session/deleted":
392
+ return `session:${notification.sessionId}`;
393
+ case "session/branched":
394
+ case "session/runtime":
395
+ return `session:${notification.sessionId}`;
396
+ case "turn/started":
397
+ case "turn/completed":
398
+ return `session:${notification.turn.sessionId}`;
399
+ case "turn/event":
400
+ return `session:${notification.sessionId}`;
401
+ case "input/request":
402
+ return `session:${notification.request.sessionId}`;
403
+ case "input/resolved":
404
+ return `session:${notification.sessionId}`;
405
+ case "team/notification":
406
+ return `team:${notification.teamId}`;
407
+ default:
408
+ return null;
409
+ }
410
+ }
411
+
412
+ // src/runtime/in-process-server.ts
413
+ var InProcessAgentServer = class {
414
+ adapter;
415
+ capabilities;
416
+ followUpPolicy;
417
+ eventBus;
418
+ turns = /* @__PURE__ */ new Map();
419
+ followUps = /* @__PURE__ */ new Map();
420
+ sessionRuntime = /* @__PURE__ */ new Map();
421
+ listeners = /* @__PURE__ */ new Set();
422
+ pendingInputs = /* @__PURE__ */ new Map();
423
+ teamNotificationUnsubscribe = null;
424
+ constructor(adapter, options = {}) {
425
+ this.adapter = adapter;
426
+ this.followUpPolicy = options.followUpPolicy ?? adapter.followUpPolicy ?? {};
427
+ this.eventBus = options.eventBus ?? null;
428
+ let capabilities = mergeAgentServerCapabilities(
429
+ createDefaultAgentServerCapabilities(),
430
+ adapter.getCapabilities?.()
431
+ );
432
+ if (this.eventBus) {
433
+ capabilities = mergeAgentServerCapabilities(capabilities, {
434
+ events: { eventBus: true, eventReplay: true }
435
+ });
436
+ }
437
+ this.capabilities = capabilities;
438
+ if (this.adapter.subscribeTeamNotifications) {
439
+ this.teamNotificationUnsubscribe = this.adapter.subscribeTeamNotifications(
440
+ (notification) => {
441
+ this.emit({
442
+ type: "team/notification",
443
+ teamId: notification.teamId,
444
+ notification
445
+ });
446
+ }
447
+ );
448
+ }
449
+ }
450
+ getCapabilities() {
451
+ return this.capabilities;
452
+ }
453
+ async getWorkspaceSummary() {
454
+ const [sessions, modelState, tools, skills, subAgents] = await Promise.all([
455
+ this.listSessions(),
456
+ this.getModelState().catch(() => null),
457
+ this.listTools(),
458
+ this.listSkills(),
459
+ this.listSubAgents()
460
+ ]);
461
+ return {
462
+ workspace: {
463
+ cwd: this.adapter.cwd,
464
+ sessionCount: sessions.length
465
+ },
466
+ agent: {
467
+ modelLabel: modelState?.label ?? "server-managed",
468
+ switchable: modelState?.switchable ?? false,
469
+ toolCount: tools.length,
470
+ skillCount: skills.length,
471
+ subAgentCount: subAgents.length
472
+ }
473
+ };
474
+ }
475
+ async getSessionRuntimeStatus(sessionId) {
476
+ if (!this.adapter.getSessionRuntimeStatus) {
477
+ throw new Error("Runtime status is not available on this agent-server.");
478
+ }
479
+ return await this.adapter.getSessionRuntimeStatus(sessionId);
480
+ }
481
+ async getModelState() {
482
+ if (!this.adapter.getModelState) {
483
+ throw new Error("Model inspection is not available on this agent-server.");
484
+ }
485
+ return await this.adapter.getModelState();
486
+ }
487
+ async switchModel(spec) {
488
+ if (!this.adapter.switchModel) {
489
+ throw new Error("Model switching is not available on this agent-server.");
490
+ }
491
+ return await this.adapter.switchModel(spec);
492
+ }
493
+ async listTools() {
494
+ return [...await this.adapter.listTools?.() ?? []].sort(
495
+ (left, right) => left.id.localeCompare(right.id)
496
+ );
497
+ }
498
+ async listSkills() {
499
+ return [...await this.adapter.listSkills?.() ?? []].sort(
500
+ (left, right) => left.name.localeCompare(right.name)
501
+ );
502
+ }
503
+ async listSubAgents() {
504
+ return [...await this.adapter.listSubAgents?.() ?? []].sort(
505
+ (left, right) => left.name.localeCompare(right.name)
506
+ );
507
+ }
508
+ async listPluginCommands() {
509
+ return [...await this.adapter.listPluginCommands?.() ?? []].sort(
510
+ (left, right) => left.name.localeCompare(right.name)
511
+ );
512
+ }
513
+ async executePluginCommand(name, args) {
514
+ if (!this.adapter.executePluginCommand) {
515
+ throw new Error("Plugin command execution is not available on this agent-server.");
516
+ }
517
+ return await this.adapter.executePluginCommand(name, args);
518
+ }
519
+ async compactSessionContext(sessionId) {
520
+ if (!this.adapter.compactSessionContext) {
521
+ throw new Error("Context compaction is not available on this agent-server.");
522
+ }
523
+ return await this.adapter.compactSessionContext(sessionId);
524
+ }
525
+ async undoSessionTurn(sessionId) {
526
+ if (!this.adapter.undoSessionTurn) {
527
+ throw new Error("Turn undo is not available on this agent-server.");
528
+ }
529
+ return await this.adapter.undoSessionTurn(sessionId);
530
+ }
531
+ async getSessionTurnDiff(sessionId) {
532
+ if (!this.adapter.getSessionTurnDiff) {
533
+ throw new Error("Turn diff is not available on this agent-server.");
534
+ }
535
+ return await this.adapter.getSessionTurnDiff(sessionId);
536
+ }
537
+ async listSessions() {
538
+ const infos = await this.adapter.listSessions();
539
+ return infos.map((info) => toSessionSummary(info, this.getRuntimeSnapshot(info.id))).sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
540
+ }
541
+ async createSession(options = {}) {
542
+ const manager = new SessionManager(this.adapter.getSessionStorage());
543
+ const id = await manager.create({
544
+ ...options.id ? { id: options.id } : {},
545
+ cwd: options.cwd ?? this.adapter.cwd,
546
+ ...options.title ? { title: options.title } : {}
547
+ });
548
+ const detail = await this.readSessionOrThrow(id);
549
+ this.emit({ type: "session/created", session: toSessionSummaryFromDetail(detail) });
550
+ return detail;
551
+ }
552
+ async readSession(sessionId) {
553
+ const manager = new SessionManager(this.adapter.getSessionStorage());
554
+ if (!await manager.sessionExists(sessionId)) {
555
+ return null;
556
+ }
557
+ await manager.load(sessionId);
558
+ const info = await this.findSessionInfo(sessionId);
559
+ const context = manager.getContext();
560
+ return {
561
+ id: sessionId,
562
+ cwd: context.metadata.cwd,
563
+ createdAt: info?.createdAt.toISOString() ?? nowIso(),
564
+ updatedAt: info?.updatedAt.toISOString() ?? nowIso(),
565
+ messageCount: info?.messageCount ?? context.messages.length,
566
+ ...context.metadata.title ? { title: context.metadata.title } : {},
567
+ ...context.metadata.name ? { name: context.metadata.name } : {},
568
+ ...info?.parentSessionId ? { parentSessionId: info.parentSessionId } : {},
569
+ ...info?.firstMessage ? { preview: info.firstMessage } : {},
570
+ ...context.leafId ? { leafId: context.leafId } : {},
571
+ entries: manager.getEntries(),
572
+ messages: context.messages,
573
+ runtime: this.getRuntimeSnapshot(sessionId)
574
+ };
575
+ }
576
+ async deleteSession(sessionId) {
577
+ const runtime = this.sessionRuntime.get(sessionId);
578
+ if (runtime && runtime.activeTurnIds.size > 0) {
579
+ throw new Error(`Cannot delete session "${sessionId}" while turns are running.`);
580
+ }
581
+ const deleted = await this.adapter.deleteSession(sessionId);
582
+ if (deleted) {
583
+ this.sessionRuntime.delete(sessionId);
584
+ this.emit({ type: "session/deleted", sessionId });
585
+ }
586
+ return deleted;
587
+ }
588
+ async branchSession(sessionId, options = {}) {
589
+ const manager = new SessionManager(this.adapter.getSessionStorage());
590
+ if (!await manager.sessionExists(sessionId)) {
591
+ throw new Error(`Session not found: ${sessionId}`);
592
+ }
593
+ await manager.load(sessionId);
594
+ if (options.leafId) {
595
+ manager.switchToLeaf(options.leafId);
596
+ }
597
+ const branchFrom = manager.getLeafId();
598
+ if (!branchFrom) {
599
+ throw new Error(`Session "${sessionId}" has no leaf to branch from.`);
600
+ }
601
+ const leafId = await manager.branch(branchFrom, options.summary);
602
+ this.emit({
603
+ type: "session/branched",
604
+ sessionId,
605
+ leafId,
606
+ ...options.summary ? { summary: options.summary } : {}
607
+ });
608
+ return { sessionId, leafId };
609
+ }
610
+ async startTurn(sessionId, message, options = {}) {
611
+ if (this.hasRunningTurnForSession(sessionId)) {
612
+ throw new Error(`Session "${sessionId}" already has a running turn.`);
613
+ }
614
+ await this.ensureSessionExists(sessionId);
615
+ const turn = {
616
+ id: globalThis.crypto.randomUUID(),
617
+ sessionId,
618
+ message,
619
+ startedAt: nowIso(),
620
+ status: "running",
621
+ events: [],
622
+ queuedFollowUpIds: [],
623
+ abortController: new AbortController()
624
+ };
625
+ await this.seedQueuedFollowUps(sessionId, turn.id);
626
+ this.turns.set(turn.id, turn);
627
+ const runtime = this.getOrCreateRuntimeRecord(sessionId);
628
+ runtime.activeTurnIds.add(turn.id);
629
+ runtime.lastTurnId = turn.id;
630
+ this.emit({
631
+ type: "session/runtime",
632
+ sessionId,
633
+ runtime: this.getRuntimeSnapshot(sessionId)
634
+ });
635
+ this.emit({ type: "turn/started", turn: this.toTurnSnapshot(turn) });
636
+ void this.runTurn(turn, options);
637
+ return this.toTurnSnapshot(turn);
638
+ }
639
+ getTurn(turnId) {
640
+ const turn = this.turns.get(turnId);
641
+ return turn ? this.toTurnDetail(turn) : null;
642
+ }
643
+ async waitForTurn(turnId, options = {}) {
644
+ const turn = this.turns.get(turnId);
645
+ if (!turn) {
646
+ throw new Error(`Turn not found: ${turnId}`);
647
+ }
648
+ if (isTerminalTurnStatus(turn.status)) {
649
+ return this.toTurnSnapshot(turn);
650
+ }
651
+ return await new Promise((resolve, reject) => {
652
+ const off = this.subscribe(
653
+ (notification) => {
654
+ if (notification.type === "turn/completed" && notification.turn.id === turnId) {
655
+ cleanup();
656
+ resolve(notification.turn);
657
+ }
658
+ },
659
+ { turnId }
660
+ );
661
+ const timeoutMs = options.timeoutMs ?? 0;
662
+ const timer = timeoutMs > 0 ? setTimeout(() => {
663
+ cleanup();
664
+ reject(new Error(`Timed out waiting for turn "${turnId}"`));
665
+ }, timeoutMs) : null;
666
+ const cleanup = () => {
667
+ off();
668
+ if (timer) {
669
+ clearTimeout(timer);
670
+ }
671
+ };
672
+ });
673
+ }
674
+ steerTurn(turnId, message) {
675
+ const turn = this.turns.get(turnId);
676
+ if (!turn || isTerminalTurnStatus(turn.status) || !this.adapter.steer) {
677
+ return { id: "", accepted: false };
678
+ }
679
+ return this.adapter.steer({
680
+ turnId,
681
+ sessionId: turn.sessionId,
682
+ message
683
+ });
684
+ }
685
+ followUpTurn(turnId, message) {
686
+ const turn = this.turns.get(turnId);
687
+ if (!turn || isTerminalTurnStatus(turn.status) || !this.adapter.followUp) {
688
+ return { id: "", accepted: false };
689
+ }
690
+ const response = this.adapter.followUp({
691
+ turnId,
692
+ sessionId: turn.sessionId,
693
+ message
694
+ });
695
+ if (!response.accepted) {
696
+ return response;
697
+ }
698
+ const record = createQueuedFollowUpRecord(
699
+ {
700
+ id: response.id,
701
+ sessionId: turn.sessionId,
702
+ message,
703
+ timestamp: Date.now()
704
+ },
705
+ {
706
+ mode: this.followUpPolicy.mode,
707
+ sourceTurnId: turn.id,
708
+ createdAt: nowIso(),
709
+ updatedAt: nowIso()
710
+ }
711
+ );
712
+ this.followUps.set(record.id, record);
713
+ if (!turn.queuedFollowUpIds.includes(record.id)) {
714
+ turn.queuedFollowUpIds.push(record.id);
715
+ }
716
+ return response;
717
+ }
718
+ async listFollowUps(options = {}) {
719
+ if (this.adapter.listFollowUps) {
720
+ return await this.adapter.listFollowUps(options);
721
+ }
722
+ const statuses = options.status ? new Set(Array.isArray(options.status) ? options.status : [options.status]) : void 0;
723
+ return [...this.followUps.values()].filter((record) => {
724
+ if (options.sessionId && record.sessionId !== options.sessionId) {
725
+ return false;
726
+ }
727
+ if (options.sourceTurnId && record.sourceTurnId !== options.sourceTurnId) {
728
+ return false;
729
+ }
730
+ if (statuses && !statuses.has(record.status)) {
731
+ return false;
732
+ }
733
+ return true;
734
+ }).sort((left, right) => left.createdAt.localeCompare(right.createdAt)).map((record) => structuredClone(record));
735
+ }
736
+ async getFollowUp(followUpId) {
737
+ if (this.adapter.getFollowUp) {
738
+ return await this.adapter.getFollowUp(followUpId);
739
+ }
740
+ const record = this.followUps.get(followUpId);
741
+ return record ? structuredClone(record) : null;
742
+ }
743
+ async resolveFollowUp(followUpId, action) {
744
+ if (this.adapter.resolveFollowUp) {
745
+ const resolved = await this.adapter.resolveFollowUp(followUpId, action);
746
+ if (resolved) {
747
+ this.followUps.set(followUpId, structuredClone(resolved));
748
+ }
749
+ return resolved;
750
+ }
751
+ const existing = this.followUps.get(followUpId);
752
+ if (!existing || existing.status === "discarded" || existing.status === "applied") {
753
+ return existing ? structuredClone(existing) : null;
754
+ }
755
+ const updated = resolveQueuedFollowUp(existing, action, nowIso());
756
+ this.followUps.set(followUpId, updated);
757
+ return structuredClone(updated);
758
+ }
759
+ async listTeamNotifications(options = {}) {
760
+ if (!this.adapter.listTeamNotifications) {
761
+ return [];
762
+ }
763
+ const kinds = options.kind ? new Set(Array.isArray(options.kind) ? options.kind : [options.kind]) : void 0;
764
+ return [...await this.adapter.listTeamNotifications(options)].filter((notification) => {
765
+ if (options.teamId && notification.teamId !== options.teamId) {
766
+ return false;
767
+ }
768
+ if (options.memberId && notification.memberId !== options.memberId) {
769
+ return false;
770
+ }
771
+ if (kinds && !kinds.has(notification.kind)) {
772
+ return false;
773
+ }
774
+ return true;
775
+ }).sort((left, right) => left.createdAt.localeCompare(right.createdAt)).map((notification) => structuredClone(notification));
776
+ }
777
+ interruptTurn(turnId) {
778
+ const turn = this.turns.get(turnId);
779
+ if (!turn || isTerminalTurnStatus(turn.status)) {
780
+ return false;
781
+ }
782
+ this.resolvePendingInputsForTurn(turnId, "deny");
783
+ void this.adapter.interruptTurn?.({
784
+ turnId,
785
+ sessionId: turn.sessionId
786
+ });
787
+ turn.abortController.abort();
788
+ return true;
789
+ }
790
+ respondToInputRequest(requestId, payload) {
791
+ const pending = this.pendingInputs.get(requestId);
792
+ if (!pending || pending.resolved) {
793
+ return false;
794
+ }
795
+ if (pending.resolve) {
796
+ this.resolvePendingInput(requestId, pending, payload);
797
+ } else {
798
+ pending.resolved = true;
799
+ this.pendingInputs.delete(requestId);
800
+ void this.adapter.respondToInputRequest?.(requestId, payload);
801
+ this.emit({
802
+ type: "input/resolved",
803
+ requestId,
804
+ sessionId: pending.request.sessionId,
805
+ ...pending.request.turnId ? { turnId: pending.request.turnId } : {}
806
+ });
807
+ }
808
+ if (payload.kind === "approval" && pending.request.kind === "approval") {
809
+ this.resolveMatchingApprovalRequests(
810
+ pending.request.request,
811
+ {
812
+ action: payload.action,
813
+ ...payload.feedback ? { feedback: payload.feedback } : {},
814
+ ...payload.rememberScope ? { rememberScope: payload.rememberScope } : {}
815
+ }
816
+ );
817
+ }
818
+ return true;
819
+ }
820
+ subscribe(listener, options) {
821
+ const record = { listener, options };
822
+ this.listeners.add(record);
823
+ return () => {
824
+ this.listeners.delete(record);
825
+ };
826
+ }
827
+ async close() {
828
+ this.teamNotificationUnsubscribe?.();
829
+ this.resolveAllPendingInputs("deny");
830
+ const running = [...this.turns.values()].filter(
831
+ (turn) => !isTerminalTurnStatus(turn.status)
832
+ );
833
+ for (const turn of running) {
834
+ turn.abortController.abort();
835
+ }
836
+ await Promise.allSettled(
837
+ running.map((turn) => this.waitForTurn(turn.id, { timeoutMs: 2e3 }))
838
+ );
839
+ this.listeners.clear();
840
+ }
841
+ requestApproval(request) {
842
+ const route = this.resolveInteractiveRequestRoute(request.sessionId);
843
+ const inputRequest = {
844
+ id: request.id,
845
+ kind: "approval",
846
+ sessionId: route.sessionId,
847
+ ...route.turnId ? { turnId: route.turnId } : {},
848
+ createdAt: nowIso(),
849
+ request
850
+ };
851
+ return new Promise((resolve) => {
852
+ this.pendingInputs.set(request.id, {
853
+ request: inputRequest,
854
+ resolve,
855
+ resolved: false
856
+ });
857
+ this.emit({ type: "input/request", request: inputRequest });
858
+ });
859
+ }
860
+ requestHumanInput(request) {
861
+ const route = this.resolveInteractiveRequestRoute(request.sessionId);
862
+ const inputRequest = {
863
+ id: request.id,
864
+ kind: "human",
865
+ sessionId: route.sessionId,
866
+ ...route.turnId ? { turnId: route.turnId } : {},
867
+ createdAt: nowIso(),
868
+ request
869
+ };
870
+ return new Promise((resolve) => {
871
+ this.pendingInputs.set(request.id, {
872
+ request: inputRequest,
873
+ resolve,
874
+ resolved: false
875
+ });
876
+ this.emit({ type: "input/request", request: inputRequest });
877
+ });
878
+ }
879
+ async runTurn(turn, options) {
880
+ try {
881
+ let sequence = 0;
882
+ for await (const event of this.adapter.chat(turn.sessionId, turn.message, {
883
+ abort: turn.abortController.signal,
884
+ ...options.system ? { system: options.system } : {}
885
+ })) {
886
+ const record = {
887
+ sequence: ++sequence,
888
+ timestamp: nowIso(),
889
+ event
890
+ };
891
+ turn.events.push(record);
892
+ switch (event.type) {
893
+ case "approval-request":
894
+ this.registerEventInputRequest(turn, event);
895
+ break;
896
+ case "human-input-request":
897
+ this.registerEventInputRequest(turn, event);
898
+ break;
899
+ case "approval-resolved":
900
+ this.resolveEventInputRequest(turn, event.id);
901
+ break;
902
+ case "human-input-resolved":
903
+ this.resolveEventInputRequest(turn, event.id);
904
+ break;
905
+ case "complete":
906
+ turn.output = event.output;
907
+ turn.usage = event.usage;
908
+ break;
909
+ case "error":
910
+ turn.error = event.error.message;
911
+ break;
912
+ }
913
+ this.emit({
914
+ type: "turn/event",
915
+ sessionId: turn.sessionId,
916
+ turnId: turn.id,
917
+ sequence: record.sequence,
918
+ timestamp: record.timestamp,
919
+ event
920
+ });
921
+ }
922
+ if (turn.status === "running") {
923
+ turn.status = turn.abortController.signal.aborted ? "interrupted" : turn.error ? "failed" : "completed";
924
+ }
925
+ } catch (error) {
926
+ if (isAbortError(error, turn.abortController.signal)) {
927
+ turn.status = "interrupted";
928
+ } else {
929
+ turn.status = "failed";
930
+ turn.error = error instanceof Error ? error.message : String(error);
931
+ }
932
+ } finally {
933
+ this.captureDeferredFollowUps(turn);
934
+ this.resolvePendingInputsForTurn(turn.id, "deny");
935
+ turn.completedAt = nowIso();
936
+ const runtime = this.getOrCreateRuntimeRecord(turn.sessionId);
937
+ runtime.activeTurnIds.delete(turn.id);
938
+ runtime.lastTurnId = turn.id;
939
+ this.emit({
940
+ type: "session/runtime",
941
+ sessionId: turn.sessionId,
942
+ runtime: this.getRuntimeSnapshot(turn.sessionId)
943
+ });
944
+ this.emit({ type: "turn/completed", turn: this.toTurnSnapshot(turn) });
945
+ }
946
+ }
947
+ async ensureSessionExists(sessionId) {
948
+ const manager = new SessionManager(this.adapter.getSessionStorage());
949
+ if (await manager.sessionExists(sessionId)) {
950
+ return;
951
+ }
952
+ await manager.create({ id: sessionId, cwd: this.adapter.cwd });
953
+ const detail = await this.readSessionOrThrow(sessionId);
954
+ this.emit({ type: "session/created", session: toSessionSummaryFromDetail(detail) });
955
+ }
956
+ getOrCreateRuntimeRecord(sessionId) {
957
+ let record = this.sessionRuntime.get(sessionId);
958
+ if (!record) {
959
+ record = { activeTurnIds: /* @__PURE__ */ new Set() };
960
+ this.sessionRuntime.set(sessionId, record);
961
+ }
962
+ return record;
963
+ }
964
+ getRuntimeSnapshot(sessionId) {
965
+ const runtime = this.sessionRuntime.get(sessionId);
966
+ const activeTurnIds = runtime ? [...runtime.activeTurnIds] : [];
967
+ return {
968
+ status: activeTurnIds.length > 0 ? "running" : "idle",
969
+ activeTurnCount: activeTurnIds.length,
970
+ activeTurnIds,
971
+ ...runtime?.lastTurnId ? { lastTurnId: runtime.lastTurnId } : {}
972
+ };
973
+ }
974
+ hasRunningTurnForSession(sessionId) {
975
+ const runtime = this.sessionRuntime.get(sessionId);
976
+ return Boolean(runtime && runtime.activeTurnIds.size > 0);
977
+ }
978
+ findRunningTurnIdForSession(sessionId) {
979
+ const runtime = this.sessionRuntime.get(sessionId);
980
+ if (!runtime) {
981
+ return void 0;
982
+ }
983
+ const [turnId] = runtime.activeTurnIds;
984
+ return turnId;
985
+ }
986
+ resolveInteractiveRequestRoute(sessionId) {
987
+ let currentSessionId = sessionId;
988
+ const visited = /* @__PURE__ */ new Set();
989
+ while (!visited.has(currentSessionId)) {
990
+ visited.add(currentSessionId);
991
+ const turnId = this.findRunningTurnIdForSession(currentSessionId);
992
+ if (turnId) {
993
+ return { sessionId: currentSessionId, turnId };
994
+ }
995
+ const parentSessionId = getDerivedParentSessionId(currentSessionId);
996
+ if (!parentSessionId) {
997
+ break;
998
+ }
999
+ currentSessionId = parentSessionId;
1000
+ }
1001
+ return { sessionId };
1002
+ }
1003
+ async findSessionInfo(sessionId) {
1004
+ const sessions = await this.adapter.listSessions();
1005
+ return sessions.find((session) => session.id === sessionId) ?? null;
1006
+ }
1007
+ async readSessionOrThrow(sessionId) {
1008
+ const detail = await this.readSession(sessionId);
1009
+ if (!detail) {
1010
+ throw new Error(`Session not found: ${sessionId}`);
1011
+ }
1012
+ return detail;
1013
+ }
1014
+ emit(notification) {
1015
+ for (const record of this.listeners) {
1016
+ if (!matchesNotificationSubscription(notification, record.options)) {
1017
+ continue;
1018
+ }
1019
+ record.listener(notification);
1020
+ }
1021
+ if (this.eventBus) {
1022
+ const channel = getNotificationChannel(notification);
1023
+ if (channel) {
1024
+ this.eventBus.publish(channel, notification.type, notification);
1025
+ }
1026
+ }
1027
+ }
1028
+ captureDeferredFollowUps(turn) {
1029
+ if (this.adapter.listFollowUps) {
1030
+ return;
1031
+ }
1032
+ const queued = this.adapter.drainQueuedFollowUps?.() ?? [];
1033
+ if (queued.length === 0) {
1034
+ return;
1035
+ }
1036
+ for (const item of queued) {
1037
+ if (!this.followUps.has(item.id)) {
1038
+ this.followUps.set(
1039
+ item.id,
1040
+ createQueuedFollowUpRecord(
1041
+ {
1042
+ id: item.id,
1043
+ sessionId: turn.sessionId,
1044
+ message: item.message,
1045
+ timestamp: item.createdAt.getTime()
1046
+ },
1047
+ {
1048
+ mode: this.followUpPolicy.mode,
1049
+ sourceTurnId: turn.id,
1050
+ createdAt: item.createdAt.toISOString(),
1051
+ updatedAt: nowIso()
1052
+ }
1053
+ )
1054
+ );
1055
+ }
1056
+ if (!turn.queuedFollowUpIds.includes(item.id)) {
1057
+ turn.queuedFollowUpIds.push(item.id);
1058
+ }
1059
+ }
1060
+ }
1061
+ async seedQueuedFollowUps(sessionId, seededTurnId) {
1062
+ if (this.adapter.listFollowUps) {
1063
+ return;
1064
+ }
1065
+ const ready = [...this.followUps.values()].filter(
1066
+ (record) => record.sessionId === sessionId && canSeedQueuedFollowUp(record)
1067
+ );
1068
+ if (ready.length === 0) {
1069
+ return;
1070
+ }
1071
+ const manager = new SessionManager(this.adapter.getSessionStorage());
1072
+ if (!await manager.sessionExists(sessionId)) {
1073
+ await manager.create({ id: sessionId, cwd: this.adapter.cwd });
1074
+ } else {
1075
+ await manager.load(sessionId);
1076
+ }
1077
+ for (const record of ready) {
1078
+ await manager.addMessage({
1079
+ id: globalThis.crypto.randomUUID(),
1080
+ role: "user",
1081
+ content: record.message,
1082
+ createdAt: new Date(record.createdAt)
1083
+ });
1084
+ this.followUps.set(
1085
+ record.id,
1086
+ markQueuedFollowUpApplied(record, nowIso(), seededTurnId)
1087
+ );
1088
+ }
1089
+ }
1090
+ toTurnSnapshot(turn) {
1091
+ return {
1092
+ id: turn.id,
1093
+ sessionId: turn.sessionId,
1094
+ message: turn.message,
1095
+ startedAt: turn.startedAt,
1096
+ status: turn.status,
1097
+ ...turn.completedAt ? { completedAt: turn.completedAt } : {},
1098
+ ...turn.output !== void 0 ? { output: turn.output } : {},
1099
+ ...turn.usage ? { usage: turn.usage } : {},
1100
+ ...turn.error ? { error: turn.error } : {},
1101
+ ...turn.queuedFollowUpIds.length > 0 ? {
1102
+ queuedFollowUps: turn.queuedFollowUpIds.map((id) => this.followUps.get(id)).filter((record) => Boolean(record)).map((record) => structuredClone(record))
1103
+ } : {}
1104
+ };
1105
+ }
1106
+ toTurnDetail(turn) {
1107
+ return {
1108
+ ...this.toTurnSnapshot(turn),
1109
+ events: [...turn.events]
1110
+ };
1111
+ }
1112
+ resolvePendingInputsForTurn(turnId, action) {
1113
+ for (const [requestId, pending] of this.pendingInputs) {
1114
+ if (pending.resolved || pending.request.turnId !== turnId) {
1115
+ continue;
1116
+ }
1117
+ if (pending.request.kind === "approval") {
1118
+ this.respondToInputRequest(requestId, {
1119
+ kind: "approval",
1120
+ action
1121
+ });
1122
+ continue;
1123
+ }
1124
+ this.respondToInputRequest(requestId, {
1125
+ kind: "human",
1126
+ response: {
1127
+ kind: "text",
1128
+ text: "Human input request cancelled."
1129
+ }
1130
+ });
1131
+ }
1132
+ }
1133
+ resolveAllPendingInputs(action) {
1134
+ for (const requestId of [...this.pendingInputs.keys()]) {
1135
+ const pending = this.pendingInputs.get(requestId);
1136
+ if (!pending) continue;
1137
+ if (pending.request.kind === "approval") {
1138
+ this.respondToInputRequest(requestId, {
1139
+ kind: "approval",
1140
+ action
1141
+ });
1142
+ continue;
1143
+ }
1144
+ this.respondToInputRequest(requestId, {
1145
+ kind: "human",
1146
+ response: {
1147
+ kind: "text",
1148
+ text: "Human input request cancelled."
1149
+ }
1150
+ });
1151
+ }
1152
+ }
1153
+ resolveMatchingApprovalRequests(resolvedRequest, decision) {
1154
+ const payload = {
1155
+ kind: "approval",
1156
+ action: decision.action,
1157
+ ...decision.feedback ? { feedback: decision.feedback } : {},
1158
+ ...decision.rememberScope ? { rememberScope: decision.rememberScope } : {}
1159
+ };
1160
+ for (const [requestId, pending] of [...this.pendingInputs.entries()]) {
1161
+ if (pending.resolved || pending.request.kind !== "approval") {
1162
+ continue;
1163
+ }
1164
+ if (!shouldCascadeApprovalDecision(
1165
+ resolvedRequest,
1166
+ pending.request.request,
1167
+ decision
1168
+ )) {
1169
+ continue;
1170
+ }
1171
+ this.resolvePendingInput(requestId, pending, payload);
1172
+ }
1173
+ }
1174
+ resolvePendingInput(requestId, pending, payload) {
1175
+ pending.resolved = true;
1176
+ this.pendingInputs.delete(requestId);
1177
+ switch (payload.kind) {
1178
+ case "approval":
1179
+ pending.resolve?.({
1180
+ action: payload.action,
1181
+ ...payload.feedback ? { feedback: payload.feedback } : {},
1182
+ ...payload.rememberScope ? { rememberScope: payload.rememberScope } : {}
1183
+ });
1184
+ break;
1185
+ case "human":
1186
+ pending.resolve?.(payload.response);
1187
+ break;
1188
+ }
1189
+ this.emit({
1190
+ type: "input/resolved",
1191
+ requestId,
1192
+ sessionId: pending.request.sessionId,
1193
+ ...pending.request.turnId ? { turnId: pending.request.turnId } : {}
1194
+ });
1195
+ }
1196
+ registerEventInputRequest(turn, event) {
1197
+ const request = event.type === "approval-request" ? {
1198
+ id: event.request.id,
1199
+ kind: "approval",
1200
+ sessionId: turn.sessionId,
1201
+ turnId: turn.id,
1202
+ createdAt: nowIso(),
1203
+ request: {
1204
+ id: event.request.id,
1205
+ sessionId: turn.sessionId,
1206
+ tool: event.request.tool,
1207
+ args: event.request.args,
1208
+ description: event.request.description,
1209
+ risk: event.request.risk,
1210
+ patterns: [],
1211
+ timestamp: Date.now(),
1212
+ ...event.request.rememberScopes ? { rememberScopes: event.request.rememberScopes } : {},
1213
+ ...event.request.defaultRememberScope ? { defaultRememberScope: event.request.defaultRememberScope } : {}
1214
+ }
1215
+ } : {
1216
+ id: event.request.id,
1217
+ kind: "human",
1218
+ sessionId: turn.sessionId,
1219
+ turnId: turn.id,
1220
+ createdAt: nowIso(),
1221
+ request: {
1222
+ id: event.request.id,
1223
+ sessionId: turn.sessionId,
1224
+ kind: event.request.kind,
1225
+ title: event.request.title,
1226
+ question: event.request.question,
1227
+ timestamp: Date.now(),
1228
+ ...event.request.options ? { options: event.request.options } : {},
1229
+ ...event.request.allowMultiple !== void 0 ? { allowMultiple: event.request.allowMultiple } : {},
1230
+ ...event.request.placeholder ? { placeholder: event.request.placeholder } : {},
1231
+ ...event.request.confirmLabel ? { confirmLabel: event.request.confirmLabel } : {},
1232
+ ...event.request.denyLabel ? { denyLabel: event.request.denyLabel } : {}
1233
+ }
1234
+ };
1235
+ const existing = this.pendingInputs.get(request.id);
1236
+ if (existing && !existing.resolved) {
1237
+ return;
1238
+ }
1239
+ this.pendingInputs.set(request.id, {
1240
+ request,
1241
+ resolved: false
1242
+ });
1243
+ this.emit({ type: "input/request", request });
1244
+ }
1245
+ resolveEventInputRequest(turn, requestId) {
1246
+ const pending = this.pendingInputs.get(requestId);
1247
+ if (pending && !pending.resolved) {
1248
+ pending.resolved = true;
1249
+ this.pendingInputs.delete(requestId);
1250
+ this.emit({
1251
+ type: "input/resolved",
1252
+ requestId,
1253
+ sessionId: turn.sessionId,
1254
+ turnId: turn.id
1255
+ });
1256
+ return;
1257
+ }
1258
+ this.emit({
1259
+ type: "input/resolved",
1260
+ requestId,
1261
+ sessionId: turn.sessionId,
1262
+ turnId: turn.id
1263
+ });
1264
+ }
1265
+ };
1266
+
1267
+ // src/runtime/interactive-handlers.ts
1268
+ function createServerApprovalRequestHandler(bridge) {
1269
+ if (!bridge.requestApproval) {
1270
+ throw new Error("Interactive request bridge does not support approvals.");
1271
+ }
1272
+ return async (request) => await bridge.requestApproval(request);
1273
+ }
1274
+ function createServerHumanInputRequestHandler(bridge) {
1275
+ if (!bridge.requestHumanInput) {
1276
+ throw new Error("Interactive request bridge does not support human input.");
1277
+ }
1278
+ return async (request) => await bridge.requestHumanInput(request);
1279
+ }
1280
+ function createServerInteractiveHandlers(bridge) {
1281
+ return {
1282
+ approval: createServerApprovalRequestHandler(bridge),
1283
+ humanInput: createServerHumanInputRequestHandler(bridge)
1284
+ };
1285
+ }
1286
+
1287
+ // src/transport/rpc-connection.ts
1288
+ var AgentServerRpcConnection = class {
1289
+ server;
1290
+ capabilities;
1291
+ sendEnvelope;
1292
+ unsubscribe = null;
1293
+ constructor(options) {
1294
+ this.server = options.server;
1295
+ this.capabilities = mergeAgentServerCapabilities(
1296
+ this.server.getCapabilities(),
1297
+ options.capabilityOverride
1298
+ );
1299
+ this.sendEnvelope = options.send;
1300
+ this.unsubscribe = this.server.subscribe((notification) => {
1301
+ void this.sendEnvelope({
1302
+ kind: "notification",
1303
+ notification
1304
+ });
1305
+ });
1306
+ }
1307
+ async close() {
1308
+ this.unsubscribe?.();
1309
+ this.unsubscribe = null;
1310
+ }
1311
+ async handleRequest(envelope) {
1312
+ try {
1313
+ const result = await this.dispatchRequest({
1314
+ method: envelope.method,
1315
+ params: envelope.params
1316
+ });
1317
+ await this.sendEnvelope({
1318
+ kind: "response",
1319
+ id: envelope.id,
1320
+ ok: true,
1321
+ result
1322
+ });
1323
+ } catch (error) {
1324
+ await this.sendEnvelope(toErrorResponse(envelope.id, error));
1325
+ }
1326
+ }
1327
+ async dispatchRequest(request) {
1328
+ switch (request.method) {
1329
+ case "initialize":
1330
+ return {
1331
+ protocolVersion: 1,
1332
+ capabilities: this.capabilities
1333
+ };
1334
+ case "capabilities/get":
1335
+ return this.capabilities;
1336
+ case "workspace/summary":
1337
+ return await this.server.getWorkspaceSummary();
1338
+ case "agent/model/get":
1339
+ return await this.server.getModelState();
1340
+ case "agent/model/switch":
1341
+ return await this.server.switchModel(request.params.spec);
1342
+ case "session/status":
1343
+ return await this.server.getSessionRuntimeStatus(request.params.sessionId);
1344
+ case "agent/tools":
1345
+ return await this.server.listTools();
1346
+ case "agent/skills":
1347
+ return await this.server.listSkills();
1348
+ case "agent/sub-agents":
1349
+ return await this.server.listSubAgents();
1350
+ case "plugin-command/list":
1351
+ return await this.server.listPluginCommands();
1352
+ case "plugin-command/execute":
1353
+ return await this.server.executePluginCommand(
1354
+ request.params.name,
1355
+ request.params.args ?? ""
1356
+ );
1357
+ case "session/compact":
1358
+ return await this.server.compactSessionContext(request.params.sessionId);
1359
+ case "session/undo":
1360
+ return await this.server.undoSessionTurn(request.params.sessionId);
1361
+ case "session/diff":
1362
+ return await this.server.getSessionTurnDiff(request.params.sessionId);
1363
+ case "session/list":
1364
+ return await this.server.listSessions();
1365
+ case "session/create":
1366
+ return await this.server.createSession(request.params);
1367
+ case "session/read":
1368
+ return await this.server.readSession(request.params.sessionId);
1369
+ case "session/delete":
1370
+ return await this.server.deleteSession(request.params.sessionId);
1371
+ case "session/branch":
1372
+ return await this.server.branchSession(
1373
+ request.params.sessionId,
1374
+ request.params.options
1375
+ );
1376
+ case "turn/start":
1377
+ return await this.server.startTurn(
1378
+ request.params.sessionId,
1379
+ request.params.message,
1380
+ request.params.options
1381
+ );
1382
+ case "turn/get":
1383
+ return this.server.getTurn(request.params.turnId);
1384
+ case "turn/wait":
1385
+ return await this.server.waitForTurn(
1386
+ request.params.turnId,
1387
+ request.params.options
1388
+ );
1389
+ case "turn/interrupt":
1390
+ return this.server.interruptTurn(request.params.turnId);
1391
+ case "turn/steer":
1392
+ return this.server.steerTurn(request.params.turnId, request.params.message);
1393
+ case "turn/follow-up":
1394
+ return this.server.followUpTurn(
1395
+ request.params.turnId,
1396
+ request.params.message
1397
+ );
1398
+ case "follow-up/list":
1399
+ return await this.server.listFollowUps(request.params?.options);
1400
+ case "follow-up/get":
1401
+ return await this.server.getFollowUp(request.params.followUpId);
1402
+ case "follow-up/resolve":
1403
+ return await this.server.resolveFollowUp(
1404
+ request.params.followUpId,
1405
+ request.params.action
1406
+ );
1407
+ case "team/notification/list":
1408
+ return await this.server.listTeamNotifications(request.params?.options);
1409
+ case "input/respond":
1410
+ return this.server.respondToInputRequest(
1411
+ request.params.requestId,
1412
+ request.params.payload
1413
+ );
1414
+ }
1415
+ }
1416
+ };
1417
+ function toErrorResponse(id, error) {
1418
+ return {
1419
+ kind: "response",
1420
+ id,
1421
+ ok: false,
1422
+ error: {
1423
+ message: error instanceof Error ? error.message : String(error)
1424
+ }
1425
+ };
1426
+ }
1427
+
1428
+ // src/transport/stdio.ts
1429
+ import { spawn } from "child_process";
1430
+ import readline from "readline";
1431
+
1432
+ // src/client/remote-client.ts
1433
+ var RemoteAgentServerClient = class {
1434
+ transport;
1435
+ requestTimeoutMs;
1436
+ pending = /* @__PURE__ */ new Map();
1437
+ listeners = /* @__PURE__ */ new Set();
1438
+ turns = /* @__PURE__ */ new Map();
1439
+ capabilities = null;
1440
+ counter = 0;
1441
+ connected = false;
1442
+ constructor(transport, options = {}) {
1443
+ this.transport = transport;
1444
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
1445
+ this.transport.setMessageHandler((envelope) => {
1446
+ void this.handleEnvelope(envelope);
1447
+ });
1448
+ this.transport.setCloseHandler((error) => {
1449
+ this.failPending(error ?? new Error("agent-server transport closed"));
1450
+ this.connected = false;
1451
+ });
1452
+ }
1453
+ async connect() {
1454
+ if (this.connected) {
1455
+ return;
1456
+ }
1457
+ await this.transport.connect();
1458
+ const result = await this.request(
1459
+ "initialize",
1460
+ {}
1461
+ );
1462
+ this.capabilities = result.capabilities;
1463
+ this.connected = true;
1464
+ }
1465
+ getCapabilities() {
1466
+ if (!this.capabilities) {
1467
+ throw new Error("Agent server client is not connected.");
1468
+ }
1469
+ return this.capabilities;
1470
+ }
1471
+ async getWorkspaceSummary() {
1472
+ return await this.request("workspace/summary", {});
1473
+ }
1474
+ async getSessionRuntimeStatus(sessionId) {
1475
+ return await this.request("session/status", {
1476
+ sessionId
1477
+ });
1478
+ }
1479
+ async getModelState() {
1480
+ return await this.request("agent/model/get", {});
1481
+ }
1482
+ async switchModel(spec) {
1483
+ return await this.request("agent/model/switch", { spec });
1484
+ }
1485
+ async listTools() {
1486
+ return await this.request("agent/tools", {});
1487
+ }
1488
+ async listSkills() {
1489
+ return await this.request("agent/skills", {});
1490
+ }
1491
+ async listSubAgents() {
1492
+ return await this.request("agent/sub-agents", {});
1493
+ }
1494
+ async listPluginCommands() {
1495
+ return await this.request("plugin-command/list", {});
1496
+ }
1497
+ async executePluginCommand(name, args) {
1498
+ return await this.request("plugin-command/execute", { name, args });
1499
+ }
1500
+ async compactSessionContext(sessionId) {
1501
+ return await this.request("session/compact", {
1502
+ sessionId
1503
+ });
1504
+ }
1505
+ async undoSessionTurn(sessionId) {
1506
+ return await this.request("session/undo", { sessionId });
1507
+ }
1508
+ async getSessionTurnDiff(sessionId) {
1509
+ return await this.request("session/diff", { sessionId });
1510
+ }
1511
+ async listSessions() {
1512
+ return await this.request("session/list", {});
1513
+ }
1514
+ async createSession(options) {
1515
+ return await this.request("session/create", options ?? {});
1516
+ }
1517
+ async readSession(sessionId) {
1518
+ return await this.request("session/read", { sessionId });
1519
+ }
1520
+ async deleteSession(sessionId) {
1521
+ return await this.request("session/delete", { sessionId });
1522
+ }
1523
+ async branchSession(sessionId, options) {
1524
+ return await this.request("session/branch", {
1525
+ sessionId,
1526
+ ...options ? { options } : {}
1527
+ });
1528
+ }
1529
+ async startTurn(sessionId, message, options) {
1530
+ return await this.request("turn/start", {
1531
+ sessionId,
1532
+ message,
1533
+ ...options ? { options } : {}
1534
+ });
1535
+ }
1536
+ steerTurn(turnId, message) {
1537
+ void this.request("turn/steer", { turnId, message }).catch((error) => {
1538
+ logFireAndForgetError("turn/steer", error);
1539
+ });
1540
+ return { id: "", accepted: true };
1541
+ }
1542
+ followUpTurn(turnId, message) {
1543
+ void this.request("turn/follow-up", { turnId, message }).catch((error) => {
1544
+ logFireAndForgetError("turn/follow-up", error);
1545
+ });
1546
+ return { id: "", accepted: true };
1547
+ }
1548
+ async listFollowUps(options) {
1549
+ return await this.request("follow-up/list", {
1550
+ ...options ? { options } : {}
1551
+ });
1552
+ }
1553
+ async getFollowUp(followUpId) {
1554
+ return await this.request("follow-up/get", {
1555
+ followUpId
1556
+ });
1557
+ }
1558
+ async resolveFollowUp(followUpId, action) {
1559
+ return await this.request("follow-up/resolve", {
1560
+ followUpId,
1561
+ action
1562
+ });
1563
+ }
1564
+ async listTeamNotifications(options) {
1565
+ return await this.request("team/notification/list", {
1566
+ ...options ? { options } : {}
1567
+ });
1568
+ }
1569
+ getTurn(turnId) {
1570
+ return this.turns.get(turnId) ?? null;
1571
+ }
1572
+ async waitForTurn(turnId, options) {
1573
+ return await this.request("turn/wait", {
1574
+ turnId,
1575
+ ...options ? { options } : {}
1576
+ });
1577
+ }
1578
+ interruptTurn(turnId) {
1579
+ void this.request("turn/interrupt", { turnId }).catch((error) => {
1580
+ logFireAndForgetError("turn/interrupt", error);
1581
+ });
1582
+ return true;
1583
+ }
1584
+ respondToInputRequest(requestId, payload) {
1585
+ void this.request("input/respond", { requestId, payload }).catch((error) => {
1586
+ logFireAndForgetError("input/respond", error);
1587
+ });
1588
+ return true;
1589
+ }
1590
+ subscribe(listener, options) {
1591
+ const record = { listener, options };
1592
+ this.listeners.add(record);
1593
+ return () => {
1594
+ this.listeners.delete(record);
1595
+ };
1596
+ }
1597
+ async close() {
1598
+ this.failPending(new Error("agent-server client closed"));
1599
+ await this.transport.close();
1600
+ this.connected = false;
1601
+ }
1602
+ async disconnect() {
1603
+ await this.close();
1604
+ }
1605
+ async request(method, params) {
1606
+ if (!this.connected && method !== "initialize") {
1607
+ await this.connect();
1608
+ }
1609
+ const id = `req-${++this.counter}`;
1610
+ const result = new Promise((resolve, reject) => {
1611
+ const timer = setTimeout(() => {
1612
+ this.pending.delete(id);
1613
+ reject(new Error(`agent-server timeout: ${method}`));
1614
+ }, this.requestTimeoutMs);
1615
+ this.pending.set(id, { resolve, reject, timer });
1616
+ });
1617
+ await this.transport.send({
1618
+ kind: "request",
1619
+ id,
1620
+ method,
1621
+ params
1622
+ });
1623
+ return await result;
1624
+ }
1625
+ async handleEnvelope(envelope) {
1626
+ if (envelope.kind === "notification") {
1627
+ this.trackNotification(envelope.notification);
1628
+ for (const record of this.listeners) {
1629
+ if (!matchesNotificationSubscription(envelope.notification, record.options)) {
1630
+ continue;
1631
+ }
1632
+ record.listener(envelope.notification);
1633
+ }
1634
+ return;
1635
+ }
1636
+ if (envelope.kind !== "response") {
1637
+ return;
1638
+ }
1639
+ const pending = this.pending.get(envelope.id);
1640
+ if (!pending) {
1641
+ return;
1642
+ }
1643
+ clearTimeout(pending.timer);
1644
+ this.pending.delete(envelope.id);
1645
+ if (envelope.ok) {
1646
+ pending.resolve(envelope.result);
1647
+ return;
1648
+ }
1649
+ pending.reject(new Error(envelope.error?.message ?? "agent-server request failed"));
1650
+ }
1651
+ failPending(error) {
1652
+ for (const [id, pending] of this.pending) {
1653
+ clearTimeout(pending.timer);
1654
+ pending.reject(error);
1655
+ this.pending.delete(id);
1656
+ }
1657
+ }
1658
+ trackNotification(notification) {
1659
+ switch (notification.type) {
1660
+ case "turn/started":
1661
+ case "turn/completed": {
1662
+ const existing = this.turns.get(notification.turn.id);
1663
+ this.turns.set(notification.turn.id, {
1664
+ ...notification.turn,
1665
+ events: existing?.events ?? []
1666
+ });
1667
+ break;
1668
+ }
1669
+ case "turn/event": {
1670
+ const existing = this.turns.get(notification.turnId);
1671
+ if (!existing) {
1672
+ this.turns.set(notification.turnId, {
1673
+ id: notification.turnId,
1674
+ sessionId: notification.sessionId,
1675
+ message: "",
1676
+ startedAt: notification.timestamp,
1677
+ status: "running",
1678
+ events: [
1679
+ {
1680
+ sequence: notification.sequence,
1681
+ timestamp: notification.timestamp,
1682
+ event: notification.event
1683
+ }
1684
+ ]
1685
+ });
1686
+ return;
1687
+ }
1688
+ existing.events = [
1689
+ ...existing.events,
1690
+ {
1691
+ sequence: notification.sequence,
1692
+ timestamp: notification.timestamp,
1693
+ event: notification.event
1694
+ }
1695
+ ];
1696
+ this.turns.set(notification.turnId, existing);
1697
+ break;
1698
+ }
1699
+ }
1700
+ }
1701
+ };
1702
+ function logFireAndForgetError(method, error) {
1703
+ const message = error instanceof Error ? error.message : String(error);
1704
+ console.warn(`[agent-server] ${method} fire-and-forget error: ${message}`);
1705
+ }
1706
+
1707
+ // src/transport/stdio.ts
1708
+ function attachAgentServerStdio(server, options = {}) {
1709
+ const input = options.input ?? process.stdin;
1710
+ const output = options.output ?? process.stdout;
1711
+ const onError = options.onError ?? defaultTransportErrorHandler;
1712
+ const connection = new AgentServerRpcConnection({
1713
+ server,
1714
+ capabilityOverride: {
1715
+ protocol: {
1716
+ transport: "stdio",
1717
+ reconnectable: false,
1718
+ multiClient: false
1719
+ }
1720
+ },
1721
+ send: async (envelope) => {
1722
+ output.write(`${JSON.stringify(envelope)}
1723
+ `);
1724
+ }
1725
+ });
1726
+ const reader = readline.createInterface({ input });
1727
+ reader.on("line", (line) => {
1728
+ void handleStdioLine(line, connection, onError);
1729
+ });
1730
+ reader.on("close", () => {
1731
+ void connection.close();
1732
+ });
1733
+ return {
1734
+ close: async () => {
1735
+ reader.close();
1736
+ await connection.close();
1737
+ }
1738
+ };
1739
+ }
1740
+ async function connectStdioAgentServerClient(options) {
1741
+ const child = spawn(options.command, options.args ?? [], {
1742
+ cwd: options.cwd,
1743
+ env: options.env,
1744
+ stdio: ["pipe", "pipe", "inherit"]
1745
+ });
1746
+ if (!child.stdout || !child.stdin) {
1747
+ throw new Error("stdio agent-server child process did not expose pipes");
1748
+ }
1749
+ const transport = new StdioAgentServerTransport(child);
1750
+ const client = new RemoteAgentServerClient(transport, {
1751
+ requestTimeoutMs: options.requestTimeoutMs
1752
+ });
1753
+ await client.connect();
1754
+ return client;
1755
+ }
1756
+ var StdioAgentServerTransport = class {
1757
+ child;
1758
+ reader;
1759
+ onMessage = () => {
1760
+ };
1761
+ onClose = () => {
1762
+ };
1763
+ connected = false;
1764
+ constructor(child) {
1765
+ this.child = child;
1766
+ this.reader = readline.createInterface({ input: child.stdout });
1767
+ }
1768
+ async connect() {
1769
+ if (this.connected) {
1770
+ return;
1771
+ }
1772
+ this.reader.on("line", (line) => {
1773
+ const parsed = parseWireEnvelope(line);
1774
+ if (parsed) {
1775
+ this.onMessage(parsed);
1776
+ }
1777
+ });
1778
+ this.child.once("error", (error) => {
1779
+ this.onClose(toError(error));
1780
+ });
1781
+ this.child.once("close", () => {
1782
+ this.onClose(new Error("agent-server stdio process closed"));
1783
+ });
1784
+ this.connected = true;
1785
+ }
1786
+ async send(envelope) {
1787
+ if (!this.connected || this.child.stdin.destroyed) {
1788
+ throw new Error("agent-server stdio transport is not connected");
1789
+ }
1790
+ this.child.stdin.write(`${JSON.stringify(envelope)}
1791
+ `);
1792
+ }
1793
+ async close() {
1794
+ this.reader.close();
1795
+ if (!this.child.killed) {
1796
+ this.child.kill();
1797
+ }
1798
+ }
1799
+ setMessageHandler(handler) {
1800
+ this.onMessage = handler;
1801
+ }
1802
+ setCloseHandler(handler) {
1803
+ this.onClose = handler;
1804
+ }
1805
+ };
1806
+ async function handleStdioLine(line, connection, onError) {
1807
+ const envelope = parseWireEnvelope(line);
1808
+ if (!envelope) {
1809
+ return;
1810
+ }
1811
+ if (envelope.kind !== "request") {
1812
+ return;
1813
+ }
1814
+ try {
1815
+ await connection.handleRequest(envelope);
1816
+ } catch (error) {
1817
+ onError(toError(error));
1818
+ }
1819
+ }
1820
+ function parseWireEnvelope(line) {
1821
+ const trimmed = line.trim();
1822
+ if (!trimmed) {
1823
+ return null;
1824
+ }
1825
+ let parsed;
1826
+ try {
1827
+ parsed = JSON.parse(trimmed);
1828
+ } catch {
1829
+ return null;
1830
+ }
1831
+ return isAgentServerWireEnvelope(parsed) ? parsed : null;
1832
+ }
1833
+ function defaultTransportErrorHandler(error) {
1834
+ process.stderr.write(`agent-server stdio transport error: ${error.message}
1835
+ `);
1836
+ }
1837
+ function toError(error) {
1838
+ return error instanceof Error ? error : new Error(String(error));
1839
+ }
1840
+
1841
+ // src/transport/websocket.ts
1842
+ import WebSocket, { WebSocketServer } from "ws";
1843
+ function startAgentServerWebSocketServer(server, options) {
1844
+ const attached = Boolean(options.httpServer);
1845
+ const wss = options.httpServer ? new WebSocketServer({ server: options.httpServer }) : new WebSocketServer({ port: options.port, host: options.host });
1846
+ const ready = attached ? Promise.resolve() : waitForWebSocketServerReady(wss);
1847
+ const connections = /* @__PURE__ */ new Set();
1848
+ wss.on("connection", (socket) => {
1849
+ const connection = new AgentServerRpcConnection({
1850
+ server,
1851
+ capabilityOverride: {
1852
+ protocol: {
1853
+ transport: "websocket",
1854
+ reconnectable: true,
1855
+ multiClient: true
1856
+ }
1857
+ },
1858
+ send: async (envelope) => {
1859
+ if (socket.readyState === WebSocket.OPEN) {
1860
+ socket.send(JSON.stringify(envelope));
1861
+ }
1862
+ }
1863
+ });
1864
+ connections.add(connection);
1865
+ socket.on("message", (data) => {
1866
+ const envelope = parseWireEnvelope2(data);
1867
+ if (!envelope || envelope.kind !== "request") {
1868
+ return;
1869
+ }
1870
+ void connection.handleRequest(envelope);
1871
+ });
1872
+ socket.on("close", () => {
1873
+ connections.delete(connection);
1874
+ void connection.close();
1875
+ });
1876
+ });
1877
+ return {
1878
+ ready,
1879
+ close: async () => {
1880
+ for (const connection of connections) {
1881
+ await connection.close();
1882
+ }
1883
+ await new Promise((resolve, reject) => {
1884
+ wss.close((error) => {
1885
+ if (error) {
1886
+ reject(error);
1887
+ return;
1888
+ }
1889
+ resolve();
1890
+ });
1891
+ });
1892
+ },
1893
+ address: () => {
1894
+ const bound = attached ? options.httpServer.address() : wss.address();
1895
+ if (bound && typeof bound === "object") {
1896
+ return `ws://${formatConnectableHost(bound.address, options.host)}:${bound.port}`;
1897
+ }
1898
+ const host = formatConnectableHost(options.host ?? "127.0.0.1");
1899
+ return `ws://${host}:${options.port ?? 0}`;
1900
+ }
1901
+ };
1902
+ }
1903
+ async function connectWebSocketAgentServerClient(options) {
1904
+ const transport = new WebSocketAgentServerTransport(options.url, options.headers);
1905
+ const client = new RemoteAgentServerClient(transport, {
1906
+ requestTimeoutMs: options.requestTimeoutMs
1907
+ });
1908
+ await client.connect();
1909
+ return client;
1910
+ }
1911
+ var WebSocketAgentServerTransport = class {
1912
+ url;
1913
+ headers;
1914
+ socket = null;
1915
+ onMessage = () => {
1916
+ };
1917
+ onClose = () => {
1918
+ };
1919
+ constructor(url, headers) {
1920
+ this.url = url;
1921
+ this.headers = headers;
1922
+ }
1923
+ async connect() {
1924
+ if (this.socket?.readyState === WebSocket.OPEN) {
1925
+ return;
1926
+ }
1927
+ this.socket = await new Promise((resolve, reject) => {
1928
+ const socket = new WebSocket(this.url, {
1929
+ headers: this.headers
1930
+ });
1931
+ socket.once("open", () => resolve(socket));
1932
+ socket.once("error", (error) => reject(error));
1933
+ });
1934
+ this.socket.on("message", (data) => {
1935
+ const envelope = parseWireEnvelope2(data);
1936
+ if (envelope) {
1937
+ this.onMessage(envelope);
1938
+ }
1939
+ });
1940
+ this.socket.on("close", () => {
1941
+ this.onClose(new Error("agent-server websocket closed"));
1942
+ this.socket = null;
1943
+ });
1944
+ this.socket.on("error", (error) => {
1945
+ this.onClose(error instanceof Error ? error : new Error(String(error)));
1946
+ });
1947
+ }
1948
+ async send(envelope) {
1949
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
1950
+ throw new Error("agent-server websocket is not connected");
1951
+ }
1952
+ this.socket.send(JSON.stringify(envelope));
1953
+ }
1954
+ async close() {
1955
+ const socket = this.socket;
1956
+ this.socket = null;
1957
+ if (!socket) {
1958
+ return;
1959
+ }
1960
+ await new Promise((resolve) => {
1961
+ socket.once("close", () => resolve());
1962
+ socket.close();
1963
+ setTimeout(resolve, 250);
1964
+ });
1965
+ }
1966
+ setMessageHandler(handler) {
1967
+ this.onMessage = handler;
1968
+ }
1969
+ setCloseHandler(handler) {
1970
+ this.onClose = handler;
1971
+ }
1972
+ };
1973
+ function parseWireEnvelope2(data) {
1974
+ const text = typeof data === "string" ? data : Buffer.isBuffer(data) ? data.toString("utf8") : Buffer.from(data).toString("utf8");
1975
+ let parsed;
1976
+ try {
1977
+ parsed = JSON.parse(text);
1978
+ } catch {
1979
+ return null;
1980
+ }
1981
+ return isAgentServerWireEnvelope(parsed) ? parsed : null;
1982
+ }
1983
+ function formatConnectableHost(address, fallback = "127.0.0.1") {
1984
+ const host = address === "::" || address === "0.0.0.0" ? fallback : address;
1985
+ return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
1986
+ }
1987
+ function waitForWebSocketServerReady(wss) {
1988
+ if (wss.address()) {
1989
+ return Promise.resolve();
1990
+ }
1991
+ return new Promise((resolve, reject) => {
1992
+ const onListening = () => {
1993
+ cleanup();
1994
+ resolve();
1995
+ };
1996
+ const onError = (error) => {
1997
+ cleanup();
1998
+ reject(error);
1999
+ };
2000
+ const cleanup = () => {
2001
+ wss.off("listening", onListening);
2002
+ wss.off("error", onError);
2003
+ };
2004
+ wss.once("listening", onListening);
2005
+ wss.once("error", onError);
2006
+ });
2007
+ }
2008
+
2009
+ // src/transport/sse.ts
2010
+ function createAgentServerSSEStream(server, sessionId, options) {
2011
+ const encoder = new TextEncoder();
2012
+ let idCounter = 0;
2013
+ return new ReadableStream({
2014
+ start(controller) {
2015
+ controller.enqueue(encoder.encode(":ok\n\n"));
2016
+ const unsubscribe = server.subscribe(
2017
+ (notification) => {
2018
+ const id = String(++idCounter);
2019
+ const event = notification.type;
2020
+ const data = JSON.stringify(notification);
2021
+ controller.enqueue(
2022
+ encoder.encode(`id: ${id}
2023
+ event: ${event}
2024
+ data: ${data}
2025
+
2026
+ `)
2027
+ );
2028
+ },
2029
+ { sessionId }
2030
+ );
2031
+ const cleanup = () => {
2032
+ unsubscribe();
2033
+ try {
2034
+ controller.close();
2035
+ } catch {
2036
+ }
2037
+ };
2038
+ options?.signal?.addEventListener("abort", cleanup, { once: true });
2039
+ }
2040
+ });
2041
+ }
2042
+ function createEventBusSSEStream(eventBus, channel, options) {
2043
+ const encoder = new TextEncoder();
2044
+ const afterSeq = options?.lastEventId ? parseInt(options.lastEventId, 10) : void 0;
2045
+ return new ReadableStream({
2046
+ start(controller) {
2047
+ controller.enqueue(encoder.encode(":ok\n\n"));
2048
+ const subscription = eventBus.subscribe(channel, {
2049
+ ...afterSeq !== void 0 ? { afterSeq } : {}
2050
+ });
2051
+ const pump = async () => {
2052
+ try {
2053
+ for await (const message of subscription) {
2054
+ const data = JSON.stringify(message.data);
2055
+ controller.enqueue(
2056
+ encoder.encode(
2057
+ `id: ${message.seq}
2058
+ event: ${message.type}
2059
+ data: ${data}
2060
+
2061
+ `
2062
+ )
2063
+ );
2064
+ }
2065
+ } catch {
2066
+ } finally {
2067
+ try {
2068
+ controller.close();
2069
+ } catch {
2070
+ }
2071
+ }
2072
+ };
2073
+ void pump();
2074
+ options?.signal?.addEventListener(
2075
+ "abort",
2076
+ () => {
2077
+ subscription.unsubscribe();
2078
+ },
2079
+ { once: true }
2080
+ );
2081
+ }
2082
+ });
2083
+ }
2084
+ function createAgentServerSSEResponse(server, sessionId, options) {
2085
+ const body = createAgentServerSSEStream(server, sessionId, options);
2086
+ return new Response(body, {
2087
+ status: 200,
2088
+ headers: {
2089
+ "Content-Type": "text/event-stream",
2090
+ "Cache-Control": "no-cache",
2091
+ ...options?.headers
2092
+ }
2093
+ });
2094
+ }
2095
+
2096
+ // src/client/stream-turn.ts
2097
+ async function* streamTurn(client, sessionId, message, options = {}) {
2098
+ const queue = [];
2099
+ let waiter = null;
2100
+ let targetTurnId = null;
2101
+ const unsubscribe = client.subscribe(
2102
+ (notification) => {
2103
+ if (waiter) {
2104
+ const resolve = waiter;
2105
+ waiter = null;
2106
+ resolve(notification);
2107
+ return;
2108
+ }
2109
+ queue.push(notification);
2110
+ },
2111
+ { sessionId }
2112
+ );
2113
+ try {
2114
+ const turn = await client.startTurn(sessionId, message, options);
2115
+ targetTurnId = turn.id;
2116
+ while (true) {
2117
+ const notification = queue.shift() ?? await new Promise((resolve) => {
2118
+ waiter = resolve;
2119
+ });
2120
+ if (!matchesTurnStreamNotification(notification, sessionId, targetTurnId)) {
2121
+ continue;
2122
+ }
2123
+ yield notification;
2124
+ if (notification.type === "turn/completed" && notification.turn.id === targetTurnId) {
2125
+ break;
2126
+ }
2127
+ }
2128
+ } finally {
2129
+ unsubscribe();
2130
+ }
2131
+ }
2132
+ function matchesTurnStreamNotification(notification, sessionId, turnId) {
2133
+ switch (notification.type) {
2134
+ case "session/created":
2135
+ return notification.session.id === sessionId;
2136
+ case "session/deleted":
2137
+ return notification.sessionId === sessionId;
2138
+ case "session/branched":
2139
+ case "session/runtime":
2140
+ return notification.sessionId === sessionId;
2141
+ case "team/notification":
2142
+ return false;
2143
+ case "turn/started":
2144
+ case "input/request":
2145
+ case "input/resolved":
2146
+ case "turn/completed":
2147
+ if (notification.type === "turn/started" || notification.type === "turn/completed") {
2148
+ return turnId !== null && notification.turn.id === turnId;
2149
+ }
2150
+ return turnId !== null && (notification.type === "input/request" ? notification.request.turnId === turnId : notification.turnId === turnId);
2151
+ case "turn/event":
2152
+ return turnId !== null && notification.turnId === turnId;
2153
+ }
2154
+ }
2155
+
2156
+ // src/client/in-process-client.ts
2157
+ var InProcessAgentServerClient = class {
2158
+ server;
2159
+ constructor(server) {
2160
+ this.server = server;
2161
+ }
2162
+ getCapabilities() {
2163
+ return this.server.getCapabilities();
2164
+ }
2165
+ getWorkspaceSummary() {
2166
+ return this.server.getWorkspaceSummary();
2167
+ }
2168
+ getSessionRuntimeStatus(sessionId) {
2169
+ return this.server.getSessionRuntimeStatus(sessionId);
2170
+ }
2171
+ getModelState() {
2172
+ return this.server.getModelState();
2173
+ }
2174
+ switchModel(spec) {
2175
+ return this.server.switchModel(spec);
2176
+ }
2177
+ listTools() {
2178
+ return this.server.listTools();
2179
+ }
2180
+ listSkills() {
2181
+ return this.server.listSkills();
2182
+ }
2183
+ listSubAgents() {
2184
+ return this.server.listSubAgents();
2185
+ }
2186
+ listPluginCommands() {
2187
+ return this.server.listPluginCommands();
2188
+ }
2189
+ executePluginCommand(name, args) {
2190
+ return this.server.executePluginCommand(name, args);
2191
+ }
2192
+ compactSessionContext(sessionId) {
2193
+ return this.server.compactSessionContext(sessionId);
2194
+ }
2195
+ undoSessionTurn(sessionId) {
2196
+ return this.server.undoSessionTurn(sessionId);
2197
+ }
2198
+ getSessionTurnDiff(sessionId) {
2199
+ return this.server.getSessionTurnDiff(sessionId);
2200
+ }
2201
+ listSessions() {
2202
+ return this.server.listSessions();
2203
+ }
2204
+ createSession(options) {
2205
+ return this.server.createSession(options);
2206
+ }
2207
+ readSession(sessionId) {
2208
+ return this.server.readSession(sessionId);
2209
+ }
2210
+ deleteSession(sessionId) {
2211
+ return this.server.deleteSession(sessionId);
2212
+ }
2213
+ branchSession(sessionId, options) {
2214
+ return this.server.branchSession(sessionId, options);
2215
+ }
2216
+ startTurn(sessionId, message, options) {
2217
+ return this.server.startTurn(sessionId, message, options);
2218
+ }
2219
+ steerTurn(turnId, message) {
2220
+ return this.server.steerTurn(turnId, message);
2221
+ }
2222
+ followUpTurn(turnId, message) {
2223
+ return this.server.followUpTurn(turnId, message);
2224
+ }
2225
+ listFollowUps(options) {
2226
+ return this.server.listFollowUps(options);
2227
+ }
2228
+ getFollowUp(followUpId) {
2229
+ return this.server.getFollowUp(followUpId);
2230
+ }
2231
+ resolveFollowUp(followUpId, action) {
2232
+ return this.server.resolveFollowUp(followUpId, action);
2233
+ }
2234
+ listTeamNotifications(options) {
2235
+ return this.server.listTeamNotifications(options);
2236
+ }
2237
+ getTurn(turnId) {
2238
+ return this.server.getTurn(turnId);
2239
+ }
2240
+ waitForTurn(turnId, options) {
2241
+ return this.server.waitForTurn(turnId, options);
2242
+ }
2243
+ interruptTurn(turnId) {
2244
+ return this.server.interruptTurn(turnId);
2245
+ }
2246
+ respondToInputRequest(requestId, payload) {
2247
+ return this.server.respondToInputRequest(requestId, payload);
2248
+ }
2249
+ subscribe(listener, options) {
2250
+ return this.server.subscribe(listener, options);
2251
+ }
2252
+ async close() {
2253
+ await this.server.close();
2254
+ }
2255
+ async disconnect() {
2256
+ await this.close();
2257
+ }
2258
+ };
2259
+ function createInProcessAgentServer(agent) {
2260
+ return new InProcessAgentServer(createAgentServerAdapter(agent));
2261
+ }
2262
+ function createInProcessAgentServerClient(agent) {
2263
+ return new InProcessAgentServerClient(createInProcessAgentServer(agent));
2264
+ }
2265
+ export {
2266
+ AgentServerRpcConnection,
2267
+ InProcessAgentServer,
2268
+ InProcessAgentServerClient,
2269
+ RemoteAgentServerClient,
2270
+ attachAgentServerStdio,
2271
+ connectStdioAgentServerClient,
2272
+ connectWebSocketAgentServerClient,
2273
+ createAgentServerAdapter,
2274
+ createAgentServerSSEResponse,
2275
+ createAgentServerSSEStream,
2276
+ createDefaultAgentServerCapabilities,
2277
+ createEventBusSSEStream,
2278
+ createInProcessAgentServer,
2279
+ createInProcessAgentServerClient,
2280
+ createServerApprovalRequestHandler,
2281
+ createServerHumanInputRequestHandler,
2282
+ createServerInteractiveHandlers,
2283
+ isAgentServerWireEnvelope,
2284
+ matchesNotificationSubscription,
2285
+ mergeAgentServerCapabilities,
2286
+ startAgentServerWebSocketServer,
2287
+ streamTurn
2288
+ };