@agentknight/sdk 0.9.42 → 0.9.44

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.
@@ -0,0 +1,1040 @@
1
+ import {
2
+ hasClaudeProviderConfiguration
3
+ } from "./chunk-EIFNERXM.js";
4
+ import {
5
+ latestPreviewItems
6
+ } from "./chunk-AK3YVBEP.js";
7
+ import {
8
+ UnsupportedAgentOperationError,
9
+ validateSessionName
10
+ } from "./chunk-UNRPSTRM.js";
11
+ import {
12
+ ConversationItemSchema,
13
+ InteractionRequestSchema,
14
+ InteractionResponseSchema,
15
+ SESSION_TIMELINE_PAGE_SIZE,
16
+ SessionSummarySchema
17
+ } from "./chunk-W6TZVD7W.js";
18
+
19
+ // src/claude-adapter.ts
20
+ import { randomUUID as randomUUID2 } from "node:crypto";
21
+ import { mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
22
+ import { isAbsolute, join, resolve } from "node:path";
23
+ import { z as z4 } from "zod";
24
+
25
+ // src/adapters/claude/input.ts
26
+ var ClaudeInput = class {
27
+ #value;
28
+ #wake;
29
+ #closed = false;
30
+ push(value) {
31
+ if (this.#closed || this.#value !== void 0) throw new Error("Claude input is unavailable");
32
+ this.#value = value;
33
+ this.#wake?.();
34
+ }
35
+ close() {
36
+ this.#closed = true;
37
+ this.#value = void 0;
38
+ this.#wake?.();
39
+ }
40
+ async *[Symbol.asyncIterator]() {
41
+ while (!this.#closed) {
42
+ if (this.#value === void 0) await new Promise((resolve2) => {
43
+ this.#wake = resolve2;
44
+ });
45
+ this.#wake = void 0;
46
+ if (this.#closed) return;
47
+ const value = this.#value;
48
+ this.#value = void 0;
49
+ if (value !== void 0) yield value;
50
+ }
51
+ }
52
+ };
53
+
54
+ // src/adapters/claude/interactions.ts
55
+ import { randomUUID } from "node:crypto";
56
+ import { z } from "zod";
57
+ var QuestionsSchema = z.object({
58
+ questions: z.array(z.object({
59
+ question: z.string().trim().min(1).max(1024),
60
+ header: z.string().optional(),
61
+ options: z.array(z.object({ label: z.string().max(1024), description: z.string().optional() })).max(100),
62
+ multiSelect: z.boolean().optional()
63
+ })).min(1).max(100)
64
+ });
65
+ var ClaudeInteractions = class {
66
+ constructor(callbacks, timeoutMs) {
67
+ this.callbacks = callbacks;
68
+ this.timeoutMs = timeoutMs;
69
+ }
70
+ callbacks;
71
+ timeoutMs;
72
+ #pending = /* @__PURE__ */ new Map();
73
+ get size() {
74
+ return this.#pending.size;
75
+ }
76
+ request(sessionId, tool, input, signal) {
77
+ if (signal.aborted) return Promise.resolve({ behavior: "deny", message: "Request cancelled" });
78
+ const questions = tool === "AskUserQuestion" ? QuestionsSchema.safeParse(input) : void 0;
79
+ if (questions && !questions.success) {
80
+ return Promise.resolve({ behavior: "deny", message: "Unsupported AskUserQuestion input" });
81
+ }
82
+ const questionList = questions?.success ? questions.data.questions : void 0;
83
+ const single = questionList?.length === 1 ? questionList[0] : void 0;
84
+ const request = InteractionRequestSchema.parse(single && !single.multiSelect ? {
85
+ kind: "user_input",
86
+ question: single.question,
87
+ choices: single.options.map((option) => option.label),
88
+ allowFreeform: true
89
+ } : questionList ? {
90
+ kind: "elicitation",
91
+ mode: "form",
92
+ message: "Claude needs your input",
93
+ source: "Claude",
94
+ fields: questionList.map((question, index) => ({
95
+ id: `q${index}`,
96
+ label: question.question,
97
+ inputType: question.multiSelect ? "multi-select" : "select",
98
+ required: true,
99
+ options: question.options.map((option) => option.label)
100
+ }))
101
+ } : {
102
+ kind: "permission",
103
+ permissionKind: tool,
104
+ title: `Allow ${tool}?`.slice(0, 256),
105
+ description: JSON.stringify(input).slice(0, 4096),
106
+ canApproveSession: false,
107
+ canApproveAlways: false
108
+ });
109
+ const id = randomUUID();
110
+ return new Promise((resolve2) => {
111
+ let settled = false;
112
+ const cancel = () => {
113
+ finish();
114
+ };
115
+ const timer = setTimeout(cancel, this.timeoutMs);
116
+ const finish = (response) => {
117
+ if (settled) return false;
118
+ let result = { behavior: "deny", message: "Request cancelled or denied" };
119
+ if (response !== void 0) {
120
+ if (response.kind !== request.kind) return false;
121
+ if (response.kind === "permission") {
122
+ if (response.decision !== "approve-once" && response.decision !== "reject") return false;
123
+ result = response.decision === "approve-once" ? { behavior: "allow", updatedInput: input } : { behavior: "deny", message: response.feedback ?? "User denied permission" };
124
+ } else if (response.kind === "user_input" && single) {
125
+ result = { behavior: "allow", updatedInput: { ...input, answers: { [single.question]: response.answer } } };
126
+ } else if (response.kind === "elicitation" && questionList) {
127
+ if (response.action === "accept") {
128
+ const answers = {};
129
+ for (const [index, question] of questionList.entries()) {
130
+ const answer = response.values[`q${index}`];
131
+ if (question.multiSelect) {
132
+ if (!Array.isArray(answer) || !answer.length || answer.some((value) => !question.options.some((option) => option.label === value))) return false;
133
+ answers[question.question] = answer.join(", ");
134
+ } else {
135
+ if (typeof answer !== "string" || !answer.trim()) return false;
136
+ answers[question.question] = answer;
137
+ }
138
+ }
139
+ result = { behavior: "allow", updatedInput: { ...input, answers } };
140
+ }
141
+ } else return false;
142
+ }
143
+ settled = true;
144
+ clearTimeout(timer);
145
+ signal.removeEventListener("abort", cancel);
146
+ this.#pending.delete(id);
147
+ this.callbacks()?.onInteractionDismissed?.(sessionId, id);
148
+ resolve2(result);
149
+ return true;
150
+ };
151
+ this.#pending.set(id, { sessionId, request, finish });
152
+ signal.addEventListener("abort", cancel, { once: true });
153
+ if (signal.aborted) cancel();
154
+ else this.#emit(id, this.#pending.get(id));
155
+ });
156
+ }
157
+ respond(id, response) {
158
+ const parsed = InteractionResponseSchema.safeParse(response);
159
+ return parsed.success ? this.#pending.get(id)?.finish(parsed.data) ?? false : false;
160
+ }
161
+ cancel(sessionId) {
162
+ for (const pending of [...this.#pending.values()]) if (pending.sessionId === sessionId) pending.finish();
163
+ }
164
+ replay() {
165
+ for (const [id, pending] of this.#pending) this.#emit(id, pending);
166
+ }
167
+ #emit(id, pending) {
168
+ if (!pending) return;
169
+ const callbacks = this.callbacks();
170
+ if (callbacks?.onInteractionRequest) callbacks.onInteractionRequest(pending.sessionId, id, pending.request);
171
+ else if (pending.request.kind === "permission" && callbacks) {
172
+ callbacks.onPermissionRequest(pending.sessionId, id, { kind: pending.request.permissionKind });
173
+ } else pending.finish();
174
+ }
175
+ };
176
+
177
+ // src/adapters/claude/timeline.ts
178
+ import { createHash } from "node:crypto";
179
+ import { z as z2 } from "zod";
180
+ var identity = (value) => createHash("sha256").update(value).digest("hex");
181
+ var ObjectSchema = z2.record(z2.string(), z2.unknown());
182
+ var EnvelopeSchema = z2.object({
183
+ type: z2.string(),
184
+ uuid: z2.string().optional(),
185
+ parent_tool_use_id: z2.string().nullable().optional()
186
+ }).passthrough();
187
+ var BlockSchema = z2.object({
188
+ type: z2.string(),
189
+ text: z2.string().optional(),
190
+ id: z2.string().optional(),
191
+ name: z2.string().optional(),
192
+ tool_use_id: z2.string().optional(),
193
+ is_error: z2.boolean().optional()
194
+ }).passthrough();
195
+ var ClaudeTimeline = class {
196
+ constructor(sessionId, items, publish) {
197
+ this.sessionId = sessionId;
198
+ this.items = items;
199
+ this.publish = publish;
200
+ }
201
+ sessionId;
202
+ items;
203
+ publish;
204
+ #streams = /* @__PURE__ */ new Map();
205
+ #seen = /* @__PURE__ */ new Set();
206
+ upsert(value) {
207
+ const index = this.items.findIndex((item2) => item2.id === value.id);
208
+ const previous = this.items[index];
209
+ const item = ConversationItemSchema.parse({
210
+ ...value,
211
+ timestamp: previous?.timestamp ?? value.timestamp,
212
+ revision: previous?.revision ?? 1
213
+ });
214
+ if (previous && JSON.stringify(previous) === JSON.stringify(item)) return;
215
+ if (previous) item.revision++;
216
+ if (index < 0) this.items.push(item);
217
+ else this.items[index] = item;
218
+ this.publish(structuredClone(item));
219
+ }
220
+ base(id, turnId) {
221
+ return { id, turnId, revision: 1, timestamp: (/* @__PURE__ */ new Date()).toISOString(), sourceEventIds: [id] };
222
+ }
223
+ notice(turnId, content, severity = "error") {
224
+ const id = identity(`${turnId}:notice:${content}`);
225
+ this.upsert({
226
+ ...this.base(id, turnId),
227
+ kind: "notice",
228
+ actor: "system",
229
+ severity,
230
+ title: severity === "error" ? "Claude stopped" : "Claude interrupted",
231
+ content: content.slice(0, 4096)
232
+ });
233
+ }
234
+ finish() {
235
+ for (const item of [...this.items]) {
236
+ if (item.kind === "assistant-message" && !item.final) this.upsert({ ...item, final: true });
237
+ if (item.kind === "activity-group" && item.status === "running") {
238
+ this.upsert({
239
+ ...item,
240
+ status: "failed",
241
+ steps: item.steps.map((step) => step.status === "running" ? { ...step, status: "failed" } : step)
242
+ });
243
+ }
244
+ }
245
+ this.#streams.clear();
246
+ }
247
+ project(raw, turnId) {
248
+ const message = EnvelopeSchema.parse(raw);
249
+ if (message.uuid && this.#seen.has(message.uuid)) return;
250
+ if (message.uuid) this.#seen.add(message.uuid);
251
+ const source = message.uuid ?? turnId;
252
+ if (message.type === "assistant") {
253
+ const body = z2.object({ id: z2.string(), content: z2.array(BlockSchema) }).parse(message.message);
254
+ body.content.forEach((block, index) => {
255
+ if (block.type === "text" && block.text !== void 0) {
256
+ this.#text(body.id, index, block.text, true, turnId);
257
+ } else if (block.type === "tool_use" && block.id) {
258
+ this.#tool(block.id, block.name, "running", turnId);
259
+ }
260
+ });
261
+ } else if (message.type === "user") {
262
+ const detached = z2.object({ detachedToolCall: z2.literal(true) }).safeParse(message.tool_use_result);
263
+ if (detached.success) return;
264
+ const body = z2.object({ content: z2.unknown() }).parse(message.message);
265
+ if (!Array.isArray(body.content)) return;
266
+ for (const rawBlock of body.content) {
267
+ const block = BlockSchema.parse(rawBlock);
268
+ if (block.type === "tool_result" && block.tool_use_id) {
269
+ this.#tool(block.tool_use_id, void 0, block.is_error ? "failed" : "completed", turnId);
270
+ }
271
+ }
272
+ } else if (message.type === "tool_progress") {
273
+ const progress = z2.object({ tool_use_id: z2.string(), tool_name: z2.string() }).parse(message);
274
+ this.#tool(progress.tool_use_id, progress.tool_name, "running", turnId);
275
+ } else if (message.type === "stream_event") {
276
+ const event = z2.object({ type: z2.string() }).passthrough().parse(message.event);
277
+ const key = message.parent_tool_use_id ?? "main";
278
+ if (event.type === "message_start") {
279
+ const body = z2.object({ id: z2.string() }).parse(event.message);
280
+ this.#streams.set(key, { id: body.id, blocks: /* @__PURE__ */ new Map() });
281
+ }
282
+ const stream = this.#streams.get(key);
283
+ if (!stream) return;
284
+ if (event.type === "content_block_start") {
285
+ const index = z2.number().int().nonnegative().parse(event.index);
286
+ const block = BlockSchema.parse(event.content_block);
287
+ if (block.type === "text") {
288
+ stream.blocks.set(index, block.text ?? "");
289
+ this.#text(stream.id, index, block.text ?? "", false, turnId);
290
+ } else if (block.type === "tool_use" && block.id) {
291
+ this.#tool(block.id, block.name, "running", turnId);
292
+ }
293
+ } else if (event.type === "content_block_delta") {
294
+ const delta = ObjectSchema.parse(event.delta);
295
+ const index = z2.number().int().nonnegative().parse(event.index);
296
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
297
+ const text = (stream.blocks.get(index) ?? "") + delta.text;
298
+ stream.blocks.set(index, text);
299
+ this.#text(stream.id, index, text, false, turnId);
300
+ }
301
+ } else if (event.type === "content_block_stop") {
302
+ const index = z2.number().int().nonnegative().parse(event.index);
303
+ const text = stream.blocks.get(index);
304
+ if (text !== void 0) this.#text(stream.id, index, text, true, turnId);
305
+ }
306
+ } else if (message.type === "system" && message.subtype === "status" && message.status === "compacting") {
307
+ const id = identity(`${this.sessionId}:status:${source}`);
308
+ this.upsert({
309
+ ...this.base(id, turnId),
310
+ kind: "notice",
311
+ actor: "system",
312
+ severity: "info",
313
+ title: "Compacting conversation",
314
+ content: ""
315
+ });
316
+ }
317
+ }
318
+ #text(messageId, index, content, final, turnId) {
319
+ const id = identity(`${this.sessionId}:message:${messageId}:${index}`);
320
+ const old = this.items.find((item) => item.id === id);
321
+ if (!final && old?.kind === "assistant-message" && old.final) return;
322
+ this.upsert({
323
+ ...this.base(id, old?.turnId ?? turnId),
324
+ sourceMessageIds: [messageId],
325
+ kind: "assistant-message",
326
+ actor: "assistant",
327
+ content,
328
+ final
329
+ });
330
+ }
331
+ #tool(toolId, name, status, turnId) {
332
+ const id = identity(`${this.sessionId}:tool:${toolId}`);
333
+ const previous = this.items.find((item) => item.id === id);
334
+ if (previous?.kind === "activity-group" && previous.status !== "running" && status === "running") return;
335
+ const base = this.base(id, previous?.turnId ?? turnId);
336
+ const oldStep = previous?.kind === "activity-group" ? previous.steps[0] : void 0;
337
+ this.upsert({
338
+ ...base,
339
+ kind: "activity-group",
340
+ actor: "assistant",
341
+ status,
342
+ sourceMessageIds: [toolId],
343
+ steps: [{ id, kind: "tool", name: (name ?? oldStep?.name ?? "Tool").slice(0, 256), status }]
344
+ });
345
+ }
346
+ };
347
+
348
+ // src/adapters/claude/auth.ts
349
+ import { z as z3 } from "zod";
350
+ var EnvironmentSchema = z3.record(z3.string(), z3.string().optional());
351
+ function prepareClaudeEnvironment(value, configDirectory) {
352
+ const env = EnvironmentSchema.parse(value);
353
+ if (!hasClaudeProviderConfiguration(env)) {
354
+ throw new Error("Claude requires ANTHROPIC_API_KEY or a configured Bedrock/Vertex/Foundry provider (flag 1); CLI OAuth is not reused");
355
+ }
356
+ for (const key of Object.keys(env)) {
357
+ if (key.startsWith("CLAUDE") && (/OAUTH|TOKEN|CREDS|CREDENTIAL|HOST_AUTH|FILE_DESCRIPTOR/.test(key) || key === "CLAUDE_SECURESTORAGE_CONFIG_DIR" || key === "CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST")) {
358
+ delete env[key];
359
+ }
360
+ }
361
+ delete env.ANTHROPIC_AUTH_TOKEN;
362
+ env.CLAUDE_CONFIG_DIR = configDirectory;
363
+ return env;
364
+ }
365
+
366
+ // src/adapters/claude/process.ts
367
+ import { spawn } from "node:child_process";
368
+ var ClaudeProcessTerminationError = class extends Error {
369
+ };
370
+ var ClaudeProcess = class {
371
+ #child;
372
+ #exit;
373
+ #error;
374
+ spawn = (options) => {
375
+ if (this.#child) throw new Error("Claude query attempted to spawn more than one process");
376
+ const child = spawn(options.command, options.args, {
377
+ ...options.cwd === void 0 ? {} : { cwd: options.cwd },
378
+ env: options.env,
379
+ signal: options.signal,
380
+ stdio: ["pipe", "pipe", "pipe"],
381
+ windowsHide: true,
382
+ shell: false
383
+ });
384
+ this.#child = child;
385
+ this.#exit = new Promise((resolve2) => {
386
+ const onExit = () => {
387
+ resolve2();
388
+ };
389
+ const onError = (error) => {
390
+ this.#error = error;
391
+ if (child.pid === void 0) resolve2();
392
+ };
393
+ const onStderrError = (error) => {
394
+ this.#error = error;
395
+ };
396
+ child.once("exit", onExit);
397
+ child.on("error", onError);
398
+ child.stderr.on("error", onStderrError);
399
+ child.once("close", () => {
400
+ child.off("exit", onExit);
401
+ child.off("error", onError);
402
+ child.stderr.off("error", onStderrError);
403
+ });
404
+ });
405
+ child.stderr.resume();
406
+ return child;
407
+ };
408
+ async finish(timeoutMs) {
409
+ const child = this.#child;
410
+ const exit = this.#exit;
411
+ if (!child || !exit) return;
412
+ try {
413
+ if (await this.#wait(exit, timeoutMs)) return;
414
+ try {
415
+ child.kill("SIGKILL");
416
+ } catch (error) {
417
+ this.#error = error instanceof Error ? error : new Error(String(error));
418
+ }
419
+ if (await this.#wait(exit, timeoutMs)) return;
420
+ throw new ClaudeProcessTerminationError(
421
+ "Claude process termination timed out; native session reuse is blocked",
422
+ { cause: this.#error }
423
+ );
424
+ } finally {
425
+ child.stdin.destroy();
426
+ child.stdout.destroy();
427
+ child.stderr.destroy();
428
+ }
429
+ }
430
+ async #wait(exit, timeoutMs) {
431
+ let timer;
432
+ try {
433
+ return await Promise.race([
434
+ exit.then(() => true),
435
+ new Promise((resolve2) => {
436
+ timer = setTimeout(() => resolve2(false), timeoutMs);
437
+ })
438
+ ]);
439
+ } finally {
440
+ clearTimeout(timer);
441
+ }
442
+ }
443
+ };
444
+
445
+ // src/claude-adapter.ts
446
+ var CLAUDE_DEFAULT_MODEL_ID = "claude:agent-default";
447
+ var UuidSchema = z4.custom((value) => z4.uuid().safeParse(value).success);
448
+ var ReceiptSchema = z4.object({
449
+ id: UuidSchema,
450
+ prompt: z4.string().min(1).max(1e5),
451
+ mode: z4.enum(["enqueue", "immediate"]),
452
+ submittedAt: z4.iso.datetime(),
453
+ operationId: z4.string().min(1).max(256).optional(),
454
+ state: z4.enum(["queued", "submitted", "confirmed", "completed", "cancelled", "failed"])
455
+ });
456
+ var StoredSchema = z4.object({
457
+ version: z4.literal(1),
458
+ summary: SessionSummarySchema,
459
+ nativeId: UuidSchema.optional(),
460
+ modelId: z4.string().min(1).max(256),
461
+ items: z4.array(ConversationItemSchema),
462
+ receipts: z4.array(ReceiptSchema),
463
+ unsafe: z4.boolean()
464
+ });
465
+ var MessageSchema = z4.object({
466
+ type: z4.string(),
467
+ subtype: z4.string().optional(),
468
+ uuid: z4.string().optional(),
469
+ session_id: z4.string().optional(),
470
+ state: z4.string().optional(),
471
+ user_message_uuid: z4.string().optional(),
472
+ user_message_uuids: z4.array(z4.string()).optional(),
473
+ parent_tool_use_id: z4.string().nullable().optional(),
474
+ is_error: z4.boolean().optional()
475
+ }).passthrough();
476
+ var ClaudeCodingAgentAdapter = class {
477
+ agentId = "claude";
478
+ sdkVersion = "0.3.289";
479
+ agentModes = ["interactive"];
480
+ supportsReasoningEffort = false;
481
+ supportsSessionListUpdates = true;
482
+ supportsAssistantPreview = true;
483
+ supportsSessionDelete = false;
484
+ supportsFileAttachments = false;
485
+ groupChatEnabled = false;
486
+ #options;
487
+ #directory;
488
+ #sessions = /* @__PURE__ */ new Map();
489
+ #interactions;
490
+ #callbacks;
491
+ #started = false;
492
+ #stopping = false;
493
+ #starting;
494
+ #stopPromise;
495
+ constructor(options) {
496
+ if (!options.dataDirectory.trim()) throw new Error("Claude requires a dataDirectory");
497
+ if (options.command !== void 0 && (!options.command.trim() || options.command.includes("\0"))) {
498
+ throw new Error("Invalid Claude executable");
499
+ }
500
+ const timeout = options.interactionTimeoutMs ?? 5 * 6e4;
501
+ if (!Number.isSafeInteger(timeout) || timeout < 1) throw new Error("Invalid Claude interaction timeout");
502
+ const exitTimeout = options.processExitTimeoutMs ?? 5e3;
503
+ if (!Number.isSafeInteger(exitTimeout) || exitTimeout < 1) throw new Error("Invalid Claude process exit timeout");
504
+ this.#options = options;
505
+ this.#directory = join(resolve(options.dataDirectory), "claude");
506
+ this.#interactions = new ClaudeInteractions(() => this.#callbacks, timeout);
507
+ }
508
+ setSessionCallbacks(callbacks) {
509
+ this.#callbacks = callbacks;
510
+ }
511
+ start() {
512
+ if (this.#stopping) return Promise.reject(new Error("Create a new Claude adapter after stopping"));
513
+ if (this.#started) return Promise.resolve();
514
+ return this.#starting ??= this.#start();
515
+ }
516
+ async #start() {
517
+ await mkdir(this.#directory, { recursive: true, mode: 448 });
518
+ await mkdir(join(this.#directory, "native"), { recursive: true, mode: 448 });
519
+ for (const file of await readdir(this.#directory)) {
520
+ if (!/^[a-f0-9]{64}\.json$/.test(file)) continue;
521
+ const raw = JSON.parse(await readFile(join(this.#directory, file), "utf8"));
522
+ const stored = StoredSchema.parse(raw);
523
+ if (!stored.summary.sessionId.startsWith("claude:") || `${identity(stored.summary.sessionId)}.json` !== file) {
524
+ throw new Error("Invalid Claude session ownership record");
525
+ }
526
+ const session = this.#newSession(stored);
527
+ this.#sessions.set(stored.summary.sessionId, session);
528
+ for (const receipt of stored.receipts) {
529
+ if (["queued", "submitted", "confirmed"].includes(receipt.state)) {
530
+ receipt.state = "failed";
531
+ session.timeline.notice(receipt.id, "Previous host stopped before this prompt settled; it was not retried.");
532
+ if (!stored.nativeId) stored.unsafe = true;
533
+ }
534
+ }
535
+ session.timeline.finish();
536
+ await this.#save(session);
537
+ }
538
+ if (this.#stopping) throw new Error("Claude stopped during startup");
539
+ this.#started = true;
540
+ }
541
+ #newSession(stored) {
542
+ const session = {
543
+ stored,
544
+ timeline: new ClaudeTimeline(stored.summary.sessionId, stored.items, (item) => {
545
+ stored.summary.modifiedAt = (/* @__PURE__ */ new Date()).toISOString();
546
+ this.#callbacks?.onConversationItem(stored.summary.sessionId, item);
547
+ }),
548
+ queue: [],
549
+ active: void 0,
550
+ input: void 0,
551
+ query: void 0,
552
+ controller: void 0,
553
+ work: void 0,
554
+ dispatch: void 0,
555
+ aborting: void 0,
556
+ cancellations: 0,
557
+ operations: 0,
558
+ lock: Promise.resolve(),
559
+ saving: Promise.resolve(),
560
+ saves: 0,
561
+ error: void 0,
562
+ epoch: 0,
563
+ busy: false,
564
+ background: false,
565
+ resultSeen: false
566
+ };
567
+ return session;
568
+ }
569
+ #ready() {
570
+ if (!this.#started || this.#stopping) throw new Error("Claude adapter is not running");
571
+ }
572
+ #known(id) {
573
+ this.#ready();
574
+ const session = this.#sessions.get(id);
575
+ if (!session) throw new Error(`Unknown host-owned Claude session: ${id}`);
576
+ return session;
577
+ }
578
+ isSessionVisible(id) {
579
+ return this.#started && !this.#stopping && this.#sessions.has(id);
580
+ }
581
+ sessionPublicationSource(id) {
582
+ this.#known(id);
583
+ return "claude:host-owned";
584
+ }
585
+ async canUpgrade() {
586
+ return this.#started && !this.#stopping && this.#interactions.size === 0 && [...this.#sessions.values()].every((s) => (!s.work || s.query !== void 0) && !s.dispatch && !s.active && !s.busy && !s.background && !s.cancellations && !s.operations && !s.aborting && !s.queue.length && !s.saves && !s.error);
587
+ }
588
+ async listSessions(userId, machineId) {
589
+ this.#ready();
590
+ return [...this.#sessions.values()].filter((s) => s.stored.summary.userId === userId && s.stored.summary.machineId === machineId).map((s) => structuredClone(s.stored.summary));
591
+ }
592
+ async listModels() {
593
+ this.#ready();
594
+ return [{ id: CLAUDE_DEFAULT_MODEL_ID, name: "Claude agent default", isDefault: true }];
595
+ }
596
+ getSessionCapabilities() {
597
+ return { agentModes: this.agentModes, supportsReasoningEffort: false, supportsFileAttachments: false, agentName: "Claude", supportsPlanRead: false };
598
+ }
599
+ async getSessionConfiguration(id) {
600
+ return { modelId: this.#known(id).stored.modelId, agentMode: "interactive", reasoningEffort: "default" };
601
+ }
602
+ async getSessionDirectoryMetadata(id) {
603
+ const workingDirectory = this.#known(id).stored.summary.workingDirectory;
604
+ return workingDirectory === void 0 ? {} : { workingDirectory };
605
+ }
606
+ async getSessionEnvironment(id) {
607
+ this.#known(id);
608
+ return { tools: [], skills: [], slashCommands: [] };
609
+ }
610
+ #unsupported(operation) {
611
+ throw new UnsupportedAgentOperationError("claude", operation);
612
+ }
613
+ async getSessionRemoteStatus(id) {
614
+ this.#known(id);
615
+ return this.#unsupported("vendor remote sessions");
616
+ }
617
+ async setSessionRemote(id, _enabled) {
618
+ return this.getSessionRemoteStatus(id);
619
+ }
620
+ async deleteSession(id) {
621
+ this.#known(id);
622
+ return this.#unsupported("native session deletion");
623
+ }
624
+ async readSessionPlan(id) {
625
+ this.#known(id);
626
+ return this.#unsupported("plan reading");
627
+ }
628
+ async setGroupChatEnabled(enabled) {
629
+ if (enabled) this.#unsupported("group chat");
630
+ }
631
+ async setPeerChatEnabled(enabled) {
632
+ if (enabled) this.#unsupported("peer chat");
633
+ }
634
+ async watchSession(id, _watched) {
635
+ this.#known(id);
636
+ }
637
+ #validate(options) {
638
+ if (options.agentMode !== void 0 && options.agentMode !== "interactive") this.#unsupported("agent modes");
639
+ if (options.reasoningEffort !== void 0 && options.reasoningEffort !== "default") this.#unsupported("reasoning controls");
640
+ if (options.configOption !== void 0) this.#unsupported("configuration options");
641
+ if (options.modelId !== void 0 && options.modelId !== CLAUDE_DEFAULT_MODEL_ID) this.#unsupported("model selection");
642
+ }
643
+ async createSession(options) {
644
+ this.#ready();
645
+ this.#validate(options);
646
+ if (!isAbsolute(options.workingDirectory)) throw new Error("Claude working directory must be absolute");
647
+ const now = (/* @__PURE__ */ new Date()).toISOString();
648
+ const summary = SessionSummarySchema.parse({
649
+ sessionId: `claude:${randomUUID2()}`,
650
+ userId: options.userId,
651
+ machineId: options.machineId,
652
+ workingDirectory: options.workingDirectory,
653
+ createdAt: now,
654
+ modifiedAt: now,
655
+ isRemote: false,
656
+ renameSupported: true
657
+ });
658
+ const session = this.#newSession({ version: 1, summary, modelId: options.modelId, items: [], receipts: [], unsafe: false });
659
+ this.#sessions.set(summary.sessionId, session);
660
+ await this.#save(session);
661
+ this.#callbacks?.onSessionInventoryChanged(summary.sessionId);
662
+ return structuredClone(summary);
663
+ }
664
+ async configureSession(id, options) {
665
+ this.#known(id);
666
+ this.#validate(options);
667
+ return this.getSessionConfiguration(id);
668
+ }
669
+ async renameSession(id, name, assertAccess) {
670
+ const session = this.#known(id);
671
+ const title = validateSessionName(name);
672
+ assertAccess?.();
673
+ session.stored.summary.summary = title;
674
+ await this.#save(session);
675
+ this.#callbacks?.onSessionInventoryChanged(id);
676
+ return title;
677
+ }
678
+ async readSessionTimelinePage(id, options = {}) {
679
+ const session = this.#known(id);
680
+ const size = options.pageSize ?? SESSION_TIMELINE_PAGE_SIZE;
681
+ if (!Number.isSafeInteger(size) || size < 1 || size > SESSION_TIMELINE_PAGE_SIZE) throw new Error("Invalid Claude page size");
682
+ const items = session.stored.items;
683
+ const boundary = options.cursor === void 0 ? items.length : items.findIndex((item) => `${identity(id)}:${item.id}` === options.cursor);
684
+ const end = boundary < 0 ? items.length : boundary;
685
+ const start = Math.max(0, end - size);
686
+ const selected = options.metadataOnly ? options.assistantPreview ? latestPreviewItems(items) : [] : items.slice(start, end);
687
+ return {
688
+ items: structuredClone(selected),
689
+ cursor: `${identity(id)}:${items[start]?.id ?? "empty"}`,
690
+ hasMore: options.metadataOnly ? false : start > 0,
691
+ cursorStatus: boundary < 0 ? "expired" : "ok"
692
+ };
693
+ }
694
+ async sendPrompt(id, prompt, mode, attachments = [], _persistInteractions = true, files = [], operationId, avatar) {
695
+ const session = this.#known(id);
696
+ if (attachments.length || files.length) this.#unsupported("attachments");
697
+ if (avatar !== void 0) this.#unsupported("avatar personas");
698
+ if (!prompt.trim() || prompt.length > 1e5) throw new Error("Invalid Claude prompt");
699
+ if (session.aborting) throw new Error("Claude cancellation is still settling");
700
+ session.operations++;
701
+ const epoch = session.epoch;
702
+ const run = session.lock.then(async () => {
703
+ this.#ready();
704
+ if (session.epoch !== epoch || session.aborting) throw new Error("Claude prompt cancelled before acceptance");
705
+ if (session.error instanceof ClaudeProcessTerminationError) throw session.error;
706
+ if (session.error) throw new Error("Claude session persistence failed", { cause: session.error });
707
+ if (session.stored.unsafe) throw new Error("Claude native session identity is uncertain; create a new session");
708
+ const duplicate = operationId === void 0 ? void 0 : session.stored.receipts.find((entry) => entry.operationId === operationId);
709
+ if (duplicate) {
710
+ if (duplicate.prompt !== prompt || duplicate.mode !== mode) throw new Error("Claude operation ID already has different content");
711
+ if (duplicate.state === "failed" || duplicate.state === "cancelled") throw new Error("Claude operation already ended; it was not retried");
712
+ return { messageId: duplicate.id, deliveryState: duplicate.state === "queued" ? "queued" : "processing", submittedAt: duplicate.submittedAt };
713
+ }
714
+ if (mode === "immediate" && session.busy) {
715
+ await this.#cancel(session, false);
716
+ if (session.aborting || this.#stopping || session.epoch !== epoch + 1) throw new Error("Claude prompt cancelled before acceptance");
717
+ if (session.stored.unsafe) throw new Error("Claude native session identity is uncertain; create a new session");
718
+ }
719
+ const acceptedEpoch = session.epoch;
720
+ const queued = session.busy || session.queue.length > 0;
721
+ const receipt = ReceiptSchema.parse({
722
+ id: randomUUID2(),
723
+ prompt,
724
+ mode,
725
+ submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
726
+ state: "queued",
727
+ ...operationId === void 0 ? {} : { operationId }
728
+ });
729
+ session.stored.receipts.push(receipt);
730
+ if (mode === "immediate") session.queue.unshift(receipt);
731
+ else session.queue.push(receipt);
732
+ await this.#save(session);
733
+ if (session.epoch !== acceptedEpoch || session.aborting || this.#stopping) throw new Error("Claude prompt cancelled before delivery");
734
+ this.#pump(session);
735
+ return { messageId: receipt.id, deliveryState: queued ? "queued" : "processing", submittedAt: receipt.submittedAt };
736
+ });
737
+ session.lock = run.then(() => void 0, () => void 0);
738
+ try {
739
+ return await run;
740
+ } finally {
741
+ session.operations--;
742
+ }
743
+ }
744
+ #pump(session) {
745
+ if (this.#stopping || session.aborting || session.busy || session.error || !session.queue.length) return;
746
+ if (session.work) {
747
+ session.busy = true;
748
+ const dispatch = this.#release(session, session.epoch);
749
+ session.dispatch = dispatch;
750
+ void dispatch.catch((error) => {
751
+ session.error = error;
752
+ session.query?.close();
753
+ this.#callbacks?.onSyncError(session.stored.summary.sessionId, error);
754
+ }).finally(() => {
755
+ if (session.dispatch === dispatch) session.dispatch = void 0;
756
+ });
757
+ return;
758
+ }
759
+ session.busy = true;
760
+ const work = this.#consume(session, session.epoch);
761
+ session.work = work;
762
+ void work.finally(() => {
763
+ if (session.work === work) session.work = void 0;
764
+ }).catch((error) => {
765
+ session.error = error;
766
+ this.#callbacks?.onSyncError(session.stored.summary.sessionId, error);
767
+ });
768
+ }
769
+ #environment() {
770
+ return prepareClaudeEnvironment(this.#options.env ?? process.env, join(this.#directory, "native"));
771
+ }
772
+ async #consume(session, epoch) {
773
+ const id = session.stored.summary.sessionId;
774
+ const input = new ClaudeInput();
775
+ const controller = new AbortController();
776
+ const ownedProcess = new ClaudeProcess();
777
+ session.input = input;
778
+ session.controller = controller;
779
+ const seen = /* @__PURE__ */ new Set();
780
+ try {
781
+ const env = this.#environment();
782
+ const sdk = this.#options.sdk ?? await import("@anthropic-ai/claude-agent-sdk");
783
+ if (epoch !== session.epoch || this.#stopping) return;
784
+ const query = sdk.query({
785
+ prompt: input,
786
+ options: {
787
+ ...session.stored.summary.workingDirectory ? { cwd: session.stored.summary.workingDirectory } : {},
788
+ env,
789
+ settingSources: [],
790
+ includePartialMessages: true,
791
+ persistSession: true,
792
+ permissionMode: "default",
793
+ abortController: controller,
794
+ spawnClaudeCodeProcess: ownedProcess.spawn,
795
+ ...session.stored.nativeId ? { resume: session.stored.nativeId } : {},
796
+ ...this.#options.command ? { pathToClaudeCodeExecutable: this.#options.command } : {},
797
+ canUseTool: (tool, args, context) => epoch !== session.epoch || controller.signal.aborted ? Promise.resolve({ behavior: "deny", message: "Stale Claude query" }) : this.#interactions.request(id, tool, args, context.signal)
798
+ }
799
+ });
800
+ session.query = query;
801
+ await this.#release(session, epoch);
802
+ for await (const raw of query) {
803
+ if (epoch !== session.epoch || this.#stopping) continue;
804
+ const message = MessageSchema.parse(raw);
805
+ if (message.uuid && seen.has(message.uuid)) continue;
806
+ if (message.uuid) seen.add(message.uuid);
807
+ if (message.type === "system" && message.subtype === "init") {
808
+ const nativeId = UuidSchema.parse(message.session_id);
809
+ if (session.stored.nativeId && session.stored.nativeId !== nativeId) throw new Error("Claude resumed a different native session");
810
+ session.stored.nativeId = nativeId;
811
+ } else if (message.session_id && session.stored.nativeId && message.session_id !== session.stored.nativeId) {
812
+ throw new Error("Claude emitted a foreign session");
813
+ }
814
+ if (!session.stored.nativeId && ["assistant", "stream_event", "user", "result"].includes(message.type)) {
815
+ throw new Error("Claude produced conversation output without a native session identity");
816
+ }
817
+ const turnId = session.active?.id ?? id;
818
+ const confirmations = message.user_message_uuids ?? (message.user_message_uuid ? [message.user_message_uuid] : []);
819
+ if (message.type === "user" && message.uuid) confirmations.push(message.uuid);
820
+ for (const receiptId of confirmations) this.#confirm(session, receiptId);
821
+ session.timeline.project(raw, turnId);
822
+ if (message.type === "result") {
823
+ const isError = z4.boolean().parse(message.is_error);
824
+ session.resultSeen = true;
825
+ if (session.active) session.active.state = isError ? "failed" : "completed";
826
+ if (isError) {
827
+ session.timeline.notice(turnId, "Claude returned an error result; this prompt was not retried.");
828
+ this.#callbacks?.onSyncError(id, new Error("Claude returned an error result"));
829
+ }
830
+ }
831
+ if (message.type === "system" && message.subtype === "background_tasks_changed") {
832
+ const background = z4.object({ tasks: z4.array(z4.unknown()) }).safeParse(message);
833
+ session.background = !background.success || background.data.tasks.length > 0;
834
+ }
835
+ if (message.type === "system" && message.subtype === "session_state_changed" && (message.state === "running" || message.state === "requires_action")) {
836
+ session.busy = true;
837
+ this.#callbacks?.onSessionActivity?.(id, "working", (/* @__PURE__ */ new Date()).toISOString());
838
+ }
839
+ if (message.type === "system" && message.subtype === "session_state_changed" && message.state === "idle" && session.resultSeen) {
840
+ session.timeline.finish();
841
+ session.active = void 0;
842
+ session.busy = false;
843
+ session.resultSeen = false;
844
+ this.#callbacks?.onSessionActivity?.(id, "idle", (/* @__PURE__ */ new Date()).toISOString());
845
+ }
846
+ await this.#save(session);
847
+ if (!session.busy && session.queue.length) await this.#release(session, epoch);
848
+ }
849
+ if (epoch === session.epoch && session.busy) throw new Error("Claude process ended before the turn settled");
850
+ } catch (error) {
851
+ controller.abort();
852
+ this.#interactions.cancel(id);
853
+ if (epoch === session.epoch && !this.#stopping) {
854
+ session.timeline.notice(session.active?.id ?? id, error instanceof Error ? error.message : String(error));
855
+ this.#callbacks?.onSyncError(id, error);
856
+ }
857
+ } finally {
858
+ controller.abort();
859
+ this.#interactions.cancel(id);
860
+ input.close();
861
+ session.query?.close();
862
+ session.query = void 0;
863
+ session.busy = true;
864
+ try {
865
+ await ownedProcess.finish(this.#options.processExitTimeoutMs ?? 5e3);
866
+ } catch (error) {
867
+ const failure = error instanceof ClaudeProcessTerminationError ? error : new ClaudeProcessTerminationError("Claude process termination failed; native session reuse is blocked", { cause: error });
868
+ session.error = failure;
869
+ session.stored.unsafe = true;
870
+ try {
871
+ await this.#save(session);
872
+ } catch (saveError) {
873
+ session.error = new ClaudeProcessTerminationError(
874
+ "Claude process termination failed and unsafe session state could not be persisted",
875
+ { cause: new AggregateError([failure, saveError]) }
876
+ );
877
+ throw session.error;
878
+ }
879
+ throw failure;
880
+ }
881
+ if (epoch === session.epoch) {
882
+ this.#retire(session, true);
883
+ session.timeline.finish();
884
+ await this.#save(session);
885
+ }
886
+ session.query = void 0;
887
+ session.input = void 0;
888
+ session.controller = void 0;
889
+ session.busy = false;
890
+ session.background = false;
891
+ if (epoch === session.epoch) this.#settled(session);
892
+ }
893
+ }
894
+ async #release(session, epoch) {
895
+ if (epoch !== session.epoch || this.#stopping) return;
896
+ const receipt = session.queue.shift();
897
+ if (!receipt || !session.input) return;
898
+ session.active = receipt;
899
+ session.busy = true;
900
+ session.resultSeen = false;
901
+ receipt.state = "submitted";
902
+ await this.#save(session);
903
+ if (epoch !== session.epoch || this.#stopping) return;
904
+ session.input.push({
905
+ type: "user",
906
+ uuid: receipt.id,
907
+ message: { role: "user", content: receipt.prompt },
908
+ parent_tool_use_id: null,
909
+ priority: receipt.mode === "enqueue" ? "later" : "next",
910
+ client_composed: true,
911
+ ...session.stored.nativeId ? { session_id: session.stored.nativeId } : {}
912
+ });
913
+ this.#callbacks?.onSessionActivity?.(session.stored.summary.sessionId, "working", (/* @__PURE__ */ new Date()).toISOString());
914
+ }
915
+ #confirm(session, id) {
916
+ const receipt = session.stored.receipts.find((entry) => entry.id === id);
917
+ if (!receipt || receipt.state !== "submitted" && receipt.state !== "confirmed") return;
918
+ receipt.state = "confirmed";
919
+ session.timeline.upsert({
920
+ ...session.timeline.base(receipt.id, receipt.id),
921
+ timestamp: receipt.submittedAt,
922
+ sourceMessageIds: [receipt.id],
923
+ kind: "human-message",
924
+ actor: "human",
925
+ content: receipt.prompt,
926
+ attachments: [],
927
+ ...receipt.operationId ? { operationId: receipt.operationId } : {}
928
+ });
929
+ }
930
+ #retire(session, clearQueue) {
931
+ if (session.active && !session.stored.nativeId) session.stored.unsafe = true;
932
+ if (session.active && !["completed", "failed"].includes(session.active.state)) {
933
+ session.active.state = "cancelled";
934
+ session.timeline.notice(session.active.id, "Prompt interrupted; it was not retried.", "warning");
935
+ }
936
+ session.active = void 0;
937
+ if (clearQueue) {
938
+ for (const receipt of session.queue) {
939
+ receipt.state = "cancelled";
940
+ session.timeline.notice(receipt.id, "Queued prompt cancelled before delivery.", "warning");
941
+ }
942
+ session.queue = [];
943
+ }
944
+ }
945
+ async abortSession(id) {
946
+ const session = this.#known(id);
947
+ if (session.aborting) return session.aborting;
948
+ const cancellation = this.#cancel(session, true);
949
+ const work = (async () => {
950
+ await cancellation;
951
+ await session.lock;
952
+ await session.saving;
953
+ })();
954
+ session.aborting = work;
955
+ try {
956
+ await work;
957
+ } finally {
958
+ session.aborting = void 0;
959
+ }
960
+ }
961
+ async #cancel(session, clearQueue) {
962
+ session.cancellations++;
963
+ try {
964
+ if (session.error instanceof ClaudeProcessTerminationError) throw session.error;
965
+ session.epoch++;
966
+ session.input?.close();
967
+ session.controller?.abort();
968
+ this.#interactions.cancel(session.stored.summary.sessionId);
969
+ session.query?.close();
970
+ await session.work;
971
+ await session.dispatch;
972
+ this.#retire(session, clearQueue);
973
+ session.timeline.finish();
974
+ session.busy = false;
975
+ await this.#save(session);
976
+ this.#settled(session);
977
+ } finally {
978
+ session.cancellations--;
979
+ }
980
+ }
981
+ #settled(session) {
982
+ const id = session.stored.summary.sessionId;
983
+ this.#callbacks?.onSessionActivity?.(id, "idle", (/* @__PURE__ */ new Date()).toISOString());
984
+ this.#callbacks?.onSessionInventoryChanged(id);
985
+ }
986
+ respondPermission(id, decision, feedback) {
987
+ return this.#interactions.respond(id, { kind: "permission", decision, ...feedback ? { feedback } : {} });
988
+ }
989
+ async respondInteraction(id, response) {
990
+ return this.#interactions.respond(id, response);
991
+ }
992
+ replayPendingInteractions() {
993
+ this.#interactions.replay();
994
+ }
995
+ #save(session) {
996
+ const data = JSON.stringify(StoredSchema.parse(session.stored));
997
+ const file = join(this.#directory, `${identity(session.stored.summary.sessionId)}.json`);
998
+ session.saves++;
999
+ const work = session.saving.then(async () => {
1000
+ const temporary = `${file}.${randomUUID2()}.tmp`;
1001
+ try {
1002
+ await writeFile(temporary, data, { encoding: "utf8", mode: 384, flag: "wx" });
1003
+ await rename(temporary, file);
1004
+ } finally {
1005
+ await rm(temporary, { force: true });
1006
+ }
1007
+ });
1008
+ session.saving = work.catch((error) => {
1009
+ session.error = error;
1010
+ this.#callbacks?.onSyncError(session.stored.summary.sessionId, error);
1011
+ }).finally(() => {
1012
+ session.saves--;
1013
+ });
1014
+ return work;
1015
+ }
1016
+ stop() {
1017
+ if (this.#stopPromise) return this.#stopPromise;
1018
+ this.#stopping = true;
1019
+ this.#stopPromise = this.#stop();
1020
+ return this.#stopPromise;
1021
+ }
1022
+ async #stop() {
1023
+ if (this.#starting && !this.#started) {
1024
+ const [result] = await Promise.allSettled([this.#starting]);
1025
+ if (result?.status === "rejected") {
1026
+ this.#options.onDiagnostic?.(`Claude startup did not complete: ${String(result.reason)}`);
1027
+ }
1028
+ }
1029
+ await Promise.all([...this.#sessions.values()].map(async (session) => {
1030
+ await (session.aborting ?? this.#cancel(session, true));
1031
+ await session.lock;
1032
+ await session.saving;
1033
+ }));
1034
+ this.#started = false;
1035
+ }
1036
+ };
1037
+ export {
1038
+ CLAUDE_DEFAULT_MODEL_ID,
1039
+ ClaudeCodingAgentAdapter
1040
+ };