acpx 0.4.0 → 0.5.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.
Files changed (47) hide show
  1. package/dist/agent-registry-DGw0-3Tc.js +54 -0
  2. package/dist/agent-registry-DGw0-3Tc.js.map +1 -0
  3. package/dist/{cli-5s-E-Y99.js → cli-CLRrs6eQ.js} +8 -12
  4. package/dist/cli-CLRrs6eQ.js.map +1 -0
  5. package/dist/cli.d.ts +2 -2
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +1018 -1009
  8. package/dist/cli.js.map +1 -1
  9. package/dist/client-DLTWuu4w.d.ts +116 -0
  10. package/dist/client-DLTWuu4w.d.ts.map +1 -0
  11. package/dist/{flags-BkWInxAq.js → flags-BmubjvOw.js} +5 -55
  12. package/dist/flags-BmubjvOw.js.map +1 -0
  13. package/dist/{flows-DnIYoHI1.js → flows-CR7xCmkR.js} +471 -281
  14. package/dist/flows-CR7xCmkR.js.map +1 -0
  15. package/dist/flows.d.ts +5 -9
  16. package/dist/flows.d.ts.map +1 -1
  17. package/dist/flows.js +1 -1
  18. package/dist/{queue-ipc-CgWf63GN.js → ipc-DN6M4Ui9.js} +12 -571
  19. package/dist/ipc-DN6M4Ui9.js.map +1 -0
  20. package/dist/{acp-jsonrpc-C7pPk9Tw.js → jsonrpc-M3y-qzy8.js} +16 -3
  21. package/dist/jsonrpc-M3y-qzy8.js.map +1 -0
  22. package/dist/{output-C58ukIo3.js → output-Di0M9Et8.js} +8 -22
  23. package/dist/output-Di0M9Et8.js.map +1 -0
  24. package/dist/perf-metrics-D9QC81lB.js +568 -0
  25. package/dist/perf-metrics-D9QC81lB.js.map +1 -0
  26. package/dist/{session-BtpTC2pM.js → prompt-turn-Bt8T3SRR.js} +2304 -3632
  27. package/dist/prompt-turn-Bt8T3SRR.js.map +1 -0
  28. package/dist/{output-render-C7w9NZ2H.js → render-BL5ynRkN.js} +7 -6
  29. package/dist/render-BL5ynRkN.js.map +1 -0
  30. package/dist/runtime.d.ts +266 -0
  31. package/dist/runtime.d.ts.map +1 -0
  32. package/dist/runtime.js +984 -0
  33. package/dist/runtime.js.map +1 -0
  34. package/dist/session-BbN0SBgf.js +1488 -0
  35. package/dist/session-BbN0SBgf.js.map +1 -0
  36. package/dist/{types-CeRKmEQ1.d.ts → types-DXxLBQc3.d.ts} +40 -3
  37. package/dist/types-DXxLBQc3.d.ts.map +1 -0
  38. package/package.json +20 -9
  39. package/dist/acp-jsonrpc-C7pPk9Tw.js.map +0 -1
  40. package/dist/cli-5s-E-Y99.js.map +0 -1
  41. package/dist/flags-BkWInxAq.js.map +0 -1
  42. package/dist/flows-DnIYoHI1.js.map +0 -1
  43. package/dist/output-C58ukIo3.js.map +0 -1
  44. package/dist/output-render-C7w9NZ2H.js.map +0 -1
  45. package/dist/queue-ipc-CgWf63GN.js.map +0 -1
  46. package/dist/session-BtpTC2pM.js.map +0 -1
  47. package/dist/types-CeRKmEQ1.d.ts.map +0 -1
@@ -0,0 +1,984 @@
1
+ import { g as textPrompt, x as normalizeOutputError } from "./perf-metrics-D9QC81lB.js";
2
+ import { i as resolveAgentCommand, n as listBuiltInAgents, t as DEFAULT_AGENT_NAME } from "./agent-registry-DGw0-3Tc.js";
3
+ import { B as serializeSessionRecordForDisk, M as parseSessionRecord, P as defaultSessionEventLog, _ as recordSessionUpdate, a as applyConversation, f as cloneSessionAcpxState, g as recordPromptSubmission, h as recordClientOperation, i as connectAndLoadSession, l as setDesiredModeId, m as createSessionConversation, n as withConnectedSession, o as applyLifecycleSnapshotToRecord, p as cloneSessionConversation, s as reconcileAgentSessionId, t as runPromptTurn, v as trimConversationForRuntime, y as AcpClient, z as assertPersistedKeyPolicy } from "./prompt-turn-Bt8T3SRR.js";
4
+ import path from "node:path";
5
+ import fs from "node:fs/promises";
6
+ import { randomUUID } from "node:crypto";
7
+ //#region src/runtime/public/errors.ts
8
+ var AcpRuntimeError = class extends Error {
9
+ code;
10
+ cause;
11
+ constructor(code, message, options) {
12
+ super(message);
13
+ this.name = "AcpRuntimeError";
14
+ this.code = code;
15
+ this.cause = options?.cause;
16
+ }
17
+ };
18
+ function isAcpRuntimeError(value) {
19
+ return value instanceof AcpRuntimeError;
20
+ }
21
+ //#endregion
22
+ //#region src/runtime/public/shared.ts
23
+ function isRecord(value) {
24
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25
+ }
26
+ function asTrimmedString(value) {
27
+ return typeof value === "string" ? value.trim() : "";
28
+ }
29
+ function asString(value) {
30
+ return typeof value === "string" ? value : void 0;
31
+ }
32
+ function asOptionalString(value) {
33
+ return asTrimmedString(value) || void 0;
34
+ }
35
+ function asOptionalBoolean(value) {
36
+ return typeof value === "boolean" ? value : void 0;
37
+ }
38
+ function deriveAgentFromSessionKey(sessionKey, fallbackAgent) {
39
+ const match = sessionKey.match(/^agent:([^:]+):/i);
40
+ return (match?.[1] ? asTrimmedString(match[1]) : "") || fallbackAgent;
41
+ }
42
+ //#endregion
43
+ //#region src/runtime/public/events.ts
44
+ function safeParseJsonObject(line) {
45
+ try {
46
+ const parsed = JSON.parse(line);
47
+ return isRecord(parsed) ? parsed : null;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+ function asOptionalFiniteNumber(value) {
53
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
54
+ }
55
+ function resolveStructuredPromptPayload(parsed) {
56
+ if (asTrimmedString(parsed.method) === "session/update") {
57
+ const params = parsed.params;
58
+ if (isRecord(params) && isRecord(params.update)) {
59
+ const update = params.update;
60
+ const tag = asOptionalString(update.sessionUpdate);
61
+ return {
62
+ type: tag ?? "",
63
+ payload: update,
64
+ ...tag ? { tag } : {}
65
+ };
66
+ }
67
+ }
68
+ const sessionUpdate = asOptionalString(parsed.sessionUpdate);
69
+ if (sessionUpdate) return {
70
+ type: sessionUpdate,
71
+ payload: parsed,
72
+ tag: sessionUpdate
73
+ };
74
+ const type = asTrimmedString(parsed.type);
75
+ const tag = asOptionalString(parsed.tag);
76
+ return {
77
+ type,
78
+ payload: parsed,
79
+ ...tag ? { tag } : {}
80
+ };
81
+ }
82
+ function resolveStatusTextForTag(params) {
83
+ const { tag, payload } = params;
84
+ if (tag === "available_commands_update") {
85
+ const commands = Array.isArray(payload.availableCommands) ? payload.availableCommands : [];
86
+ return commands.length > 0 ? `available commands updated (${commands.length})` : "available commands updated";
87
+ }
88
+ if (tag === "current_mode_update") {
89
+ const mode = asTrimmedString(payload.currentModeId) || asTrimmedString(payload.modeId) || asTrimmedString(payload.mode);
90
+ return mode ? `mode updated: ${mode}` : "mode updated";
91
+ }
92
+ if (tag === "config_option_update") {
93
+ const id = asTrimmedString(payload.id) || asTrimmedString(payload.configOptionId);
94
+ const value = asTrimmedString(payload.currentValue) || asTrimmedString(payload.value) || asTrimmedString(payload.optionValue);
95
+ if (id && value) return `config updated: ${id}=${value}`;
96
+ if (id) return `config updated: ${id}`;
97
+ return "config updated";
98
+ }
99
+ if (tag === "session_info_update") return asTrimmedString(payload.summary) || asTrimmedString(payload.message) || "session updated";
100
+ if (tag === "plan") {
101
+ const content = asTrimmedString((Array.isArray(payload.entries) ? payload.entries : []).find((entry) => isRecord(entry))?.content);
102
+ return content ? `plan: ${content}` : null;
103
+ }
104
+ return null;
105
+ }
106
+ function resolveTextChunk(params) {
107
+ const contentRaw = params.payload.content;
108
+ if (isRecord(contentRaw)) {
109
+ const contentType = asTrimmedString(contentRaw.type);
110
+ if (contentType && contentType !== "text") return null;
111
+ const text = asString(contentRaw.text);
112
+ if (text && text.length > 0) return {
113
+ type: "text_delta",
114
+ text,
115
+ stream: params.stream,
116
+ tag: params.tag
117
+ };
118
+ }
119
+ const text = asString(params.payload.text);
120
+ if (!text || text.length === 0) return null;
121
+ return {
122
+ type: "text_delta",
123
+ text,
124
+ stream: params.stream,
125
+ tag: params.tag
126
+ };
127
+ }
128
+ function createTextDeltaEvent(params) {
129
+ if (params.content == null || params.content.length === 0) return null;
130
+ return {
131
+ type: "text_delta",
132
+ text: params.content,
133
+ stream: params.stream,
134
+ ...params.tag ? { tag: params.tag } : {}
135
+ };
136
+ }
137
+ function createToolCallEvent(params) {
138
+ const title = asTrimmedString(params.payload.title) || "tool call";
139
+ const status = asTrimmedString(params.payload.status);
140
+ const toolCallId = asOptionalString(params.payload.toolCallId);
141
+ return {
142
+ type: "tool_call",
143
+ text: status ? `${title} (${status})` : title,
144
+ tag: params.tag,
145
+ ...toolCallId ? { toolCallId } : {},
146
+ ...status ? { status } : {},
147
+ title
148
+ };
149
+ }
150
+ function parsePromptEventLine(line) {
151
+ const trimmed = line.trim();
152
+ if (!trimmed) return null;
153
+ const parsed = safeParseJsonObject(trimmed);
154
+ if (!parsed) return {
155
+ type: "status",
156
+ text: trimmed
157
+ };
158
+ const structured = resolveStructuredPromptPayload(parsed);
159
+ const type = structured.type;
160
+ const payload = structured.payload;
161
+ const tag = structured.tag;
162
+ switch (type) {
163
+ case "text": return createTextDeltaEvent({
164
+ content: asString(payload.content),
165
+ stream: "output",
166
+ tag
167
+ });
168
+ case "thought": return createTextDeltaEvent({
169
+ content: asString(payload.content),
170
+ stream: "thought",
171
+ tag
172
+ });
173
+ case "tool_call": return createToolCallEvent({
174
+ payload,
175
+ tag: tag ?? "tool_call"
176
+ });
177
+ case "tool_call_update": return createToolCallEvent({
178
+ payload,
179
+ tag: tag ?? "tool_call_update"
180
+ });
181
+ case "agent_message_chunk": return resolveTextChunk({
182
+ payload,
183
+ stream: "output",
184
+ tag: "agent_message_chunk"
185
+ });
186
+ case "agent_thought_chunk": return resolveTextChunk({
187
+ payload,
188
+ stream: "thought",
189
+ tag: "agent_thought_chunk"
190
+ });
191
+ case "usage_update": {
192
+ const used = asOptionalFiniteNumber(payload.used);
193
+ const size = asOptionalFiniteNumber(payload.size);
194
+ return {
195
+ type: "status",
196
+ text: used != null && size != null ? `usage updated: ${used}/${size}` : "usage updated",
197
+ tag: "usage_update",
198
+ ...used != null ? { used } : {},
199
+ ...size != null ? { size } : {}
200
+ };
201
+ }
202
+ case "available_commands_update":
203
+ case "current_mode_update":
204
+ case "config_option_update":
205
+ case "session_info_update":
206
+ case "plan": {
207
+ const text = resolveStatusTextForTag({
208
+ tag: type,
209
+ payload
210
+ });
211
+ if (!text) return null;
212
+ return {
213
+ type: "status",
214
+ text,
215
+ tag: type
216
+ };
217
+ }
218
+ case "client_operation": {
219
+ const text = [
220
+ asTrimmedString(payload.method) || "operation",
221
+ asTrimmedString(payload.status),
222
+ asTrimmedString(payload.summary)
223
+ ].filter(Boolean).join(" ");
224
+ if (!text) return null;
225
+ return {
226
+ type: "status",
227
+ text,
228
+ ...tag ? { tag } : {}
229
+ };
230
+ }
231
+ case "update": {
232
+ const update = asTrimmedString(payload.update);
233
+ if (!update) return null;
234
+ return {
235
+ type: "status",
236
+ text: update,
237
+ ...tag ? { tag } : {}
238
+ };
239
+ }
240
+ case "done": return {
241
+ type: "done",
242
+ stopReason: asOptionalString(payload.stopReason)
243
+ };
244
+ case "error": return {
245
+ type: "error",
246
+ message: asTrimmedString(payload.message) || "acpx runtime error",
247
+ code: asOptionalString(payload.code),
248
+ retryable: asOptionalBoolean(payload.retryable)
249
+ };
250
+ default: return null;
251
+ }
252
+ }
253
+ //#endregion
254
+ //#region src/runtime/engine/reuse-policy.ts
255
+ function shouldReuseExistingRecord(record, params) {
256
+ if (path.resolve(record.cwd) !== path.resolve(params.cwd)) return false;
257
+ if (record.agentCommand !== params.agentCommand) return false;
258
+ if (params.resumeSessionId && record.acpSessionId !== params.resumeSessionId) return false;
259
+ return true;
260
+ }
261
+ //#endregion
262
+ //#region src/runtime/engine/manager.ts
263
+ function createDeferred() {
264
+ let resolve;
265
+ let reject;
266
+ return {
267
+ promise: new Promise((res, rej) => {
268
+ resolve = res;
269
+ reject = rej;
270
+ }),
271
+ resolve,
272
+ reject
273
+ };
274
+ }
275
+ var AsyncEventQueue = class {
276
+ items = [];
277
+ waits = [];
278
+ closed = false;
279
+ push(item) {
280
+ if (this.closed) return;
281
+ const waiter = this.waits.shift();
282
+ if (waiter) {
283
+ waiter.resolve(item);
284
+ return;
285
+ }
286
+ this.items.push(item);
287
+ }
288
+ close() {
289
+ if (this.closed) return;
290
+ this.closed = true;
291
+ for (const waiter of this.waits.splice(0)) waiter.resolve(null);
292
+ }
293
+ async next() {
294
+ if (this.items.length > 0) return this.items.shift() ?? null;
295
+ if (this.closed) return null;
296
+ const waiter = createDeferred();
297
+ this.waits.push(waiter);
298
+ return await waiter.promise;
299
+ }
300
+ async *iterate() {
301
+ while (true) {
302
+ const next = await this.next();
303
+ if (!next) return;
304
+ yield next;
305
+ }
306
+ }
307
+ };
308
+ function isoNow() {
309
+ return (/* @__PURE__ */ new Date()).toISOString();
310
+ }
311
+ function toPromptInput(text, attachments) {
312
+ if (!attachments || attachments.length === 0) return text;
313
+ const blocks = [];
314
+ if (text) blocks.push({
315
+ type: "text",
316
+ text
317
+ });
318
+ for (const attachment of attachments) {
319
+ if (!attachment.mediaType.startsWith("image/")) throw new AcpRuntimeError("ACP_TURN_FAILED", `Unsupported ACP runtime attachment media type: ${attachment.mediaType}`);
320
+ blocks.push({
321
+ type: "image",
322
+ mimeType: attachment.mediaType,
323
+ data: attachment.data
324
+ });
325
+ }
326
+ return blocks.length > 0 ? blocks : textPrompt(text);
327
+ }
328
+ function createInitialRecord(params) {
329
+ const now = isoNow();
330
+ return {
331
+ schema: "acpx.session.v1",
332
+ acpxRecordId: params.recordId,
333
+ acpSessionId: params.sessionId,
334
+ agentSessionId: params.agentSessionId,
335
+ agentCommand: params.agentCommand,
336
+ cwd: params.cwd,
337
+ name: params.sessionName,
338
+ createdAt: now,
339
+ lastUsedAt: now,
340
+ lastSeq: 0,
341
+ eventLog: defaultSessionEventLog(params.recordId),
342
+ closed: false,
343
+ closedAt: void 0,
344
+ ...createSessionConversation(now),
345
+ acpx: {}
346
+ };
347
+ }
348
+ function createRecordId(sessionKey, mode) {
349
+ if (mode === "persistent") return sessionKey;
350
+ return `${sessionKey}:oneshot:${randomUUID()}`;
351
+ }
352
+ function resumePolicyForSessionMode(mode) {
353
+ return mode === "persistent" ? "same-session-only" : "allow-new";
354
+ }
355
+ function statusSummary(record) {
356
+ return [
357
+ `session=${record.acpxRecordId}`,
358
+ `backendSessionId=${record.acpSessionId}`,
359
+ record.agentSessionId ? `agentSessionId=${record.agentSessionId}` : null,
360
+ record.pid != null ? `pid=${record.pid}` : null,
361
+ record.closed ? "closed" : "open"
362
+ ].filter(Boolean).join(" ");
363
+ }
364
+ var AcpRuntimeManager = class {
365
+ activeControllers = /* @__PURE__ */ new Map();
366
+ pendingPersistentClients = /* @__PURE__ */ new Map();
367
+ constructor(options, deps = {}) {
368
+ this.options = options;
369
+ this.deps = deps;
370
+ }
371
+ createClient(options) {
372
+ return this.deps.clientFactory?.(options) ?? new AcpClient(options);
373
+ }
374
+ async ensureSession(input) {
375
+ const cwd = path.resolve(input.cwd?.trim() || this.options.cwd);
376
+ const agentCommand = this.options.agentRegistry.resolve(input.agent);
377
+ const existing = await this.options.sessionStore.load(input.sessionKey);
378
+ if (input.mode === "persistent" && existing && shouldReuseExistingRecord(existing, {
379
+ cwd,
380
+ agentCommand,
381
+ resumeSessionId: input.resumeSessionId
382
+ })) {
383
+ existing.closed = false;
384
+ existing.closedAt = void 0;
385
+ await this.options.sessionStore.save(existing);
386
+ return existing;
387
+ }
388
+ const client = this.createClient({
389
+ agentCommand,
390
+ cwd,
391
+ mcpServers: [...this.options.mcpServers ?? []],
392
+ permissionMode: this.options.permissionMode,
393
+ nonInteractivePermissions: this.options.nonInteractivePermissions,
394
+ verbose: this.options.verbose
395
+ });
396
+ let keepClientOpen = false;
397
+ try {
398
+ await client.start();
399
+ let sessionId;
400
+ let agentSessionId;
401
+ if (input.resumeSessionId) {
402
+ const loaded = await client.loadSession(input.resumeSessionId, cwd);
403
+ sessionId = input.resumeSessionId;
404
+ agentSessionId = loaded.agentSessionId;
405
+ } else {
406
+ const created = await client.createSession(cwd);
407
+ sessionId = created.sessionId;
408
+ agentSessionId = created.agentSessionId;
409
+ }
410
+ const record = createInitialRecord({
411
+ recordId: createRecordId(input.sessionKey, input.mode),
412
+ sessionName: input.sessionKey,
413
+ sessionId,
414
+ agentCommand,
415
+ cwd,
416
+ agentSessionId
417
+ });
418
+ record.protocolVersion = client.initializeResult?.protocolVersion;
419
+ record.agentCapabilities = client.initializeResult?.agentCapabilities;
420
+ applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
421
+ await this.options.sessionStore.save(record);
422
+ if (input.mode === "persistent") {
423
+ const previousClient = this.pendingPersistentClients.get(record.acpxRecordId);
424
+ this.pendingPersistentClients.set(record.acpxRecordId, client);
425
+ keepClientOpen = true;
426
+ await previousClient?.close().catch(() => {});
427
+ }
428
+ return record;
429
+ } finally {
430
+ if (!keepClientOpen) await client.close();
431
+ }
432
+ }
433
+ async *runTurn(input) {
434
+ const record = await this.requireRecord(input.handle.acpxRecordId ?? input.handle.sessionKey);
435
+ const conversation = cloneSessionConversation(record);
436
+ let acpxState = cloneSessionAcpxState(record.acpx);
437
+ const promptInput = toPromptInput(input.text, input.attachments);
438
+ const promptMessageId = recordPromptSubmission(conversation, promptInput, isoNow());
439
+ trimConversationForRuntime(conversation);
440
+ const queue = new AsyncEventQueue();
441
+ let pendingClient = this.pendingPersistentClients.get(record.acpxRecordId);
442
+ if (pendingClient) {
443
+ this.pendingPersistentClients.delete(record.acpxRecordId);
444
+ if (!pendingClient.hasReusableSession(record.acpSessionId)) {
445
+ await pendingClient.close().catch(() => {});
446
+ pendingClient = void 0;
447
+ }
448
+ }
449
+ const client = pendingClient ?? this.createClient({
450
+ agentCommand: record.agentCommand,
451
+ cwd: record.cwd,
452
+ mcpServers: [...this.options.mcpServers ?? []],
453
+ permissionMode: this.options.permissionMode,
454
+ nonInteractivePermissions: this.options.nonInteractivePermissions,
455
+ verbose: this.options.verbose
456
+ });
457
+ let activeSessionId = record.acpSessionId;
458
+ let sawDone = false;
459
+ let pendingCancel = false;
460
+ let turnActive = true;
461
+ const sessionReady = createDeferred();
462
+ sessionReady.promise.catch(() => {});
463
+ const applyPendingCancel = async () => {
464
+ if (!pendingCancel || !client.hasActivePrompt()) return false;
465
+ const cancelled = await client.requestCancelActivePrompt();
466
+ if (cancelled) pendingCancel = false;
467
+ return cancelled;
468
+ };
469
+ const activeController = {
470
+ hasActivePrompt: () => client.hasActivePrompt(),
471
+ requestCancelActivePrompt: async () => {
472
+ if (client.hasActivePrompt()) return await client.requestCancelActivePrompt();
473
+ if (!turnActive) return false;
474
+ pendingCancel = true;
475
+ return true;
476
+ },
477
+ setSessionMode: async (modeId) => {
478
+ if (!client.hasActivePrompt()) await sessionReady.promise;
479
+ await client.setSessionMode(activeSessionId, modeId);
480
+ },
481
+ setSessionModel: async (modelId) => {
482
+ if (!client.hasActivePrompt()) await sessionReady.promise;
483
+ await client.setSessionModel(activeSessionId, modelId);
484
+ },
485
+ setSessionConfigOption: async (configId, value) => {
486
+ if (!client.hasActivePrompt()) await sessionReady.promise;
487
+ return await client.setSessionConfigOption(activeSessionId, configId, value);
488
+ }
489
+ };
490
+ const emitParsed = (payload) => {
491
+ const parsed = parsePromptEventLine(JSON.stringify(payload));
492
+ if (!parsed) return;
493
+ if (parsed.type === "done") sawDone = true;
494
+ queue.push(parsed);
495
+ };
496
+ const abortHandler = () => {
497
+ activeController.requestCancelActivePrompt();
498
+ };
499
+ if (input.signal) {
500
+ if (input.signal.aborted) {
501
+ queue.close();
502
+ return;
503
+ }
504
+ input.signal.addEventListener("abort", abortHandler, { once: true });
505
+ }
506
+ this.activeControllers.set(record.acpxRecordId, activeController);
507
+ (async () => {
508
+ try {
509
+ client.setEventHandlers({
510
+ onSessionUpdate: (notification) => {
511
+ acpxState = recordSessionUpdate(conversation, acpxState, notification);
512
+ trimConversationForRuntime(conversation);
513
+ emitParsed({
514
+ jsonrpc: "2.0",
515
+ method: "session/update",
516
+ params: notification
517
+ });
518
+ },
519
+ onClientOperation: (operation) => {
520
+ acpxState = recordClientOperation(conversation, acpxState, operation);
521
+ trimConversationForRuntime(conversation);
522
+ emitParsed({
523
+ type: "client_operation",
524
+ ...operation
525
+ });
526
+ }
527
+ });
528
+ const { sessionId, resumed, loadError } = pendingClient ? {
529
+ sessionId: record.acpSessionId,
530
+ resumed: false,
531
+ loadError: void 0
532
+ } : await connectAndLoadSession({
533
+ client,
534
+ record,
535
+ resumePolicy: resumePolicyForSessionMode(input.sessionMode),
536
+ timeoutMs: this.options.timeoutMs,
537
+ activeController,
538
+ onClientAvailable: (controller) => {
539
+ this.activeControllers.set(record.acpxRecordId, controller);
540
+ },
541
+ onConnectedRecord: (connectedRecord) => {
542
+ connectedRecord.lastPromptAt = isoNow();
543
+ },
544
+ onSessionIdResolved: (sessionIdValue) => {
545
+ activeSessionId = sessionIdValue;
546
+ }
547
+ });
548
+ sessionReady.resolve();
549
+ record.lastRequestId = input.requestId;
550
+ record.lastPromptAt = isoNow();
551
+ record.closed = false;
552
+ record.closedAt = void 0;
553
+ record.lastUsedAt = isoNow();
554
+ if (resumed || loadError) emitParsed({
555
+ type: "status",
556
+ text: loadError ? `load fallback: ${loadError}` : "session resumed"
557
+ });
558
+ if (pendingCancel || input.signal?.aborted) {
559
+ pendingCancel = false;
560
+ if (!sawDone) queue.push({
561
+ type: "done",
562
+ stopReason: "cancelled"
563
+ });
564
+ return;
565
+ }
566
+ await applyPendingCancel();
567
+ const response = await runPromptTurn({
568
+ client,
569
+ sessionId,
570
+ prompt: promptInput,
571
+ timeoutMs: input.timeoutMs ?? this.options.timeoutMs,
572
+ conversation,
573
+ promptMessageId
574
+ });
575
+ record.acpSessionId = activeSessionId;
576
+ reconcileAgentSessionId(record, record.agentSessionId);
577
+ record.protocolVersion = client.initializeResult?.protocolVersion;
578
+ record.agentCapabilities = client.initializeResult?.agentCapabilities;
579
+ record.acpx = acpxState;
580
+ applyConversation(record, conversation);
581
+ applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
582
+ await this.options.sessionStore.save(record);
583
+ if (!sawDone) queue.push({
584
+ type: "done",
585
+ stopReason: response.stopReason
586
+ });
587
+ } catch (error) {
588
+ sessionReady.reject(error);
589
+ const normalized = normalizeOutputError(error, { origin: "runtime" });
590
+ queue.push({
591
+ type: "error",
592
+ message: normalized.message,
593
+ code: normalized.code,
594
+ retryable: normalized.retryable
595
+ });
596
+ } finally {
597
+ turnActive = false;
598
+ if (input.signal) input.signal.removeEventListener("abort", abortHandler);
599
+ this.activeControllers.delete(record.acpxRecordId);
600
+ client.clearEventHandlers();
601
+ applyLifecycleSnapshotToRecord(record, client.getAgentLifecycleSnapshot());
602
+ record.acpx = acpxState;
603
+ applyConversation(record, conversation);
604
+ record.lastUsedAt = isoNow();
605
+ await this.options.sessionStore.save(record).catch(() => {});
606
+ await client.close().catch(() => {});
607
+ queue.close();
608
+ }
609
+ })();
610
+ yield* queue.iterate();
611
+ }
612
+ async getStatus(handle) {
613
+ const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
614
+ return {
615
+ summary: statusSummary(record),
616
+ acpxRecordId: record.acpxRecordId,
617
+ backendSessionId: record.acpSessionId,
618
+ agentSessionId: record.agentSessionId,
619
+ details: {
620
+ cwd: record.cwd,
621
+ lastUsedAt: record.lastUsedAt,
622
+ closed: record.closed === true
623
+ }
624
+ };
625
+ }
626
+ async setMode(handle, mode, sessionMode = "persistent") {
627
+ const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
628
+ const controller = this.activeControllers.get(record.acpxRecordId);
629
+ let targetRecord = record;
630
+ if (controller) await controller.setSessionMode(mode);
631
+ else targetRecord = (await withConnectedSession({
632
+ sessionRecordId: record.acpxRecordId,
633
+ loadRecord: async (sessionRecordId) => await this.requireRecord(sessionRecordId),
634
+ saveRecord: async (connectedRecord) => await this.options.sessionStore.save(connectedRecord),
635
+ createClient: (options) => this.createClient(options),
636
+ mcpServers: [...this.options.mcpServers ?? []],
637
+ permissionMode: this.options.permissionMode,
638
+ nonInteractivePermissions: this.options.nonInteractivePermissions,
639
+ verbose: this.options.verbose,
640
+ timeoutMs: this.options.timeoutMs,
641
+ resumePolicy: resumePolicyForSessionMode(sessionMode),
642
+ run: async ({ client, sessionId }) => {
643
+ await client.setSessionMode(sessionId, mode);
644
+ }
645
+ })).record;
646
+ setDesiredModeId(targetRecord, mode);
647
+ await this.options.sessionStore.save(targetRecord);
648
+ }
649
+ async setConfigOption(handle, key, value, sessionMode = "persistent") {
650
+ const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
651
+ const controller = this.activeControllers.get(record.acpxRecordId);
652
+ let targetRecord = record;
653
+ if (controller) await controller.setSessionConfigOption(key, value);
654
+ else targetRecord = (await withConnectedSession({
655
+ sessionRecordId: record.acpxRecordId,
656
+ loadRecord: async (sessionRecordId) => await this.requireRecord(sessionRecordId),
657
+ saveRecord: async (connectedRecord) => await this.options.sessionStore.save(connectedRecord),
658
+ createClient: (options) => this.createClient(options),
659
+ mcpServers: [...this.options.mcpServers ?? []],
660
+ permissionMode: this.options.permissionMode,
661
+ nonInteractivePermissions: this.options.nonInteractivePermissions,
662
+ verbose: this.options.verbose,
663
+ timeoutMs: this.options.timeoutMs,
664
+ resumePolicy: resumePolicyForSessionMode(sessionMode),
665
+ run: async ({ client, sessionId, record: connectedRecord }) => {
666
+ await client.setSessionConfigOption(sessionId, key, value);
667
+ if (key === "mode") setDesiredModeId(connectedRecord, value);
668
+ }
669
+ })).record;
670
+ if (key === "mode") setDesiredModeId(targetRecord, value);
671
+ await this.options.sessionStore.save(targetRecord);
672
+ }
673
+ async cancel(handle) {
674
+ await this.activeControllers.get(handle.acpxRecordId ?? handle.sessionKey)?.requestCancelActivePrompt();
675
+ }
676
+ async close(handle) {
677
+ const record = await this.requireRecord(handle.acpxRecordId ?? handle.sessionKey);
678
+ await this.cancel(handle);
679
+ record.closed = true;
680
+ record.closedAt = isoNow();
681
+ await this.options.sessionStore.save(record);
682
+ }
683
+ async requireRecord(sessionId) {
684
+ const record = await this.options.sessionStore.load(sessionId);
685
+ if (!record) throw new Error(`ACP session not found: ${sessionId}`);
686
+ return record;
687
+ }
688
+ };
689
+ //#endregion
690
+ //#region src/runtime/public/file-session-store.ts
691
+ function safeSessionId(sessionId) {
692
+ return encodeURIComponent(sessionId);
693
+ }
694
+ var FileSessionStore = class {
695
+ constructor(stateDir) {
696
+ this.stateDir = stateDir;
697
+ }
698
+ get sessionDir() {
699
+ return path.join(this.stateDir, "sessions");
700
+ }
701
+ filePath(sessionId) {
702
+ return path.join(this.sessionDir, `${safeSessionId(sessionId)}.json`);
703
+ }
704
+ async ensureDir() {
705
+ await fs.mkdir(this.sessionDir, { recursive: true });
706
+ }
707
+ async load(sessionId) {
708
+ await this.ensureDir();
709
+ try {
710
+ const payload = await fs.readFile(this.filePath(sessionId), "utf8");
711
+ return parseSessionRecord(JSON.parse(payload)) ?? void 0;
712
+ } catch (error) {
713
+ if (error.code === "ENOENT") return;
714
+ throw error;
715
+ }
716
+ }
717
+ async save(record) {
718
+ await this.ensureDir();
719
+ const persisted = serializeSessionRecordForDisk(record);
720
+ assertPersistedKeyPolicy(persisted);
721
+ const file = this.filePath(record.acpxRecordId);
722
+ const tempFile = `${file}.${process.pid}.${Date.now()}.tmp`;
723
+ const payload = JSON.stringify(persisted, null, 2);
724
+ await fs.writeFile(tempFile, `${payload}\n`, "utf8");
725
+ await fs.rename(tempFile, file);
726
+ }
727
+ };
728
+ function createFileSessionStore(options) {
729
+ return new FileSessionStore(path.resolve(options.stateDir));
730
+ }
731
+ //#endregion
732
+ //#region src/runtime/public/handle-state.ts
733
+ const ACPX_RUNTIME_HANDLE_PREFIX = "acpx:v2:";
734
+ function encodeAcpxRuntimeHandleState(state) {
735
+ return `${ACPX_RUNTIME_HANDLE_PREFIX}${Buffer.from(JSON.stringify(state), "utf8").toString("base64url")}`;
736
+ }
737
+ function decodeAcpxRuntimeHandleState(runtimeSessionName) {
738
+ const trimmed = runtimeSessionName.trim();
739
+ if (!trimmed.startsWith(ACPX_RUNTIME_HANDLE_PREFIX)) return null;
740
+ try {
741
+ const raw = Buffer.from(trimmed.slice(8), "base64url").toString("utf8");
742
+ const parsed = JSON.parse(raw);
743
+ const name = asOptionalString(parsed.name);
744
+ const agent = asOptionalString(parsed.agent);
745
+ const cwd = asOptionalString(parsed.cwd);
746
+ const mode = asOptionalString(parsed.mode);
747
+ if (!name || !agent || !cwd || mode !== "persistent" && mode !== "oneshot") return null;
748
+ return {
749
+ name,
750
+ agent,
751
+ cwd,
752
+ mode,
753
+ acpxRecordId: asOptionalString(parsed.acpxRecordId),
754
+ backendSessionId: asOptionalString(parsed.backendSessionId),
755
+ agentSessionId: asOptionalString(parsed.agentSessionId)
756
+ };
757
+ } catch {
758
+ return null;
759
+ }
760
+ }
761
+ function writeHandleState(handle, state) {
762
+ handle.runtimeSessionName = encodeAcpxRuntimeHandleState(state);
763
+ handle.cwd = state.cwd;
764
+ handle.acpxRecordId = state.acpxRecordId;
765
+ handle.backendSessionId = state.backendSessionId;
766
+ handle.agentSessionId = state.agentSessionId;
767
+ }
768
+ //#endregion
769
+ //#region src/runtime/public/probe.ts
770
+ async function probeRuntime(options, deps = {}) {
771
+ const agentName = options.probeAgent?.trim() || "codex";
772
+ const agentCommand = options.agentRegistry.resolve(agentName);
773
+ const client = deps.clientFactory?.({
774
+ agentCommand,
775
+ cwd: options.cwd,
776
+ mcpServers: [...options.mcpServers ?? []],
777
+ permissionMode: options.permissionMode,
778
+ nonInteractivePermissions: options.nonInteractivePermissions,
779
+ verbose: options.verbose
780
+ }) ?? new AcpClient({
781
+ agentCommand,
782
+ cwd: options.cwd,
783
+ mcpServers: [...options.mcpServers ?? []],
784
+ permissionMode: options.permissionMode,
785
+ nonInteractivePermissions: options.nonInteractivePermissions,
786
+ verbose: options.verbose
787
+ });
788
+ try {
789
+ await client.start();
790
+ return {
791
+ ok: true,
792
+ message: "embedded ACP runtime ready",
793
+ details: [
794
+ `agent=${agentName}`,
795
+ `command=${agentCommand}`,
796
+ `cwd=${options.cwd}`,
797
+ ...client.initializeResult?.protocolVersion ? [`protocolVersion=${client.initializeResult.protocolVersion}`] : []
798
+ ]
799
+ };
800
+ } catch (error) {
801
+ return {
802
+ ok: false,
803
+ message: "embedded ACP runtime probe failed",
804
+ details: [
805
+ `agent=${agentName}`,
806
+ `command=${agentCommand}`,
807
+ `cwd=${options.cwd}`,
808
+ error instanceof Error ? error.message : String(error)
809
+ ]
810
+ };
811
+ } finally {
812
+ await client.close().catch(() => {});
813
+ }
814
+ }
815
+ //#endregion
816
+ //#region src/runtime.ts
817
+ const ACPX_BACKEND_ID = "acpx";
818
+ const ACPX_CAPABILITIES = { controls: [
819
+ "session/set_mode",
820
+ "session/set_config_option",
821
+ "session/status"
822
+ ] };
823
+ function createAgentRegistry(params) {
824
+ return {
825
+ resolve(agentName) {
826
+ return resolveAgentCommand(agentName, params?.overrides);
827
+ },
828
+ list() {
829
+ return listBuiltInAgents(params?.overrides);
830
+ }
831
+ };
832
+ }
833
+ var AcpxRuntime = class {
834
+ healthy = false;
835
+ manager = null;
836
+ managerPromise = null;
837
+ constructor(options, testOptions) {
838
+ this.options = options;
839
+ this.testOptions = testOptions;
840
+ }
841
+ isHealthy() {
842
+ return this.healthy;
843
+ }
844
+ async probeAvailability() {
845
+ this.healthy = (await this.runProbe()).ok;
846
+ }
847
+ async doctor() {
848
+ const report = await this.runProbe();
849
+ this.healthy = report.ok;
850
+ return {
851
+ ok: report.ok,
852
+ code: report.ok ? void 0 : "ACP_BACKEND_UNAVAILABLE",
853
+ message: report.message,
854
+ details: report.details
855
+ };
856
+ }
857
+ async ensureSession(input) {
858
+ const sessionName = input.sessionKey.trim();
859
+ if (!sessionName) throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", "ACP session key is required.");
860
+ const agent = input.agent.trim();
861
+ if (!agent) throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", "ACP agent id is required.");
862
+ const record = await (await this.getManager()).ensureSession({
863
+ sessionKey: sessionName,
864
+ agent,
865
+ mode: input.mode,
866
+ cwd: input.cwd ?? this.options.cwd,
867
+ resumeSessionId: input.resumeSessionId
868
+ });
869
+ const handle = {
870
+ sessionKey: input.sessionKey,
871
+ backend: ACPX_BACKEND_ID,
872
+ runtimeSessionName: "",
873
+ cwd: record.cwd,
874
+ acpxRecordId: record.acpxRecordId,
875
+ backendSessionId: record.acpSessionId,
876
+ agentSessionId: record.agentSessionId
877
+ };
878
+ writeHandleState(handle, {
879
+ name: sessionName,
880
+ agent,
881
+ cwd: record.cwd,
882
+ mode: input.mode,
883
+ acpxRecordId: record.acpxRecordId,
884
+ backendSessionId: record.acpSessionId,
885
+ agentSessionId: record.agentSessionId
886
+ });
887
+ return handle;
888
+ }
889
+ async *runTurn(input) {
890
+ const state = this.resolveHandleState(input.handle);
891
+ yield* (await this.getManager()).runTurn({
892
+ handle: {
893
+ ...input.handle,
894
+ acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
895
+ },
896
+ text: input.text,
897
+ attachments: input.attachments,
898
+ mode: input.mode,
899
+ sessionMode: state.mode,
900
+ requestId: input.requestId,
901
+ timeoutMs: input.timeoutMs,
902
+ signal: input.signal
903
+ });
904
+ }
905
+ getCapabilities() {
906
+ return ACPX_CAPABILITIES;
907
+ }
908
+ async getStatus(input) {
909
+ const state = this.resolveHandleState(input.handle);
910
+ return await (await this.getManager()).getStatus({
911
+ ...input.handle,
912
+ acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
913
+ });
914
+ }
915
+ async setMode(input) {
916
+ const state = this.resolveHandleState(input.handle);
917
+ await (await this.getManager()).setMode({
918
+ ...input.handle,
919
+ acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
920
+ }, input.mode, state.mode);
921
+ }
922
+ async setConfigOption(input) {
923
+ const state = this.resolveHandleState(input.handle);
924
+ await (await this.getManager()).setConfigOption({
925
+ ...input.handle,
926
+ acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
927
+ }, input.key, input.value, state.mode);
928
+ }
929
+ async cancel(input) {
930
+ const state = this.resolveHandleState(input.handle);
931
+ await (await this.getManager()).cancel({
932
+ ...input.handle,
933
+ acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
934
+ });
935
+ }
936
+ async close(input) {
937
+ const state = this.resolveHandleState(input.handle);
938
+ await (await this.getManager()).close({
939
+ ...input.handle,
940
+ acpxRecordId: state.acpxRecordId ?? input.handle.acpxRecordId ?? input.handle.sessionKey
941
+ });
942
+ }
943
+ async getManager() {
944
+ if (this.manager) return this.manager;
945
+ if (!this.managerPromise) this.managerPromise = Promise.resolve(this.testOptions?.managerFactory?.(this.options) ?? new AcpRuntimeManager(this.options)).then((manager) => {
946
+ this.manager = manager;
947
+ return manager;
948
+ });
949
+ return await this.managerPromise;
950
+ }
951
+ async runProbe() {
952
+ return await (this.testOptions?.probeRunner?.(this.options) ?? probeRuntime(this.options));
953
+ }
954
+ resolveHandleState(handle) {
955
+ const decoded = decodeAcpxRuntimeHandleState(handle.runtimeSessionName);
956
+ if (decoded) return {
957
+ ...decoded,
958
+ acpxRecordId: decoded.acpxRecordId ?? handle.acpxRecordId,
959
+ backendSessionId: decoded.backendSessionId ?? handle.backendSessionId,
960
+ agentSessionId: decoded.agentSessionId ?? handle.agentSessionId
961
+ };
962
+ const runtimeSessionName = handle.runtimeSessionName.trim();
963
+ if (!runtimeSessionName) throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", "Invalid embedded ACP runtime handle: runtimeSessionName is missing.");
964
+ return {
965
+ name: runtimeSessionName,
966
+ agent: deriveAgentFromSessionKey(handle.sessionKey, DEFAULT_AGENT_NAME),
967
+ cwd: handle.cwd ?? this.options.cwd,
968
+ mode: "persistent",
969
+ acpxRecordId: handle.acpxRecordId,
970
+ backendSessionId: handle.backendSessionId,
971
+ agentSessionId: handle.agentSessionId
972
+ };
973
+ }
974
+ };
975
+ function createAcpRuntime(options) {
976
+ return new AcpxRuntime(options);
977
+ }
978
+ function createRuntimeStore(options) {
979
+ return createFileSessionStore(options);
980
+ }
981
+ //#endregion
982
+ export { ACPX_BACKEND_ID, AcpRuntimeError, AcpxRuntime, DEFAULT_AGENT_NAME, createAcpRuntime, createAgentRegistry, createFileSessionStore, createRuntimeStore, decodeAcpxRuntimeHandleState, encodeAcpxRuntimeHandleState, isAcpRuntimeError };
983
+
984
+ //# sourceMappingURL=runtime.js.map