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.
- package/dist/cli/index.js +63 -34
- package/dist/cli/index.js.map +1 -1
- package/dist/gateway/process.js +1032 -136
- package/dist/gateway/process.js.map +1 -1
- package/package.json +1 -1
package/dist/gateway/process.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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 =
|
|
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-${
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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 =
|
|
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
|
|
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?.() ??
|
|
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?.() ??
|
|
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
|
|
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
|
-
|
|
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;
|