@kodizm/acp 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +2362 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -52133,10 +52133,2367 @@ var init_driver2 = __esm(() => {
52133
52133
  };
52134
52134
  logger = createLogger({ env: process.env });
52135
52135
  });
52136
+
52137
+ // src/live/redact.ts
52138
+ function redact(text, knownValues) {
52139
+ const values = knownValues.filter((value) => value !== "").sort((left, right) => right.length - left.length);
52140
+ let result = text;
52141
+ for (const value of values) {
52142
+ result = result.replaceAll(value, REDACTED);
52143
+ }
52144
+ for (const pattern of CREDENTIAL_PATTERNS) {
52145
+ result = result.replace(pattern, REDACTED);
52146
+ }
52147
+ return result.replace(URL_CREDENTIAL, `$1${REDACTED}@`);
52148
+ }
52149
+ var REDACTED = "[REDACTED]", CREDENTIAL_PATTERNS, URL_CREDENTIAL;
52150
+ var init_redact = __esm(() => {
52151
+ CREDENTIAL_PATTERNS = [
52152
+ /kgw-[A-Za-z0-9]{8,}/g,
52153
+ /sk-ant-[\w-]+/g,
52154
+ /kdz-int-jwt\.[\w.-]+/g,
52155
+ /-----BEGIN[\s\S]*?(?:-----END[^\n]*?-----|$)/g
52156
+ ];
52157
+ URL_CREDENTIAL = /(x-access-token:)[^@\s/]+@/g;
52158
+ });
52159
+
52160
+ // src/live/events.ts
52161
+ function redactDeep(value, knownValues) {
52162
+ if (typeof value === "string") {
52163
+ return redact(value, knownValues);
52164
+ }
52165
+ if (Array.isArray(value)) {
52166
+ return value.map((item) => redactDeep(item, knownValues));
52167
+ }
52168
+ if (isRecord(value)) {
52169
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactDeep(item, knownValues)]));
52170
+ }
52171
+ return value;
52172
+ }
52173
+ function sessionStarted(hook, source) {
52174
+ return draft("session_started", {
52175
+ claudeSessionId: text(hook.session_id),
52176
+ model: text(hook.model),
52177
+ source
52178
+ });
52179
+ }
52180
+ function turnStarted(promptId) {
52181
+ return draft("turn_started", { promptId });
52182
+ }
52183
+ function turnCompleted(stopReason, lastAssistantMessage) {
52184
+ return draft("turn_completed", {
52185
+ stopReason,
52186
+ lastAssistantMessage
52187
+ });
52188
+ }
52189
+ function apiStopReason(reason) {
52190
+ return reason !== null && API_STOP_REASONS.has(reason) ? reason : "end_turn";
52191
+ }
52192
+ function turnFailed(hook) {
52193
+ const error = text(hook.error) ?? "unknown";
52194
+ const details = text(hook.error_details);
52195
+ return draft("turn_failed", {
52196
+ reason: FAILURE_REASONS[error] ?? "api_error",
52197
+ detail: details === undefined ? `StopFailure ${error}` : `StopFailure ${error}: ${details}`.slice(0, TITLE_MAX * 2)
52198
+ });
52199
+ }
52200
+ function sessionEnded(reason) {
52201
+ return draft("session_ended", { reason });
52202
+ }
52203
+ function toolCallBegin(hook, toolCallId) {
52204
+ return draft("tool_call_begin", {
52205
+ toolCallId,
52206
+ name: text(hook.tool_name),
52207
+ input: hook.tool_input
52208
+ });
52209
+ }
52210
+ function toolCallEnd(toolCallId, status, output) {
52211
+ return draft("tool_call_end", {
52212
+ toolCallId,
52213
+ status,
52214
+ output
52215
+ });
52216
+ }
52217
+ function toolOutput(response) {
52218
+ if (response === undefined || response === null) {
52219
+ return;
52220
+ }
52221
+ if (typeof response === "string") {
52222
+ return response.slice(0, OUTPUT_MAX);
52223
+ }
52224
+ if (isRecord(response) && typeof response.stdout === "string") {
52225
+ const stderr = typeof response.stderr === "string" && response.stderr !== "" ? `
52226
+ ${response.stderr}` : "";
52227
+ return `${response.stdout}${stderr}`.slice(0, OUTPUT_MAX);
52228
+ }
52229
+ return JSON.stringify(response).slice(0, OUTPUT_MAX);
52230
+ }
52231
+ function permissionRequest(hook, toolCallId) {
52232
+ const name = text(hook.tool_name) ?? "unknown";
52233
+ return {
52234
+ toolCallId,
52235
+ title: toolTitle(name, hook.tool_input),
52236
+ kind: TOOL_KINDS[name] ?? "other",
52237
+ rawInput: hook.tool_input ?? null,
52238
+ options: PERMISSION_OPTIONS
52239
+ };
52240
+ }
52241
+ function subagentSpawn(hook) {
52242
+ const type = text(hook.agent_type);
52243
+ return draft("subagent_spawn", {
52244
+ subagent_id: text(hook.agent_id),
52245
+ subagent_type: type === undefined || type === "" ? "general-purpose" : type
52246
+ });
52247
+ }
52248
+ function subagentComplete(hook) {
52249
+ return draft("subagent_complete", {
52250
+ subagent_id: text(hook.agent_id)
52251
+ });
52252
+ }
52253
+ function compactionStarted(hook) {
52254
+ return draft("compaction_started", {
52255
+ trigger: hook.trigger === "auto" ? "auto" : "manual"
52256
+ });
52257
+ }
52258
+ function compactionCompleted(hook) {
52259
+ return draft("compaction_completed", {
52260
+ succeeded: true,
52261
+ trigger: hook.trigger === "auto" ? "auto" : "manual"
52262
+ });
52263
+ }
52264
+ function usage(counts) {
52265
+ return draft("usage", { ...counts });
52266
+ }
52267
+ function outputChunk(chunk) {
52268
+ return draft("output_chunk", { text: chunk });
52269
+ }
52270
+ function thinkingChunk(chunk) {
52271
+ return draft("thinking_chunk", { text: chunk });
52272
+ }
52273
+ function heartbeat() {
52274
+ return draft("heartbeat", {});
52275
+ }
52276
+ function processDied() {
52277
+ return draft("process_died", { exitCode: null });
52278
+ }
52279
+ function sessionFailed(reason, detail) {
52280
+ return draft("session_failed", {
52281
+ reason,
52282
+ detail
52283
+ });
52284
+ }
52285
+ function text(value) {
52286
+ return typeof value === "string" ? value : undefined;
52287
+ }
52288
+ function isRecord(value) {
52289
+ return typeof value === "object" && value !== null && !Array.isArray(value);
52290
+ }
52291
+ function toolTitle(name, input) {
52292
+ if (!isRecord(input)) {
52293
+ return name;
52294
+ }
52295
+ const field = TITLE_FIELDS.find((key) => typeof input[key] === "string" && input[key] !== "");
52296
+ const subject = field === undefined ? JSON.stringify(input) : input[field];
52297
+ return `${name}: ${subject}`.slice(0, TITLE_MAX);
52298
+ }
52299
+ function draft(type, payload) {
52300
+ return {
52301
+ type,
52302
+ payload: Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== undefined))
52303
+ };
52304
+ }
52305
+ var PERMISSION_OPTIONS, API_STOP_REASONS, TOOL_KINDS, TITLE_FIELDS, TITLE_MAX = 200, OUTPUT_MAX = 16000, FAILURE_REASONS;
52306
+ var init_events2 = __esm(() => {
52307
+ init_redact();
52308
+ PERMISSION_OPTIONS = [
52309
+ {
52310
+ optionId: "allow",
52311
+ kind: "allow_once"
52312
+ },
52313
+ {
52314
+ optionId: "deny",
52315
+ kind: "reject_once"
52316
+ }
52317
+ ];
52318
+ API_STOP_REASONS = new Set(["end_turn", "max_tokens", "refusal"]);
52319
+ TOOL_KINDS = {
52320
+ Bash: "execute",
52321
+ BashOutput: "execute",
52322
+ KillShell: "execute",
52323
+ Edit: "edit",
52324
+ MultiEdit: "edit",
52325
+ Write: "edit",
52326
+ NotebookEdit: "edit",
52327
+ Read: "read",
52328
+ Glob: "search",
52329
+ Grep: "search",
52330
+ WebFetch: "fetch",
52331
+ WebSearch: "fetch"
52332
+ };
52333
+ TITLE_FIELDS = ["command", "file_path", "notebook_path", "path", "url", "pattern", "query", "description"];
52334
+ FAILURE_REASONS = {
52335
+ rate_limit: "rate_limit",
52336
+ authentication_failed: "auth_error",
52337
+ oauth_org_not_allowed: "auth_error",
52338
+ account_on_hold: "auth_error",
52339
+ billing_error: "auth_error",
52340
+ cloud_credential_error: "auth_error"
52341
+ };
52342
+ });
52343
+
52344
+ // src/live/hooks.ts
52345
+ import { readFile as readFile2, rename as rename3, writeFile } from "fs/promises";
52346
+ import { createConnection } from "net";
52347
+ async function buildPaneSettings(options) {
52348
+ const base = JSON.parse(await readFile2(options.basePath, "utf8"));
52349
+ if (!isRecord2(base)) {
52350
+ throw new Error(`${options.basePath} does not hold a JSON object`);
52351
+ }
52352
+ const hooks = Object.fromEntries(HOOK_EVENTS.map((event) => [
52353
+ event,
52354
+ [
52355
+ {
52356
+ hooks: [
52357
+ {
52358
+ type: "command",
52359
+ command: hookCommand(options, event),
52360
+ timeout: event === "PermissionRequest" ? PERMISSION_HOOK_TIMEOUT_S : HOOK_TIMEOUT_S
52361
+ }
52362
+ ]
52363
+ }
52364
+ ]
52365
+ ]));
52366
+ return {
52367
+ ...base,
52368
+ disableAllHooks: false,
52369
+ hooks
52370
+ };
52371
+ }
52372
+ async function writePaneSettings(path, options) {
52373
+ const settings = await buildPaneSettings(options);
52374
+ const staging = `${path}.${process.pid}.tmp`;
52375
+ await writeFile(staging, JSON.stringify(settings, null, 2), { mode: 384 });
52376
+ await rename3(staging, path);
52377
+ }
52378
+ async function runHookHelper(options) {
52379
+ const payload = parseObject(options.input);
52380
+ if (payload === null) {
52381
+ return "";
52382
+ }
52383
+ const blocking = options.event === "PermissionRequest";
52384
+ const timeoutMs = options.timeoutMs ?? (blocking ? PERMISSION_HELPER_TIMEOUT_MS : HELPER_TIMEOUT_MS);
52385
+ let response;
52386
+ try {
52387
+ response = await socketRequest(options.socketPath, {
52388
+ kind: "hook",
52389
+ pane: options.paneId,
52390
+ event: options.event,
52391
+ payload
52392
+ }, timeoutMs);
52393
+ } catch (error) {
52394
+ if (error instanceof SocketRequestError) {
52395
+ process.stderr.write(`${JSON.stringify({ level: "warn", message: error.message, event: options.event })}
52396
+ `);
52397
+ return "";
52398
+ }
52399
+ throw error;
52400
+ }
52401
+ return blocking && isRecord2(response) ? hookDecisionOutput(response.decision) : "";
52402
+ }
52403
+ function hookDecisionOutput(decision) {
52404
+ if (!isRecord2(decision) || decision.behavior !== "allow" && decision.behavior !== "deny") {
52405
+ return "";
52406
+ }
52407
+ const answer = {
52408
+ behavior: decision.behavior
52409
+ };
52410
+ if (decision.behavior === "deny" && typeof decision.message === "string") {
52411
+ answer.message = decision.message;
52412
+ }
52413
+ return JSON.stringify({
52414
+ hookSpecificOutput: {
52415
+ hookEventName: "PermissionRequest",
52416
+ decision: answer
52417
+ }
52418
+ });
52419
+ }
52420
+ function socketRequest(path, message, timeoutMs) {
52421
+ return new Promise((resolve, reject) => {
52422
+ const socket = createConnection(path);
52423
+ let buffer = "";
52424
+ let settled = false;
52425
+ const finish = (outcome) => {
52426
+ if (settled) {
52427
+ return;
52428
+ }
52429
+ settled = true;
52430
+ clearTimeout(timer);
52431
+ socket.destroy();
52432
+ outcome();
52433
+ };
52434
+ const timer = setTimeout(() => finish(() => reject(new SocketRequestError("timeout"))), timeoutMs);
52435
+ let connected = false;
52436
+ socket.on("connect", () => {
52437
+ connected = true;
52438
+ socket.write(`${JSON.stringify(message)}
52439
+ `);
52440
+ });
52441
+ socket.on("data", (chunk) => {
52442
+ buffer += chunk.toString("utf8");
52443
+ const newline = buffer.indexOf(`
52444
+ `);
52445
+ if (newline === -1) {
52446
+ return;
52447
+ }
52448
+ const answer = parseJson(buffer.slice(0, newline));
52449
+ finish(() => answer === undefined ? reject(new SocketRequestError("malformed")) : resolve(answer.value));
52450
+ });
52451
+ socket.on("error", (error) => finish(() => reject(new SocketRequestError(connected ? "closed" : "unreachable", error.message))));
52452
+ socket.on("close", () => finish(() => reject(new SocketRequestError(connected ? "closed" : "unreachable"))));
52453
+ });
52454
+ }
52455
+ function shellQuote(argument) {
52456
+ return `'${argument.replaceAll("'", "'\\''")}'`;
52457
+ }
52458
+ function hookCommand(options, event) {
52459
+ return [...options.helperArgv, "tmuxd", "hook", event, "--pane", options.paneId, "--socket", options.socketPath].map(shellQuote).join(" ");
52460
+ }
52461
+ function parseObject(text) {
52462
+ const parsed = parseJson(text);
52463
+ return parsed !== undefined && isRecord2(parsed.value) ? parsed.value : null;
52464
+ }
52465
+ function parseJson(text) {
52466
+ try {
52467
+ return { value: JSON.parse(text) };
52468
+ } catch (error) {
52469
+ if (error instanceof SyntaxError) {
52470
+ return;
52471
+ }
52472
+ throw error;
52473
+ }
52474
+ }
52475
+ function isRecord2(value) {
52476
+ return typeof value === "object" && value !== null && !Array.isArray(value);
52477
+ }
52478
+ var HOOK_EVENTS, DEFAULT_BASE_SETTINGS_PATH = "/home/agent/.claude/kodizm-live-base.json", HOOK_TIMEOUT_S = 10, PERMISSION_HOOK_TIMEOUT_S = 3600, HELPER_TIMEOUT_MS = 3000, PERMISSION_HELPER_TIMEOUT_MS, SocketRequestError;
52479
+ var init_hooks = __esm(() => {
52480
+ HOOK_EVENTS = [
52481
+ "SessionStart",
52482
+ "UserPromptSubmit",
52483
+ "PreToolUse",
52484
+ "PermissionRequest",
52485
+ "PermissionDenied",
52486
+ "PostToolUse",
52487
+ "PostToolUseFailure",
52488
+ "Stop",
52489
+ "StopFailure",
52490
+ "SessionEnd",
52491
+ "SubagentStart",
52492
+ "SubagentStop",
52493
+ "PreCompact",
52494
+ "PostCompact"
52495
+ ];
52496
+ PERMISSION_HELPER_TIMEOUT_MS = PERMISSION_HOOK_TIMEOUT_S * 1000 - 5000;
52497
+ SocketRequestError = class SocketRequestError extends Error {
52498
+ reason;
52499
+ constructor(reason, detail) {
52500
+ super(detail === undefined ? `daemon socket ${reason}` : `daemon socket ${reason}: ${detail}`);
52501
+ this.name = "SocketRequestError";
52502
+ this.reason = reason;
52503
+ }
52504
+ };
52505
+ });
52506
+
52507
+ // src/live/input.ts
52508
+ function sanitizePrompt(text) {
52509
+ let sanitized = "";
52510
+ for (const char of text.replaceAll("\t", TAB_REPLACEMENT)) {
52511
+ if (!isStrippedControl(char.charCodeAt(0))) {
52512
+ sanitized += char;
52513
+ }
52514
+ }
52515
+ const first = sanitized.trimStart().charAt(0);
52516
+ if (first === "") {
52517
+ throw new InputRejectedError("empty");
52518
+ }
52519
+ if (first === "!") {
52520
+ throw new InputRejectedError("bash_mode");
52521
+ }
52522
+ if (first === "/") {
52523
+ throw new InputRejectedError("slash_command");
52524
+ }
52525
+ return sanitized;
52526
+ }
52527
+ function isStrippedControl(code) {
52528
+ return code <= 31 && code !== 10 || code >= 127 && code <= 159;
52529
+ }
52530
+ function sanitizeCommand(command) {
52531
+ const trimmed = command.trim();
52532
+ if (BARE_COMMANDS.has(trimmed)) {
52533
+ return trimmed;
52534
+ }
52535
+ if (trimmed !== "/model" && !trimmed.startsWith("/model ")) {
52536
+ throw new InputRejectedError("unknown_command");
52537
+ }
52538
+ const alias = trimmed.slice("/model ".length);
52539
+ if (!MODEL_NAME.test(alias)) {
52540
+ throw new InputRejectedError("invalid_model_alias");
52541
+ }
52542
+ return `/model ${alias}`;
52543
+ }
52544
+ var InputRejectedError, TAB_REPLACEMENT = " ", MODEL_NAME, BARE_COMMANDS;
52545
+ var init_input = __esm(() => {
52546
+ InputRejectedError = class InputRejectedError extends Error {
52547
+ reason;
52548
+ constructor(reason) {
52549
+ super(`input rejected: ${reason}`);
52550
+ this.name = "InputRejectedError";
52551
+ this.reason = reason;
52552
+ }
52553
+ };
52554
+ MODEL_NAME = /^[A-Za-z0-9._:[\]-]{1,64}$/;
52555
+ BARE_COMMANDS = new Set(["/compact", "/clear"]);
52556
+ });
52557
+
52558
+ // src/live/pane.ts
52559
+ import { mkdir as mkdir3, readFile as readFile3, realpath as realpath3, rename as rename4, writeFile as writeFile2 } from "fs/promises";
52560
+ import { join as join6 } from "path";
52561
+ function supportsPasteBufferS(versionOutput) {
52562
+ const match = /^tmux (?:next-)?(\d+)\.(\d+)[a-z]?$/.exec(versionOutput.trim());
52563
+ if (match === null) {
52564
+ return false;
52565
+ }
52566
+ const major = Number(match[1]);
52567
+ const minor = Number(match[2]);
52568
+ return major > PASTE_S_SINCE[0] || major === PASTE_S_SINCE[0] && minor >= PASTE_S_SINCE[1];
52569
+ }
52570
+ async function createPane(options) {
52571
+ const pane = {
52572
+ id: options.id,
52573
+ socket: options.socket ?? LIVE_SOCKET
52574
+ };
52575
+ if (!PANE_ID.test(pane.id)) {
52576
+ throw new PaneError("invalid_pane_id");
52577
+ }
52578
+ if (options.resumeSessionId !== undefined && !RESUME_ID.test(options.resumeSessionId)) {
52579
+ throw new PaneError("invalid_resume_id");
52580
+ }
52581
+ if (options.model !== undefined && (!MODEL_NAME.test(options.model) || options.model.startsWith("-"))) {
52582
+ throw new PaneError("invalid_model");
52583
+ }
52584
+ const env = paneEnv(options);
52585
+ const cwd = await realpath3(options.cwd);
52586
+ await seedTrust(env.CLAUDE_CONFIG_DIR === undefined ? [env.HOME] : [env.HOME, env.CLAUDE_CONFIG_DIR], cwd);
52587
+ const { PATH: path, TERM: term, ...sessionEnv } = env;
52588
+ const created = await run(pane.socket, [
52589
+ "start-server",
52590
+ ";",
52591
+ "set-option",
52592
+ "-g",
52593
+ "default-terminal",
52594
+ term,
52595
+ ";",
52596
+ "new-session",
52597
+ "-d",
52598
+ "-s",
52599
+ pane.id,
52600
+ "-x",
52601
+ "200",
52602
+ "-y",
52603
+ "50",
52604
+ "-c",
52605
+ cwd,
52606
+ ...Object.entries(sessionEnv).flatMap(([name, value]) => ["-e", `${name}=${value}`]),
52607
+ "--",
52608
+ ...launchArgv(options)
52609
+ ], { path });
52610
+ if (created.exitCode !== 0) {
52611
+ throw new PaneError("tmux_failed", created.stderr);
52612
+ }
52613
+ if (options.confirmStarted !== undefined && !await options.confirmStarted(SESSION_START_TIMEOUT_MS)) {
52614
+ await killPane(pane);
52615
+ throw new PaneError("start_timeout");
52616
+ }
52617
+ return pane;
52618
+ }
52619
+ async function deliverPrompt(pane, text, confirmSubmitted, abortIf) {
52620
+ const payload = sanitizePrompt(text);
52621
+ bufferSequence += 1;
52622
+ const buffer = `${pane.id}-${bufferSequence}`;
52623
+ await runOnPane(pane, ["load-buffer", "-b", buffer, "-"], new TextEncoder().encode(payload));
52624
+ const flags = await pasteKeepsControlBytes() ? ["-p", "-d", "-r", "-S"] : ["-p", "-d", "-r"];
52625
+ const pasted = await run(pane.socket, ["paste-buffer", ...flags, "-b", buffer, "-t", paneTarget(pane)]);
52626
+ if (pasted.exitCode !== 0) {
52627
+ await run(pane.socket, ["delete-buffer", "-b", buffer]);
52628
+ await failOnPane(pane, pasted.stderr);
52629
+ }
52630
+ await Bun.sleep(PASTE_ENTER_DELAY_MS);
52631
+ for (const windowMs of SUBMIT_WINDOWS_MS) {
52632
+ if (abortIf?.() === true) {
52633
+ return await confirmSubmitted(0) ? "submitted" : "aborted";
52634
+ }
52635
+ await runOnPane(pane, ["send-keys", "-t", paneTarget(pane), "Enter"]);
52636
+ if (await confirmSubmitted(windowMs)) {
52637
+ return "submitted";
52638
+ }
52639
+ }
52640
+ throw new PaneError("not_submitted");
52641
+ }
52642
+ async function deliverCommand(pane, command) {
52643
+ const typed = sanitizeCommand(command);
52644
+ await runOnPane(pane, ["send-keys", "-t", paneTarget(pane), "-l", "--", typed]);
52645
+ await Bun.sleep(PASTE_ENTER_DELAY_MS);
52646
+ await runOnPane(pane, ["send-keys", "-t", paneTarget(pane), "Enter"]);
52647
+ }
52648
+ async function cancelPane(pane) {
52649
+ await runOnPane(pane, ["send-keys", "-t", paneTarget(pane), "Escape"]);
52650
+ await Bun.sleep(CANCEL_CLEAR_DELAY_MS);
52651
+ await runOnPane(pane, ["send-keys", "-t", paneTarget(pane), "C-u"]);
52652
+ }
52653
+ async function paneAlive(pane) {
52654
+ return (await run(pane.socket, ["has-session", "-t", sessionTarget(pane)])).exitCode === 0;
52655
+ }
52656
+ async function killPane(pane) {
52657
+ const result = await run(pane.socket, ["kill-session", "-t", sessionTarget(pane)]);
52658
+ if (result.exitCode !== 0 && await paneAlive(pane)) {
52659
+ throw new PaneError("tmux_failed", result.stderr);
52660
+ }
52661
+ }
52662
+ function paneEnv(options) {
52663
+ const env = {
52664
+ PATH: process.env.PATH ?? "/usr/bin:/bin"
52665
+ };
52666
+ for (const [name, value] of Object.entries(options.env ?? {})) {
52667
+ if (!DROPPED_ENV.has(name)) {
52668
+ env[name] = value;
52669
+ }
52670
+ }
52671
+ return {
52672
+ ...env,
52673
+ HOME: options.home,
52674
+ CI: "1",
52675
+ TERM: "xterm-256color"
52676
+ };
52677
+ }
52678
+ function launchArgv(options) {
52679
+ const argv = [options.claudeBin ?? "claude", "--settings", options.settingsPath, "--setting-sources", "user"];
52680
+ if (options.model !== undefined) {
52681
+ argv.push("--model", options.model);
52682
+ }
52683
+ if (options.resumeSessionId !== undefined) {
52684
+ argv.push("--resume", options.resumeSessionId);
52685
+ }
52686
+ return argv;
52687
+ }
52688
+ async function seedTrust(dirs, cwd) {
52689
+ for (const dir of dirs) {
52690
+ const path = join6(dir, ".claude.json");
52691
+ const config = await readConfig(path);
52692
+ const projects = isRecord3(config.projects) ? config.projects : {};
52693
+ const project = isRecord3(projects[cwd]) ? projects[cwd] : {};
52694
+ projects[cwd] = {
52695
+ allowedTools: [],
52696
+ ...project,
52697
+ hasTrustDialogAccepted: true,
52698
+ hasCompletedProjectOnboarding: true
52699
+ };
52700
+ const seeded = {
52701
+ theme: "dark",
52702
+ ...config,
52703
+ hasCompletedOnboarding: true,
52704
+ projects
52705
+ };
52706
+ await mkdir3(dir, { recursive: true });
52707
+ const staging = `${path}.${process.pid}.tmp`;
52708
+ await writeFile2(staging, JSON.stringify(seeded, null, 2), { mode: 384 });
52709
+ await rename4(staging, path);
52710
+ }
52711
+ }
52712
+ async function readConfig(path) {
52713
+ const file = Bun.file(path);
52714
+ if (!await file.exists()) {
52715
+ return {};
52716
+ }
52717
+ const config = JSON.parse(await readFile3(path, "utf8"));
52718
+ if (!isRecord3(config)) {
52719
+ throw new Error(`${path} does not hold a JSON object`);
52720
+ }
52721
+ return config;
52722
+ }
52723
+ function isRecord3(value) {
52724
+ return typeof value === "object" && value !== null && !Array.isArray(value);
52725
+ }
52726
+ function sessionTarget(pane) {
52727
+ return `=${pane.id}`;
52728
+ }
52729
+ function paneTarget(pane) {
52730
+ return `=${pane.id}:`;
52731
+ }
52732
+ function tmuxBinary() {
52733
+ const binary = Bun.which("tmux");
52734
+ if (binary === null) {
52735
+ throw new PaneError("tmux_failed", "tmux is not on PATH");
52736
+ }
52737
+ return binary;
52738
+ }
52739
+ function pasteKeepsControlBytes() {
52740
+ const binary = tmuxBinary();
52741
+ let known = pasteSFlags.get(binary);
52742
+ if (known === undefined) {
52743
+ known = tmuxVersion(binary).then(supportsPasteBufferS);
52744
+ pasteSFlags.set(binary, known);
52745
+ }
52746
+ return known;
52747
+ }
52748
+ async function tmuxVersion(binary) {
52749
+ const child = Bun.spawn([binary, "-V"], {
52750
+ env: {
52751
+ PATH: process.env.PATH ?? "/usr/bin:/bin"
52752
+ },
52753
+ stdin: "ignore",
52754
+ stdout: "pipe",
52755
+ stderr: "ignore"
52756
+ });
52757
+ const [exitCode, output] = await Promise.all([child.exited, new Response(child.stdout).text()]);
52758
+ return exitCode === 0 ? output : "";
52759
+ }
52760
+ async function run(socket, args, options = {}) {
52761
+ const binary = tmuxBinary();
52762
+ const env = {
52763
+ PATH: options.path ?? process.env.PATH ?? "/usr/bin:/bin"
52764
+ };
52765
+ if (process.env.TMUX_TMPDIR !== undefined) {
52766
+ env.TMUX_TMPDIR = process.env.TMUX_TMPDIR;
52767
+ }
52768
+ const child = Bun.spawn([binary, "-L", socket, "-f", "/dev/null", ...args], {
52769
+ env,
52770
+ stdin: options.stdin ?? "ignore",
52771
+ stdout: "ignore",
52772
+ stderr: "pipe"
52773
+ });
52774
+ const [exitCode, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]);
52775
+ return {
52776
+ exitCode,
52777
+ stderr: stderr.trim()
52778
+ };
52779
+ }
52780
+ async function runOnPane(pane, args, stdin) {
52781
+ const result = await run(pane.socket, args, { stdin });
52782
+ if (result.exitCode !== 0) {
52783
+ await failOnPane(pane, result.stderr);
52784
+ }
52785
+ }
52786
+ async function failOnPane(pane, stderr) {
52787
+ if (!await paneAlive(pane)) {
52788
+ throw new PaneError("pane_dead");
52789
+ }
52790
+ throw new PaneError("tmux_failed", stderr);
52791
+ }
52792
+ var LIVE_SOCKET = "kodizm-live", PASTE_ENTER_DELAY_MS = 328, SUBMIT_WINDOWS_MS, SESSION_START_TIMEOUT_MS = 1e4, CANCEL_CLEAR_DELAY_MS = 414, PANE_ID, RESUME_ID, DROPPED_ENV, PASTE_S_SINCE, PaneError, bufferSequence = 0, pasteSFlags;
52793
+ var init_pane = __esm(() => {
52794
+ init_input();
52795
+ SUBMIT_WINDOWS_MS = [328, 656, 1312, 2624];
52796
+ PANE_ID = /^[A-Za-z0-9_-]{1,64}$/;
52797
+ RESUME_ID = /^[A-Za-z0-9][A-Za-z0-9-]{0,127}$/;
52798
+ DROPPED_ENV = new Set(["CLAUDE_CODE_SESSION_ID", "CLAUDE_CODE_CHILD_SESSION"]);
52799
+ PASTE_S_SINCE = [3, 7];
52800
+ PaneError = class PaneError extends Error {
52801
+ reason;
52802
+ constructor(reason, detail) {
52803
+ super(detail === undefined ? `pane ${reason}` : `pane ${reason}: ${detail}`);
52804
+ this.name = "PaneError";
52805
+ this.reason = reason;
52806
+ }
52807
+ };
52808
+ pasteSFlags = new Map;
52809
+ });
52810
+
52811
+ // src/live/transcript.ts
52812
+ import { stat as stat2 } from "fs/promises";
52813
+
52814
+ class TranscriptTail {
52815
+ path;
52816
+ skipped = 0;
52817
+ offset;
52818
+ pending = new Uint8Array(0);
52819
+ constructor(path, offset = 0) {
52820
+ this.path = path;
52821
+ this.offset = offset;
52822
+ }
52823
+ static async atEnd(path) {
52824
+ const file = Bun.file(path);
52825
+ return new TranscriptTail(path, await file.exists() ? file.size : 0);
52826
+ }
52827
+ async poll() {
52828
+ const size = await fileSize(this.path);
52829
+ if (size === null) {
52830
+ return [];
52831
+ }
52832
+ if (size < this.offset) {
52833
+ this.offset = 0;
52834
+ this.pending = new Uint8Array(0);
52835
+ }
52836
+ if (size === this.offset) {
52837
+ return [];
52838
+ }
52839
+ const chunk = new Uint8Array(await Bun.file(this.path).slice(this.offset, size).arrayBuffer());
52840
+ this.offset += chunk.byteLength;
52841
+ const bytes = concat(this.pending, chunk);
52842
+ const end = bytes.lastIndexOf(NEWLINE);
52843
+ if (end === -1) {
52844
+ this.pending = bytes;
52845
+ return [];
52846
+ }
52847
+ this.pending = bytes.slice(end + 1);
52848
+ return this.parse(new TextDecoder().decode(bytes.subarray(0, end)));
52849
+ }
52850
+ parse(text) {
52851
+ const records = [];
52852
+ for (const line of text.split(`
52853
+ `)) {
52854
+ if (line.trim() === "") {
52855
+ continue;
52856
+ }
52857
+ const record = parseLine(line);
52858
+ if (record === null) {
52859
+ this.skipped += 1;
52860
+ continue;
52861
+ }
52862
+ records.push(record);
52863
+ }
52864
+ return records;
52865
+ }
52866
+ }
52867
+ function classifyInterrupt(record) {
52868
+ if (record.type !== "user" || !isMainChain(record) || typeof record.timestamp !== "string") {
52869
+ return null;
52870
+ }
52871
+ const content = messageOf(record)?.content;
52872
+ if (!Array.isArray(content) || content.length !== 1) {
52873
+ return null;
52874
+ }
52875
+ const [block] = content;
52876
+ if (!isRecord4(block) || block.type !== "text") {
52877
+ return null;
52878
+ }
52879
+ if (block.text === INTERRUPT_MARKER) {
52880
+ return "turn";
52881
+ }
52882
+ if (block.text === INTERRUPT_TOOL_MARKER) {
52883
+ return "tool_use";
52884
+ }
52885
+ return null;
52886
+ }
52887
+
52888
+ class UsageLedger {
52889
+ counted = new Map;
52890
+ take(record) {
52891
+ const message = messageOf(record);
52892
+ if (record.type !== "assistant" || !isMainChain(record) || message === null) {
52893
+ return null;
52894
+ }
52895
+ if (typeof message.id !== "string" || !isRecord4(message.usage)) {
52896
+ return null;
52897
+ }
52898
+ const seen = this.counted.get(message.id) ?? emptyCounts();
52899
+ const now = {
52900
+ inputTokens: Math.max(seen.inputTokens, count(message.usage.input_tokens)),
52901
+ outputTokens: Math.max(seen.outputTokens, count(message.usage.output_tokens)),
52902
+ cacheReadTokens: Math.max(seen.cacheReadTokens, count(message.usage.cache_read_input_tokens)),
52903
+ cacheCreationTokens: Math.max(seen.cacheCreationTokens, count(message.usage.cache_creation_input_tokens))
52904
+ };
52905
+ this.counted.set(message.id, now);
52906
+ const delta = {
52907
+ inputTokens: now.inputTokens - seen.inputTokens,
52908
+ outputTokens: now.outputTokens - seen.outputTokens,
52909
+ cacheReadTokens: now.cacheReadTokens - seen.cacheReadTokens,
52910
+ cacheCreationTokens: now.cacheCreationTokens - seen.cacheCreationTokens
52911
+ };
52912
+ return Object.values(delta).some((value) => value > 0) ? delta : null;
52913
+ }
52914
+ }
52915
+ function assistantBlocks(record) {
52916
+ const content = messageOf(record)?.content;
52917
+ if (record.type !== "assistant" || !isMainChain(record) || !Array.isArray(content)) {
52918
+ return [];
52919
+ }
52920
+ const blocks = [];
52921
+ for (const block of content) {
52922
+ if (!isRecord4(block)) {
52923
+ continue;
52924
+ }
52925
+ if (block.type === "text" && typeof block.text === "string" && block.text !== "") {
52926
+ blocks.push({ kind: "text", text: block.text });
52927
+ }
52928
+ if (block.type === "thinking" && typeof block.thinking === "string" && block.thinking !== "") {
52929
+ blocks.push({ kind: "thinking", text: block.thinking });
52930
+ }
52931
+ }
52932
+ return blocks;
52933
+ }
52934
+ function assistantStopReason(record) {
52935
+ const reason = messageOf(record)?.stop_reason;
52936
+ return record.type === "assistant" && typeof reason === "string" ? reason : null;
52937
+ }
52938
+ function isMainChain(record) {
52939
+ return record.isSidechain !== true;
52940
+ }
52941
+ function messageOf(record) {
52942
+ return isRecord4(record.message) ? record.message : null;
52943
+ }
52944
+ function parseLine(line) {
52945
+ try {
52946
+ const value = JSON.parse(line);
52947
+ return isRecord4(value) ? value : null;
52948
+ } catch (error) {
52949
+ if (error instanceof SyntaxError) {
52950
+ return null;
52951
+ }
52952
+ throw error;
52953
+ }
52954
+ }
52955
+ async function fileSize(path) {
52956
+ try {
52957
+ return (await stat2(path)).size;
52958
+ } catch (error) {
52959
+ if (error.code === "ENOENT") {
52960
+ return null;
52961
+ }
52962
+ throw error;
52963
+ }
52964
+ }
52965
+ function concat(left, right) {
52966
+ if (left.byteLength === 0) {
52967
+ return right;
52968
+ }
52969
+ const joined = new Uint8Array(left.byteLength + right.byteLength);
52970
+ joined.set(left);
52971
+ joined.set(right, left.byteLength);
52972
+ return joined;
52973
+ }
52974
+ function count(value) {
52975
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
52976
+ }
52977
+ function emptyCounts() {
52978
+ return {
52979
+ inputTokens: 0,
52980
+ outputTokens: 0,
52981
+ cacheReadTokens: 0,
52982
+ cacheCreationTokens: 0
52983
+ };
52984
+ }
52985
+ function isRecord4(value) {
52986
+ return typeof value === "object" && value !== null && !Array.isArray(value);
52987
+ }
52988
+ var INTERRUPT_MARKER = "[Request interrupted by user]", INTERRUPT_TOOL_MARKER = "[Request interrupted by user for tool use]", NEWLINE = 10;
52989
+ var init_transcript = () => {};
52990
+
52991
+ // src/live/daemon.ts
52992
+ import { randomUUID as randomUUID5 } from "crypto";
52993
+ import { chmod, mkdir as mkdir4, readFile as readFile4, stat as stat3, unlink as unlink2 } from "fs/promises";
52994
+ import { createServer } from "net";
52995
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join7 } from "path";
52996
+ function exitCodeFor(code) {
52997
+ return EXIT_CODES[code];
52998
+ }
52999
+
53000
+ class Latch {
53001
+ fired = false;
53002
+ waiters = [];
53003
+ arm() {
53004
+ this.fired = false;
53005
+ }
53006
+ fire() {
53007
+ this.fired = true;
53008
+ for (const waiter of this.waiters.splice(0)) {
53009
+ waiter();
53010
+ }
53011
+ }
53012
+ wait(timeoutMs) {
53013
+ if (this.fired) {
53014
+ return Promise.resolve(true);
53015
+ }
53016
+ return new Promise((resolve) => {
53017
+ const waiter = () => {
53018
+ clearTimeout(timer);
53019
+ resolve(true);
53020
+ };
53021
+ const timer = setTimeout(() => {
53022
+ this.waiters = this.waiters.filter((candidate) => candidate !== waiter);
53023
+ resolve(false);
53024
+ }, timeoutMs);
53025
+ this.waiters.push(waiter);
53026
+ });
53027
+ }
53028
+ }
53029
+
53030
+ class EventQueue {
53031
+ options;
53032
+ seq = 0;
53033
+ pending = [];
53034
+ running = false;
53035
+ stopped = false;
53036
+ constructor(options) {
53037
+ this.options = options;
53038
+ }
53039
+ get lastSeq() {
53040
+ return this.seq;
53041
+ }
53042
+ get size() {
53043
+ return this.pending.length;
53044
+ }
53045
+ push(draft) {
53046
+ if (this.stopped) {
53047
+ return;
53048
+ }
53049
+ this.seq += 1;
53050
+ this.pending.push({
53051
+ type: draft.type,
53052
+ seq: this.seq,
53053
+ gen: this.options.gen,
53054
+ ts: new Date().toISOString(),
53055
+ payload: redactDeep(draft.payload, this.options.secrets())
53056
+ });
53057
+ this.kick();
53058
+ }
53059
+ async drain(timeoutMs) {
53060
+ const deadline = Date.now() + timeoutMs;
53061
+ while (this.pending.length > 0 && !this.stopped && Date.now() < deadline) {
53062
+ await Bun.sleep(20);
53063
+ }
53064
+ }
53065
+ stop() {
53066
+ this.stopped = true;
53067
+ }
53068
+ kick() {
53069
+ if (this.running || this.stopped) {
53070
+ return;
53071
+ }
53072
+ this.running = true;
53073
+ this.loop().finally(() => {
53074
+ this.running = false;
53075
+ });
53076
+ }
53077
+ async loop() {
53078
+ let failures = 0;
53079
+ while (!this.stopped && this.pending.length > 0) {
53080
+ const outcome = await this.send(this.pending.slice(0, MAX_BATCH));
53081
+ if (outcome === "stale") {
53082
+ this.stopped = true;
53083
+ this.pending = [];
53084
+ this.options.onStaleGen();
53085
+ return;
53086
+ }
53087
+ if (outcome === "acknowledged") {
53088
+ failures = 0;
53089
+ continue;
53090
+ }
53091
+ failures += 1;
53092
+ const timings = this.options.timings();
53093
+ await Bun.sleep(Math.min(timings.retryMaxMs, timings.retryBaseMs * 2 ** (failures - 1)));
53094
+ }
53095
+ }
53096
+ async send(batch) {
53097
+ const response = await fetchJson(this.options.endpoint, {
53098
+ method: "POST",
53099
+ bearer: this.options.bearer(),
53100
+ body: {
53101
+ events: batch
53102
+ },
53103
+ timeoutMs: this.options.timings().ingestTimeoutMs
53104
+ });
53105
+ if (response.status === 200 && isRecord(response.body) && typeof response.body.lastSeq === "number") {
53106
+ return this.acknowledge(response.body.lastSeq);
53107
+ }
53108
+ const error = isRecord(response.body) && isRecord(response.body.error) ? response.body.error : {};
53109
+ if (response.status === 409 && error.code === "stale_gen") {
53110
+ this.options.logger.warn("tmuxd ingest moved to a newer gen", {
53111
+ gen: this.options.gen,
53112
+ currentGen: error.currentGen
53113
+ });
53114
+ return "stale";
53115
+ }
53116
+ if (response.status === 409 && error.code === "seq_gap" && typeof error.expectedSeq === "number") {
53117
+ return this.acknowledge(error.expectedSeq - 1);
53118
+ }
53119
+ this.options.logger.warn("tmuxd event push failed", {
53120
+ gen: this.options.gen,
53121
+ status: response.status,
53122
+ code: error.code
53123
+ });
53124
+ return "retry";
53125
+ }
53126
+ acknowledge(lastSeq) {
53127
+ const before = this.pending.length;
53128
+ this.pending = this.pending.filter((event) => event.seq > lastSeq);
53129
+ const first = this.pending[0];
53130
+ if (first !== undefined && first.seq !== lastSeq + 1) {
53131
+ this.options.logger.error("tmuxd ingest expects an event that was already acknowledged", {
53132
+ gen: this.options.gen,
53133
+ expectedSeq: lastSeq + 1,
53134
+ firstPending: first.seq
53135
+ });
53136
+ return "retry";
53137
+ }
53138
+ return this.pending.length < before ? "acknowledged" : "retry";
53139
+ }
53140
+ }
53141
+
53142
+ class LivePane {
53143
+ pane;
53144
+ gen;
53145
+ ingestUrl;
53146
+ daemon;
53147
+ started = new Latch;
53148
+ submitted = new Latch;
53149
+ turnClosed = new Latch;
53150
+ queue;
53151
+ claudeSessionId;
53152
+ model;
53153
+ pid;
53154
+ turn;
53155
+ pendingPromptId;
53156
+ dialog;
53157
+ compacting = false;
53158
+ lastCloseCause;
53159
+ token;
53160
+ secrets;
53161
+ openTools = new Map;
53162
+ subagents = new Set;
53163
+ tail;
53164
+ ledger = new UsageLedger;
53165
+ reading;
53166
+ skippedLines = 0;
53167
+ lastAssistantText;
53168
+ lastStopReason = null;
53169
+ settled = false;
53170
+ lastActivityAt = Date.now();
53171
+ sessionEndReason;
53172
+ timers = [];
53173
+ monitoring = false;
53174
+ closed = false;
53175
+ constructor(pane, gen, ingestUrl, token, authValues, daemon) {
53176
+ this.pane = pane;
53177
+ this.gen = gen;
53178
+ this.ingestUrl = ingestUrl;
53179
+ this.daemon = daemon;
53180
+ this.token = token;
53181
+ this.secrets = [token, ...authValues.filter((value) => value.length >= MIN_SECRET_LENGTH)];
53182
+ this.queue = new EventQueue({
53183
+ gen,
53184
+ endpoint: `${ingestUrl}/events`,
53185
+ bearer: () => this.token,
53186
+ secrets: () => this.secrets,
53187
+ timings: () => daemon.timings,
53188
+ onStaleGen: () => void this.abandon(),
53189
+ logger: daemon.logger
53190
+ });
53191
+ }
53192
+ get id() {
53193
+ return this.pane.id;
53194
+ }
53195
+ get busy() {
53196
+ return this.turn !== undefined || this.compacting;
53197
+ }
53198
+ begin() {
53199
+ const timings = this.daemon.timings;
53200
+ this.timers = [
53201
+ setInterval(() => void this.guard("transcript", () => this.readTranscript()), timings.transcriptPollMs),
53202
+ setInterval(() => void this.guard("monitor", () => this.monitor()), timings.monitorMs),
53203
+ setInterval(() => void this.guard("heartbeat", () => this.beat()), timings.heartbeatMs)
53204
+ ];
53205
+ }
53206
+ rotate(token) {
53207
+ this.token = token;
53208
+ this.secrets.push(token);
53209
+ }
53210
+ async handleHook(event, payload, signal) {
53211
+ if (this.closed) {
53212
+ return { ok: false };
53213
+ }
53214
+ this.lastActivityAt = Date.now();
53215
+ switch (event) {
53216
+ case "SessionStart":
53217
+ await this.onSessionStart(payload);
53218
+ break;
53219
+ case "UserPromptSubmit":
53220
+ this.onPromptSubmit(payload);
53221
+ break;
53222
+ case "PreToolUse":
53223
+ this.onPreToolUse(payload);
53224
+ break;
53225
+ case "PermissionRequest":
53226
+ return this.onPermissionRequest(payload, signal);
53227
+ case "PostToolUse":
53228
+ this.closeTool(payload, "completed", toolOutput(payload.tool_response));
53229
+ break;
53230
+ case "PostToolUseFailure":
53231
+ this.closeTool(payload, "failed", text(payload.error));
53232
+ break;
53233
+ case "PermissionDenied":
53234
+ this.closeTool(payload, "failed");
53235
+ break;
53236
+ case "Stop":
53237
+ await this.onStop(payload);
53238
+ break;
53239
+ case "StopFailure":
53240
+ await this.onStopFailure(payload);
53241
+ break;
53242
+ case "SessionEnd":
53243
+ if (payload.reason !== "clear") {
53244
+ this.sessionEndReason = text(payload.reason) ?? "other";
53245
+ }
53246
+ break;
53247
+ case "SubagentStart":
53248
+ this.onSubagentStart(payload);
53249
+ break;
53250
+ case "SubagentStop":
53251
+ this.onSubagentStop(payload);
53252
+ break;
53253
+ case "PreCompact":
53254
+ this.compacting = true;
53255
+ this.queue.push(compactionStarted(payload));
53256
+ break;
53257
+ case "PostCompact":
53258
+ this.compacting = false;
53259
+ this.queue.push(compactionCompleted(payload));
53260
+ break;
53261
+ }
53262
+ return { ok: true };
53263
+ }
53264
+ closeTurn(draft, cause) {
53265
+ if (this.turn === undefined) {
53266
+ return;
53267
+ }
53268
+ this.closeOpenTools();
53269
+ this.clearDialog();
53270
+ this.queue.push(draft);
53271
+ this.turn = undefined;
53272
+ this.lastCloseCause = cause;
53273
+ this.turnClosed.fire();
53274
+ }
53275
+ clearDialog() {
53276
+ this.dialog?.abort.abort();
53277
+ this.dialog = undefined;
53278
+ }
53279
+ interruptedTurn() {
53280
+ return turnCompleted("interrupted", this.lastAssistantText);
53281
+ }
53282
+ async stop() {
53283
+ this.close();
53284
+ await killPane(this.pane);
53285
+ this.queue.push(sessionEnded("stopped"));
53286
+ await this.queue.drain(STOP_DRAIN_MS);
53287
+ this.queue.stop();
53288
+ }
53289
+ async dispose() {
53290
+ this.close();
53291
+ this.queue.stop();
53292
+ await this.killLogged();
53293
+ }
53294
+ close() {
53295
+ this.closed = true;
53296
+ for (const timer of this.timers.splice(0)) {
53297
+ clearInterval(timer);
53298
+ }
53299
+ this.clearDialog();
53300
+ }
53301
+ async onSessionStart(payload) {
53302
+ const source = text(payload.source);
53303
+ this.model = text(payload.model) ?? this.model;
53304
+ if (source === "compact") {
53305
+ return;
53306
+ }
53307
+ const path = text(payload.transcript_path);
53308
+ if (path !== undefined) {
53309
+ this.tail = source === "resume" ? await TranscriptTail.atEnd(path) : new TranscriptTail(path);
53310
+ }
53311
+ this.claudeSessionId = text(payload.session_id) ?? this.claudeSessionId;
53312
+ this.queue.push(sessionStarted({
53313
+ ...payload,
53314
+ model: this.model
53315
+ }, source === "resume" ? "resume" : "startup"));
53316
+ this.started.fire();
53317
+ }
53318
+ onPromptSubmit(payload) {
53319
+ if (this.turn !== undefined) {
53320
+ return;
53321
+ }
53322
+ this.turn = {
53323
+ promptId: this.pendingPromptId ?? text(payload.prompt_id) ?? randomUUID5()
53324
+ };
53325
+ this.pendingPromptId = undefined;
53326
+ this.settled = false;
53327
+ this.lastAssistantText = undefined;
53328
+ this.lastStopReason = null;
53329
+ this.queue.push(turnStarted(this.turn.promptId));
53330
+ this.submitted.fire();
53331
+ }
53332
+ onPreToolUse(payload) {
53333
+ if (this.turn === undefined) {
53334
+ return;
53335
+ }
53336
+ const toolCallId = text(payload.tool_use_id) ?? randomUUID5();
53337
+ this.openTools.set(toolCallId, {
53338
+ name: text(payload.tool_name),
53339
+ input: payload.tool_input
53340
+ });
53341
+ this.settled = false;
53342
+ this.queue.push(toolCallBegin(payload, toolCallId));
53343
+ }
53344
+ async onPermissionRequest(payload, signal) {
53345
+ const correlated = this.findOpenTool(payload);
53346
+ const dialog = {
53347
+ toolCallId: correlated ?? randomUUID5(),
53348
+ correlated: correlated !== undefined,
53349
+ toolName: text(payload.tool_name),
53350
+ input: payload.tool_input,
53351
+ abort: new AbortController
53352
+ };
53353
+ this.dialog?.abort.abort();
53354
+ this.dialog = dialog;
53355
+ signal.addEventListener("abort", () => dialog.abort.abort());
53356
+ const request = permissionRequest(payload, dialog.toolCallId);
53357
+ this.queue.push({
53358
+ type: "permission_request",
53359
+ payload: {
53360
+ ...request
53361
+ }
53362
+ });
53363
+ const decision = await this.askIngest(request, dialog.abort.signal);
53364
+ if (decision === undefined || this.dialog !== dialog) {
53365
+ return { ok: true };
53366
+ }
53367
+ this.dialog = undefined;
53368
+ return {
53369
+ ok: true,
53370
+ decision
53371
+ };
53372
+ }
53373
+ async askIngest(request, signal) {
53374
+ const timings = this.daemon.timings;
53375
+ const opened = await fetchJson(`${this.ingestUrl}/permissions`, {
53376
+ method: "POST",
53377
+ bearer: this.token,
53378
+ body: redactDeep({
53379
+ gen: this.gen,
53380
+ ...request
53381
+ }, this.secrets),
53382
+ timeoutMs: timings.ingestTimeoutMs,
53383
+ signal
53384
+ });
53385
+ if (opened.status !== 200 && opened.status !== 201) {
53386
+ this.daemon.logger.warn("tmuxd permission open failed", { pane: this.id, status: opened.status });
53387
+ return;
53388
+ }
53389
+ const immediate = decisionFrom(opened.body);
53390
+ if (immediate !== "pending") {
53391
+ return immediate;
53392
+ }
53393
+ const id = isRecord(opened.body) ? text(opened.body.id) : undefined;
53394
+ if (id === undefined) {
53395
+ return;
53396
+ }
53397
+ const deadline = Date.now() + PERMISSION_WAIT_MS;
53398
+ while (!signal.aborted && Date.now() < deadline) {
53399
+ await Bun.sleep(timings.permissionPollMs);
53400
+ if (signal.aborted) {
53401
+ return;
53402
+ }
53403
+ const polled = await fetchJson(`${this.ingestUrl}/permissions/${encodeURIComponent(id)}`, {
53404
+ method: "GET",
53405
+ bearer: this.token,
53406
+ timeoutMs: timings.ingestTimeoutMs,
53407
+ signal
53408
+ });
53409
+ if (polled.status === 404) {
53410
+ return;
53411
+ }
53412
+ if (polled.status !== 200) {
53413
+ continue;
53414
+ }
53415
+ const decision = decisionFrom(polled.body);
53416
+ if (decision !== "pending") {
53417
+ return decision;
53418
+ }
53419
+ }
53420
+ return;
53421
+ }
53422
+ closeTool(payload, status, output) {
53423
+ const toolCallId = text(payload.tool_use_id) ?? this.findOpenTool(payload);
53424
+ if (this.dialog !== undefined && this.isDialogOf(this.dialog, payload, toolCallId)) {
53425
+ this.clearDialog();
53426
+ }
53427
+ if (toolCallId === undefined || !this.openTools.delete(toolCallId)) {
53428
+ return;
53429
+ }
53430
+ this.queue.push(toolCallEnd(toolCallId, status, output));
53431
+ }
53432
+ isDialogOf(dialog, payload, toolCallId) {
53433
+ if (dialog.correlated) {
53434
+ return toolCallId === dialog.toolCallId;
53435
+ }
53436
+ return dialog.toolName === text(payload.tool_name) && sameJson(dialog.input, payload.tool_input);
53437
+ }
53438
+ findOpenTool(payload) {
53439
+ const name = text(payload.tool_name);
53440
+ const matches = [...this.openTools.entries()].filter(([, tool]) => tool.name === name && sameJson(tool.input, payload.tool_input));
53441
+ return matches.at(-1)?.[0];
53442
+ }
53443
+ closeOpenTools() {
53444
+ for (const toolCallId of this.openTools.keys()) {
53445
+ this.queue.push(toolCallEnd(toolCallId, "failed"));
53446
+ }
53447
+ this.openTools.clear();
53448
+ }
53449
+ async onStop(payload) {
53450
+ if (this.turn === undefined) {
53451
+ return;
53452
+ }
53453
+ await this.syncTranscript();
53454
+ this.closeTurn(turnCompleted(apiStopReason(this.lastStopReason), text(payload.last_assistant_message) ?? this.lastAssistantText), "stop");
53455
+ }
53456
+ async onStopFailure(payload) {
53457
+ if (this.turn === undefined) {
53458
+ return;
53459
+ }
53460
+ await this.syncTranscript();
53461
+ this.closeTurn(turnFailed(payload), "stop");
53462
+ }
53463
+ onSubagentStart(payload) {
53464
+ const id = text(payload.agent_id);
53465
+ if (id !== undefined) {
53466
+ this.subagents.add(id);
53467
+ }
53468
+ this.queue.push(subagentSpawn(payload));
53469
+ }
53470
+ onSubagentStop(payload) {
53471
+ const id = text(payload.agent_id);
53472
+ if (id === undefined || !this.subagents.delete(id)) {
53473
+ return;
53474
+ }
53475
+ this.queue.push(subagentComplete(payload));
53476
+ }
53477
+ async syncTranscript() {
53478
+ await this.reading;
53479
+ await this.readTranscript();
53480
+ }
53481
+ readTranscript() {
53482
+ if (this.reading === undefined) {
53483
+ this.reading = this.consumeTranscript().finally(() => {
53484
+ this.reading = undefined;
53485
+ });
53486
+ }
53487
+ return this.reading;
53488
+ }
53489
+ async consumeTranscript() {
53490
+ const tail = this.tail;
53491
+ if (tail === undefined || this.closed) {
53492
+ return;
53493
+ }
53494
+ const records = await tail.poll();
53495
+ if (tail.skipped > this.skippedLines) {
53496
+ this.daemon.logger.warn("tmuxd skipped transcript lines that are not JSON", {
53497
+ pane: this.id,
53498
+ skipped: tail.skipped - this.skippedLines
53499
+ });
53500
+ this.skippedLines = tail.skipped;
53501
+ }
53502
+ for (const record of records) {
53503
+ if (record.isSidechain === true) {
53504
+ continue;
53505
+ }
53506
+ this.lastActivityAt = Date.now();
53507
+ if (classifyInterrupt(record) !== null) {
53508
+ this.closeTurn(this.interruptedTurn(), "marker");
53509
+ this.clearDialog();
53510
+ continue;
53511
+ }
53512
+ for (const block of assistantBlocks(record)) {
53513
+ this.queue.push(block.kind === "text" ? outputChunk(block.text) : thinkingChunk(block.text));
53514
+ if (block.kind === "text") {
53515
+ this.lastAssistantText = block.text;
53516
+ }
53517
+ }
53518
+ const counts = this.ledger.take(record);
53519
+ if (counts !== null) {
53520
+ this.queue.push(usage(counts));
53521
+ }
53522
+ if (record.type === "assistant") {
53523
+ this.lastStopReason = assistantStopReason(record);
53524
+ this.settled = this.lastStopReason !== null && this.lastStopReason !== "tool_use";
53525
+ } else if (record.type === "user") {
53526
+ this.settled = false;
53527
+ }
53528
+ }
53529
+ }
53530
+ async monitor() {
53531
+ if (this.monitoring || this.closed) {
53532
+ return;
53533
+ }
53534
+ this.monitoring = true;
53535
+ try {
53536
+ if (!await this.alive()) {
53537
+ await this.onDeath();
53538
+ return;
53539
+ }
53540
+ const quiet = Date.now() - this.lastActivityAt >= this.daemon.timings.watchdogQuietMs;
53541
+ const idle = this.dialog === undefined && this.openTools.size === 0 && !this.compacting && this.settled;
53542
+ if (this.turn !== undefined && quiet && idle) {
53543
+ this.closeTurn(turnCompleted("watchdog", this.lastAssistantText), "watchdog");
53544
+ }
53545
+ } finally {
53546
+ this.monitoring = false;
53547
+ }
53548
+ }
53549
+ async beat() {
53550
+ if (!this.closed && await this.alive()) {
53551
+ this.queue.push(heartbeat());
53552
+ }
53553
+ }
53554
+ async alive() {
53555
+ return await paneAlive(this.pane) && (this.pid === undefined || processAlive(this.pid));
53556
+ }
53557
+ async onDeath() {
53558
+ if (this.closed) {
53559
+ return;
53560
+ }
53561
+ const turnRunning = this.turn !== undefined;
53562
+ this.close();
53563
+ if (this.sessionEndReason !== undefined) {
53564
+ this.queue.push(sessionEnded("stopped"));
53565
+ } else {
53566
+ this.queue.push(sessionEnded("crashed"));
53567
+ this.queue.push(processDied());
53568
+ if (turnRunning) {
53569
+ this.queue.push(sessionFailed("process_died", "claude exited while a turn was running."));
53570
+ }
53571
+ }
53572
+ await this.queue.drain(STOP_DRAIN_MS);
53573
+ this.queue.stop();
53574
+ this.daemon.remove(this);
53575
+ }
53576
+ async abandon() {
53577
+ if (this.closed) {
53578
+ return;
53579
+ }
53580
+ this.close();
53581
+ await this.killLogged();
53582
+ this.daemon.remove(this);
53583
+ }
53584
+ async killLogged() {
53585
+ try {
53586
+ await killPane(this.pane);
53587
+ } catch (error) {
53588
+ if (!(error instanceof PaneError)) {
53589
+ throw error;
53590
+ }
53591
+ this.daemon.logger.error("tmuxd could not kill a pane", { pane: this.id, reason: error.reason });
53592
+ }
53593
+ }
53594
+ async guard(task, run) {
53595
+ try {
53596
+ await run();
53597
+ } catch (error) {
53598
+ this.daemon.logger.error("tmuxd pane task failed", {
53599
+ pane: this.id,
53600
+ task,
53601
+ error: error instanceof Error ? error.message : String(error)
53602
+ });
53603
+ }
53604
+ }
53605
+ }
53606
+
53607
+ class LiveDaemon {
53608
+ options;
53609
+ logger;
53610
+ timings;
53611
+ panes = new Map;
53612
+ server;
53613
+ constructor(options) {
53614
+ this.options = options;
53615
+ this.logger = options.logger ?? createLogger({ env: process.env });
53616
+ this.timings = {
53617
+ ...DEFAULT_TIMINGS,
53618
+ ...options.timings
53619
+ };
53620
+ }
53621
+ setTimings(timings) {
53622
+ this.timings = {
53623
+ ...this.timings,
53624
+ ...timings
53625
+ };
53626
+ }
53627
+ async listen() {
53628
+ const { socketPath } = this.options;
53629
+ await mkdir4(dirname4(socketPath), { recursive: true, mode: 448 });
53630
+ if (await Bun.file(socketPath).exists()) {
53631
+ if (await this.answering(socketPath)) {
53632
+ throw new Error(`a tmuxd daemon already listens on ${socketPath}`);
53633
+ }
53634
+ await unlink2(socketPath);
53635
+ }
53636
+ const server = createServer((socket) => this.accept(socket));
53637
+ await new Promise((resolve, reject) => {
53638
+ server.once("error", reject);
53639
+ server.listen(socketPath, () => {
53640
+ server.off("error", reject);
53641
+ resolve();
53642
+ });
53643
+ });
53644
+ await chmod(socketPath, 384);
53645
+ this.server = server;
53646
+ }
53647
+ async close() {
53648
+ for (const pane of [...this.panes.values()]) {
53649
+ this.panes.delete(pane.id);
53650
+ await pane.dispose();
53651
+ }
53652
+ const server = this.server;
53653
+ this.server = undefined;
53654
+ if (server !== undefined) {
53655
+ await new Promise((resolve) => server.close(() => resolve()));
53656
+ await unlink2(this.options.socketPath).catch((error) => {
53657
+ if (error.code !== "ENOENT") {
53658
+ throw error;
53659
+ }
53660
+ });
53661
+ }
53662
+ }
53663
+ remove(pane) {
53664
+ if (this.panes.get(pane.id) !== pane) {
53665
+ return;
53666
+ }
53667
+ this.panes.delete(pane.id);
53668
+ if (this.panes.size === 0 && this.options.onIdle !== undefined) {
53669
+ setTimeout(this.options.onIdle, 0);
53670
+ }
53671
+ }
53672
+ async hook(paneId, event, payload, signal) {
53673
+ const pane = this.panes.get(paneId);
53674
+ return pane === undefined ? { ok: false } : pane.handleHook(event, payload, signal);
53675
+ }
53676
+ async ctl(verb, args) {
53677
+ switch (verb) {
53678
+ case "start":
53679
+ return this.start(args);
53680
+ case "prompt":
53681
+ return this.prompt(args);
53682
+ case "command":
53683
+ return this.command(args);
53684
+ case "cancel":
53685
+ return this.cancel(args);
53686
+ case "stop":
53687
+ return this.stop(args);
53688
+ case "rotate-token":
53689
+ return this.rotateToken(args);
53690
+ case "status":
53691
+ return this.status(args);
53692
+ default:
53693
+ return refuse("usage_error");
53694
+ }
53695
+ }
53696
+ async start(args) {
53697
+ const envFile = args["env-file"];
53698
+ if (envFile === undefined || !isAbsolute4(envFile)) {
53699
+ return refuse("usage_error");
53700
+ }
53701
+ try {
53702
+ return await this.launch(args, envFile);
53703
+ } finally {
53704
+ await removeFile(envFile);
53705
+ }
53706
+ }
53707
+ async launch(args, envFile) {
53708
+ const { pane: id, cwd, model, resume } = args;
53709
+ const settingsBase = args["settings-base"];
53710
+ if (id === undefined || !PANE_ID2.test(id) || cwd === undefined || !isAbsolute4(cwd)) {
53711
+ return refuse("usage_error");
53712
+ }
53713
+ if (settingsBase === undefined || !isAbsolute4(settingsBase)) {
53714
+ return refuse("usage_error");
53715
+ }
53716
+ if (model !== undefined && !MODEL_NAME.test(model)) {
53717
+ return refuse("usage_error");
53718
+ }
53719
+ if (this.panes.has(id)) {
53720
+ return refuse("pane_exists");
53721
+ }
53722
+ if (this.panes.size >= (this.options.maxPanes ?? DEFAULT_MAX_PANES)) {
53723
+ return refuse("pane_cap");
53724
+ }
53725
+ const content = await readSecretFile(envFile);
53726
+ const env = content === null ? null : parseStartEnv(content);
53727
+ if (env === null) {
53728
+ return refuse("usage_error");
53729
+ }
53730
+ const settingsPath = join7(settingsBase, `${id}.settings.json`);
53731
+ await mkdir4(settingsBase, { recursive: true, mode: 448 });
53732
+ await writePaneSettings(settingsPath, {
53733
+ basePath: this.options.baseSettingsPath ?? DEFAULT_BASE_SETTINGS_PATH,
53734
+ helperArgv: this.options.helperArgv,
53735
+ socketPath: this.options.socketPath,
53736
+ paneId: id
53737
+ });
53738
+ const pane = {
53739
+ id,
53740
+ socket: this.options.tmuxSocket ?? LIVE_SOCKET
53741
+ };
53742
+ const live = new LivePane(pane, env.gen, env.ingestUrl, env.token, Object.values(env.authEnv), this);
53743
+ this.panes.set(id, live);
53744
+ try {
53745
+ await createPane({
53746
+ id,
53747
+ cwd,
53748
+ home: this.options.home,
53749
+ settingsPath,
53750
+ env: env.authEnv,
53751
+ model,
53752
+ resumeSessionId: resume,
53753
+ claudeBin: this.options.claudeBin,
53754
+ socket: pane.socket,
53755
+ confirmStarted: (timeoutMs) => live.started.wait(timeoutMs)
53756
+ });
53757
+ } catch (error) {
53758
+ this.panes.delete(id);
53759
+ live.queue.stop();
53760
+ return refuse(launchFailure(error));
53761
+ }
53762
+ live.pid = await panePid(pane);
53763
+ live.begin();
53764
+ return accept({
53765
+ pane: id,
53766
+ gen: env.gen,
53767
+ pid: live.pid,
53768
+ claudeSessionId: live.claudeSessionId,
53769
+ resumed: resume !== undefined
53770
+ });
53771
+ }
53772
+ async prompt(args) {
53773
+ const { pane: id, file } = args;
53774
+ if (file === undefined || !isAbsolute4(file)) {
53775
+ return refuse("usage_error");
53776
+ }
53777
+ const content = await takeFile(file, readFileOrNull);
53778
+ if (id === undefined || content === null) {
53779
+ return refuse("usage_error");
53780
+ }
53781
+ const live = this.panes.get(id);
53782
+ if (live === undefined) {
53783
+ return notRunning(id);
53784
+ }
53785
+ const refused = this.deliveryRefusal(live);
53786
+ if (refused !== null) {
53787
+ return refused;
53788
+ }
53789
+ let sanitized;
53790
+ try {
53791
+ sanitized = sanitizePrompt(content);
53792
+ } catch (error) {
53793
+ if (error instanceof InputRejectedError) {
53794
+ return refuse("prompt_rejected", { rule: REJECTION_RULES[error.reason] });
53795
+ }
53796
+ throw error;
53797
+ }
53798
+ const promptId = randomUUID5();
53799
+ live.pendingPromptId = promptId;
53800
+ live.submitted.arm();
53801
+ let outcome;
53802
+ try {
53803
+ outcome = await deliverPrompt(live.pane, content, (timeoutMs) => live.submitted.wait(timeoutMs), () => live.dialog !== undefined);
53804
+ } catch (error) {
53805
+ live.pendingPromptId = undefined;
53806
+ return deliveryFailure(error, id);
53807
+ }
53808
+ if (outcome === "aborted") {
53809
+ live.pendingPromptId = undefined;
53810
+ return refuse("dialog_open");
53811
+ }
53812
+ return accept({
53813
+ promptId,
53814
+ bytes: Buffer.byteLength(sanitized)
53815
+ });
53816
+ }
53817
+ async command(args) {
53818
+ const { pane: id, name, arg } = args;
53819
+ if (id === undefined || name === undefined || !COMMAND_NAMES.has(name) || name === "model" !== (arg !== undefined)) {
53820
+ return refuse("usage_error");
53821
+ }
53822
+ let typed;
53823
+ try {
53824
+ typed = sanitizeCommand(arg === undefined ? `/${name}` : `/${name} ${arg}`);
53825
+ } catch (error) {
53826
+ if (error instanceof InputRejectedError) {
53827
+ return refuse("usage_error");
53828
+ }
53829
+ throw error;
53830
+ }
53831
+ const live = this.panes.get(id);
53832
+ if (live === undefined) {
53833
+ return notRunning(id);
53834
+ }
53835
+ const refused = this.deliveryRefusal(live);
53836
+ if (refused !== null) {
53837
+ return refused;
53838
+ }
53839
+ try {
53840
+ await deliverCommand(live.pane, typed);
53841
+ } catch (error) {
53842
+ return deliveryFailure(error, id);
53843
+ }
53844
+ return accept(arg === undefined ? { name } : { name, arg });
53845
+ }
53846
+ async cancel(args) {
53847
+ const id = args.pane;
53848
+ if (id === undefined) {
53849
+ return refuse("usage_error");
53850
+ }
53851
+ const live = this.panes.get(id);
53852
+ if (live === undefined) {
53853
+ return notRunning(id);
53854
+ }
53855
+ if (live.turn === undefined) {
53856
+ return accept({ wasBusy: false });
53857
+ }
53858
+ live.turnClosed.arm();
53859
+ live.clearDialog();
53860
+ try {
53861
+ await cancelPane(live.pane);
53862
+ } catch (error) {
53863
+ return deliveryFailure(error, id);
53864
+ }
53865
+ if (await live.turnClosed.wait(this.timings.cancelTimeoutMs)) {
53866
+ return accept({
53867
+ wasBusy: true,
53868
+ detectedBy: live.lastCloseCause === "marker" ? "transcript_marker" : "timeout"
53869
+ });
53870
+ }
53871
+ live.closeTurn(live.interruptedTurn(), "timeout");
53872
+ return accept({
53873
+ wasBusy: true,
53874
+ detectedBy: "timeout"
53875
+ });
53876
+ }
53877
+ async stop(args) {
53878
+ const id = args.pane;
53879
+ if (id === undefined) {
53880
+ return refuse("usage_error");
53881
+ }
53882
+ const live = this.panes.get(id);
53883
+ if (live === undefined) {
53884
+ return notRunning(id);
53885
+ }
53886
+ try {
53887
+ await live.stop();
53888
+ } catch (error) {
53889
+ if (error instanceof PaneError) {
53890
+ return refuse("timeout");
53891
+ }
53892
+ throw error;
53893
+ } finally {
53894
+ this.remove(live);
53895
+ }
53896
+ return accept({ claudeSessionId: live.claudeSessionId });
53897
+ }
53898
+ async rotateToken(args) {
53899
+ const id = args.pane;
53900
+ const file = args["token-file"];
53901
+ if (file === undefined || !isAbsolute4(file)) {
53902
+ return refuse("usage_error");
53903
+ }
53904
+ const content = await takeFile(file, readSecretFile);
53905
+ if (id === undefined) {
53906
+ return refuse("usage_error");
53907
+ }
53908
+ const live = this.panes.get(id);
53909
+ if (live === undefined) {
53910
+ return notRunning(id);
53911
+ }
53912
+ const token = content?.trim() ?? "";
53913
+ if (token === "" || tokenGen(token) !== live.gen) {
53914
+ return refuse("usage_error");
53915
+ }
53916
+ live.rotate(token);
53917
+ return accept({ gen: live.gen });
53918
+ }
53919
+ status(args) {
53920
+ const id = args.pane;
53921
+ if (id === undefined) {
53922
+ return refuse("usage_error");
53923
+ }
53924
+ const live = this.panes.get(id);
53925
+ if (live === undefined) {
53926
+ return notRunning(id);
53927
+ }
53928
+ return accept({
53929
+ pane: id,
53930
+ running: true,
53931
+ gen: live.gen,
53932
+ busy: live.busy,
53933
+ dialogOpen: live.dialog !== undefined,
53934
+ lastSeq: live.queue.lastSeq,
53935
+ pendingEvents: live.queue.size,
53936
+ claudeSessionId: live.claudeSessionId,
53937
+ model: live.model
53938
+ });
53939
+ }
53940
+ deliveryRefusal(live) {
53941
+ if (live.dialog !== undefined) {
53942
+ return refuse("dialog_open");
53943
+ }
53944
+ if (live.busy || live.pendingPromptId !== undefined) {
53945
+ return refuse("busy");
53946
+ }
53947
+ return null;
53948
+ }
53949
+ accept(socket) {
53950
+ const controller = new AbortController;
53951
+ let buffer = "";
53952
+ let handled = false;
53953
+ socket.on("close", () => controller.abort());
53954
+ socket.on("error", (error) => {
53955
+ controller.abort();
53956
+ this.logger.debug("tmuxd socket client error", { error: error.message });
53957
+ });
53958
+ socket.on("data", (chunk) => {
53959
+ if (handled) {
53960
+ return;
53961
+ }
53962
+ buffer += chunk.toString("utf8");
53963
+ if (buffer.length > MAX_REQUEST_BYTES) {
53964
+ socket.destroy();
53965
+ return;
53966
+ }
53967
+ const newline = buffer.indexOf(`
53968
+ `);
53969
+ if (newline === -1) {
53970
+ return;
53971
+ }
53972
+ handled = true;
53973
+ this.answer(buffer.slice(0, newline), controller.signal).then((response) => {
53974
+ if (!socket.destroyed) {
53975
+ socket.end(`${JSON.stringify(response)}
53976
+ `);
53977
+ }
53978
+ });
53979
+ });
53980
+ }
53981
+ async answer(line, signal) {
53982
+ const request = parseRequest(line);
53983
+ try {
53984
+ if (request?.kind === "hook" && typeof request.pane === "string" && typeof request.event === "string") {
53985
+ return await this.hook(request.pane, request.event, isRecord(request.payload) ? request.payload : {}, signal);
53986
+ }
53987
+ if (request?.kind === "ctl" && typeof request.verb === "string") {
53988
+ return await this.ctl(request.verb, stringArgs(request.args));
53989
+ }
53990
+ } catch (error) {
53991
+ this.logger.error("tmuxd request failed", {
53992
+ kind: request?.kind,
53993
+ error: error instanceof Error ? error.message : String(error)
53994
+ });
53995
+ }
53996
+ return refuse("usage_error");
53997
+ }
53998
+ async answering(socketPath) {
53999
+ try {
54000
+ await socketRequest(socketPath, { kind: "ping" }, 1000);
54001
+ return true;
54002
+ } catch (error) {
54003
+ if (error instanceof SocketRequestError) {
54004
+ return error.reason !== "unreachable";
54005
+ }
54006
+ throw error;
54007
+ }
54008
+ }
54009
+ }
54010
+ function defaultSocketPath(env, tempDir) {
54011
+ const uid = typeof process.getuid === "function" ? process.getuid() : "agent";
54012
+ return join7(env.XDG_RUNTIME_DIR ?? tempDir, `kodizm-live-${uid}`, "tmuxd.sock");
54013
+ }
54014
+ function tokenGen(token) {
54015
+ if (!token.startsWith(TOKEN_PREFIX)) {
54016
+ return null;
54017
+ }
54018
+ const [payload] = token.slice(TOKEN_PREFIX.length).split(".");
54019
+ try {
54020
+ const claims = JSON.parse(Buffer.from(payload ?? "", "base64url").toString("utf8"));
54021
+ return isRecord(claims) && Number.isInteger(claims.gen) ? claims.gen : null;
54022
+ } catch (error) {
54023
+ if (error instanceof SyntaxError) {
54024
+ return null;
54025
+ }
54026
+ throw error;
54027
+ }
54028
+ }
54029
+ function parseStartEnv(content) {
54030
+ const values = {};
54031
+ for (const line of content.split(`
54032
+ `)) {
54033
+ if (line.trim() === "") {
54034
+ continue;
54035
+ }
54036
+ const separator = line.indexOf("=");
54037
+ const key = line.slice(0, separator);
54038
+ if (separator <= 0 || !ENV_KEY.test(key)) {
54039
+ return null;
54040
+ }
54041
+ values[key] = line.slice(separator + 1);
54042
+ }
54043
+ const ingestUrl = values.KODIZM_LIVE_INGEST_URL ?? "";
54044
+ const token = values.KODIZM_LIVE_TOKEN ?? "";
54045
+ const gen = Number(values.KODIZM_LIVE_GEN);
54046
+ if (!/^https?:\/\//.test(ingestUrl) || !Number.isInteger(gen) || gen < 1 || tokenGen(token) !== gen) {
54047
+ return null;
54048
+ }
54049
+ return {
54050
+ ingestUrl: ingestUrl.replace(/\/+$/, ""),
54051
+ token,
54052
+ gen,
54053
+ authEnv: Object.fromEntries(Object.entries(values).filter(([key]) => !key.startsWith(LIVE_KEY_PREFIX)))
54054
+ };
54055
+ }
54056
+ async function readSecretFile(path) {
54057
+ const info = await stat3(path).catch((error) => {
54058
+ if (error.code === "ENOENT") {
54059
+ return null;
54060
+ }
54061
+ throw error;
54062
+ });
54063
+ if (info === null || !info.isFile() || (info.mode & 511) !== 384) {
54064
+ return null;
54065
+ }
54066
+ return readFile4(path, "utf8");
54067
+ }
54068
+ async function readFileOrNull(path) {
54069
+ return readFile4(path, "utf8").catch((error) => {
54070
+ if (error.code === "ENOENT" || error.code === "EISDIR") {
54071
+ return null;
54072
+ }
54073
+ throw error;
54074
+ });
54075
+ }
54076
+ async function takeFile(path, read) {
54077
+ try {
54078
+ return await read(path);
54079
+ } finally {
54080
+ await removeFile(path);
54081
+ }
54082
+ }
54083
+ async function removeFile(path) {
54084
+ await unlink2(path).catch((error) => {
54085
+ if (error.code !== "ENOENT") {
54086
+ throw error;
54087
+ }
54088
+ });
54089
+ }
54090
+ function decisionFrom(body) {
54091
+ const status = isRecord(body) ? body.status : undefined;
54092
+ switch (status) {
54093
+ case "pending":
54094
+ return "pending";
54095
+ case "allowed":
54096
+ return { behavior: "allow" };
54097
+ case "denied":
54098
+ return {
54099
+ behavior: "deny",
54100
+ message: (isRecord(body) ? text(body.message) : undefined) ?? "Denied by the operator."
54101
+ };
54102
+ case "expired":
54103
+ return {
54104
+ behavior: "deny",
54105
+ message: "The permission request expired."
54106
+ };
54107
+ default:
54108
+ return;
54109
+ }
54110
+ }
54111
+ async function fetchJson(url, options) {
54112
+ const timeout = AbortSignal.timeout(options.timeoutMs);
54113
+ const signal = options.signal === undefined ? timeout : AbortSignal.any([options.signal, timeout]);
54114
+ let status;
54115
+ let raw;
54116
+ try {
54117
+ const response = await fetch(url, {
54118
+ method: options.method,
54119
+ headers: {
54120
+ authorization: `Bearer ${options.bearer}`,
54121
+ accept: "application/json",
54122
+ ...options.body === undefined ? {} : { "content-type": "application/json" }
54123
+ },
54124
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
54125
+ signal
54126
+ });
54127
+ status = response.status;
54128
+ raw = await response.text();
54129
+ } catch (error) {
54130
+ if (error instanceof TypeError || error instanceof DOMException && isAbort(error)) {
54131
+ return { status: 0, body: undefined };
54132
+ }
54133
+ throw error;
54134
+ }
54135
+ let body;
54136
+ try {
54137
+ body = raw === "" ? undefined : JSON.parse(raw);
54138
+ } catch (error) {
54139
+ if (!(error instanceof SyntaxError)) {
54140
+ throw error;
54141
+ }
54142
+ }
54143
+ return {
54144
+ status,
54145
+ body
54146
+ };
54147
+ }
54148
+ function isAbort(error) {
54149
+ return error.name === "AbortError" || error.name === "TimeoutError";
54150
+ }
54151
+ function launchFailure(error) {
54152
+ if (error instanceof PaneError) {
54153
+ return error.reason === "start_timeout" ? "timeout" : "usage_error";
54154
+ }
54155
+ if (isErrno(error)) {
54156
+ return "usage_error";
54157
+ }
54158
+ throw error;
54159
+ }
54160
+ function deliveryFailure(error, id) {
54161
+ if (!(error instanceof PaneError)) {
54162
+ throw error;
54163
+ }
54164
+ return error.reason === "pane_dead" ? notRunning(id) : refuse("timeout");
54165
+ }
54166
+ function isErrno(error) {
54167
+ return error instanceof Error && typeof error.code === "string";
54168
+ }
54169
+ async function panePid(pane) {
54170
+ const tmux = Bun.which("tmux");
54171
+ if (tmux === null) {
54172
+ return;
54173
+ }
54174
+ const env = {
54175
+ PATH: process.env.PATH ?? "/usr/bin:/bin"
54176
+ };
54177
+ if (process.env.TMUX_TMPDIR !== undefined) {
54178
+ env.TMUX_TMPDIR = process.env.TMUX_TMPDIR;
54179
+ }
54180
+ const child = Bun.spawn([tmux, "-L", pane.socket, "display-message", "-p", "-t", `=${pane.id}:`, "#{pane_pid}"], {
54181
+ env,
54182
+ stdin: "ignore",
54183
+ stdout: "pipe",
54184
+ stderr: "ignore"
54185
+ });
54186
+ const [exitCode, output] = await Promise.all([child.exited, new Response(child.stdout).text()]);
54187
+ const pid = Number(output.trim());
54188
+ return exitCode === 0 && Number.isInteger(pid) && pid > 0 ? pid : undefined;
54189
+ }
54190
+ function processAlive(pid) {
54191
+ try {
54192
+ process.kill(pid, 0);
54193
+ return true;
54194
+ } catch (error) {
54195
+ if (isErrno(error) && error.code === "ESRCH") {
54196
+ return false;
54197
+ }
54198
+ if (isErrno(error) && error.code === "EPERM") {
54199
+ return true;
54200
+ }
54201
+ throw error;
54202
+ }
54203
+ }
54204
+ function sameJson(left, right) {
54205
+ return JSON.stringify(left) === JSON.stringify(right);
54206
+ }
54207
+ function parseRequest(line) {
54208
+ try {
54209
+ const value = JSON.parse(line);
54210
+ return isRecord(value) ? value : null;
54211
+ } catch (error) {
54212
+ if (error instanceof SyntaxError) {
54213
+ return null;
54214
+ }
54215
+ throw error;
54216
+ }
54217
+ }
54218
+ function stringArgs(value) {
54219
+ if (!isRecord(value)) {
54220
+ return {};
54221
+ }
54222
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
54223
+ }
54224
+ function accept(data) {
54225
+ return {
54226
+ ok: true,
54227
+ code: "ok",
54228
+ data: Object.fromEntries(Object.entries(data).filter(([, value]) => value !== undefined))
54229
+ };
54230
+ }
54231
+ function refuse(code, data) {
54232
+ return data === undefined ? { ok: false, code } : { ok: false, code, data };
54233
+ }
54234
+ function notRunning(id) {
54235
+ return refuse("pane_not_running", {
54236
+ pane: id,
54237
+ running: false
54238
+ });
54239
+ }
54240
+ var HEARTBEAT_INTERVAL_MS = 20000, WATCHDOG_QUIET_MS = 30000, CANCEL_TIMEOUT_MS = 3000, PERMISSION_POLL_MS = 1500, INGEST_TIMEOUT_MS = 1e4, MAX_BATCH = 200, DEFAULT_MAX_PANES = 4, STOP_DRAIN_MS = 1e4, PERMISSION_WAIT_MS, MIN_SECRET_LENGTH = 8, MAX_REQUEST_BYTES, PANE_ID2, ENV_KEY, LIVE_KEY_PREFIX = "KODIZM_LIVE_", TOKEN_PREFIX = "kdz-int-jwt.", COMMAND_NAMES, REJECTION_RULES, EXIT_CODES, DEFAULT_TIMINGS;
54241
+ var init_daemon = __esm(() => {
54242
+ init_logger();
54243
+ init_events2();
54244
+ init_hooks();
54245
+ init_input();
54246
+ init_pane();
54247
+ init_transcript();
54248
+ PERMISSION_WAIT_MS = PERMISSION_HOOK_TIMEOUT_S * 1000 - 1e4;
54249
+ MAX_REQUEST_BYTES = 8 * 1024 * 1024;
54250
+ PANE_ID2 = /^[A-Za-z0-9_-]{1,64}$/;
54251
+ ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
54252
+ COMMAND_NAMES = new Set(["compact", "clear", "model"]);
54253
+ REJECTION_RULES = {
54254
+ slash_command: "leading_slash",
54255
+ bash_mode: "leading_bang",
54256
+ empty: "empty",
54257
+ unknown_command: "unknown_command",
54258
+ invalid_model_alias: "invalid_model_alias"
54259
+ };
54260
+ EXIT_CODES = {
54261
+ ok: 0,
54262
+ usage_error: 2,
54263
+ dialog_open: 3,
54264
+ prompt_rejected: 3,
54265
+ pane_cap: 3,
54266
+ pane_exists: 3,
54267
+ busy: 3,
54268
+ pane_not_running: 4,
54269
+ timeout: 5
54270
+ };
54271
+ DEFAULT_TIMINGS = {
54272
+ heartbeatMs: HEARTBEAT_INTERVAL_MS,
54273
+ watchdogQuietMs: WATCHDOG_QUIET_MS,
54274
+ cancelTimeoutMs: CANCEL_TIMEOUT_MS,
54275
+ permissionPollMs: PERMISSION_POLL_MS,
54276
+ ingestTimeoutMs: INGEST_TIMEOUT_MS,
54277
+ monitorMs: 2000,
54278
+ transcriptPollMs: 250,
54279
+ retryBaseMs: 250,
54280
+ retryMaxMs: 1e4
54281
+ };
54282
+ });
54283
+
54284
+ // src/live/ctl.ts
54285
+ import { spawn as spawn2 } from "child_process";
54286
+ import { homedir as homedir2, tmpdir } from "os";
54287
+ import { resolve as resolve5 } from "path";
54288
+ async function runTmuxd(args) {
54289
+ const [command, ...rest] = args;
54290
+ switch (command) {
54291
+ case "ctl":
54292
+ return runCtl(rest);
54293
+ case "hook":
54294
+ return runHook(rest);
54295
+ case "daemon":
54296
+ return runDaemon();
54297
+ case undefined:
54298
+ case "--help":
54299
+ case "help":
54300
+ process.stdout.write(USAGE);
54301
+ return 0;
54302
+ default:
54303
+ process.stderr.write(USAGE);
54304
+ return 2;
54305
+ }
54306
+ }
54307
+ async function runCtl(args) {
54308
+ const [verb, ...rest] = args;
54309
+ const flags = parseFlags(rest);
54310
+ if (verb === undefined || !VERBS.has(verb) || flags === null) {
54311
+ await discardHandoff(verb, rest);
54312
+ return print({ ok: false, code: "usage_error" });
54313
+ }
54314
+ const socketPath = socketPathFromEnv();
54315
+ const message = {
54316
+ kind: "ctl",
54317
+ verb,
54318
+ args: flags
54319
+ };
54320
+ try {
54321
+ return print(await ask(socketPath, message, verb === "start"));
54322
+ } catch (error) {
54323
+ if (!(error instanceof SocketRequestError)) {
54324
+ throw error;
54325
+ }
54326
+ process.stderr.write(`${JSON.stringify({ level: "warn", message: error.message, verb })}
54327
+ `);
54328
+ if (error.reason === "unreachable") {
54329
+ await discardHandoff(verb, rest);
54330
+ }
54331
+ return print(error.reason === "unreachable" ? { ok: false, code: "pane_not_running", data: { pane: flags.pane, running: false } } : { ok: false, code: "timeout" });
54332
+ }
54333
+ }
54334
+ async function ask(socketPath, message, spawnIfMissing) {
54335
+ try {
54336
+ return toResult(await socketRequest(socketPath, message, CTL_TIMEOUT_MS));
54337
+ } catch (error) {
54338
+ if (!spawnIfMissing || !(error instanceof SocketRequestError) || error.reason !== "unreachable") {
54339
+ throw error;
54340
+ }
54341
+ }
54342
+ spawnDaemon();
54343
+ const deadline = Date.now() + DAEMON_BOOT_MS;
54344
+ for (;; ) {
54345
+ try {
54346
+ return toResult(await socketRequest(socketPath, message, CTL_TIMEOUT_MS));
54347
+ } catch (error) {
54348
+ if (!(error instanceof SocketRequestError) || error.reason !== "unreachable" || Date.now() > deadline) {
54349
+ throw error;
54350
+ }
54351
+ await Bun.sleep(50);
54352
+ }
54353
+ }
54354
+ }
54355
+ async function runHook(args) {
54356
+ const [event, ...rest] = args;
54357
+ const flags = parseFlags(rest);
54358
+ try {
54359
+ if (event === undefined || flags?.pane === undefined || flags.socket === undefined) {
54360
+ return 0;
54361
+ }
54362
+ const output = await runHookHelper({
54363
+ event,
54364
+ paneId: flags.pane,
54365
+ socketPath: flags.socket,
54366
+ input: await readStdin()
54367
+ });
54368
+ if (output !== "") {
54369
+ process.stdout.write(output);
54370
+ }
54371
+ } catch (error) {
54372
+ process.stderr.write(`${JSON.stringify({
54373
+ level: "error",
54374
+ message: "tmuxd hook failed",
54375
+ event,
54376
+ error: error instanceof Error ? error.message : String(error)
54377
+ })}
54378
+ `);
54379
+ }
54380
+ return 0;
54381
+ }
54382
+ async function runDaemon() {
54383
+ const env = process.env;
54384
+ const maxPanes = Number(env.KODIZM_TMUXD_MAX_PANES);
54385
+ let shutdown = () => {};
54386
+ const stopped = new Promise((resolveStopped) => {
54387
+ shutdown = resolveStopped;
54388
+ });
54389
+ const daemon = new LiveDaemon({
54390
+ socketPath: socketPathFromEnv(),
54391
+ helperArgv: [process.execPath, process.argv[1] ?? ""],
54392
+ home: env.HOME ?? homedir2(),
54393
+ tmuxSocket: env.KODIZM_TMUXD_TMUX_SOCKET ?? LIVE_SOCKET,
54394
+ claudeBin: env.CLAUDE_CODE_PATH,
54395
+ baseSettingsPath: env.KODIZM_LIVE_BASE_SETTINGS ?? DEFAULT_BASE_SETTINGS_PATH,
54396
+ maxPanes: Number.isInteger(maxPanes) && maxPanes > 0 ? maxPanes : DEFAULT_MAX_PANES,
54397
+ logger: createLogger({ env }),
54398
+ onIdle: () => void daemon.close().then(shutdown)
54399
+ });
54400
+ await daemon.listen();
54401
+ for (const signal of ["SIGTERM", "SIGINT"]) {
54402
+ process.on(signal, () => void daemon.close().then(shutdown));
54403
+ }
54404
+ await stopped;
54405
+ return 0;
54406
+ }
54407
+ function spawnDaemon() {
54408
+ const child = spawn2(process.execPath, [process.argv[1] ?? "", "tmuxd", "daemon"], {
54409
+ detached: true,
54410
+ stdio: "ignore",
54411
+ env: process.env
54412
+ });
54413
+ child.unref();
54414
+ }
54415
+ function socketPathFromEnv() {
54416
+ return process.env.KODIZM_TMUXD_SOCKET ?? defaultSocketPath(process.env, tmpdir());
54417
+ }
54418
+ function parseFlags(args) {
54419
+ const flags = {};
54420
+ for (let index = 0;index < args.length; index += 2) {
54421
+ const flag = args[index] ?? "";
54422
+ const value = args[index + 1];
54423
+ const name = flag.slice(2);
54424
+ if (!flag.startsWith("--") || name === "" || value === undefined || name in flags) {
54425
+ return null;
54426
+ }
54427
+ flags[name] = PATH_FLAGS.has(name) ? resolve5(value) : value;
54428
+ }
54429
+ return flags;
54430
+ }
54431
+ async function discardHandoff(verb, args) {
54432
+ const flag = verb === undefined ? undefined : HANDOFF_FLAGS[verb];
54433
+ if (flag === undefined) {
54434
+ return;
54435
+ }
54436
+ for (let index = 0;index < args.length - 1; index += 1) {
54437
+ const value = args[index + 1];
54438
+ if (args[index] === `--${flag}` && value !== undefined) {
54439
+ await removeFile(resolve5(value));
54440
+ }
54441
+ }
54442
+ }
54443
+ function toResult(response) {
54444
+ const code = typeof response === "object" && response !== null ? response.code : undefined;
54445
+ if (typeof code !== "string" || typeof exitCodeFor(code) !== "number") {
54446
+ throw new SocketRequestError("malformed");
54447
+ }
54448
+ return response;
54449
+ }
54450
+ function print(result) {
54451
+ process.stdout.write(`${JSON.stringify(result)}
54452
+ `);
54453
+ return exitCodeFor(result.code);
54454
+ }
54455
+ async function readStdin() {
54456
+ let timer;
54457
+ const timeout = new Promise((resolveTimeout) => {
54458
+ timer = setTimeout(() => resolveTimeout(""), STDIN_TIMEOUT_MS);
54459
+ });
54460
+ const input = await Promise.race([Bun.stdin.text(), timeout]);
54461
+ clearTimeout(timer);
54462
+ return input;
54463
+ }
54464
+ var USAGE, VERBS, PATH_FLAGS, HANDOFF_FLAGS, CTL_TIMEOUT_MS = 60000, DAEMON_BOOT_MS = 5000, STDIN_TIMEOUT_MS = 2000;
54465
+ var init_ctl = __esm(() => {
54466
+ init_logger();
54467
+ init_daemon();
54468
+ init_hooks();
54469
+ init_pane();
54470
+ USAGE = `Usage: kodizm-acp tmuxd <command>
54471
+
54472
+ Commands:
54473
+ ctl <verb> [flags] Drive a Live pane through the daemon; prints one JSON line.
54474
+ Verbs: start, prompt, command, cancel, stop, rotate-token, status.
54475
+ hook <Event> --pane <id> --socket <path> Hook helper the pane settings run; reads the payload on stdin.
54476
+ daemon Run the daemon in the foreground; ctl start spawns it detached.
54477
+
54478
+ Environment:
54479
+ KODIZM_TMUXD_SOCKET daemon socket (default: $XDG_RUNTIME_DIR or the temp dir, kodizm-live-<uid>/tmuxd.sock)
54480
+ KODIZM_TMUXD_TMUX_SOCKET tmux socket name (default: ${LIVE_SOCKET})
54481
+ KODIZM_TMUXD_MAX_PANES panes per daemon (default: ${DEFAULT_MAX_PANES})
54482
+ KODIZM_LIVE_BASE_SETTINGS base settings with the deny list (default: ${DEFAULT_BASE_SETTINGS_PATH})
54483
+ CLAUDE_CODE_PATH claude binary (default: claude on PATH)
54484
+ `;
54485
+ VERBS = new Set(["start", "prompt", "command", "cancel", "stop", "rotate-token", "status"]);
54486
+ PATH_FLAGS = new Set(["settings-base", "env-file", "file", "token-file"]);
54487
+ HANDOFF_FLAGS = {
54488
+ start: "env-file",
54489
+ prompt: "file",
54490
+ "rotate-token": "token-file"
54491
+ };
54492
+ });
52136
54493
  // package.json
52137
54494
  var package_default = {
52138
54495
  name: "@kodizm/acp",
52139
- version: "0.7.0",
54496
+ version: "0.8.0",
52140
54497
  description: "Custom ACP server bridging Claude Code and opencode CLIs through a single Kodizm-flavored protocol surface.",
52141
54498
  type: "module",
52142
54499
  bin: {
@@ -52864,6 +55221,10 @@ async function main() {
52864
55221
  }
52865
55222
  }
52866
55223
  if (import.meta.main) {
55224
+ if (process.argv[2] === "tmuxd") {
55225
+ await Promise.resolve().then(() => init_ctl());
55226
+ process.exit(await runTmuxd(process.argv.slice(3)));
55227
+ }
52867
55228
  await main();
52868
55229
  }
52869
55230
  export {