handsoff 0.1.1 → 0.1.2-beta.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.
@@ -4077,8 +4077,1004 @@ var ClaudeAdapter = class _ClaudeAdapter {
4077
4077
  }
4078
4078
  };
4079
4079
 
4080
+ // src/adapters/agent/claude/session-context.ts
4081
+ import { readFile as readFile2 } from "fs/promises";
4082
+ function sleep(ms) {
4083
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
4084
+ }
4085
+ var ClaudeSessionContextProvider = class {
4086
+ async getSessionSummary(_sessionId, transcriptPath) {
4087
+ if (!transcriptPath) {
4088
+ return void 0;
4089
+ }
4090
+ const summary = await this.tryReadTranscript(transcriptPath);
4091
+ return summary;
4092
+ }
4093
+ async tryReadTranscript(transcriptPath, maxAttempts = 5, delayMs = 200) {
4094
+ let lastError;
4095
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4096
+ try {
4097
+ const content = await readFile2(transcriptPath, "utf-8");
4098
+ const summary = this.parseTranscript(content);
4099
+ if (summary && (summary.lastAssistantMessage !== void 0 || summary.durationMs !== void 0)) {
4100
+ return summary;
4101
+ }
4102
+ if (summary) {
4103
+ getLogger().debug({ attempt, summary }, "Transcript read but incomplete, retrying...");
4104
+ }
4105
+ } catch (error2) {
4106
+ lastError = error2;
4107
+ }
4108
+ if (attempt < maxAttempts) {
4109
+ await sleep(delayMs);
4110
+ }
4111
+ }
4112
+ getLogger().warn({ error: lastError instanceof Error ? lastError.message : String(lastError), transcriptPath }, "Failed to read transcript or transcript incomplete");
4113
+ return void 0;
4114
+ }
4115
+ parseTranscript(content) {
4116
+ const lines = content.split("\n").filter((line) => line.trim().length > 0);
4117
+ let lastAssistantMessage;
4118
+ let inputTokens;
4119
+ let outputTokens;
4120
+ let lastTextAssistant;
4121
+ let lastAnyAssistant;
4122
+ const userTimestamps = /* @__PURE__ */ new Map();
4123
+ const parentMap = /* @__PURE__ */ new Map();
4124
+ for (const line of lines) {
4125
+ let entry;
4126
+ try {
4127
+ entry = JSON.parse(line);
4128
+ } catch (parseError) {
4129
+ getLogger().warn(
4130
+ { error: parseError instanceof Error ? parseError.message : String(parseError), line: line.slice(0, 200) },
4131
+ "Skipping malformed transcript line"
4132
+ );
4133
+ continue;
4134
+ }
4135
+ const ts = typeof entry.timestamp === "string" ? new Date(entry.timestamp).getTime() : typeof entry.timestamp === "number" ? entry.timestamp : void 0;
4136
+ if (entry.uuid && typeof entry.parentUuid === "string") {
4137
+ parentMap.set(entry.uuid, entry.parentUuid);
4138
+ }
4139
+ if (entry.type === "user" && ts !== void 0 && entry.uuid) {
4140
+ userTimestamps.set(entry.uuid, ts);
4141
+ }
4142
+ if (entry.type === "assistant" && ts !== void 0) {
4143
+ const arr = Array.isArray(entry.message?.content) ? entry.message.content : [];
4144
+ const textBlock = arr.find((c) => c.type === "text");
4145
+ const assistantRef = { ts, uuid: entry.uuid, parentUuid: entry.parentUuid };
4146
+ lastAnyAssistant = assistantRef;
4147
+ if (textBlock && typeof textBlock.text === "string") {
4148
+ lastAssistantMessage = textBlock.text;
4149
+ lastTextAssistant = assistantRef;
4150
+ const usage = entry.message?.usage;
4151
+ if (usage) {
4152
+ if (typeof usage.input_tokens === "number") {
4153
+ inputTokens = usage.input_tokens;
4154
+ }
4155
+ if (typeof usage.output_tokens === "number") {
4156
+ outputTokens = usage.output_tokens;
4157
+ }
4158
+ }
4159
+ }
4160
+ }
4161
+ }
4162
+ function resolveDuration(assistantRef) {
4163
+ if (!assistantRef) return void 0;
4164
+ if (assistantRef.parentUuid) {
4165
+ const directParentTs = userTimestamps.get(assistantRef.parentUuid);
4166
+ if (directParentTs !== void 0) {
4167
+ return assistantRef.ts - directParentTs;
4168
+ }
4169
+ }
4170
+ let currentUuid = assistantRef.uuid;
4171
+ const visited = /* @__PURE__ */ new Set();
4172
+ while (currentUuid && !visited.has(currentUuid)) {
4173
+ visited.add(currentUuid);
4174
+ const parentUuid = parentMap.get(currentUuid);
4175
+ if (!parentUuid) break;
4176
+ const parentTs = userTimestamps.get(parentUuid);
4177
+ if (parentTs !== void 0) {
4178
+ return assistantRef.ts - parentTs;
4179
+ }
4180
+ currentUuid = parentUuid;
4181
+ }
4182
+ return void 0;
4183
+ }
4184
+ const durationMs = resolveDuration(lastTextAssistant) ?? resolveDuration(lastAnyAssistant);
4185
+ const summary = {
4186
+ ...lastAssistantMessage !== void 0 && { lastAssistantMessage },
4187
+ ...inputTokens !== void 0 && { inputTokens },
4188
+ ...outputTokens !== void 0 && { outputTokens },
4189
+ ...durationMs !== void 0 && { durationMs }
4190
+ };
4191
+ return summary;
4192
+ }
4193
+ };
4194
+
4195
+ // src/adapters/agent/claude/ClaudeRemoteAdapter.ts
4196
+ import { randomUUID as randomUUID4 } from "crypto";
4197
+
4198
+ // src/adapters/agent/claude/sdk/query.ts
4199
+ import { spawn } from "child_process";
4200
+ import { createInterface } from "readline";
4201
+ import { existsSync as existsSync5 } from "fs";
4202
+
4203
+ // src/adapters/agent/claude/sdk/stream.ts
4204
+ var Stream = class {
4205
+ constructor(returned) {
4206
+ this.returned = returned;
4207
+ }
4208
+ returned;
4209
+ queue = [];
4210
+ readResolve;
4211
+ readReject;
4212
+ isDone = false;
4213
+ hasError;
4214
+ started = false;
4215
+ [Symbol.asyncIterator]() {
4216
+ if (this.started) {
4217
+ throw new Error("Stream can only be iterated once");
4218
+ }
4219
+ this.started = true;
4220
+ return this;
4221
+ }
4222
+ async next() {
4223
+ if (this.queue.length > 0) {
4224
+ return Promise.resolve({ done: false, value: this.queue.shift() });
4225
+ }
4226
+ if (this.isDone) {
4227
+ return Promise.resolve({ done: true, value: void 0 });
4228
+ }
4229
+ if (this.hasError) {
4230
+ return Promise.reject(this.hasError);
4231
+ }
4232
+ return new Promise((resolve2, reject) => {
4233
+ this.readResolve = resolve2;
4234
+ this.readReject = reject;
4235
+ });
4236
+ }
4237
+ enqueue(value) {
4238
+ if (this.isDone || this.hasError) return;
4239
+ if (this.readResolve) {
4240
+ const resolve2 = this.readResolve;
4241
+ this.readResolve = void 0;
4242
+ this.readReject = void 0;
4243
+ resolve2({ done: false, value });
4244
+ } else {
4245
+ this.queue.push(value);
4246
+ }
4247
+ }
4248
+ done() {
4249
+ if (this.isDone || this.hasError) return;
4250
+ this.isDone = true;
4251
+ if (this.readResolve) {
4252
+ const resolve2 = this.readResolve;
4253
+ this.readResolve = void 0;
4254
+ this.readReject = void 0;
4255
+ resolve2({ done: true, value: void 0 });
4256
+ }
4257
+ }
4258
+ error(error2) {
4259
+ if (this.isDone || this.hasError) return;
4260
+ this.hasError = error2;
4261
+ if (this.readReject) {
4262
+ const reject = this.readReject;
4263
+ this.readResolve = void 0;
4264
+ this.readReject = void 0;
4265
+ reject(error2);
4266
+ }
4267
+ }
4268
+ async return() {
4269
+ this.isDone = true;
4270
+ if (this.returned) {
4271
+ this.returned();
4272
+ }
4273
+ return Promise.resolve({ done: true, value: void 0 });
4274
+ }
4275
+ };
4276
+
4277
+ // src/adapters/agent/claude/sdk/types.ts
4278
+ var AbortError = class extends Error {
4279
+ constructor(message) {
4280
+ super(message);
4281
+ this.name = "AbortError";
4282
+ }
4283
+ };
4284
+
4285
+ // src/adapters/agent/claude/sdk/query.ts
4286
+ function getCleanEnv() {
4287
+ const env = { ...process.env };
4288
+ const cwd = process.cwd();
4289
+ const pathSep = process.platform === "win32" ? ";" : ":";
4290
+ const pathKey = process.platform === "win32" ? "Path" : "PATH";
4291
+ const actualPathKey = Object.keys(env).find((k) => k.toLowerCase() === "path") || pathKey;
4292
+ if (env[actualPathKey]) {
4293
+ env[actualPathKey] = env[actualPathKey].split(pathSep).filter((p) => {
4294
+ const normalizedP = p.replace(/\\/g, "/").toLowerCase();
4295
+ const normalizedCwd = cwd.replace(/\\/g, "/").toLowerCase();
4296
+ return !normalizedP.startsWith(normalizedCwd);
4297
+ }).join(pathSep);
4298
+ }
4299
+ return env;
4300
+ }
4301
+ function buildClaudeArgs(options) {
4302
+ const args = ["--output-format", "stream-json", "--verbose"];
4303
+ if (options.customSystemPrompt) args.push("--system-prompt", options.customSystemPrompt);
4304
+ if (options.appendSystemPrompt) args.push("--append-system-prompt", options.appendSystemPrompt);
4305
+ if (options.maxTurns !== void 0) args.push("--max-turns", String(options.maxTurns));
4306
+ if (options.model) args.push("--model", options.model);
4307
+ if (options.canCallTool) {
4308
+ args.push("--permission-prompt-tool", "stdio");
4309
+ }
4310
+ if (options.continue) args.push("--continue");
4311
+ if (options.resume) args.push("--resume", options.resume);
4312
+ if (options.allowedTools && options.allowedTools.length > 0) {
4313
+ args.push("--allowedTools", options.allowedTools.join(","));
4314
+ }
4315
+ if (options.disallowedTools && options.disallowedTools.length > 0) {
4316
+ args.push("--disallowedTools", options.disallowedTools.join(","));
4317
+ }
4318
+ if (options.mcpServers && Object.keys(options.mcpServers).length > 0) {
4319
+ args.push("--mcp-config", JSON.stringify({ mcpServers: options.mcpServers }));
4320
+ }
4321
+ if (options.strictMcpConfig) args.push("--strict-mcp-config");
4322
+ if (options.permissionMode) args.push("--permission-mode", options.permissionMode);
4323
+ if (options.settingsPath) args.push("--settings", options.settingsPath);
4324
+ if (options.fallbackModel) {
4325
+ if (options.model && options.fallbackModel === options.model) {
4326
+ throw new Error("Fallback model cannot be the same as the main model. Please specify a different model for fallbackModel option.");
4327
+ }
4328
+ args.push("--fallback-model", options.fallbackModel);
4329
+ }
4330
+ return args;
4331
+ }
4332
+ var Query = class {
4333
+ constructor(childStdin, childStdout, processExitPromise, canCallTool) {
4334
+ this.childStdin = childStdin;
4335
+ this.childStdout = childStdout;
4336
+ this.processExitPromise = processExitPromise;
4337
+ this.canCallTool = canCallTool;
4338
+ this.readMessages();
4339
+ this.sdkMessages = this.readSdkMessages();
4340
+ }
4341
+ childStdin;
4342
+ childStdout;
4343
+ processExitPromise;
4344
+ canCallTool;
4345
+ pendingControlResponses = /* @__PURE__ */ new Map();
4346
+ cancelControllers = /* @__PURE__ */ new Map();
4347
+ inputStream = new Stream();
4348
+ sdkMessages;
4349
+ child;
4350
+ setChild(child) {
4351
+ this.child = child;
4352
+ }
4353
+ kill(signal = "SIGTERM") {
4354
+ if (this.child && !this.child.killed) {
4355
+ this.child.kill(signal);
4356
+ }
4357
+ }
4358
+ setError(error2) {
4359
+ this.inputStream.error(error2);
4360
+ }
4361
+ next(...args) {
4362
+ return this.sdkMessages.next(...args);
4363
+ }
4364
+ return(value) {
4365
+ if (this.sdkMessages.return) {
4366
+ return this.sdkMessages.return(value);
4367
+ }
4368
+ return Promise.resolve({ done: true, value: void 0 });
4369
+ }
4370
+ throw(e) {
4371
+ if (this.sdkMessages.throw) {
4372
+ return this.sdkMessages.throw(e);
4373
+ }
4374
+ return Promise.reject(e);
4375
+ }
4376
+ [Symbol.asyncIterator]() {
4377
+ return this.sdkMessages;
4378
+ }
4379
+ async readMessages() {
4380
+ const rl = createInterface({ input: this.childStdout });
4381
+ try {
4382
+ for await (const line of rl) {
4383
+ if (!line.trim()) continue;
4384
+ try {
4385
+ const message = JSON.parse(line);
4386
+ if (message.type === "control_response") {
4387
+ const cr = message;
4388
+ const handler = this.pendingControlResponses.get(cr.response.request_id);
4389
+ if (handler) handler(cr.response);
4390
+ continue;
4391
+ }
4392
+ if (message.type === "control_request") {
4393
+ await this.handleControlRequest(message);
4394
+ continue;
4395
+ }
4396
+ if (message.type === "control_cancel_request") {
4397
+ this.handleControlCancelRequest(message);
4398
+ continue;
4399
+ }
4400
+ this.inputStream.enqueue(message);
4401
+ } catch {
4402
+ }
4403
+ }
4404
+ await this.processExitPromise;
4405
+ } catch (error2) {
4406
+ this.inputStream.error(error2);
4407
+ } finally {
4408
+ this.inputStream.done();
4409
+ this.cleanupControllers();
4410
+ rl.close();
4411
+ }
4412
+ }
4413
+ async *readSdkMessages() {
4414
+ for await (const message of this.inputStream) {
4415
+ yield message;
4416
+ }
4417
+ }
4418
+ async interrupt() {
4419
+ if (!this.childStdin) throw new Error("Interrupt requires --input-format stream-json");
4420
+ await this.request({ subtype: "interrupt" }, this.childStdin);
4421
+ }
4422
+ request(request, childStdin) {
4423
+ const requestId = Math.random().toString(36).substring(2, 15);
4424
+ const sdkRequest = {
4425
+ request_id: requestId,
4426
+ type: "control_request",
4427
+ request
4428
+ };
4429
+ return new Promise((resolve2, reject) => {
4430
+ this.pendingControlResponses.set(requestId, (response) => {
4431
+ if (response.subtype === "success") {
4432
+ resolve2(response);
4433
+ } else {
4434
+ reject(new Error(response.error));
4435
+ }
4436
+ });
4437
+ childStdin.write(JSON.stringify(sdkRequest) + "\n");
4438
+ });
4439
+ }
4440
+ async handleControlRequest(request) {
4441
+ if (!this.childStdin) return;
4442
+ const controller = new AbortController();
4443
+ this.cancelControllers.set(request.request_id, controller);
4444
+ try {
4445
+ const response = await this.processControlRequest(request, controller.signal);
4446
+ const controlResponse = {
4447
+ type: "control_response",
4448
+ response: {
4449
+ subtype: "success",
4450
+ request_id: request.request_id,
4451
+ response
4452
+ }
4453
+ };
4454
+ this.childStdin.write(JSON.stringify(controlResponse) + "\n");
4455
+ } catch (error2) {
4456
+ const controlErrorResponse = {
4457
+ type: "control_response",
4458
+ response: {
4459
+ subtype: "error",
4460
+ request_id: request.request_id,
4461
+ error: error2 instanceof Error ? error2.message : String(error2)
4462
+ }
4463
+ };
4464
+ this.childStdin.write(JSON.stringify(controlErrorResponse) + "\n");
4465
+ } finally {
4466
+ this.cancelControllers.delete(request.request_id);
4467
+ }
4468
+ }
4469
+ handleControlCancelRequest(request) {
4470
+ const controller = this.cancelControllers.get(request.request_id);
4471
+ if (controller) {
4472
+ controller.abort();
4473
+ this.cancelControllers.delete(request.request_id);
4474
+ }
4475
+ }
4476
+ async processControlRequest(request, signal) {
4477
+ if (request.request.subtype === "can_use_tool") {
4478
+ if (!this.canCallTool) {
4479
+ throw new Error("canCallTool callback is not provided.");
4480
+ }
4481
+ return this.canCallTool(request.request.tool_name, request.request.input, { signal });
4482
+ }
4483
+ throw new Error("Unsupported control request subtype: " + request.request.subtype);
4484
+ }
4485
+ cleanupControllers() {
4486
+ for (const [requestId, controller] of this.cancelControllers.entries()) {
4487
+ controller.abort();
4488
+ this.cancelControllers.delete(requestId);
4489
+ }
4490
+ }
4491
+ };
4492
+ function query(config) {
4493
+ const {
4494
+ prompt,
4495
+ options: {
4496
+ allowedTools = [],
4497
+ appendSystemPrompt,
4498
+ customSystemPrompt,
4499
+ cwd,
4500
+ disallowedTools = [],
4501
+ executable = "node",
4502
+ executableArgs = [],
4503
+ maxTurns,
4504
+ mcpServers,
4505
+ pathToClaudeCodeExecutable = "claude",
4506
+ permissionMode = "default",
4507
+ continue: continueConversation,
4508
+ resume,
4509
+ model,
4510
+ fallbackModel,
4511
+ strictMcpConfig,
4512
+ canCallTool,
4513
+ settingsPath,
4514
+ env,
4515
+ abort
4516
+ } = {}
4517
+ } = config;
4518
+ if (abort?.aborted) {
4519
+ throw new AbortError("Claude Code process aborted by user");
4520
+ }
4521
+ if (canCallTool && typeof prompt === "string") {
4522
+ throw new Error("canCallTool callback requires --input-format stream-json. Please set prompt as an AsyncIterable.");
4523
+ }
4524
+ if (!process.env.CLAUDE_CODE_ENTRYPOINT) {
4525
+ process.env.CLAUDE_CODE_ENTRYPOINT = "sdk-ts";
4526
+ }
4527
+ const args = buildClaudeArgs({
4528
+ allowedTools,
4529
+ appendSystemPrompt,
4530
+ customSystemPrompt,
4531
+ maxTurns,
4532
+ model,
4533
+ canCallTool,
4534
+ continue: continueConversation,
4535
+ resume,
4536
+ disallowedTools,
4537
+ mcpServers,
4538
+ strictMcpConfig,
4539
+ permissionMode,
4540
+ settingsPath,
4541
+ fallbackModel
4542
+ });
4543
+ if (typeof prompt === "string") {
4544
+ args.push("--print", prompt.trim());
4545
+ } else {
4546
+ args.push("--input-format", "stream-json");
4547
+ }
4548
+ const isJsFile = pathToClaudeCodeExecutable.endsWith(".js") || pathToClaudeCodeExecutable.endsWith(".cjs");
4549
+ const isCommandOnly = pathToClaudeCodeExecutable === "claude";
4550
+ if (!isCommandOnly && !existsSync5(pathToClaudeCodeExecutable)) {
4551
+ throw new ReferenceError(`Claude Code executable not found at ${pathToClaudeCodeExecutable}`);
4552
+ }
4553
+ const spawnCommand = isJsFile ? executable : pathToClaudeCodeExecutable;
4554
+ const spawnArgs = isJsFile ? [...executableArgs, pathToClaudeCodeExecutable, ...args] : args;
4555
+ const spawnEnv = isCommandOnly ? { ...getCleanEnv(), ...env } : { ...process.env, ...env };
4556
+ const child = spawn(spawnCommand, spawnArgs, {
4557
+ cwd,
4558
+ stdio: ["pipe", "pipe", "pipe"],
4559
+ signal: abort,
4560
+ env: spawnEnv,
4561
+ shell: !isJsFile && process.platform === "win32",
4562
+ windowsHide: true
4563
+ });
4564
+ child.stderr.resume();
4565
+ let childStdin = null;
4566
+ if (typeof prompt === "string") {
4567
+ child.stdin.end();
4568
+ } else {
4569
+ (async () => {
4570
+ try {
4571
+ for await (const msg of prompt) {
4572
+ if (abort?.aborted) break;
4573
+ const data = JSON.stringify(msg) + "\n";
4574
+ if (!child.stdin.write(data)) {
4575
+ await new Promise((resolve2) => child.stdin.once("drain", resolve2));
4576
+ }
4577
+ }
4578
+ } finally {
4579
+ child.stdin.end();
4580
+ }
4581
+ })();
4582
+ childStdin = child.stdin;
4583
+ }
4584
+ const cleanup = () => {
4585
+ if (!child.killed) child.kill("SIGTERM");
4586
+ };
4587
+ abort?.addEventListener("abort", cleanup);
4588
+ const processExitPromise = new Promise((resolve2) => {
4589
+ child.on("close", (code) => {
4590
+ if (abort?.aborted) {
4591
+ q.setError(new AbortError("Claude Code process aborted by user"));
4592
+ } else if (code !== 0 && code !== null) {
4593
+ q.setError(new Error(`Claude Code process exited with code ${code}`));
4594
+ } else {
4595
+ resolve2();
4596
+ }
4597
+ });
4598
+ });
4599
+ const q = new Query(childStdin, child.stdout, processExitPromise, canCallTool);
4600
+ q.setChild(child);
4601
+ child.on("error", (error2) => {
4602
+ if (abort?.aborted) {
4603
+ q.setError(new AbortError("Claude Code process aborted by user"));
4604
+ } else {
4605
+ q.setError(new Error(`Failed to spawn Claude Code process: ${error2.message}`));
4606
+ }
4607
+ });
4608
+ processExitPromise.finally(() => {
4609
+ cleanup();
4610
+ abort?.removeEventListener("abort", cleanup);
4611
+ });
4612
+ return q;
4613
+ }
4614
+
4615
+ // src/adapters/agent/claude/ClaudeRemoteAdapter.ts
4616
+ var PushableAsyncIterable = class {
4617
+ queue = [];
4618
+ resolvers = [];
4619
+ ended = false;
4620
+ push(value) {
4621
+ if (this.ended) return;
4622
+ if (this.resolvers.length > 0) {
4623
+ const resolve2 = this.resolvers.shift();
4624
+ resolve2({ done: false, value });
4625
+ } else {
4626
+ this.queue.push(value);
4627
+ }
4628
+ }
4629
+ end() {
4630
+ this.ended = true;
4631
+ while (this.resolvers.length > 0) {
4632
+ const resolve2 = this.resolvers.shift();
4633
+ resolve2({ done: true, value: void 0 });
4634
+ }
4635
+ }
4636
+ [Symbol.asyncIterator]() {
4637
+ return {
4638
+ next: () => {
4639
+ if (this.queue.length > 0) {
4640
+ return Promise.resolve({ done: false, value: this.queue.shift() });
4641
+ }
4642
+ if (this.ended) {
4643
+ return Promise.resolve({ done: true, value: void 0 });
4644
+ }
4645
+ return new Promise((resolve2) => {
4646
+ this.resolvers.push(resolve2);
4647
+ });
4648
+ },
4649
+ return: () => {
4650
+ this.end();
4651
+ return Promise.resolve({ done: true, value: void 0 });
4652
+ }
4653
+ };
4654
+ }
4655
+ };
4656
+ var ClaudeRemoteAdapter = class _ClaudeRemoteAdapter {
4657
+ agentType = "claude";
4658
+ transportType = "remote";
4659
+ version = "1.0.0";
4660
+ protocolHandler = new ClaudeProtocolHandler();
4661
+ static scanner = null;
4662
+ bus;
4663
+ logger;
4664
+ gateway;
4665
+ token;
4666
+ sessions = /* @__PURE__ */ new Map();
4667
+ eventHandler = null;
4668
+ unsubscribeFns = [];
4669
+ command;
4670
+ spawnCwd;
4671
+ envExtra;
4672
+ defaultWorkDir;
4673
+ defaultModel;
4674
+ defaultPermissionMode;
4675
+ constructor(options) {
4676
+ this.bus = options.bus;
4677
+ this.logger = options.logger;
4678
+ this.gateway = options.gateway;
4679
+ this.token = options.token;
4680
+ this.command = options.command?.length ? options.command : ["claude"];
4681
+ this.spawnCwd = options.cwd;
4682
+ this.envExtra = options.env ?? {};
4683
+ this.defaultWorkDir = options.defaultWorkDir;
4684
+ this.defaultModel = options.defaultModel;
4685
+ this.defaultPermissionMode = options.defaultPermissionMode;
4686
+ }
4687
+ async initialize() {
4688
+ this.logger.info("Claude remote adapter initializing");
4689
+ try {
4690
+ const { execSync } = await import("child_process");
4691
+ execSync(`${this.command[0]} --version`, { encoding: "utf8", windowsHide: true });
4692
+ this.logger.info("Claude CLI detected");
4693
+ } catch (err) {
4694
+ this.logger.warn("Claude CLI not found");
4695
+ throw err;
4696
+ }
4697
+ if (!_ClaudeRemoteAdapter.scanner) {
4698
+ _ClaudeRemoteAdapter.scanner = new ClaudeSessionScanner();
4699
+ SessionScannerRegistry.getInstance().register(_ClaudeRemoteAdapter.scanner);
4700
+ this.logger.info("Claude session scanner registered");
4701
+ }
4702
+ AgentAdapterRegistry.getInstance().register(this);
4703
+ if (!this.unsubscribeFns.length) {
4704
+ this.unsubscribeFns.push(this.bus.onInbound((msg) => this.handleInboundMessage(msg)));
4705
+ this.logger.debug("Subscribed to inbound messages");
4706
+ }
4707
+ }
4708
+ async shutdown() {
4709
+ this.logger.info("Claude remote adapter shutting down");
4710
+ for (const [sessionId, state] of this.sessions) {
4711
+ try {
4712
+ state.query.kill("SIGTERM");
4713
+ state.input.end();
4714
+ } catch (err) {
4715
+ this.logger.warn({ sessionId, error: err }, "Error ending session input during shutdown");
4716
+ }
4717
+ }
4718
+ this.sessions.clear();
4719
+ this.unsubscribeFns.forEach((fn) => fn());
4720
+ this.unsubscribeFns = [];
4721
+ this.logger.info("Claude remote adapter shutdown complete");
4722
+ }
4723
+ async createSession(opts) {
4724
+ const sessionId = opts.sessionId ?? `claude-${randomUUID4().slice(0, 8)}`;
4725
+ const chatId = opts.metadata?.chatId ?? sessionId;
4726
+ const channel = opts.metadata?.channel ?? "broadcast";
4727
+ const cwd = opts.cwd ?? this.defaultWorkDir ?? process.cwd();
4728
+ this.logger.info({ sessionId, chatId, cwd }, "Creating Claude remote session");
4729
+ const input = new PushableAsyncIterable();
4730
+ const q = query({
4731
+ prompt: input,
4732
+ options: {
4733
+ cwd: this.spawnCwd,
4734
+ model: opts.model ?? this.defaultModel,
4735
+ permissionMode: this.defaultPermissionMode,
4736
+ env: this.envExtra,
4737
+ pathToClaudeCodeExecutable: this.command[0],
4738
+ canCallTool: (toolName, toolInput, { signal }) => this.handleCanCallTool(sessionId, toolName, toolInput, signal)
4739
+ }
4740
+ });
4741
+ const handle = {
4742
+ id: sessionId,
4743
+ agentType: this.agentType,
4744
+ transportType: this.transportType,
4745
+ createdAt: /* @__PURE__ */ new Date(),
4746
+ sendCommand: async (command) => {
4747
+ await this.handleCommand(sessionId, command);
4748
+ },
4749
+ close: async () => {
4750
+ this.logger.info({ sessionId }, "Closing Claude remote session");
4751
+ input.end();
4752
+ this.sessions.delete(sessionId);
4753
+ }
4754
+ };
4755
+ const state = {
4756
+ handle,
4757
+ query: q,
4758
+ input,
4759
+ chatId,
4760
+ channel,
4761
+ cwd,
4762
+ turnInProgress: false
4763
+ };
4764
+ this.sessions.set(sessionId, state);
4765
+ this.consumeQueryMessages(q, sessionId, state).catch((err) => {
4766
+ this.logger.error({ sessionId, error: err }, "Query consumer error");
4767
+ });
4768
+ this.logger.info({ sessionId }, "Claude remote session started");
4769
+ return handle;
4770
+ }
4771
+ async closeSession(handle) {
4772
+ await handle.close();
4773
+ }
4774
+ async listSessions() {
4775
+ return Array.from(this.sessions.values()).map((s) => ({
4776
+ id: s.handle.id,
4777
+ agentType: this.agentType,
4778
+ transportType: this.transportType,
4779
+ status: "active",
4780
+ startedAt: s.handle.createdAt
4781
+ }));
4782
+ }
4783
+ async getSession(sessionId) {
4784
+ const state = this.sessions.get(sessionId);
4785
+ if (!state) return void 0;
4786
+ return {
4787
+ id: state.handle.id,
4788
+ agentType: this.agentType,
4789
+ transportType: this.transportType,
4790
+ status: "active",
4791
+ startedAt: state.handle.createdAt
4792
+ };
4793
+ }
4794
+ setEventHandler(handler) {
4795
+ this.eventHandler = handler;
4796
+ this.logger.debug("Event handler registered");
4797
+ }
4798
+ async healthCheck() {
4799
+ return { healthy: true, message: `${this.sessions.size} active remote sessions` };
4800
+ }
4801
+ async consumeQueryMessages(q, sessionId, state) {
4802
+ try {
4803
+ for await (const msg of q) {
4804
+ await this.handleSdkMessage(msg, sessionId, state);
4805
+ }
4806
+ } catch (err) {
4807
+ this.logger.error({ sessionId, error: err }, "Claude remote stream ended with error");
4808
+ this.bus.publishOutbound({
4809
+ channel: state.channel,
4810
+ chatId: state.chatId,
4811
+ text: "Claude session ended unexpectedly."
4812
+ });
4813
+ } finally {
4814
+ state.turnInProgress = false;
4815
+ this.sessions.delete(sessionId);
4816
+ this.gateway.handleEvent(this.token, {
4817
+ type: "session:end",
4818
+ sessionId,
4819
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4820
+ payload: { reason: "stream_ended" },
4821
+ metadata: { agentType: this.agentType, transportType: this.transportType }
4822
+ }).catch(() => {
4823
+ });
4824
+ }
4825
+ }
4826
+ async handleSdkMessage(msg, sessionId, state) {
4827
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
4828
+ switch (msg.type) {
4829
+ case "system": {
4830
+ const system = msg;
4831
+ if (system.subtype === "init" && system.session_id) {
4832
+ this.logger.info({ sessionId, claudeSessionId: system.session_id }, "Claude session initialized");
4833
+ this.gateway.handleEvent(this.token, {
4834
+ type: "session:start",
4835
+ sessionId,
4836
+ timestamp,
4837
+ payload: { claudeSessionId: system.session_id },
4838
+ metadata: { agentType: this.agentType, transportType: this.transportType }
4839
+ }).catch(() => {
4840
+ });
4841
+ }
4842
+ break;
4843
+ }
4844
+ case "assistant": {
4845
+ const assistant = msg;
4846
+ const blocks = Array.isArray(assistant.message?.content) ? assistant.message.content : [];
4847
+ for (const block of blocks) {
4848
+ if (block.type === "text" && typeof block.text === "string") {
4849
+ this.bus.publishAgentMessage({ sessionId, content: block.text });
4850
+ }
4851
+ if (block.type === "thinking" && typeof block.thinking === "string") {
4852
+ this.bus.publishAgentMessage({ sessionId, content: block.thinking });
4853
+ }
4854
+ if (block.type === "tool_use") {
4855
+ const toolName = typeof block.name === "string" ? block.name : "unknown";
4856
+ this.bus.publishToolStart({ sessionId, toolName, toolInput: block.input ?? {} });
4857
+ }
4858
+ }
4859
+ break;
4860
+ }
4861
+ case "user": {
4862
+ const user = msg;
4863
+ const content = user.message?.content;
4864
+ if (Array.isArray(content)) {
4865
+ for (const block of content) {
4866
+ if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
4867
+ this.bus.publishToolEnd({ sessionId, toolName: "unknown", output: block.text });
4868
+ } else if (block.type === "text" && typeof block.text === "string") {
4869
+ this.bus.publishAgentMessage({ sessionId, content: block.text });
4870
+ }
4871
+ }
4872
+ }
4873
+ break;
4874
+ }
4875
+ case "result": {
4876
+ const result = msg;
4877
+ this.logger.info({ sessionId, subtype: result.subtype }, "Claude turn result");
4878
+ this.gateway.handleEvent(this.token, {
4879
+ type: "finished",
4880
+ sessionId,
4881
+ timestamp,
4882
+ payload: { result: result.result, subtype: result.subtype },
4883
+ metadata: { agentType: this.agentType, transportType: this.transportType }
4884
+ }).catch(() => {
4885
+ });
4886
+ state.turnInProgress = false;
4887
+ break;
4888
+ }
4889
+ case "log": {
4890
+ const log = msg;
4891
+ const level = log.log?.level ?? "debug";
4892
+ const message = log.log?.message ?? "";
4893
+ if (level === "error") this.logger.error({ sessionId }, message);
4894
+ else if (level === "warn") this.logger.warn({ sessionId }, message);
4895
+ else if (level === "info") this.logger.info({ sessionId }, message);
4896
+ else this.logger.debug({ sessionId }, message);
4897
+ break;
4898
+ }
4899
+ }
4900
+ }
4901
+ async handleCommand(sessionId, command) {
4902
+ const state = this.sessions.get(sessionId);
4903
+ if (!state) {
4904
+ this.logger.warn({ sessionId, commandType: command.type }, "Command received but session not found");
4905
+ throw new Error(`Session not found: ${sessionId}`);
4906
+ }
4907
+ switch (command.type) {
4908
+ case "user:message": {
4909
+ if (state.turnInProgress) {
4910
+ this.logger.warn({ sessionId }, "Concurrent turn rejected");
4911
+ this.bus.publishOutbound({
4912
+ channel: state.channel,
4913
+ chatId: state.chatId,
4914
+ text: "Claude is still processing the previous message. Please wait."
4915
+ });
4916
+ return;
4917
+ }
4918
+ state.turnInProgress = true;
4919
+ state.input.push({
4920
+ type: "user",
4921
+ message: { role: "user", content: command.content }
4922
+ });
4923
+ break;
4924
+ }
4925
+ case "interrupt": {
4926
+ this.logger.info({ sessionId }, "Interrupting turn");
4927
+ await state.query.interrupt();
4928
+ break;
4929
+ }
4930
+ case "stop": {
4931
+ this.logger.info({ sessionId }, "Stopping session");
4932
+ state.input.end();
4933
+ this.sessions.delete(sessionId);
4934
+ this.gateway.handleEvent(this.token, {
4935
+ type: "session:end",
4936
+ sessionId,
4937
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4938
+ payload: { reason: "stopped" },
4939
+ metadata: { agentType: this.agentType, transportType: this.transportType }
4940
+ }).catch(() => {
4941
+ });
4942
+ break;
4943
+ }
4944
+ default: {
4945
+ this.logger.warn({ sessionId, commandType: command.type }, "Unsupported command");
4946
+ }
4947
+ }
4948
+ }
4949
+ async handleCanCallTool(sessionId, toolName, toolInput, signal) {
4950
+ const state = this.sessions.get(sessionId);
4951
+ if (!state) {
4952
+ this.logger.warn({ sessionId, toolName }, "Permission request but session not found, auto-denying");
4953
+ return { behavior: "deny", message: "Session not found" };
4954
+ }
4955
+ const requestId = `${sessionId}-${Math.random().toString(36).substring(2, 10)}`;
4956
+ this.logger.info({ sessionId, requestId, toolName }, "Claude remote permission request");
4957
+ const envelope = {
4958
+ type: "permission:request",
4959
+ sessionId,
4960
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4961
+ payload: { sessionId, toolName, toolInput: toolInput ?? {} },
4962
+ metadata: {
4963
+ agentType: this.agentType,
4964
+ transportType: this.transportType,
4965
+ permissionRequestId: requestId
4966
+ }
4967
+ };
4968
+ return new Promise((resolve2) => {
4969
+ const timeout = setTimeout(() => {
4970
+ this.logger.warn({ sessionId, requestId }, "Permission request timeout");
4971
+ resolve2({ behavior: "deny", message: "Timeout" });
4972
+ }, 6e4);
4973
+ const onAbort = () => {
4974
+ clearTimeout(timeout);
4975
+ resolve2({ behavior: "deny", message: "Aborted" });
4976
+ };
4977
+ signal.addEventListener("abort", onAbort, { once: true });
4978
+ this.gateway.handleEvent(this.token, envelope).then((response) => {
4979
+ clearTimeout(timeout);
4980
+ signal.removeEventListener("abort", onAbort);
4981
+ const decision = response?.action === "allow" ? "allow" : "deny";
4982
+ this.logger.info({ sessionId, requestId, toolName, decision }, "Claude remote permission resolved");
4983
+ resolve2({
4984
+ behavior: decision,
4985
+ updatedInput: toolInput ?? {},
4986
+ ...decision === "deny" ? { message: "User denied this tool call" } : {}
4987
+ });
4988
+ }).catch((err) => {
4989
+ clearTimeout(timeout);
4990
+ signal.removeEventListener("abort", onAbort);
4991
+ this.logger.error({ error: err, sessionId, requestId }, "Claude remote permission handler error");
4992
+ resolve2({ behavior: "deny", message: "Error" });
4993
+ });
4994
+ });
4995
+ }
4996
+ async handleInboundMessage(message) {
4997
+ const text = message.text.trim();
4998
+ this.logger.debug({ channel: message.channel, chatId: message.chatId, targetAgent: message.targetAgent, textPreview: text.slice(0, 80) }, "Inbound message received");
4999
+ if (message.systemCommand) return;
5000
+ if (message.targetAgent !== "claude") return;
5001
+ const rawInput = this.normalizeInboundText(message);
5002
+ if (rawInput === null) return;
5003
+ const sessionId = `claude-${message.chatId}`;
5004
+ const existingSession = this.gateway.getSession(sessionId);
5005
+ if (!rawInput) {
5006
+ this.bus.publishOutbound({ channel: message.channel, chatId: message.chatId, text: this.getHelpText() });
5007
+ return;
5008
+ }
5009
+ const [keyword, ...rest] = rawInput.split(/\s+/);
5010
+ const normalizedKeyword = keyword.toLowerCase();
5011
+ const commandOnly = rest.length === 0;
5012
+ if (normalizedKeyword === "help" && commandOnly) {
5013
+ this.bus.publishOutbound({ channel: message.channel, chatId: message.chatId, text: this.getHelpText() });
5014
+ return;
5015
+ }
5016
+ if (normalizedKeyword === "status" && commandOnly) {
5017
+ const active = existingSession && existingSession.getStatus() === "active";
5018
+ this.bus.publishOutbound({
5019
+ channel: message.channel,
5020
+ chatId: message.chatId,
5021
+ text: active ? `Claude remote session is active.
5022
+ Session ID: ${sessionId}` : "No active Claude remote session for this chat."
5023
+ });
5024
+ return;
5025
+ }
5026
+ if ((normalizedKeyword === "stop" || normalizedKeyword === "reset") && commandOnly) {
5027
+ if (!existingSession) {
5028
+ this.bus.publishOutbound({ channel: message.channel, chatId: message.chatId, text: "No active session to stop." });
5029
+ return;
5030
+ }
5031
+ await existingSession.sendCommand({ type: "stop" });
5032
+ this.bus.publishOutbound({ channel: message.channel, chatId: message.chatId, text: "Claude session stopped." });
5033
+ return;
5034
+ }
5035
+ let session = existingSession;
5036
+ if (!session) {
5037
+ try {
5038
+ session = await this.gateway.createSession(this.token, sessionId, { chatId: message.chatId, channel: message.channel });
5039
+ } catch (err) {
5040
+ this.logger.error({ error: err, chatId: message.chatId }, "Failed to create Claude remote session");
5041
+ this.bus.publishOutbound({ channel: message.channel, chatId: message.chatId, text: "Failed to start Claude session. Please check that Claude CLI is installed." });
5042
+ return;
5043
+ }
5044
+ }
5045
+ if (session.getStatus() !== "active") {
5046
+ this.bus.publishOutbound({ channel: message.channel, chatId: message.chatId, text: "Session is not active. Please start a new session." });
5047
+ return;
5048
+ }
5049
+ await session.sendCommand({ type: "user:message", content: rawInput });
5050
+ }
5051
+ normalizeInboundText(message) {
5052
+ const text = message.text.trim();
5053
+ const explicitCommand = text.match(/^\/claude(?:@[^\s]+)?(?:\s+(.*))?$/s);
5054
+ if (explicitCommand) {
5055
+ return explicitCommand[1]?.trim() ?? "";
5056
+ }
5057
+ const aliasCommand = message.agentCommand ? text.match(/^\/(status|help|stop|reset)(?:@[^\s]+)?$/i) : null;
5058
+ if (aliasCommand) {
5059
+ return aliasCommand[1].toLowerCase();
5060
+ }
5061
+ return message.agentCommand ? null : text;
5062
+ }
5063
+ getHelpText() {
5064
+ return [
5065
+ "Claude remote commands:",
5066
+ "/claude help - show this help message",
5067
+ "/claude status - show the current chat session state",
5068
+ "/claude stop - stop the active session for this chat",
5069
+ "/claude reset - stop and clear the active session",
5070
+ "/claude <prompt> - run a task in the current chat",
5071
+ "Any other text is sent to Claude as a prompt."
5072
+ ].join("\n");
5073
+ }
5074
+ };
5075
+
4080
5076
  // src/adapters/agent/codex/CodexAppServerAdapter.ts
4081
- import { randomUUID as randomUUID4 } from "crypto";
5077
+ import { randomUUID as randomUUID5 } from "crypto";
4082
5078
 
4083
5079
  // src/adapters/agent/codex/CodexProtocolHandler.ts
4084
5080
  var CodexProtocolHandler = class {
@@ -4101,8 +5097,8 @@ var CodexProtocolHandler = class {
4101
5097
  };
4102
5098
 
4103
5099
  // src/adapters/agent/codex/codexAppServerClient.ts
4104
- import { spawn, execFileSync } from "child_process";
4105
- import { createInterface } from "readline";
5100
+ import { spawn as spawn2, execFileSync } from "child_process";
5101
+ import { createInterface as createInterface2 } from "readline";
4106
5102
  function isAppServerAvailable(command = "codex") {
4107
5103
  try {
4108
5104
  execFileSync(command, ["--version"], { encoding: "utf8", windowsHide: true });
@@ -4381,7 +5377,7 @@ var CodexAppServerClient = class _CodexAppServerClient {
4381
5377
  if (this.spawnCwd) {
4382
5378
  spawnOpts.cwd = this.spawnCwd;
4383
5379
  }
4384
- const proc = spawn(cmd, args, spawnOpts);
5380
+ const proc = spawn2(cmd, args, spawnOpts);
4385
5381
  this.process = proc;
4386
5382
  proc.on("error", (err) => {
4387
5383
  this.logger?.debug("Process error:", err);
@@ -4405,7 +5401,7 @@ var CodexAppServerClient = class _CodexAppServerClient {
4405
5401
  const text = chunk.toString().trim();
4406
5402
  if (text) this.logger?.debug(`[CodexAppServer:stderr] ${text}`);
4407
5403
  });
4408
- this.readline = createInterface({ input: proc.stdout });
5404
+ this.readline = createInterface2({ input: proc.stdout });
4409
5405
  this.readline.on("line", (line) => {
4410
5406
  if (this.process !== proc || this.processEpoch !== epoch) return;
4411
5407
  this.handleLine(line, epoch);
@@ -5034,7 +6030,7 @@ var CodexAppServerAdapter = class {
5034
6030
  this.logger.info("Codex adapter shutdown complete");
5035
6031
  }
5036
6032
  async createSession(opts) {
5037
- const sessionId = opts.sessionId ?? `codex-${randomUUID4().slice(0, 8)}`;
6033
+ const sessionId = opts.sessionId ?? `codex-${randomUUID5().slice(0, 8)}`;
5038
6034
  const chatId = opts.metadata?.chatId ?? sessionId;
5039
6035
  const channel = opts.metadata?.channel ?? "broadcast";
5040
6036
  const cwd = opts.cwd ?? this.defaultWorkDir ?? process.cwd();
@@ -5408,10 +6404,10 @@ Session ID: ${sessionId}` : "No active Codex session for this chat."
5408
6404
  };
5409
6405
 
5410
6406
  // src/shared/persistence.ts
5411
- import { appendFile, mkdir, readdir as readdir2, readFile as readFile2 } from "fs/promises";
6407
+ import { appendFile, mkdir, readdir as readdir2, readFile as readFile3 } from "fs/promises";
5412
6408
  import { readdirSync, statSync } from "fs";
5413
6409
  import { join as join4 } from "path";
5414
- import { existsSync as existsSync5 } from "fs";
6410
+ import { existsSync as existsSync6 } from "fs";
5415
6411
  var Persistence = class {
5416
6412
  baseDir;
5417
6413
  constructor(baseDir) {
@@ -5425,7 +6421,7 @@ var Persistence = class {
5425
6421
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5426
6422
  const dateDir = join4(this.baseDir, date);
5427
6423
  const filePath = join4(dateDir, `${agentType}-${sessionId}.jsonl`);
5428
- if (!existsSync5(dateDir)) {
6424
+ if (!existsSync6(dateDir)) {
5429
6425
  await mkdir(dateDir, { recursive: true });
5430
6426
  }
5431
6427
  const persistedEvent = {
@@ -5448,7 +6444,7 @@ var Persistence = class {
5448
6444
  const fileName = files.find((f) => f.endsWith(`-${sessionId}.jsonl`));
5449
6445
  if (fileName) {
5450
6446
  const filePath = join4(dirPath, fileName);
5451
- const content = await readFile2(filePath, "utf-8");
6447
+ const content = await readFile3(filePath, "utf-8");
5452
6448
  const lines = content.trim().split("\n").filter((line) => line.length > 0);
5453
6449
  for (const line of lines) {
5454
6450
  try {
@@ -5466,7 +6462,7 @@ var Persistence = class {
5466
6462
  */
5467
6463
  list() {
5468
6464
  const sessions = [];
5469
- if (!existsSync5(this.baseDir)) {
6465
+ if (!existsSync6(this.baseDir)) {
5470
6466
  return sessions;
5471
6467
  }
5472
6468
  const entries = readdirSync(this.baseDir, { withFileTypes: true });
@@ -5710,7 +6706,8 @@ function setupRoutes(httpServer, gateway, adapter, getClientCount) {
5710
6706
  gateway.registerAgent("claude", token);
5711
6707
  }
5712
6708
  logger2.info({ eventType, token: token.substring(0, 8), body }, "Hook payload received");
5713
- const envelope = adapter.translate(eventType, body);
6709
+ const translate = adapter.translate;
6710
+ const envelope = typeof translate === "function" ? translate.call(adapter, eventType, body) : null;
5714
6711
  if (!envelope) {
5715
6712
  sendJson(res, 202, { accepted: true });
5716
6713
  return;
@@ -6251,7 +7248,7 @@ function broadcastEvent(wsServer, frame) {
6251
7248
  }
6252
7249
 
6253
7250
  // src/shared/credentials.ts
6254
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
7251
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
6255
7252
  import { join as join5, dirname as dirname4 } from "path";
6256
7253
  import { homedir as homedir2 } from "os";
6257
7254
  function getCredentialsPath() {
@@ -6259,7 +7256,7 @@ function getCredentialsPath() {
6259
7256
  }
6260
7257
  function loadCredentials() {
6261
7258
  const path2 = getCredentialsPath();
6262
- if (!existsSync6(path2)) {
7259
+ if (!existsSync7(path2)) {
6263
7260
  return null;
6264
7261
  }
6265
7262
  try {
@@ -6275,7 +7272,7 @@ function getHookToken() {
6275
7272
  }
6276
7273
 
6277
7274
  // src/adapters/interaction/queue.ts
6278
- import { randomUUID as randomUUID5 } from "crypto";
7275
+ import { randomUUID as randomUUID6 } from "crypto";
6279
7276
  var InteractionQueue = class {
6280
7277
  pending = /* @__PURE__ */ new Map();
6281
7278
  timeoutMs;
@@ -6285,7 +7282,7 @@ var InteractionQueue = class {
6285
7282
  this.defaultOnTimeout = options.defaultOnTimeout ?? "deny";
6286
7283
  }
6287
7284
  request(request) {
6288
- const requestId = randomUUID5();
7285
+ const requestId = randomUUID6();
6289
7286
  const responsePromise = new Promise((resolve2, reject) => {
6290
7287
  const timeoutId = setTimeout(() => {
6291
7288
  this.pending.delete(requestId);
@@ -8446,121 +9443,6 @@ var NotificationHandler = class {
8446
9443
  }
8447
9444
  };
8448
9445
 
8449
- // src/adapters/agent/claude/session-context.ts
8450
- import { readFile as readFile3 } from "fs/promises";
8451
- function sleep(ms) {
8452
- return new Promise((resolve2) => setTimeout(resolve2, ms));
8453
- }
8454
- var ClaudeSessionContextProvider = class {
8455
- async getSessionSummary(_sessionId, transcriptPath) {
8456
- if (!transcriptPath) {
8457
- return void 0;
8458
- }
8459
- const summary = await this.tryReadTranscript(transcriptPath);
8460
- return summary;
8461
- }
8462
- async tryReadTranscript(transcriptPath, maxAttempts = 5, delayMs = 200) {
8463
- let lastError;
8464
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
8465
- try {
8466
- const content = await readFile3(transcriptPath, "utf-8");
8467
- const summary = this.parseTranscript(content);
8468
- if (summary && (summary.lastAssistantMessage !== void 0 || summary.durationMs !== void 0)) {
8469
- return summary;
8470
- }
8471
- if (summary) {
8472
- getLogger().debug({ attempt, summary }, "Transcript read but incomplete, retrying...");
8473
- }
8474
- } catch (error2) {
8475
- lastError = error2;
8476
- }
8477
- if (attempt < maxAttempts) {
8478
- await sleep(delayMs);
8479
- }
8480
- }
8481
- getLogger().warn({ error: lastError instanceof Error ? lastError.message : String(lastError), transcriptPath }, "Failed to read transcript or transcript incomplete");
8482
- return void 0;
8483
- }
8484
- parseTranscript(content) {
8485
- const lines = content.split("\n").filter((line) => line.trim().length > 0);
8486
- let lastAssistantMessage;
8487
- let inputTokens;
8488
- let outputTokens;
8489
- let lastTextAssistant;
8490
- let lastAnyAssistant;
8491
- const userTimestamps = /* @__PURE__ */ new Map();
8492
- const parentMap = /* @__PURE__ */ new Map();
8493
- for (const line of lines) {
8494
- let entry;
8495
- try {
8496
- entry = JSON.parse(line);
8497
- } catch (parseError) {
8498
- getLogger().warn(
8499
- { error: parseError instanceof Error ? parseError.message : String(parseError), line: line.slice(0, 200) },
8500
- "Skipping malformed transcript line"
8501
- );
8502
- continue;
8503
- }
8504
- const ts = typeof entry.timestamp === "string" ? new Date(entry.timestamp).getTime() : typeof entry.timestamp === "number" ? entry.timestamp : void 0;
8505
- if (entry.uuid && typeof entry.parentUuid === "string") {
8506
- parentMap.set(entry.uuid, entry.parentUuid);
8507
- }
8508
- if (entry.type === "user" && ts !== void 0 && entry.uuid) {
8509
- userTimestamps.set(entry.uuid, ts);
8510
- }
8511
- if (entry.type === "assistant" && ts !== void 0) {
8512
- const arr = Array.isArray(entry.message?.content) ? entry.message.content : [];
8513
- const textBlock = arr.find((c) => c.type === "text");
8514
- const assistantRef = { ts, uuid: entry.uuid, parentUuid: entry.parentUuid };
8515
- lastAnyAssistant = assistantRef;
8516
- if (textBlock && typeof textBlock.text === "string") {
8517
- lastAssistantMessage = textBlock.text;
8518
- lastTextAssistant = assistantRef;
8519
- const usage = entry.message?.usage;
8520
- if (usage) {
8521
- if (typeof usage.input_tokens === "number") {
8522
- inputTokens = usage.input_tokens;
8523
- }
8524
- if (typeof usage.output_tokens === "number") {
8525
- outputTokens = usage.output_tokens;
8526
- }
8527
- }
8528
- }
8529
- }
8530
- }
8531
- function resolveDuration(assistantRef) {
8532
- if (!assistantRef) return void 0;
8533
- if (assistantRef.parentUuid) {
8534
- const directParentTs = userTimestamps.get(assistantRef.parentUuid);
8535
- if (directParentTs !== void 0) {
8536
- return assistantRef.ts - directParentTs;
8537
- }
8538
- }
8539
- let currentUuid = assistantRef.uuid;
8540
- const visited = /* @__PURE__ */ new Set();
8541
- while (currentUuid && !visited.has(currentUuid)) {
8542
- visited.add(currentUuid);
8543
- const parentUuid = parentMap.get(currentUuid);
8544
- if (!parentUuid) break;
8545
- const parentTs = userTimestamps.get(parentUuid);
8546
- if (parentTs !== void 0) {
8547
- return assistantRef.ts - parentTs;
8548
- }
8549
- currentUuid = parentUuid;
8550
- }
8551
- return void 0;
8552
- }
8553
- const durationMs = resolveDuration(lastTextAssistant) ?? resolveDuration(lastAnyAssistant);
8554
- const summary = {
8555
- ...lastAssistantMessage !== void 0 && { lastAssistantMessage },
8556
- ...inputTokens !== void 0 && { inputTokens },
8557
- ...outputTokens !== void 0 && { outputTokens },
8558
- ...durationMs !== void 0 && { durationMs }
8559
- };
8560
- return summary;
8561
- }
8562
- };
8563
-
8564
9446
  // src/core/channel/base.ts
8565
9447
  var BaseChannel = class {
8566
9448
  /**
@@ -11173,7 +12055,7 @@ function readFrameId(frame) {
11173
12055
  }
11174
12056
 
11175
12057
  // src/adapters/channels/app/protocol.ts
11176
- import { randomUUID as randomUUID6 } from "crypto";
12058
+ import { randomUUID as randomUUID7 } from "crypto";
11177
12059
 
11178
12060
  // src/adapters/channels/app/query-service.ts
11179
12061
  var DEFAULT_CAPABILITIES = {
@@ -11329,7 +12211,7 @@ async function handleSessionCreate(frame, connection, gateway, queryService, cre
11329
12211
  "session.create payload.agentId must match the authenticated agent"
11330
12212
  );
11331
12213
  }
11332
- const sessionId = createSessionId?.() ?? randomUUID6();
12214
+ const sessionId = createSessionId?.() ?? randomUUID7();
11333
12215
  const metadata = {
11334
12216
  channel: connection.channel,
11335
12217
  channelId: connection.channelId,
@@ -11389,7 +12271,7 @@ async function handleMessageSend(frame, connection, gateway, sentMessageResults,
11389
12271
  return ok(frame.id, outcome);
11390
12272
  }
11391
12273
  }
11392
- const messageId = providedMessageId ?? createMessageId?.() ?? randomUUID6();
12274
+ const messageId = providedMessageId ?? createMessageId?.() ?? randomUUID7();
11393
12275
  const resultPromise = (async () => {
11394
12276
  await session.sendCommand({
11395
12277
  type: "user:message",
@@ -11679,13 +12561,27 @@ async function main() {
11679
12561
  }
11680
12562
  setupAllHandlers(bus, gateway, interactionQueue, logger5);
11681
12563
  logger5.info("Command and permission handlers setup complete");
11682
- const adapter = new ClaudeAdapter({
12564
+ const claudeConfig = config.agent?.claude;
12565
+ const adapter = claudeConfig?.adapter === "remote" ? new ClaudeRemoteAdapter({
12566
+ logger: createAgentLogger("claude"),
12567
+ bus,
12568
+ gateway,
12569
+ token: hookToken,
12570
+ command: claudeConfig.binary ? [claudeConfig.binary] : void 0,
12571
+ defaultWorkDir: claudeConfig.work_dir,
12572
+ defaultModel: claudeConfig.model,
12573
+ defaultPermissionMode: claudeConfig.permission_mode
12574
+ }) : new ClaudeAdapter({
11683
12575
  logger: createAgentLogger("claude"),
11684
12576
  bus,
11685
12577
  gateway,
11686
12578
  token: hookToken
11687
12579
  });
11688
- await adapter.initialize();
12580
+ try {
12581
+ await adapter.initialize();
12582
+ } catch (err) {
12583
+ logger5.warn({ error: err instanceof Error ? err.message : String(err) }, "Claude adapter initialization failed, skipping");
12584
+ }
11689
12585
  setupRoutes(httpServer, gateway, adapter, () => wsServer.getClientCount());
11690
12586
  setupWebSocketHandlers(wsServer, gateway, { interactionQueue });
11691
12587
  let appWebSocketServer;