@kodizm/acp 0.8.2 → 0.9.1

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 +1193 -415
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -46,6 +46,75 @@ var __esm = (fn, res, err) => () => {
46
46
  };
47
47
  var __require = import.meta.require;
48
48
 
49
+ // package.json
50
+ var package_default;
51
+ var init_package = __esm(() => {
52
+ package_default = {
53
+ name: "@kodizm/acp",
54
+ version: "0.9.1",
55
+ description: "Custom ACP server bridging Claude Code and opencode CLIs through a single Kodizm-flavored protocol surface.",
56
+ type: "module",
57
+ bin: {
58
+ "kodizm-acp": "./dist/index.js"
59
+ },
60
+ main: "./dist/index.js",
61
+ files: [
62
+ "dist/",
63
+ "README.md",
64
+ "LICENSE"
65
+ ],
66
+ scripts: {
67
+ dev: "bun run src/index.ts",
68
+ build: "bun build src/index.ts --target=bun --outdir=dist",
69
+ test: "bun test test/unit test/e2e",
70
+ "test:unit": "bun test test/unit",
71
+ "test:e2e": "bun test test/e2e",
72
+ "test:integration": "./scripts/reap-opencode.sh bun test test/integration",
73
+ "test:watch": "bun test --watch",
74
+ lint: "biome check src test",
75
+ "lint:fix": "biome check --write src test",
76
+ format: "biome format --write src test",
77
+ typecheck: "tsc --noEmit",
78
+ "typecheck:src": "tsc --noEmit -p tsconfig.publish.json",
79
+ prepublishOnly: "bun run build && bun run typecheck:src"
80
+ },
81
+ engines: {
82
+ bun: ">=1.1.0"
83
+ },
84
+ publishConfig: {
85
+ access: "public"
86
+ },
87
+ config: {
88
+ claudeAgentSdk: "0.3.280"
89
+ },
90
+ devDependencies: {
91
+ "@anthropic-ai/claude-agent-sdk": "0.3.280",
92
+ "@anthropic-ai/sdk": ">=0.93.0",
93
+ "@biomejs/biome": "^1.9.0",
94
+ "@modelcontextprotocol/sdk": "^1.29.0",
95
+ "@opencode-ai/sdk": "^1.18.32",
96
+ "@types/bun": "latest",
97
+ typescript: "^5.6.0",
98
+ zod: "^4.0.0"
99
+ },
100
+ license: "Apache-2.0",
101
+ homepage: "https://github.com/kodizm/acp",
102
+ repository: {
103
+ type: "git",
104
+ url: "git+https://github.com/kodizm/acp.git"
105
+ },
106
+ keywords: [
107
+ "acp",
108
+ "agent-client-protocol",
109
+ "claude",
110
+ "opencode",
111
+ "ai",
112
+ "bridge",
113
+ "jsonrpc"
114
+ ]
115
+ };
116
+ });
117
+
49
118
  // src/server/errors.ts
50
119
  var JsonRpcErrorCode, JsonRpcError, InvalidParamsError, SessionNotFoundError, MethodNotSupportedError, AcpTimeoutError, BackendNotConfiguredError, UnknownBackendError;
51
120
  var init_errors = __esm(() => {
@@ -4983,6 +5052,151 @@ var init_zod = __esm(() => {
4983
5052
  init_external();
4984
5053
  });
4985
5054
 
5055
+ // src/wire/schemas.ts
5056
+ function metaCarriesNoCanonicalFields(data) {
5057
+ if (data._meta === undefined) {
5058
+ return true;
5059
+ }
5060
+ for (const banned of CANONICAL_FIELDS_BANNED_IN_META) {
5061
+ if (banned in data._meta) {
5062
+ return false;
5063
+ }
5064
+ }
5065
+ return true;
5066
+ }
5067
+ function timeoutsAreExclusive(data) {
5068
+ return !(data.permissionTimeoutMs !== undefined && data.permissionDeferTimeoutMs !== undefined);
5069
+ }
5070
+ var AbsolutePathSchema, PLUGIN_ROOT = "/opt/kodizm/plugins/", PluginPathSchema, McpServerSchema, SystemPromptSchema, MetaSchema, CANONICAL_FIELDS_BANNED_IN_META, SettingSourcesSchema, PermissionModeSchema, ToolPolicySchema, FeaturesSchema, META_SMUGGLE_MESSAGE = "canonical fields (systemPrompt, additionalDirectories, mcpServers, model, skills, cwd) must be top-level, not nested in _meta", InitializeRequestSchema, SessionOptionFields, TIMEOUT_EXCLUSION, NewSessionRequestSchema, PromptRequestSchema, CancelRequestSchema, LoadSessionRequestSchema, CompactSessionRequestSchema, ForkSessionRequestSchema;
5071
+ var init_schemas3 = __esm(() => {
5072
+ init_zod();
5073
+ AbsolutePathSchema = string2().regex(/^\//, 'path must be absolute (start with "/")');
5074
+ PluginPathSchema = AbsolutePathSchema.refine((path) => path.length > PLUGIN_ROOT.length && path.startsWith(PLUGIN_ROOT) && !path.split("/").includes(".."), { message: `plugin path must live under ${PLUGIN_ROOT} with no ".." segment` });
5075
+ McpServerSchema = object({
5076
+ type: literal("http"),
5077
+ name: string2().min(1),
5078
+ url: string2().url(),
5079
+ headers: array(object({
5080
+ name: string2().min(1),
5081
+ value: string2()
5082
+ })).optional()
5083
+ });
5084
+ SystemPromptSchema = union([
5085
+ string2(),
5086
+ object({
5087
+ append: string2(),
5088
+ excludeDynamicSections: boolean2().optional()
5089
+ })
5090
+ ]);
5091
+ MetaSchema = record(string2(), unknown());
5092
+ CANONICAL_FIELDS_BANNED_IN_META = [
5093
+ "systemPrompt",
5094
+ "additionalDirectories",
5095
+ "mcpServers",
5096
+ "model",
5097
+ "skills",
5098
+ "cwd",
5099
+ "sessionId",
5100
+ "sourceSessionId",
5101
+ "toolPolicy",
5102
+ "autoCompact",
5103
+ "permissionTimeoutMs",
5104
+ "permissionDeferTimeoutMs",
5105
+ "debug",
5106
+ "debugCaptureRawSdk",
5107
+ "debugCaptureRpc",
5108
+ "heartbeatIntervalMs",
5109
+ "inactivityThresholdMs",
5110
+ "settingSources",
5111
+ "features",
5112
+ "plugins"
5113
+ ];
5114
+ SettingSourcesSchema = array(_enum(["user", "project", "local"]));
5115
+ PermissionModeSchema = _enum(["default", "acceptEdits", "plan", "dontAsk", "bypassPermissions"]);
5116
+ ToolPolicySchema = object({
5117
+ allow: array(string2()).optional(),
5118
+ deny: array(string2()).optional(),
5119
+ ask: array(string2()).optional(),
5120
+ defaultMode: PermissionModeSchema.optional()
5121
+ });
5122
+ FeaturesSchema = object({
5123
+ todos: boolean2().optional(),
5124
+ subagents: boolean2().optional(),
5125
+ backgroundTasks: boolean2().optional(),
5126
+ scheduling: boolean2().optional(),
5127
+ planMode: boolean2().optional(),
5128
+ worktree: boolean2().optional(),
5129
+ notebook: boolean2().optional(),
5130
+ clientTools: boolean2().optional(),
5131
+ autoMemory: boolean2().optional(),
5132
+ bundledSkills: boolean2().optional(),
5133
+ simple: boolean2().optional(),
5134
+ tools: array(string2().min(1)).optional()
5135
+ });
5136
+ InitializeRequestSchema = object({
5137
+ protocolVersion: number2().int().positive(),
5138
+ clientCapabilities: record(string2(), unknown()).optional(),
5139
+ _meta: MetaSchema.optional()
5140
+ });
5141
+ SessionOptionFields = {
5142
+ additionalDirectories: array(AbsolutePathSchema).optional(),
5143
+ plugins: array(PluginPathSchema).optional(),
5144
+ systemPrompt: SystemPromptSchema.optional(),
5145
+ model: string2().optional(),
5146
+ skills: array(string2()).optional(),
5147
+ toolPolicy: ToolPolicySchema.optional(),
5148
+ autoCompact: boolean2().optional(),
5149
+ permissionTimeoutMs: number2().int().positive().optional(),
5150
+ permissionDeferTimeoutMs: number2().int().positive().optional(),
5151
+ debug: boolean2().optional(),
5152
+ debugCaptureRawSdk: boolean2().optional(),
5153
+ debugCaptureRpc: boolean2().optional(),
5154
+ heartbeatIntervalMs: number2().int().positive().optional(),
5155
+ inactivityThresholdMs: number2().int().positive().optional(),
5156
+ settingSources: SettingSourcesSchema.optional(),
5157
+ features: FeaturesSchema.optional(),
5158
+ _meta: MetaSchema.optional()
5159
+ };
5160
+ TIMEOUT_EXCLUSION = {
5161
+ message: "permissionTimeoutMs and permissionDeferTimeoutMs are mutually exclusive (pick hard-deny on timeout OR soft-defer on timeout)",
5162
+ path: ["permissionDeferTimeoutMs"]
5163
+ };
5164
+ NewSessionRequestSchema = object({
5165
+ cwd: AbsolutePathSchema,
5166
+ mcpServers: array(McpServerSchema),
5167
+ ...SessionOptionFields
5168
+ }).refine(metaCarriesNoCanonicalFields, { message: META_SMUGGLE_MESSAGE, path: ["_meta"] }).refine(timeoutsAreExclusive, TIMEOUT_EXCLUSION);
5169
+ PromptRequestSchema = object({
5170
+ sessionId: string2().min(1),
5171
+ prompt: array(unknown()),
5172
+ model: string2().optional(),
5173
+ _meta: MetaSchema.optional()
5174
+ }).refine(metaCarriesNoCanonicalFields, { message: META_SMUGGLE_MESSAGE, path: ["_meta"] });
5175
+ CancelRequestSchema = object({
5176
+ sessionId: string2().min(1)
5177
+ });
5178
+ LoadSessionRequestSchema = object({
5179
+ sessionId: string2().min(1),
5180
+ cwd: AbsolutePathSchema,
5181
+ mcpServers: array(McpServerSchema),
5182
+ ...SessionOptionFields
5183
+ }).refine(metaCarriesNoCanonicalFields, { message: META_SMUGGLE_MESSAGE, path: ["_meta"] }).refine(timeoutsAreExclusive, TIMEOUT_EXCLUSION);
5184
+ CompactSessionRequestSchema = object({
5185
+ sessionId: string2().min(1)
5186
+ });
5187
+ ForkSessionRequestSchema = object({
5188
+ sourceSessionId: string2().min(1),
5189
+ cwd: AbsolutePathSchema,
5190
+ mcpServers: array(McpServerSchema),
5191
+ systemPrompt: SystemPromptSchema.optional(),
5192
+ model: string2().optional(),
5193
+ additionalDirectories: array(AbsolutePathSchema).optional(),
5194
+ settingSources: SettingSourcesSchema.optional(),
5195
+ features: FeaturesSchema.optional(),
5196
+ _meta: MetaSchema.optional()
5197
+ }).refine(metaCarriesNoCanonicalFields, { message: META_SMUGGLE_MESSAGE, path: ["_meta"] });
5198
+ });
5199
+
4986
5200
  // src/server/heartbeat.ts
4987
5201
  class HeartbeatTimer {
4988
5202
  options;
@@ -5206,58 +5420,7 @@ var init_events = __esm(() => {
5206
5420
  ]);
5207
5421
  });
5208
5422
 
5209
- // src/backends/claude/permission-bridge.ts
5210
- function buildCanUseTool(deps) {
5211
- return async function canUseTool(toolName, input, options) {
5212
- const payload = {
5213
- sessionId: deps.sessionId,
5214
- toolCall: {
5215
- toolCallId: options.toolUseID,
5216
- rawInput: input,
5217
- title: options.title ?? toolName,
5218
- kind: "tool_use"
5219
- },
5220
- options: DEFAULT_OPTIONS
5221
- };
5222
- if (options.agentID !== undefined) {
5223
- payload.agentId = options.agentID;
5224
- }
5225
- const event = {
5226
- sessionId: deps.sessionId,
5227
- type: "permission_request",
5228
- toolUseId: options.toolUseID,
5229
- name: toolName,
5230
- options: DEFAULT_OPTIONS.map((o) => ({ optionId: o.optionId, label: o.name })),
5231
- ...options.agentID === undefined ? {} : { agentId: options.agentID, parentSessionId: deps.sessionId }
5232
- };
5233
- deps.emit.send(event);
5234
- const raceOptions = {
5235
- signal: composeSignals(deps.signal, options.signal)
5236
- };
5237
- if (deps.permissionTimeoutMs !== undefined) {
5238
- raceOptions.timeoutMs = deps.permissionTimeoutMs;
5239
- }
5240
- if (deps.deferTimeoutMs !== undefined && deps.onDefer !== undefined) {
5241
- raceOptions.deferTimeoutMs = deps.deferTimeoutMs;
5242
- }
5243
- let raced;
5244
- try {
5245
- raced = await awaitPermissionResponse(deps.server, "session/request_permission", payload, raceOptions);
5246
- } catch (error) {
5247
- if (error instanceof AcpTimeoutError) {
5248
- return { behavior: "deny", message: "Permission RPC timed out" };
5249
- }
5250
- throw error;
5251
- }
5252
- if (raced === DEFERRED_SENTINEL) {
5253
- if (deps.onDefer === undefined) {
5254
- return { behavior: "deny", message: "Permission deferred (no handler)" };
5255
- }
5256
- return await deps.onDefer({ toolName, input, options });
5257
- }
5258
- return mapPermissionOutcome(raced, input, options.suggestions);
5259
- };
5260
- }
5423
+ // src/backends/permission-rpc.ts
5261
5424
  async function awaitPermissionResponse(server, method, params, options = {}) {
5262
5425
  const racers = [server.request(method, params)];
5263
5426
  let timeoutHandle;
@@ -5304,49 +5467,9 @@ async function awaitPermissionResponse(server, method, params, options = {}) {
5304
5467
  }
5305
5468
  }
5306
5469
  }
5307
- function composeSignals(a, b) {
5308
- if (a.aborted)
5309
- return a;
5310
- if (b.aborted)
5311
- return b;
5312
- const controller = new AbortController;
5313
- const onAbort = () => controller.abort();
5314
- a.addEventListener("abort", onAbort, { once: true });
5315
- b.addEventListener("abort", onAbort, { once: true });
5316
- return controller.signal;
5317
- }
5318
- function mapPermissionOutcome(response, input, suggestions) {
5319
- const outcome = response.outcome;
5320
- if (outcome.outcome === "cancelled") {
5321
- throw new Error("Tool use aborted");
5322
- }
5323
- if (outcome.optionId === "allow") {
5324
- return { behavior: "allow", updatedInput: input };
5325
- }
5326
- if (outcome.optionId === "allow_always") {
5327
- return {
5328
- behavior: "allow",
5329
- updatedInput: input,
5330
- updatedPermissions: suggestions !== undefined && suggestions.length > 0 ? suggestions : [
5331
- {
5332
- type: "addRules",
5333
- rules: [{ toolName: "unknown" }],
5334
- behavior: "allow",
5335
- destination: "session"
5336
- }
5337
- ]
5338
- };
5339
- }
5340
- return { behavior: "deny", message: "User refused permission to run tool" };
5341
- }
5342
- var DEFAULT_OPTIONS, DEFERRED_SENTINEL;
5343
- var init_permission_bridge = __esm(() => {
5470
+ var DEFERRED_SENTINEL;
5471
+ var init_permission_rpc = __esm(() => {
5344
5472
  init_errors();
5345
- DEFAULT_OPTIONS = [
5346
- { kind: "allow_always", name: "Always allow this tool", optionId: "allow_always" },
5347
- { kind: "allow_once", name: "Allow", optionId: "allow" },
5348
- { kind: "reject_once", name: "Reject", optionId: "reject" }
5349
- ];
5350
5473
  DEFERRED_SENTINEL = Symbol.for("kodizm.permission.deferred");
5351
5474
  });
5352
5475
 
@@ -5399,7 +5522,7 @@ var QuestionsArraySchema;
5399
5522
  var init_ask_user_question = __esm(() => {
5400
5523
  init_zod();
5401
5524
  init_events();
5402
- init_permission_bridge();
5525
+ init_permission_rpc();
5403
5526
  QuestionsArraySchema = array(KodizmQuestionSchema).min(1).max(4);
5404
5527
  });
5405
5528
 
@@ -5784,6 +5907,104 @@ function buildSdkServer(server) {
5784
5907
  return sdkServer;
5785
5908
  }
5786
5909
 
5910
+ // src/backends/claude/permission-bridge.ts
5911
+ function buildCanUseTool(deps) {
5912
+ return async function canUseTool(toolName, input, options) {
5913
+ const payload = {
5914
+ sessionId: deps.sessionId,
5915
+ toolCall: {
5916
+ toolCallId: options.toolUseID,
5917
+ rawInput: input,
5918
+ title: options.title ?? toolName,
5919
+ kind: "tool_use"
5920
+ },
5921
+ options: DEFAULT_OPTIONS
5922
+ };
5923
+ if (options.agentID !== undefined) {
5924
+ payload.agentId = options.agentID;
5925
+ }
5926
+ const event = {
5927
+ sessionId: deps.sessionId,
5928
+ type: "permission_request",
5929
+ toolUseId: options.toolUseID,
5930
+ name: toolName,
5931
+ options: DEFAULT_OPTIONS.map((o) => ({ optionId: o.optionId, label: o.name })),
5932
+ ...options.agentID === undefined ? {} : { agentId: options.agentID, parentSessionId: deps.sessionId }
5933
+ };
5934
+ deps.emit.send(event);
5935
+ const raceOptions = {
5936
+ signal: composeSignals(deps.signal, options.signal)
5937
+ };
5938
+ if (deps.permissionTimeoutMs !== undefined) {
5939
+ raceOptions.timeoutMs = deps.permissionTimeoutMs;
5940
+ }
5941
+ if (deps.deferTimeoutMs !== undefined && deps.onDefer !== undefined) {
5942
+ raceOptions.deferTimeoutMs = deps.deferTimeoutMs;
5943
+ }
5944
+ let raced;
5945
+ try {
5946
+ raced = await awaitPermissionResponse(deps.server, "session/request_permission", payload, raceOptions);
5947
+ } catch (error) {
5948
+ if (error instanceof AcpTimeoutError) {
5949
+ return { behavior: "deny", message: "Permission RPC timed out" };
5950
+ }
5951
+ throw error;
5952
+ }
5953
+ if (raced === DEFERRED_SENTINEL) {
5954
+ if (deps.onDefer === undefined) {
5955
+ return { behavior: "deny", message: "Permission deferred (no handler)" };
5956
+ }
5957
+ return await deps.onDefer({ toolName, input, options });
5958
+ }
5959
+ return mapPermissionOutcome(raced, input, options.suggestions);
5960
+ };
5961
+ }
5962
+ function composeSignals(a, b) {
5963
+ if (a.aborted)
5964
+ return a;
5965
+ if (b.aborted)
5966
+ return b;
5967
+ const controller = new AbortController;
5968
+ const onAbort = () => controller.abort();
5969
+ a.addEventListener("abort", onAbort, { once: true });
5970
+ b.addEventListener("abort", onAbort, { once: true });
5971
+ return controller.signal;
5972
+ }
5973
+ function mapPermissionOutcome(response, input, suggestions) {
5974
+ const outcome = response.outcome;
5975
+ if (outcome.outcome === "cancelled") {
5976
+ throw new Error("Tool use aborted");
5977
+ }
5978
+ if (outcome.optionId === "allow") {
5979
+ return { behavior: "allow", updatedInput: input };
5980
+ }
5981
+ if (outcome.optionId === "allow_always") {
5982
+ return {
5983
+ behavior: "allow",
5984
+ updatedInput: input,
5985
+ updatedPermissions: suggestions !== undefined && suggestions.length > 0 ? suggestions : [
5986
+ {
5987
+ type: "addRules",
5988
+ rules: [{ toolName: "unknown" }],
5989
+ behavior: "allow",
5990
+ destination: "session"
5991
+ }
5992
+ ]
5993
+ };
5994
+ }
5995
+ return { behavior: "deny", message: "User refused permission to run tool" };
5996
+ }
5997
+ var DEFAULT_OPTIONS;
5998
+ var init_permission_bridge = __esm(() => {
5999
+ init_errors();
6000
+ init_permission_rpc();
6001
+ DEFAULT_OPTIONS = [
6002
+ { kind: "allow_always", name: "Always allow this tool", optionId: "allow_always" },
6003
+ { kind: "allow_once", name: "Allow", optionId: "allow" },
6004
+ { kind: "reject_once", name: "Reject", optionId: "reject" }
6005
+ ];
6006
+ });
6007
+
5787
6008
  // src/wire/policy.ts
5788
6009
  function parseCanonicalPattern(input) {
5789
6010
  if (input.length === 0) {
@@ -51522,7 +51743,7 @@ async function safeReply2(sdk, requestId, body) {
51522
51743
  }
51523
51744
  var CANONICAL_OPTIONS;
51524
51745
  var init_permission_bridge2 = __esm(() => {
51525
- init_permission_bridge();
51746
+ init_permission_rpc();
51526
51747
  CANONICAL_OPTIONS = [
51527
51748
  { optionId: "allow", label: "Allow" },
51528
51749
  { optionId: "allow_always", label: "Always allow" },
@@ -52194,11 +52415,17 @@ function apiStopReason(reason) {
52194
52415
  function turnFailed(hook) {
52195
52416
  const error = text(hook.error) ?? "unknown";
52196
52417
  const details = text(hook.error_details);
52418
+ return turnFailedWith(failureReason(error), details === undefined ? `StopFailure ${error}` : `StopFailure ${error}: ${details}`.slice(0, TITLE_MAX * 2));
52419
+ }
52420
+ function turnFailedWith(reason, detail) {
52197
52421
  return draft("turn_failed", {
52198
- reason: FAILURE_REASONS[error] ?? "api_error",
52199
- detail: details === undefined ? `StopFailure ${error}` : `StopFailure ${error}: ${details}`.slice(0, TITLE_MAX * 2)
52422
+ reason,
52423
+ detail
52200
52424
  });
52201
52425
  }
52426
+ function failureReason(error) {
52427
+ return FAILURE_REASONS[error] ?? "api_error";
52428
+ }
52202
52429
  function sessionEnded(reason) {
52203
52430
  return draft("session_ended", { reason });
52204
52431
  }
@@ -52365,11 +52592,30 @@ async function buildPaneSettings(options) {
52365
52592
  }
52366
52593
  ]
52367
52594
  ]));
52368
- return {
52595
+ const settings = {
52369
52596
  ...base,
52370
52597
  disableAllHooks: false,
52598
+ autoContinueAtUsageLimit: false,
52371
52599
  hooks
52372
52600
  };
52601
+ if (options.rules !== undefined) {
52602
+ settings.permissions = mergePermissions(base.permissions, options.rules);
52603
+ }
52604
+ return settings;
52605
+ }
52606
+ function mergePermissions(base, rules) {
52607
+ const permissions = isRecord2(base) ? base : {};
52608
+ const merge = (list) => {
52609
+ const own = permissions[list];
52610
+ const entries = Array.isArray(own) ? own.filter((entry) => typeof entry === "string") : [];
52611
+ return [...new Set([...entries, ...rules[list]])];
52612
+ };
52613
+ return {
52614
+ ...permissions,
52615
+ allow: merge("allow"),
52616
+ deny: merge("deny"),
52617
+ ask: merge("ask")
52618
+ };
52373
52619
  }
52374
52620
  async function writePaneSettings(path, options) {
52375
52621
  const settings = await buildPaneSettings(options);
@@ -52558,8 +52804,9 @@ var init_input = __esm(() => {
52558
52804
  });
52559
52805
 
52560
52806
  // src/live/pane.ts
52561
- import { mkdir as mkdir3, readFile as readFile3, realpath as realpath3, rename as rename4, writeFile as writeFile2 } from "fs/promises";
52562
- import { join as join6 } from "path";
52807
+ import { randomUUID as randomUUID5 } from "crypto";
52808
+ import { mkdir as mkdir3, readFile as readFile3, realpath as realpath3, rename as rename4, stat as stat2, writeFile as writeFile2 } from "fs/promises";
52809
+ import { dirname as dirname4, join as join6, resolve as resolve5 } from "path";
52563
52810
  function supportsPasteBufferS(versionOutput) {
52564
52811
  const match = /^tmux (?:next-)?(\d+)\.(\d+)[a-z]?$/.exec(versionOutput.trim());
52565
52812
  if (match === null) {
@@ -52585,7 +52832,7 @@ async function createPane(options) {
52585
52832
  }
52586
52833
  const env = paneEnv(options);
52587
52834
  const cwd = await realpath3(options.cwd);
52588
- await seedTrust(env.CLAUDE_CONFIG_DIR === undefined ? [env.HOME] : [env.HOME, env.CLAUDE_CONFIG_DIR], cwd);
52835
+ await seedTrust(env.CLAUDE_CONFIG_DIR === undefined ? [env.HOME] : [env.HOME, env.CLAUDE_CONFIG_DIR], await trustRoots(cwd), approvedKeySuffix(options.env?.ANTHROPIC_API_KEY));
52589
52836
  const { PATH: path, TERM: term, ...sessionEnv } = env;
52590
52837
  const created = await run(pane.socket, [
52591
52838
  "start-server",
@@ -52674,11 +52921,27 @@ function paneEnv(options) {
52674
52921
  ...env,
52675
52922
  HOME: options.home,
52676
52923
  CI: "1",
52677
- TERM: "xterm-256color"
52924
+ TERM: "xterm-256color",
52925
+ CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1"
52678
52926
  };
52679
52927
  }
52680
52928
  function launchArgv(options) {
52681
52929
  const argv = [options.claudeBin ?? "claude", "--settings", options.settingsPath, "--setting-sources", ""];
52930
+ if (options.permissionMode !== undefined) {
52931
+ argv.push("--permission-mode", options.permissionMode);
52932
+ }
52933
+ if (options.mcpConfig !== undefined) {
52934
+ argv.push("--mcp-config", options.mcpConfig, "--strict-mcp-config");
52935
+ }
52936
+ if (options.systemPromptFile !== undefined) {
52937
+ argv.push("--append-system-prompt-file", options.systemPromptFile);
52938
+ }
52939
+ if (options.tools !== undefined) {
52940
+ argv.push("--tools", options.tools.join(","));
52941
+ }
52942
+ if (options.disallowedTools !== undefined && options.disallowedTools.length > 0) {
52943
+ argv.push("--disallowedTools", options.disallowedTools.join(","));
52944
+ }
52682
52945
  if (options.model !== undefined) {
52683
52946
  argv.push("--model", options.model);
52684
52947
  }
@@ -52687,26 +52950,129 @@ function launchArgv(options) {
52687
52950
  }
52688
52951
  return argv;
52689
52952
  }
52690
- async function seedTrust(dirs, cwd) {
52953
+ async function trustRoots(cwd) {
52954
+ const roots = new Set([cwd]);
52955
+ const repository = await findGitEntry(cwd);
52956
+ if (repository === null) {
52957
+ return [...roots];
52958
+ }
52959
+ roots.add(repository.root);
52960
+ if (repository.isFile) {
52961
+ const main = await worktreeMainRoot(repository.root);
52962
+ if (main !== null) {
52963
+ roots.add(main);
52964
+ }
52965
+ }
52966
+ return [...roots];
52967
+ }
52968
+ async function findGitEntry(start) {
52969
+ let dir = start;
52970
+ while (true) {
52971
+ const entry = await statIfPresent(join6(dir, ".git"));
52972
+ if (entry !== null) {
52973
+ return {
52974
+ root: dir,
52975
+ isFile: entry.isFile()
52976
+ };
52977
+ }
52978
+ const parent = dirname4(dir);
52979
+ if (parent === dir) {
52980
+ return null;
52981
+ }
52982
+ dir = parent;
52983
+ }
52984
+ }
52985
+ async function worktreeMainRoot(root) {
52986
+ const gitfile = await readTextIfPresent(join6(root, ".git"));
52987
+ const target = gitfile === null ? undefined : /^gitdir:\s*(.+?)\s*$/m.exec(gitfile)?.[1];
52988
+ if (target === undefined) {
52989
+ return null;
52990
+ }
52991
+ const gitdir = resolve5(root, target);
52992
+ const commondir = await readTextIfPresent(join6(gitdir, "commondir"));
52993
+ if (commondir === null) {
52994
+ return null;
52995
+ }
52996
+ return realpathIfPresent(dirname4(resolve5(gitdir, commondir.trim())));
52997
+ }
52998
+ async function statIfPresent(path) {
52999
+ try {
53000
+ return await stat2(path);
53001
+ } catch (error) {
53002
+ if (isAbsent(error)) {
53003
+ return null;
53004
+ }
53005
+ throw error;
53006
+ }
53007
+ }
53008
+ async function readTextIfPresent(path) {
53009
+ try {
53010
+ return await readFile3(path, "utf8");
53011
+ } catch (error) {
53012
+ if (isAbsent(error)) {
53013
+ return null;
53014
+ }
53015
+ throw error;
53016
+ }
53017
+ }
53018
+ async function realpathIfPresent(path) {
53019
+ try {
53020
+ return await realpath3(path);
53021
+ } catch (error) {
53022
+ if (isAbsent(error)) {
53023
+ return null;
53024
+ }
53025
+ throw error;
53026
+ }
53027
+ }
53028
+ function isAbsent(error) {
53029
+ return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
53030
+ }
53031
+ function approvedKeySuffix(apiKey) {
53032
+ return apiKey === undefined || apiKey === "" ? undefined : apiKey.slice(-20);
53033
+ }
53034
+ function approveKey(current, suffix) {
53035
+ const responses = isRecord3(current) ? current : {};
53036
+ const approved = stringList(responses.approved);
53037
+ return {
53038
+ ...responses,
53039
+ approved: approved.includes(suffix) ? approved : [...approved, suffix],
53040
+ rejected: stringList(responses.rejected)
53041
+ };
53042
+ }
53043
+ function stringList(value) {
53044
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
53045
+ }
53046
+ function seedTrust(dirs, roots, keySuffix) {
53047
+ const seeded = seeding.then(() => writeTrust(dirs, roots, keySuffix));
53048
+ seeding = seeded.catch(() => {
53049
+ return;
53050
+ });
53051
+ return seeded;
53052
+ }
53053
+ async function writeTrust(dirs, roots, keySuffix) {
52691
53054
  for (const dir of dirs) {
52692
53055
  const path = join6(dir, ".claude.json");
52693
53056
  const config = await readConfig(path);
52694
53057
  const projects = isRecord3(config.projects) ? config.projects : {};
52695
- const project = isRecord3(projects[cwd]) ? projects[cwd] : {};
52696
- projects[cwd] = {
52697
- allowedTools: [],
52698
- ...project,
52699
- hasTrustDialogAccepted: true,
52700
- hasCompletedProjectOnboarding: true
52701
- };
53058
+ for (const root of roots) {
53059
+ const project = isRecord3(projects[root]) ? projects[root] : {};
53060
+ projects[root] = {
53061
+ allowedTools: [],
53062
+ ...project,
53063
+ hasTrustDialogAccepted: true,
53064
+ hasCompletedProjectOnboarding: true
53065
+ };
53066
+ }
52702
53067
  const seeded = {
52703
53068
  theme: "dark",
52704
53069
  ...config,
52705
53070
  hasCompletedOnboarding: true,
52706
- projects
53071
+ projects,
53072
+ ...keySuffix === undefined ? {} : { customApiKeyResponses: approveKey(config.customApiKeyResponses, keySuffix) }
52707
53073
  };
52708
53074
  await mkdir3(dir, { recursive: true });
52709
- const staging = `${path}.${process.pid}.tmp`;
53075
+ const staging = `${path}.${randomUUID5()}.tmp`;
52710
53076
  await writeFile2(staging, JSON.stringify(seeded, null, 2), { mode: 384 });
52711
53077
  await rename4(staging, path);
52712
53078
  }
@@ -52791,7 +53157,7 @@ async function failOnPane(pane, stderr) {
52791
53157
  }
52792
53158
  throw new PaneError("tmux_failed", stderr);
52793
53159
  }
52794
- 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;
53160
+ 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, seeding, pasteSFlags;
52795
53161
  var init_pane = __esm(() => {
52796
53162
  init_input();
52797
53163
  SUBMIT_WINDOWS_MS = [328, 656, 1312, 2624];
@@ -52807,11 +53173,225 @@ var init_pane = __esm(() => {
52807
53173
  this.reason = reason;
52808
53174
  }
52809
53175
  };
53176
+ seeding = Promise.resolve();
52810
53177
  pasteSFlags = new Map;
52811
53178
  });
52812
53179
 
53180
+ // src/live/policy.ts
53181
+ function parseToolPolicy(text) {
53182
+ const policy = parseJson2(text);
53183
+ if (!isRecord4(policy) || !Object.keys(policy).every((key) => POLICY_KEYS.includes(key))) {
53184
+ return null;
53185
+ }
53186
+ const rules = {
53187
+ allow: [],
53188
+ deny: [],
53189
+ ask: []
53190
+ };
53191
+ for (const list of RULE_LISTS) {
53192
+ const entries = policy[list] === undefined ? [] : policy[list];
53193
+ if (!Array.isArray(entries) || !entries.every((entry) => typeof entry === "string")) {
53194
+ return null;
53195
+ }
53196
+ for (const entry of entries) {
53197
+ const rule = translatePattern(entry, list);
53198
+ if (rule === null) {
53199
+ return null;
53200
+ }
53201
+ if (!rules[list].includes(rule)) {
53202
+ rules[list].push(rule);
53203
+ }
53204
+ }
53205
+ }
53206
+ return rules;
53207
+ }
53208
+ function translatePattern(pattern, list) {
53209
+ let parsed;
53210
+ try {
53211
+ parsed = parseCanonicalPattern(pattern);
53212
+ } catch (error) {
53213
+ if (error instanceof InvalidPatternError) {
53214
+ return null;
53215
+ }
53216
+ throw error;
53217
+ }
53218
+ if (parsed.mcpPath !== undefined) {
53219
+ return mcpRule(parsed.mcpPath);
53220
+ }
53221
+ const { toolName, argPattern } = parsed;
53222
+ if (!TOOL_NAME.test(toolName)) {
53223
+ return null;
53224
+ }
53225
+ if (argPattern === undefined || argPattern === "*") {
53226
+ return toolName;
53227
+ }
53228
+ const fileTool = FILE_TOOLS.get(toolName);
53229
+ if (fileTool !== undefined && fileTool !== toolName && list === "allow") {
53230
+ return null;
53231
+ }
53232
+ const tool = fileTool ?? toolName;
53233
+ const content = ruleContent(tool, argPattern, list);
53234
+ return isEnforceableContent(tool, content) ? `${tool}(${escapeRuleContent(content)})` : null;
53235
+ }
53236
+ function mcpRule(path) {
53237
+ const wildcardMisplaced = path.some((segment, index) => segment.includes("*") && !(index === 1 && segment === "*"));
53238
+ if (path.length > 2 || wildcardMisplaced || path.some((segment) => /[()]/.test(segment))) {
53239
+ return null;
53240
+ }
53241
+ return `mcp__${path.join("__")}`;
53242
+ }
53243
+ function ruleContent(tool, argPattern, list) {
53244
+ if (tool === "Read" || tool === "Edit") {
53245
+ return anchorPath(argPattern);
53246
+ }
53247
+ return tool === "Bash" ? bashArg(argPattern, list) : argPattern;
53248
+ }
53249
+ function anchorPath(path) {
53250
+ return path.startsWith("/") && !path.startsWith("//") ? `/${path}` : path;
53251
+ }
53252
+ function bashArg(argPattern, list) {
53253
+ const prefix = argPattern.slice(0, -1);
53254
+ if (list !== "allow" || !argPattern.endsWith("*") || prefix.includes("*") || /[:\s]$/.test(prefix)) {
53255
+ return argPattern;
53256
+ }
53257
+ return `${prefix}:*`;
53258
+ }
53259
+ function escapeRuleContent(content) {
53260
+ return content.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
53261
+ }
53262
+ function isEnforceableContent(tool, content) {
53263
+ if (tool === "Bash") {
53264
+ return (!content.includes(":*") || content.endsWith(":*")) && content !== ":*";
53265
+ }
53266
+ if (tool === "Read" || tool === "Edit") {
53267
+ return !content.includes(":*") && (!content.includes("*") || /^\*|\*$|\*\*|\/\*|\*\.|\*\)/.test(content));
53268
+ }
53269
+ return true;
53270
+ }
53271
+ function parseJson2(text) {
53272
+ try {
53273
+ return JSON.parse(text);
53274
+ } catch (error) {
53275
+ if (error instanceof SyntaxError) {
53276
+ return;
53277
+ }
53278
+ throw error;
53279
+ }
53280
+ }
53281
+ function isRecord4(value) {
53282
+ return typeof value === "object" && value !== null && !Array.isArray(value);
53283
+ }
53284
+ var RULE_LISTS, POLICY_KEYS, FILE_TOOLS, TOOL_NAME;
53285
+ var init_policy4 = __esm(() => {
53286
+ init_policy();
53287
+ RULE_LISTS = ["allow", "deny", "ask"];
53288
+ POLICY_KEYS = [...RULE_LISTS, "defaultMode"];
53289
+ FILE_TOOLS = new Map([
53290
+ ["Read", "Read"],
53291
+ ["Edit", "Edit"],
53292
+ ["Write", "Edit"],
53293
+ ["MultiEdit", "Edit"],
53294
+ ["NotebookEdit", "Edit"],
53295
+ ["Glob", "Read"],
53296
+ ["Grep", "Read"]
53297
+ ]);
53298
+ TOOL_NAME = /^[A-Z][A-Za-z0-9_]*$/;
53299
+ });
53300
+
53301
+ // src/live/start-flags.ts
53302
+ function parsePaneFeatures(content, permissionMode) {
53303
+ let json;
53304
+ try {
53305
+ json = JSON.parse(content);
53306
+ } catch {
53307
+ return null;
53308
+ }
53309
+ const parsed = FeaturesSchema.safeParse(json);
53310
+ if (!parsed.success || parsed.data.simple === true) {
53311
+ return null;
53312
+ }
53313
+ const names = parsed.data.tools;
53314
+ if (names !== undefined && (names.length === 0 || !names.every((name) => TOOL_NAME2.test(name)))) {
53315
+ return null;
53316
+ }
53317
+ const translated = translateFeaturesToClaude(parsed.data, permissionMode);
53318
+ return { tools: translated.tools, disallowedTools: translated.disallowedTools, env: translated.env };
53319
+ }
53320
+ function parseStartExtras(args) {
53321
+ for (const key of Object.keys(args)) {
53322
+ if (!START_FLAGS.has(key)) {
53323
+ return null;
53324
+ }
53325
+ }
53326
+ const permissionMode = parsePermissionMode(args["permission-mode"]);
53327
+ const oneShot = parseOneShot(args["one-shot"]);
53328
+ const deadlineSeconds = parseSeconds(args["deadline-seconds"], 1);
53329
+ const retainFailedSeconds = parseSeconds(args["retain-failed-seconds"], 0);
53330
+ if (permissionMode === null || oneShot === null || deadlineSeconds === null || retainFailedSeconds === null) {
53331
+ return null;
53332
+ }
53333
+ return {
53334
+ permissionMode,
53335
+ oneShot,
53336
+ deadlineSeconds,
53337
+ retainFailedSeconds: retainFailedSeconds ?? 0
53338
+ };
53339
+ }
53340
+ function parsePermissionMode(value) {
53341
+ if (value === undefined) {
53342
+ return;
53343
+ }
53344
+ return value === "default" || value === "dontAsk" ? value : null;
53345
+ }
53346
+ function parseOneShot(value) {
53347
+ if (value === undefined || value === "0") {
53348
+ return false;
53349
+ }
53350
+ return value === "1" ? true : null;
53351
+ }
53352
+ function parseSeconds(value, min) {
53353
+ if (value === undefined) {
53354
+ return;
53355
+ }
53356
+ if (!DIGITS.test(value)) {
53357
+ return null;
53358
+ }
53359
+ const seconds = Number(value);
53360
+ return seconds >= min && seconds <= MAX_SECONDS ? seconds : null;
53361
+ }
53362
+ var START_FLAGS, START_FILE_FLAGS, TOOL_NAME2, MAX_SECONDS = 2147483, DIGITS;
53363
+ var init_start_flags = __esm(() => {
53364
+ init_features();
53365
+ init_schemas3();
53366
+ START_FLAGS = new Set([
53367
+ "pane",
53368
+ "cwd",
53369
+ "settings-base",
53370
+ "env-file",
53371
+ "model",
53372
+ "resume",
53373
+ "mcp-config",
53374
+ "tool-policy",
53375
+ "system-prompt-file",
53376
+ "features",
53377
+ "permission-mode",
53378
+ "one-shot",
53379
+ "deadline-seconds",
53380
+ "retain-failed-seconds"
53381
+ ]);
53382
+ START_FILE_FLAGS = [
53383
+ "env-file",
53384
+ "tool-policy",
53385
+ "system-prompt-file",
53386
+ "mcp-config",
53387
+ "features"
53388
+ ];
53389
+ TOOL_NAME2 = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
53390
+ DIGITS = /^[0-9]+$/;
53391
+ });
53392
+
52813
53393
  // src/live/transcript.ts
52814
- import { stat as stat2 } from "fs/promises";
53394
+ import { stat as stat3 } from "fs/promises";
52815
53395
 
52816
53396
  class TranscriptTail {
52817
53397
  path;
@@ -52875,7 +53455,7 @@ function classifyInterrupt(record) {
52875
53455
  return null;
52876
53456
  }
52877
53457
  const [block] = content;
52878
- if (!isRecord4(block) || block.type !== "text") {
53458
+ if (!isRecord5(block) || block.type !== "text") {
52879
53459
  return null;
52880
53460
  }
52881
53461
  if (block.text === INTERRUPT_MARKER) {
@@ -52886,6 +53466,29 @@ function classifyInterrupt(record) {
52886
53466
  }
52887
53467
  return null;
52888
53468
  }
53469
+ function classifyApiError(record) {
53470
+ if (record.type !== "assistant" || !isMainChain(record) || record.isApiErrorMessage !== true) {
53471
+ return null;
53472
+ }
53473
+ const error = typeof record.error === "string" ? record.error : "unknown";
53474
+ const status = typeof record.apiErrorStatus === "number" ? record.apiErrorStatus : null;
53475
+ const message = assistantBlocks(record).filter((block) => block.kind === "text").map((block) => block.text).join(`
53476
+ `);
53477
+ return {
53478
+ reason: apiErrorReason(error, status, message),
53479
+ detail: `API error ${status ?? error}: ${message.slice(0, API_ERROR_DETAIL_MAX)}`
53480
+ };
53481
+ }
53482
+ function apiErrorReason(error, status, message) {
53483
+ if (error === "rate_limit" && USAGE_LIMIT_TEXT.test(message) && LIMIT_RESETS.test(message) && !NOT_A_USAGE_LIMIT.test(message)) {
53484
+ return "usage_limit";
53485
+ }
53486
+ const reason = failureReason(error);
53487
+ if (reason !== "api_error" || status === null) {
53488
+ return reason;
53489
+ }
53490
+ return STATUS_REASONS[status] ?? reason;
53491
+ }
52889
53492
 
52890
53493
  class UsageLedger {
52891
53494
  counted = new Map;
@@ -52894,7 +53497,7 @@ class UsageLedger {
52894
53497
  if (record.type !== "assistant" || !isMainChain(record) || message === null) {
52895
53498
  return null;
52896
53499
  }
52897
- if (typeof message.id !== "string" || !isRecord4(message.usage)) {
53500
+ if (typeof message.id !== "string" || !isRecord5(message.usage)) {
52898
53501
  return null;
52899
53502
  }
52900
53503
  const seen = this.counted.get(message.id) ?? emptyCounts();
@@ -52921,7 +53524,7 @@ function assistantBlocks(record) {
52921
53524
  }
52922
53525
  const blocks = [];
52923
53526
  for (const block of content) {
52924
- if (!isRecord4(block)) {
53527
+ if (!isRecord5(block)) {
52925
53528
  continue;
52926
53529
  }
52927
53530
  if (block.type === "text" && typeof block.text === "string" && block.text !== "") {
@@ -52941,12 +53544,12 @@ function isMainChain(record) {
52941
53544
  return record.isSidechain !== true;
52942
53545
  }
52943
53546
  function messageOf(record) {
52944
- return isRecord4(record.message) ? record.message : null;
53547
+ return isRecord5(record.message) ? record.message : null;
52945
53548
  }
52946
53549
  function parseLine(line) {
52947
53550
  try {
52948
53551
  const value = JSON.parse(line);
52949
- return isRecord4(value) ? value : null;
53552
+ return isRecord5(value) ? value : null;
52950
53553
  } catch (error) {
52951
53554
  if (error instanceof SyntaxError) {
52952
53555
  return null;
@@ -52956,7 +53559,7 @@ function parseLine(line) {
52956
53559
  }
52957
53560
  async function fileSize(path) {
52958
53561
  try {
52959
- return (await stat2(path)).size;
53562
+ return (await stat3(path)).size;
52960
53563
  } catch (error) {
52961
53564
  if (error.code === "ENOENT") {
52962
53565
  return null;
@@ -52984,17 +53587,27 @@ function emptyCounts() {
52984
53587
  cacheCreationTokens: 0
52985
53588
  };
52986
53589
  }
52987
- function isRecord4(value) {
53590
+ function isRecord5(value) {
52988
53591
  return typeof value === "object" && value !== null && !Array.isArray(value);
52989
53592
  }
52990
- var INTERRUPT_MARKER = "[Request interrupted by user]", INTERRUPT_TOOL_MARKER = "[Request interrupted by user for tool use]", NEWLINE = 10;
52991
- var init_transcript = () => {};
53593
+ var INTERRUPT_MARKER = "[Request interrupted by user]", INTERRUPT_TOOL_MARKER = "[Request interrupted by user for tool use]", NEWLINE = 10, USAGE_LIMIT_TEXT, LIMIT_RESETS, NOT_A_USAGE_LIMIT, STATUS_REASONS, API_ERROR_DETAIL_MAX = 200;
53594
+ var init_transcript = __esm(() => {
53595
+ init_events2();
53596
+ USAGE_LIMIT_TEXT = /\b(usage|session|weekly) limit\b|\bhit your\b[^.\n]*\blimit\b|\bout of extra usage\b/i;
53597
+ LIMIT_RESETS = /\bresets?\b/i;
53598
+ NOT_A_USAGE_LIMIT = /\bnot your (usage )?limit\b/i;
53599
+ STATUS_REASONS = {
53600
+ 429: "rate_limit",
53601
+ 401: "auth_error"
53602
+ };
53603
+ });
52992
53604
 
52993
53605
  // src/live/daemon.ts
52994
- import { randomUUID as randomUUID5 } from "crypto";
52995
- import { chmod, mkdir as mkdir4, readFile as readFile4, stat as stat3, unlink as unlink2 } from "fs/promises";
53606
+ import { randomUUID as randomUUID6 } from "crypto";
53607
+ import { renameSync as renameSync2, rmSync as rmSync2, writeFileSync } from "fs";
53608
+ import { chmod, lstat as lstat2, mkdir as mkdir4, readFile as readFile4, realpath as realpath4, unlink as unlink2 } from "fs/promises";
52996
53609
  import { createServer } from "net";
52997
- import { dirname as dirname4, isAbsolute as isAbsolute4, join as join7 } from "path";
53610
+ import { dirname as dirname5, isAbsolute as isAbsolute4, join as join7 } from "path";
52998
53611
  function exitCodeFor(code) {
52999
53612
  return EXIT_CODES[code];
53000
53613
  }
@@ -53157,6 +53770,9 @@ class LivePane {
53157
53770
  gen;
53158
53771
  ingestUrl;
53159
53772
  daemon;
53773
+ extras;
53774
+ mcpConfig;
53775
+ paneDir;
53160
53776
  started = new Latch;
53161
53777
  submitted = new Latch;
53162
53778
  turnClosed = new Latch;
@@ -53179,17 +53795,29 @@ class LivePane {
53179
53795
  skippedLines = 0;
53180
53796
  lastAssistantText;
53181
53797
  lastStopReason = null;
53798
+ apiError;
53182
53799
  settled = false;
53183
53800
  lastActivityAt = Date.now();
53184
53801
  sessionEndReason;
53185
53802
  timers = [];
53803
+ heartbeatTimer;
53804
+ deadlineTimer;
53805
+ retentionTimer;
53806
+ interruptTimer;
53807
+ interrupt;
53808
+ isRetaining = false;
53809
+ ending = false;
53810
+ teardown;
53186
53811
  monitoring = false;
53187
53812
  closed = false;
53188
- constructor(pane, gen, ingestUrl, token, authValues, daemon) {
53813
+ constructor(pane, gen, ingestUrl, token, authValues, daemon, extras, mcpConfig, paneDir) {
53189
53814
  this.pane = pane;
53190
53815
  this.gen = gen;
53191
53816
  this.ingestUrl = ingestUrl;
53192
53817
  this.daemon = daemon;
53818
+ this.extras = extras;
53819
+ this.mcpConfig = mcpConfig;
53820
+ this.paneDir = paneDir;
53193
53821
  this.token = token;
53194
53822
  this.secrets = [token, ...authValues.filter((value) => value.length >= MIN_SECRET_LENGTH)];
53195
53823
  this.queue = new EventQueue({
@@ -53198,7 +53826,11 @@ class LivePane {
53198
53826
  bearer: () => this.token,
53199
53827
  secrets: () => this.secrets,
53200
53828
  timings: () => daemon.timings,
53201
- onStaleGen: () => void this.abandon(),
53829
+ onStaleGen: () => {
53830
+ if (!this.isRetaining) {
53831
+ this.abandon();
53832
+ }
53833
+ },
53202
53834
  logger: daemon.logger
53203
53835
  });
53204
53836
  }
@@ -53208,13 +53840,16 @@ class LivePane {
53208
53840
  get busy() {
53209
53841
  return this.turn !== undefined || this.compacting;
53210
53842
  }
53843
+ get refusesInput() {
53844
+ return this.ending || this.interrupt !== undefined;
53845
+ }
53211
53846
  begin() {
53212
53847
  const timings = this.daemon.timings;
53213
53848
  this.timers = [
53214
53849
  setInterval(() => void this.guard("transcript", () => this.readTranscript()), timings.transcriptPollMs),
53215
- setInterval(() => void this.guard("monitor", () => this.monitor()), timings.monitorMs),
53216
- setInterval(() => void this.guard("heartbeat", () => this.beat()), timings.heartbeatMs)
53850
+ setInterval(() => void this.guard("monitor", () => this.monitor()), timings.monitorMs)
53217
53851
  ];
53852
+ this.heartbeatTimer = setInterval(() => void this.guard("heartbeat", () => this.beat()), timings.heartbeatMs);
53218
53853
  }
53219
53854
  rotate(token) {
53220
53855
  this.token = token;
@@ -53280,24 +53915,28 @@ class LivePane {
53280
53915
  }
53281
53916
  this.closeOpenTools();
53282
53917
  this.clearDialog();
53918
+ const message = text(draft.payload.lastAssistantMessage);
53919
+ if (draft.type === "turn_completed" && message !== undefined) {
53920
+ this.persistLastAssistantMessage(message);
53921
+ }
53283
53922
  this.queue.push(draft);
53284
53923
  this.turn = undefined;
53924
+ clearTimeout(this.deadlineTimer);
53925
+ this.deadlineTimer = undefined;
53926
+ this.lastActivityAt = Date.now();
53285
53927
  this.lastCloseCause = cause;
53286
53928
  this.turnClosed.fire();
53929
+ this.afterTurn(draft.type);
53287
53930
  }
53288
53931
  clearDialog() {
53289
53932
  this.dialog?.abort.abort();
53290
53933
  this.dialog = undefined;
53291
53934
  }
53292
53935
  interruptedTurn() {
53293
- return turnCompleted("interrupted", this.lastAssistantText);
53936
+ return turnCompleted("interrupted", this.lastAssistantText ?? "");
53294
53937
  }
53295
- async stop() {
53296
- this.close();
53297
- await killPane(this.pane);
53298
- this.queue.push(sessionEnded("stopped"));
53299
- await this.queue.drain(STOP_DRAIN_MS);
53300
- this.queue.stop();
53938
+ stop(reason = "stopped") {
53939
+ return this.tearDown(() => this.finish(reason, () => killPane(this.pane)));
53301
53940
  }
53302
53941
  async dispose() {
53303
53942
  this.close();
@@ -53309,7 +53948,130 @@ class LivePane {
53309
53948
  for (const timer of this.timers.splice(0)) {
53310
53949
  clearInterval(timer);
53311
53950
  }
53951
+ clearInterval(this.heartbeatTimer);
53952
+ clearTimeout(this.deadlineTimer);
53953
+ clearTimeout(this.retentionTimer);
53954
+ clearTimeout(this.interruptTimer);
53312
53955
  this.clearDialog();
53956
+ this.removeMcpConfig();
53957
+ }
53958
+ tearDown(run) {
53959
+ this.teardown ??= run();
53960
+ return this.teardown;
53961
+ }
53962
+ async finish(reason, kill) {
53963
+ try {
53964
+ this.close();
53965
+ await kill();
53966
+ this.queue.push(sessionEnded(reason));
53967
+ await this.queue.drain(STOP_DRAIN_MS);
53968
+ this.queue.stop();
53969
+ } finally {
53970
+ this.daemon.remove(this);
53971
+ }
53972
+ }
53973
+ afterTurn(type) {
53974
+ if (this.closed || this.ending) {
53975
+ return;
53976
+ }
53977
+ const { oneShot, retainFailedSeconds } = this.extras;
53978
+ if (type === "turn_completed" && oneShot) {
53979
+ this.ending = true;
53980
+ this.guard("end", () => this.endAfterDelivery(0));
53981
+ return;
53982
+ }
53983
+ if (type !== "turn_failed" || !oneShot && retainFailedSeconds === 0) {
53984
+ return;
53985
+ }
53986
+ this.ending = true;
53987
+ if (retainFailedSeconds > 0) {
53988
+ this.isRetaining = true;
53989
+ clearInterval(this.heartbeatTimer);
53990
+ }
53991
+ this.guard("end", () => this.endAfterDelivery(retainFailedSeconds * this.daemon.timings.secondMs));
53992
+ }
53993
+ async endAfterDelivery(delayMs) {
53994
+ await this.queue.drain(this.daemon.timings.deliveryDrainMs);
53995
+ if (this.closed) {
53996
+ return;
53997
+ }
53998
+ if (delayMs > 0) {
53999
+ await new Promise((resolve) => {
54000
+ this.retentionTimer = setTimeout(resolve, delayMs);
54001
+ });
54002
+ if (this.closed) {
54003
+ return;
54004
+ }
54005
+ }
54006
+ await this.end("stopped");
54007
+ }
54008
+ end(reason) {
54009
+ return this.tearDown(() => this.finish(reason, () => this.killLogged()));
54010
+ }
54011
+ async onDeadline(promptId, seconds) {
54012
+ if (this.closed || this.turn?.promptId !== promptId) {
54013
+ return;
54014
+ }
54015
+ this.closeTurn(turnFailedWith("deadline", `The turn ran longer than ${seconds} seconds.`), "deadline");
54016
+ const interrupt = {
54017
+ sent: false,
54018
+ marked: false
54019
+ };
54020
+ this.interrupt = interrupt;
54021
+ this.interruptTimer = setTimeout(() => this.endInterrupt(), this.daemon.timings.cancelTimeoutMs);
54022
+ try {
54023
+ await cancelPane(this.pane);
54024
+ interrupt.sent = true;
54025
+ this.settleInterrupt();
54026
+ } catch (error) {
54027
+ if (!(error instanceof PaneError)) {
54028
+ throw error;
54029
+ }
54030
+ this.daemon.logger.warn("tmuxd could not interrupt a turn past its deadline", {
54031
+ pane: this.id,
54032
+ reason: error.reason
54033
+ });
54034
+ }
54035
+ }
54036
+ settleInterrupt() {
54037
+ if (this.interrupt?.sent === true && this.interrupt.marked) {
54038
+ this.endInterrupt();
54039
+ }
54040
+ }
54041
+ endInterrupt() {
54042
+ clearTimeout(this.interruptTimer);
54043
+ this.interruptTimer = undefined;
54044
+ this.interrupt = undefined;
54045
+ }
54046
+ removeMcpConfig() {
54047
+ if (this.mcpConfig === undefined) {
54048
+ return;
54049
+ }
54050
+ try {
54051
+ rmSync2(this.mcpConfig, { force: true });
54052
+ } catch (error) {
54053
+ if (!isErrno(error)) {
54054
+ throw error;
54055
+ }
54056
+ this.daemon.logger.error("tmuxd could not remove the mcp-config", { pane: this.id, code: error.code });
54057
+ }
54058
+ }
54059
+ persistLastAssistantMessage(message) {
54060
+ const path = join7(this.paneDir, LAST_MESSAGE_FILE);
54061
+ const temp = `${path}.${randomUUID6()}.tmp`;
54062
+ try {
54063
+ writeFileSync(temp, redactDeep(message, this.secrets), { mode: 384, flag: "wx" });
54064
+ renameSync2(temp, path);
54065
+ } catch (error) {
54066
+ if (!isErrno(error)) {
54067
+ throw error;
54068
+ }
54069
+ rmSync2(temp, { force: true });
54070
+ this.daemon.logger.error("tmuxd could not persist the last assistant message", {
54071
+ pane: this.id,
54072
+ code: error.code
54073
+ });
54074
+ }
53313
54075
  }
53314
54076
  async onSessionStart(payload) {
53315
54077
  const source = text(payload.source);
@@ -53333,20 +54095,26 @@ class LivePane {
53333
54095
  return;
53334
54096
  }
53335
54097
  this.turn = {
53336
- promptId: this.pendingPromptId ?? text(payload.prompt_id) ?? randomUUID5()
54098
+ promptId: this.pendingPromptId ?? text(payload.prompt_id) ?? randomUUID6()
53337
54099
  };
53338
54100
  this.pendingPromptId = undefined;
53339
54101
  this.settled = false;
53340
54102
  this.lastAssistantText = undefined;
53341
54103
  this.lastStopReason = null;
54104
+ this.apiError = undefined;
53342
54105
  this.queue.push(turnStarted(this.turn.promptId));
53343
54106
  this.submitted.fire();
54107
+ const seconds = this.extras.deadlineSeconds;
54108
+ if (seconds !== undefined) {
54109
+ const promptId = this.turn.promptId;
54110
+ this.deadlineTimer = setTimeout(() => void this.guard("deadline", () => this.onDeadline(promptId, seconds)), seconds * this.daemon.timings.secondMs);
54111
+ }
53344
54112
  }
53345
54113
  onPreToolUse(payload) {
53346
54114
  if (this.turn === undefined) {
53347
54115
  return;
53348
54116
  }
53349
- const toolCallId = text(payload.tool_use_id) ?? randomUUID5();
54117
+ const toolCallId = text(payload.tool_use_id) ?? randomUUID6();
53350
54118
  this.openTools.set(toolCallId, {
53351
54119
  name: text(payload.tool_name),
53352
54120
  input: payload.tool_input
@@ -53355,9 +54123,22 @@ class LivePane {
53355
54123
  this.queue.push(toolCallBegin(payload, toolCallId));
53356
54124
  }
53357
54125
  async onPermissionRequest(payload, signal) {
54126
+ if (this.extras.permissionMode === "dontAsk") {
54127
+ this.daemon.logger.debug("tmuxd denied a permission request in dontAsk mode", {
54128
+ pane: this.id,
54129
+ tool: text(payload.tool_name)
54130
+ });
54131
+ return {
54132
+ ok: true,
54133
+ decision: {
54134
+ behavior: "deny",
54135
+ message: UNATTENDED_DENIAL
54136
+ }
54137
+ };
54138
+ }
53358
54139
  const correlated = this.findOpenTool(payload);
53359
54140
  const dialog = {
53360
- toolCallId: correlated ?? randomUUID5(),
54141
+ toolCallId: correlated ?? randomUUID6(),
53361
54142
  correlated: correlated !== undefined,
53362
54143
  toolName: text(payload.tool_name),
53363
54144
  input: payload.tool_input,
@@ -53481,7 +54262,10 @@ class LivePane {
53481
54262
  return;
53482
54263
  }
53483
54264
  await this.syncTranscript();
53484
- this.closeTurn(turnCompleted(apiStopReason(this.lastStopReason), text(payload.last_assistant_message) ?? this.lastAssistantText), "stop");
54265
+ this.closeTurn(this.endOfTurn(turnCompleted(apiStopReason(this.lastStopReason), text(payload.last_assistant_message) ?? this.lastAssistantText ?? "")), "stop");
54266
+ }
54267
+ endOfTurn(completed) {
54268
+ return this.apiError === undefined ? completed : turnFailedWith(this.apiError.reason, this.apiError.detail);
53485
54269
  }
53486
54270
  async onStopFailure(payload) {
53487
54271
  if (this.turn === undefined) {
@@ -53535,13 +54319,19 @@ class LivePane {
53535
54319
  }
53536
54320
  this.lastActivityAt = Date.now();
53537
54321
  if (classifyInterrupt(record) !== null) {
53538
- this.closeTurn(this.interruptedTurn(), "marker");
54322
+ if (this.interrupt === undefined) {
54323
+ this.closeTurn(this.interruptedTurn(), "marker");
54324
+ } else {
54325
+ this.interrupt.marked = true;
54326
+ this.settleInterrupt();
54327
+ }
53539
54328
  this.clearDialog();
53540
54329
  continue;
53541
54330
  }
54331
+ const failure = classifyApiError(record);
53542
54332
  for (const block of assistantBlocks(record)) {
53543
54333
  this.queue.push(block.kind === "text" ? outputChunk(block.text) : thinkingChunk(block.text));
53544
- if (block.kind === "text") {
54334
+ if (block.kind === "text" && failure === null) {
53545
54335
  this.lastAssistantText = block.text;
53546
54336
  }
53547
54337
  }
@@ -53555,6 +54345,14 @@ class LivePane {
53555
54345
  } else if (record.type === "user") {
53556
54346
  this.settled = false;
53557
54347
  }
54348
+ if (failure !== null) {
54349
+ this.apiError = failure;
54350
+ if (failure.reason === "usage_limit") {
54351
+ this.closeTurn(turnFailedWith(failure.reason, failure.detail), "api_error");
54352
+ }
54353
+ } else if (record.type === "assistant" || record.type === "user" && record.isMeta !== true) {
54354
+ this.apiError = undefined;
54355
+ }
53558
54356
  }
53559
54357
  }
53560
54358
  async monitor() {
@@ -53563,21 +54361,33 @@ class LivePane {
53563
54361
  }
53564
54362
  this.monitoring = true;
53565
54363
  try {
53566
- if (!await this.alive()) {
54364
+ const alive = await this.alive();
54365
+ if (this.closed) {
54366
+ return;
54367
+ }
54368
+ if (!alive) {
53567
54369
  await this.onDeath();
53568
54370
  return;
53569
54371
  }
53570
54372
  const quiet = Date.now() - this.lastActivityAt >= this.daemon.timings.watchdogQuietMs;
53571
54373
  const idle = this.dialog === undefined && this.openTools.size === 0 && !this.compacting && this.settled;
53572
54374
  if (this.turn !== undefined && quiet && idle) {
53573
- this.closeTurn(turnCompleted("watchdog", this.lastAssistantText), "watchdog");
54375
+ this.closeTurn(this.endOfTurn(turnCompleted("watchdog", this.lastAssistantText ?? "")), "watchdog");
54376
+ }
54377
+ const idleTimeoutMs = this.daemon.timings.idleTimeoutMs;
54378
+ const unoccupied = this.turn === undefined && this.pendingPromptId === undefined && this.dialog === undefined && !this.compacting && !this.isRetaining;
54379
+ if (idleTimeoutMs > 0 && unoccupied && Date.now() - this.lastActivityAt >= idleTimeoutMs) {
54380
+ await this.end("idle_timeout");
53574
54381
  }
53575
54382
  } finally {
53576
54383
  this.monitoring = false;
53577
54384
  }
53578
54385
  }
53579
54386
  async beat() {
53580
- if (!this.closed && await this.alive()) {
54387
+ if (this.closed || this.isRetaining || !await this.alive()) {
54388
+ return;
54389
+ }
54390
+ if (!this.closed && !this.isRetaining) {
53581
54391
  this.queue.push(heartbeat());
53582
54392
  }
53583
54393
  }
@@ -53588,28 +54398,32 @@ class LivePane {
53588
54398
  if (this.closed) {
53589
54399
  return;
53590
54400
  }
53591
- const turnRunning = this.turn !== undefined;
53592
- this.close();
53593
- if (this.sessionEndReason !== undefined) {
53594
- this.queue.push(sessionEnded("stopped"));
53595
- } else {
53596
- this.queue.push(sessionEnded("crashed"));
53597
- this.queue.push(processDied());
53598
- if (turnRunning) {
53599
- this.queue.push(sessionFailed("process_died", "claude exited while a turn was running."));
54401
+ await this.tearDown(async () => {
54402
+ const turnRunning = this.turn !== undefined;
54403
+ this.close();
54404
+ if (this.sessionEndReason !== undefined) {
54405
+ this.queue.push(sessionEnded("stopped"));
54406
+ } else {
54407
+ this.queue.push(sessionEnded("crashed"));
54408
+ this.queue.push(processDied());
54409
+ if (turnRunning) {
54410
+ this.queue.push(sessionFailed("process_died", "claude exited while a turn was running."));
54411
+ }
53600
54412
  }
53601
- }
53602
- await this.queue.drain(STOP_DRAIN_MS);
53603
- this.queue.stop();
53604
- this.daemon.remove(this);
54413
+ await this.queue.drain(STOP_DRAIN_MS);
54414
+ this.queue.stop();
54415
+ this.daemon.remove(this);
54416
+ });
53605
54417
  }
53606
54418
  async abandon() {
53607
54419
  if (this.closed) {
53608
54420
  return;
53609
54421
  }
53610
- this.close();
53611
- await this.killLogged();
53612
- this.daemon.remove(this);
54422
+ await this.tearDown(async () => {
54423
+ this.close();
54424
+ await this.killLogged();
54425
+ this.daemon.remove(this);
54426
+ });
53613
54427
  }
53614
54428
  async killLogged() {
53615
54429
  try {
@@ -53639,6 +54453,7 @@ class LiveDaemon {
53639
54453
  logger;
53640
54454
  timings;
53641
54455
  panes = new Map;
54456
+ starting = new Set;
53642
54457
  server;
53643
54458
  constructor(options) {
53644
54459
  this.options = options;
@@ -53656,7 +54471,7 @@ class LiveDaemon {
53656
54471
  }
53657
54472
  async listen() {
53658
54473
  const { socketPath } = this.options;
53659
- await mkdir4(dirname4(socketPath), { recursive: true, mode: 448 });
54474
+ await mkdir4(dirname5(socketPath), { recursive: true, mode: 448 });
53660
54475
  if (await Bun.file(socketPath).exists()) {
53661
54476
  if (await this.answering(socketPath)) {
53662
54477
  throw new Error(`a tmuxd daemon already listens on ${socketPath}`);
@@ -53724,52 +54539,108 @@ class LiveDaemon {
53724
54539
  }
53725
54540
  }
53726
54541
  async start(args) {
53727
- const envFile = args["env-file"];
53728
- if (envFile === undefined || !isAbsolute4(envFile)) {
53729
- return refuse("usage_error");
53730
- }
54542
+ let result;
53731
54543
  try {
53732
- return await this.launch(args, envFile);
54544
+ result = await this.launch(args);
54545
+ return result;
53733
54546
  } finally {
53734
- await removeFile(envFile);
54547
+ await removeHandoff(args, result?.ok === true, this.logger);
53735
54548
  }
53736
54549
  }
53737
- async launch(args, envFile) {
54550
+ async launch(args) {
53738
54551
  const { pane: id, cwd, model, resume } = args;
54552
+ const envFile = args["env-file"];
53739
54553
  const settingsBase = args["settings-base"];
54554
+ const policyFile = args["tool-policy"];
54555
+ const systemPromptFile = args["system-prompt-file"];
54556
+ const mcpConfig = args["mcp-config"];
54557
+ const featuresFile = args.features;
53740
54558
  if (id === undefined || !PANE_ID2.test(id) || cwd === undefined || !isAbsolute4(cwd)) {
53741
54559
  return refuse("usage_error");
53742
54560
  }
53743
- if (settingsBase === undefined || !isAbsolute4(settingsBase)) {
54561
+ if (envFile === undefined || !isAbsolute4(envFile) || settingsBase === undefined || !isAbsolute4(settingsBase)) {
53744
54562
  return refuse("usage_error");
53745
54563
  }
53746
54564
  if (model !== undefined && !MODEL_NAME.test(model)) {
53747
54565
  return refuse("usage_error");
53748
54566
  }
53749
- if (this.panes.has(id)) {
54567
+ const extras = parseStartExtras(args);
54568
+ if (extras === null || [policyFile, systemPromptFile, mcpConfig, featuresFile].some((path) => !optionalAbsolute(path))) {
54569
+ return refuse("usage_error");
54570
+ }
54571
+ if (this.panes.has(id) || this.starting.has(id)) {
53750
54572
  return refuse("pane_exists");
53751
54573
  }
53752
- if (this.panes.size >= (this.options.maxPanes ?? DEFAULT_MAX_PANES)) {
54574
+ if (this.panes.size + this.starting.size >= (this.options.maxPanes ?? DEFAULT_MAX_PANES)) {
53753
54575
  return refuse("pane_cap");
53754
54576
  }
54577
+ this.starting.add(id);
54578
+ try {
54579
+ return await this.open({
54580
+ id,
54581
+ cwd,
54582
+ model,
54583
+ resume,
54584
+ envFile,
54585
+ settingsBase,
54586
+ policyFile,
54587
+ systemPromptFile,
54588
+ mcpConfig,
54589
+ featuresFile,
54590
+ extras
54591
+ });
54592
+ } finally {
54593
+ this.starting.delete(id);
54594
+ }
54595
+ }
54596
+ async open(request) {
54597
+ const {
54598
+ id,
54599
+ cwd,
54600
+ model,
54601
+ resume,
54602
+ envFile,
54603
+ settingsBase,
54604
+ policyFile,
54605
+ systemPromptFile,
54606
+ mcpConfig,
54607
+ featuresFile,
54608
+ extras
54609
+ } = request;
53755
54610
  const content = await readSecretFile(envFile);
53756
54611
  const env = content === null ? null : parseStartEnv(content);
53757
54612
  if (env === null) {
53758
54613
  return refuse("usage_error");
53759
54614
  }
54615
+ for (const path of [systemPromptFile, mcpConfig]) {
54616
+ if (path !== undefined && !await isPrivateFile(path)) {
54617
+ return refuse("usage_error");
54618
+ }
54619
+ }
54620
+ const policy = policyFile === undefined ? undefined : await readToolPolicy(policyFile);
54621
+ if (policy === null) {
54622
+ return refuse("usage_error");
54623
+ }
54624
+ const features = featuresFile === undefined ? undefined : await readPaneFeatures(featuresFile, extras.permissionMode);
54625
+ if (features === null) {
54626
+ return refuse("usage_error");
54627
+ }
54628
+ const mcpSpellings = mcpConfig === undefined ? [] : [...new Set([mcpConfig, await realpath4(mcpConfig)])];
53760
54629
  const settingsPath = join7(settingsBase, `${id}.settings.json`);
53761
54630
  await mkdir4(settingsBase, { recursive: true, mode: 448 });
53762
54631
  await writePaneSettings(settingsPath, {
53763
54632
  basePath: this.options.baseSettingsPath ?? DEFAULT_BASE_SETTINGS_PATH,
53764
54633
  helperArgv: this.options.helperArgv,
53765
54634
  socketPath: this.options.socketPath,
53766
- paneId: id
54635
+ paneId: id,
54636
+ rules: paneRules(policy, mcpSpellings, extras.permissionMode)
53767
54637
  });
53768
54638
  const pane = {
53769
54639
  id,
53770
54640
  socket: this.options.tmuxSocket ?? LIVE_SOCKET
53771
54641
  };
53772
- const live = new LivePane(pane, env.gen, env.ingestUrl, env.token, Object.values(env.authEnv), this);
54642
+ const live = new LivePane(pane, env.gen, env.ingestUrl, env.token, Object.values(env.authEnv), this, extras, mcpConfig, settingsBase);
54643
+ this.starting.delete(id);
53773
54644
  this.panes.set(id, live);
53774
54645
  try {
53775
54646
  await createPane({
@@ -53777,9 +54648,14 @@ class LiveDaemon {
53777
54648
  cwd,
53778
54649
  home: this.options.home,
53779
54650
  settingsPath,
53780
- env: env.authEnv,
54651
+ env: { ...env.authEnv, ...features?.env },
53781
54652
  model,
53782
54653
  resumeSessionId: resume,
54654
+ mcpConfig,
54655
+ systemPromptFile,
54656
+ tools: features?.tools,
54657
+ disallowedTools: features?.disallowedTools,
54658
+ permissionMode: extras.permissionMode,
53783
54659
  claudeBin: this.options.claudeBin,
53784
54660
  socket: pane.socket,
53785
54661
  confirmStarted: (timeoutMs) => live.started.wait(timeoutMs)
@@ -53789,7 +54665,13 @@ class LiveDaemon {
53789
54665
  live.queue.stop();
53790
54666
  return refuse(launchFailure(error));
53791
54667
  }
53792
- live.pid = await panePid(pane);
54668
+ try {
54669
+ live.pid = await panePid(pane);
54670
+ } catch (error) {
54671
+ this.panes.delete(id);
54672
+ await live.dispose();
54673
+ return refuse(launchFailure(error));
54674
+ }
53793
54675
  live.begin();
53794
54676
  return accept({
53795
54677
  pane: id,
@@ -53825,7 +54707,7 @@ class LiveDaemon {
53825
54707
  }
53826
54708
  throw error;
53827
54709
  }
53828
- const promptId = randomUUID5();
54710
+ const promptId = randomUUID6();
53829
54711
  live.pendingPromptId = promptId;
53830
54712
  live.submitted.arm();
53831
54713
  let outcome;
@@ -53971,7 +54853,7 @@ class LiveDaemon {
53971
54853
  if (live.dialog !== undefined) {
53972
54854
  return refuse("dialog_open");
53973
54855
  }
53974
- if (live.busy || live.pendingPromptId !== undefined) {
54856
+ if (live.busy || live.refusesInput || live.pendingPromptId !== undefined) {
53975
54857
  return refuse("busy");
53976
54858
  }
53977
54859
  return null;
@@ -54083,17 +54965,69 @@ function parseStartEnv(content) {
54083
54965
  authEnv: Object.fromEntries(Object.entries(values).filter(([key]) => !key.startsWith(LIVE_KEY_PREFIX)))
54084
54966
  };
54085
54967
  }
54086
- async function readSecretFile(path) {
54087
- const info = await stat3(path).catch((error) => {
54968
+ function paneRules(policy, mcpPaths, mode) {
54969
+ const deny = [...mcpPaths.map((path) => `Read(/${path})`), ...mode === "dontAsk" ? ["EnterPlanMode"] : []];
54970
+ if (policy === undefined && deny.length === 0) {
54971
+ return;
54972
+ }
54973
+ return {
54974
+ allow: policy?.allow ?? [],
54975
+ deny: [...policy?.deny ?? [], ...deny],
54976
+ ask: policy?.ask ?? []
54977
+ };
54978
+ }
54979
+ function optionalAbsolute(path) {
54980
+ return path === undefined || isAbsolute4(path);
54981
+ }
54982
+ async function removeHandoff(args, keepMcp, logger) {
54983
+ const files = START_FILE_FLAGS.flatMap((flag) => {
54984
+ const path = args[flag];
54985
+ return path === undefined || !isAbsolute4(path) || keepMcp && flag === "mcp-config" ? [] : [[flag, path]];
54986
+ });
54987
+ for (const failure of await removeHandoffFiles(files)) {
54988
+ logger.error("tmuxd could not remove a hand-off file", { ...failure });
54989
+ }
54990
+ }
54991
+ async function removeHandoffFiles(files) {
54992
+ const ordered = [
54993
+ ...files.filter(([flag]) => flag === "mcp-config"),
54994
+ ...files.filter(([flag]) => flag !== "mcp-config")
54995
+ ];
54996
+ const failures = [];
54997
+ for (const [flag, path] of ordered) {
54998
+ try {
54999
+ await removeFile(path);
55000
+ } catch (error) {
55001
+ if (!isErrno(error)) {
55002
+ throw error;
55003
+ }
55004
+ failures.push({
55005
+ flag,
55006
+ code: error.code
55007
+ });
55008
+ }
55009
+ }
55010
+ return failures;
55011
+ }
55012
+ async function isPrivateFile(path) {
55013
+ const info = await lstat2(path).catch((error) => {
54088
55014
  if (error.code === "ENOENT") {
54089
55015
  return null;
54090
55016
  }
54091
55017
  throw error;
54092
55018
  });
54093
- if (info === null || !info.isFile() || (info.mode & 511) !== 384) {
54094
- return null;
54095
- }
54096
- return readFile4(path, "utf8");
55019
+ return info?.isFile() === true && (info.mode & 511) === 384;
55020
+ }
55021
+ async function readSecretFile(path) {
55022
+ return await isPrivateFile(path) ? readFile4(path, "utf8") : null;
55023
+ }
55024
+ async function readToolPolicy(path) {
55025
+ const content = await readSecretFile(path);
55026
+ return content === null ? null : parseToolPolicy(content);
55027
+ }
55028
+ async function readPaneFeatures(path, permissionMode) {
55029
+ const content = await readSecretFile(path);
55030
+ return content === null ? null : parsePaneFeatures(content, permissionMode);
54097
55031
  }
54098
55032
  async function readFileOrNull(path) {
54099
55033
  return readFile4(path, "utf8").catch((error) => {
@@ -54255,11 +55189,18 @@ function accept(data) {
54255
55189
  return {
54256
55190
  ok: true,
54257
55191
  code: "ok",
54258
- data: Object.fromEntries(Object.entries(data).filter(([, value]) => value !== undefined))
55192
+ data: stamped(data)
54259
55193
  };
54260
55194
  }
54261
- function refuse(code, data) {
54262
- return data === undefined ? { ok: false, code } : { ok: false, code, data };
55195
+ function refuse(code, data = {}) {
55196
+ return {
55197
+ ok: false,
55198
+ code,
55199
+ data: stamped(data)
55200
+ };
55201
+ }
55202
+ function stamped(data) {
55203
+ return Object.fromEntries(Object.entries({ daemon_version: DAEMON_VERSION, ...data }).filter(([, value]) => value !== undefined));
54263
55204
  }
54264
55205
  function notRunning(id) {
54265
55206
  return refuse("pane_not_running", {
@@ -54267,14 +55208,18 @@ function notRunning(id) {
54267
55208
  running: false
54268
55209
  });
54269
55210
  }
54270
- 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, PERMISSION_OPEN_RETRY_MS = 90000, 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;
55211
+ var DAEMON_VERSION, 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, PERMISSION_OPEN_RETRY_MS = 90000, MIN_SECRET_LENGTH = 8, MAX_REQUEST_BYTES, PANE_ID2, ENV_KEY, LIVE_KEY_PREFIX = "KODIZM_LIVE_", TOKEN_PREFIX = "kdz-int-jwt.", LAST_MESSAGE_FILE = "last-assistant-message.txt", COMMAND_NAMES, UNATTENDED_DENIAL = "This session runs unattended and approves nothing, so the tool call is denied.", REJECTION_RULES, EXIT_CODES, DEFAULT_TIMINGS;
54271
55212
  var init_daemon = __esm(() => {
55213
+ init_package();
54272
55214
  init_logger();
54273
55215
  init_events2();
54274
55216
  init_hooks();
54275
55217
  init_input();
54276
55218
  init_pane();
55219
+ init_policy4();
55220
+ init_start_flags();
54277
55221
  init_transcript();
55222
+ DAEMON_VERSION = package_default.version;
54278
55223
  PERMISSION_WAIT_MS = PERMISSION_HOOK_TIMEOUT_S * 1000 - 1e4;
54279
55224
  MAX_REQUEST_BYTES = 8 * 1024 * 1024;
54280
55225
  PANE_ID2 = /^[A-Za-z0-9_-]{1,64}$/;
@@ -54308,14 +55253,17 @@ var init_daemon = __esm(() => {
54308
55253
  monitorMs: 2000,
54309
55254
  transcriptPollMs: 250,
54310
55255
  retryBaseMs: 250,
54311
- retryMaxMs: 1e4
55256
+ retryMaxMs: 1e4,
55257
+ secondMs: 1000,
55258
+ idleTimeoutMs: 0,
55259
+ deliveryDrainMs: 120000
54312
55260
  };
54313
55261
  });
54314
55262
 
54315
55263
  // src/live/ctl.ts
54316
55264
  import { spawn as spawn2 } from "child_process";
54317
55265
  import { homedir as homedir2, tmpdir } from "os";
54318
- import { resolve as resolve5 } from "path";
55266
+ import { resolve as resolve6 } from "path";
54319
55267
  async function runTmuxd(args) {
54320
55268
  const [command, ...rest] = args;
54321
55269
  switch (command) {
@@ -54338,9 +55286,9 @@ async function runTmuxd(args) {
54338
55286
  async function runCtl(args) {
54339
55287
  const [verb, ...rest] = args;
54340
55288
  const flags = parseFlags(rest);
54341
- if (verb === undefined || !VERBS.has(verb) || flags === null) {
55289
+ if (verb === undefined || !VERBS.has(verb) || flags === null || verb === "start" && parseStartExtras(flags) === null) {
54342
55290
  await discardHandoff(verb, rest);
54343
- return print({ ok: false, code: "usage_error" });
55291
+ return print(ctlMade("usage_error"));
54344
55292
  }
54345
55293
  const socketPath = socketPathFromEnv();
54346
55294
  const message = {
@@ -54359,7 +55307,7 @@ async function runCtl(args) {
54359
55307
  if (error.reason === "unreachable") {
54360
55308
  await discardHandoff(verb, rest);
54361
55309
  }
54362
- return print(error.reason === "unreachable" ? { ok: false, code: "pane_not_running", data: { pane: flags.pane, running: false } } : { ok: false, code: "timeout" });
55310
+ return print(error.reason === "unreachable" ? ctlMade("pane_not_running", { pane: flags.pane, running: false }) : ctlMade("timeout"));
54363
55311
  }
54364
55312
  }
54365
55313
  async function ask(socketPath, message, spawnIfMissing) {
@@ -54426,6 +55374,7 @@ async function runDaemon() {
54426
55374
  baseSettingsPath: env.KODIZM_LIVE_BASE_SETTINGS ?? DEFAULT_BASE_SETTINGS_PATH,
54427
55375
  maxPanes: Number.isInteger(maxPanes) && maxPanes > 0 ? maxPanes : DEFAULT_MAX_PANES,
54428
55376
  logger: createLogger({ env }),
55377
+ timings: { idleTimeoutMs: idleTimeoutMs(env) },
54429
55378
  onIdle: () => void daemon.close().then(shutdown)
54430
55379
  });
54431
55380
  await daemon.listen();
@@ -54435,6 +55384,10 @@ async function runDaemon() {
54435
55384
  await stopped;
54436
55385
  return 0;
54437
55386
  }
55387
+ function idleTimeoutMs(env) {
55388
+ const seconds = env.KODIZM_TMUXD_IDLE_TIMEOUT_SECONDS;
55389
+ return seconds !== undefined && /^[1-9]\d*$/.test(seconds) ? Number(seconds) * 1000 : 0;
55390
+ }
54438
55391
  function spawnDaemon() {
54439
55392
  const child = spawn2(process.execPath, [process.argv[1] ?? "", "tmuxd", "daemon"], {
54440
55393
  detached: true,
@@ -54455,21 +55408,37 @@ function parseFlags(args) {
54455
55408
  if (!flag.startsWith("--") || name === "" || value === undefined || name in flags) {
54456
55409
  return null;
54457
55410
  }
54458
- flags[name] = PATH_FLAGS.has(name) ? resolve5(value) : value;
55411
+ flags[name] = PATH_FLAGS.has(name) ? resolve6(value) : value;
54459
55412
  }
54460
55413
  return flags;
54461
55414
  }
54462
55415
  async function discardHandoff(verb, args) {
54463
- const flag = verb === undefined ? undefined : HANDOFF_FLAGS[verb];
54464
- if (flag === undefined) {
55416
+ const flags = verb === undefined ? undefined : HANDOFF_FLAGS.get(verb);
55417
+ if (flags === undefined) {
54465
55418
  return;
54466
55419
  }
55420
+ const files = [];
54467
55421
  for (let index = 0;index < args.length - 1; index += 1) {
54468
55422
  const value = args[index + 1];
54469
- if (args[index] === `--${flag}` && value !== undefined) {
54470
- await removeFile(resolve5(value));
55423
+ const flag = flags.find((name) => args[index] === `--${name}`);
55424
+ if (value !== undefined && flag !== undefined) {
55425
+ files.push([flag, resolve6(value)]);
54471
55426
  }
54472
55427
  }
55428
+ for (const failure of await removeHandoffFiles(files)) {
55429
+ process.stderr.write(`${JSON.stringify({ level: "error", message: "tmuxd ctl could not remove a hand-off file", ...failure })}
55430
+ `);
55431
+ }
55432
+ }
55433
+ function ctlMade(code, data = {}) {
55434
+ return {
55435
+ ok: false,
55436
+ code,
55437
+ data: {
55438
+ daemon_version: DAEMON_VERSION,
55439
+ ...data
55440
+ }
55441
+ };
54473
55442
  }
54474
55443
  function toResult(response) {
54475
55444
  const code = typeof response === "object" && response !== null ? response.code : undefined;
@@ -54498,11 +55467,15 @@ var init_ctl = __esm(() => {
54498
55467
  init_daemon();
54499
55468
  init_hooks();
54500
55469
  init_pane();
55470
+ init_start_flags();
54501
55471
  USAGE = `Usage: kodizm-acp tmuxd <command>
54502
55472
 
54503
55473
  Commands:
54504
55474
  ctl <verb> [flags] Drive a Live pane through the daemon; prints one JSON line.
54505
55475
  Verbs: start, prompt, command, cancel, stop, rotate-token, status.
55476
+ start also takes --permission-mode default|dontAsk, --one-shot 0|1,
55477
+ --deadline-seconds N, --retain-failed-seconds N and
55478
+ --mcp-config/--tool-policy/--system-prompt-file/--features PATH.
54506
55479
  hook <Event> --pane <id> --socket <path> Hook helper the pane settings run; reads the payload on stdin.
54507
55480
  daemon Run the daemon in the foreground; ctl start spawns it detached.
54508
55481
 
@@ -54510,82 +55483,30 @@ Environment:
54510
55483
  KODIZM_TMUXD_SOCKET daemon socket (default: $XDG_RUNTIME_DIR or the temp dir, kodizm-live-<uid>/tmuxd.sock)
54511
55484
  KODIZM_TMUXD_TMUX_SOCKET tmux socket name (default: ${LIVE_SOCKET})
54512
55485
  KODIZM_TMUXD_MAX_PANES panes per daemon (default: ${DEFAULT_MAX_PANES})
55486
+ KODIZM_TMUXD_IDLE_TIMEOUT_SECONDS
55487
+ end a pane with no turn, dialog or activity for N seconds (default: off)
54513
55488
  KODIZM_LIVE_BASE_SETTINGS base settings with the deny list (default: ${DEFAULT_BASE_SETTINGS_PATH})
54514
55489
  CLAUDE_CODE_PATH claude binary (default: claude on PATH)
54515
55490
  `;
54516
55491
  VERBS = new Set(["start", "prompt", "command", "cancel", "stop", "rotate-token", "status"]);
54517
- PATH_FLAGS = new Set(["settings-base", "env-file", "file", "token-file"]);
54518
- HANDOFF_FLAGS = {
54519
- start: "env-file",
54520
- prompt: "file",
54521
- "rotate-token": "token-file"
54522
- };
55492
+ PATH_FLAGS = new Set([
55493
+ "settings-base",
55494
+ "env-file",
55495
+ "file",
55496
+ "token-file",
55497
+ "mcp-config",
55498
+ "tool-policy",
55499
+ "system-prompt-file"
55500
+ ]);
55501
+ HANDOFF_FLAGS = new Map([
55502
+ ["start", START_FILE_FLAGS],
55503
+ ["prompt", ["file"]],
55504
+ ["rotate-token", ["token-file"]]
55505
+ ]);
54523
55506
  });
54524
- // package.json
54525
- var package_default = {
54526
- name: "@kodizm/acp",
54527
- version: "0.8.2",
54528
- description: "Custom ACP server bridging Claude Code and opencode CLIs through a single Kodizm-flavored protocol surface.",
54529
- type: "module",
54530
- bin: {
54531
- "kodizm-acp": "./dist/index.js"
54532
- },
54533
- main: "./dist/index.js",
54534
- files: [
54535
- "dist/",
54536
- "README.md",
54537
- "LICENSE"
54538
- ],
54539
- scripts: {
54540
- dev: "bun run src/index.ts",
54541
- build: "bun build src/index.ts --target=bun --outdir=dist",
54542
- test: "bun test test/unit test/e2e",
54543
- "test:unit": "bun test test/unit",
54544
- "test:e2e": "bun test test/e2e",
54545
- "test:integration": "./scripts/reap-opencode.sh bun test test/integration",
54546
- "test:watch": "bun test --watch",
54547
- lint: "biome check src test",
54548
- "lint:fix": "biome check --write src test",
54549
- format: "biome format --write src test",
54550
- typecheck: "tsc --noEmit",
54551
- "typecheck:src": "tsc --noEmit -p tsconfig.publish.json",
54552
- prepublishOnly: "bun run build && bun run typecheck:src"
54553
- },
54554
- engines: {
54555
- bun: ">=1.1.0"
54556
- },
54557
- publishConfig: {
54558
- access: "public"
54559
- },
54560
- config: {
54561
- claudeAgentSdk: "0.3.280"
54562
- },
54563
- devDependencies: {
54564
- "@anthropic-ai/claude-agent-sdk": "0.3.280",
54565
- "@anthropic-ai/sdk": ">=0.93.0",
54566
- "@biomejs/biome": "^1.9.0",
54567
- "@modelcontextprotocol/sdk": "^1.29.0",
54568
- "@opencode-ai/sdk": "^1.18.32",
54569
- "@types/bun": "latest",
54570
- typescript: "^5.6.0",
54571
- zod: "^4.0.0"
54572
- },
54573
- license: "Apache-2.0",
54574
- homepage: "https://github.com/kodizm/acp",
54575
- repository: {
54576
- type: "git",
54577
- url: "git+https://github.com/kodizm/acp.git"
54578
- },
54579
- keywords: [
54580
- "acp",
54581
- "agent-client-protocol",
54582
- "claude",
54583
- "opencode",
54584
- "ai",
54585
- "bridge",
54586
- "jsonrpc"
54587
- ]
54588
- };
55507
+
55508
+ // src/index.ts
55509
+ init_package();
54589
55510
 
54590
55511
  // src/backends/driver.ts
54591
55512
  init_errors();
@@ -54602,151 +55523,8 @@ function ensureCapability(caps, required, method) {
54602
55523
  throw new MethodNotSupportedError(method, supported);
54603
55524
  }
54604
55525
 
54605
- // src/wire/schemas.ts
54606
- init_zod();
54607
- var AbsolutePathSchema = string2().regex(/^\//, 'path must be absolute (start with "/")');
54608
- var PLUGIN_ROOT = "/opt/kodizm/plugins/";
54609
- var PluginPathSchema = AbsolutePathSchema.refine((path) => path.length > PLUGIN_ROOT.length && path.startsWith(PLUGIN_ROOT) && !path.split("/").includes(".."), { message: `plugin path must live under ${PLUGIN_ROOT} with no ".." segment` });
54610
- var McpServerSchema = object({
54611
- type: literal("http"),
54612
- name: string2().min(1),
54613
- url: string2().url(),
54614
- headers: array(object({
54615
- name: string2().min(1),
54616
- value: string2()
54617
- })).optional()
54618
- });
54619
- var SystemPromptSchema = union([
54620
- string2(),
54621
- object({
54622
- append: string2(),
54623
- excludeDynamicSections: boolean2().optional()
54624
- })
54625
- ]);
54626
- var MetaSchema = record(string2(), unknown());
54627
- var CANONICAL_FIELDS_BANNED_IN_META = [
54628
- "systemPrompt",
54629
- "additionalDirectories",
54630
- "mcpServers",
54631
- "model",
54632
- "skills",
54633
- "cwd",
54634
- "sessionId",
54635
- "sourceSessionId",
54636
- "toolPolicy",
54637
- "autoCompact",
54638
- "permissionTimeoutMs",
54639
- "permissionDeferTimeoutMs",
54640
- "debug",
54641
- "debugCaptureRawSdk",
54642
- "debugCaptureRpc",
54643
- "heartbeatIntervalMs",
54644
- "inactivityThresholdMs",
54645
- "settingSources",
54646
- "features",
54647
- "plugins"
54648
- ];
54649
- var SettingSourcesSchema = array(_enum(["user", "project", "local"]));
54650
- var PermissionModeSchema = _enum(["default", "acceptEdits", "plan", "dontAsk", "bypassPermissions"]);
54651
- var ToolPolicySchema = object({
54652
- allow: array(string2()).optional(),
54653
- deny: array(string2()).optional(),
54654
- ask: array(string2()).optional(),
54655
- defaultMode: PermissionModeSchema.optional()
54656
- });
54657
- var FeaturesSchema = object({
54658
- todos: boolean2().optional(),
54659
- subagents: boolean2().optional(),
54660
- backgroundTasks: boolean2().optional(),
54661
- scheduling: boolean2().optional(),
54662
- planMode: boolean2().optional(),
54663
- worktree: boolean2().optional(),
54664
- notebook: boolean2().optional(),
54665
- clientTools: boolean2().optional(),
54666
- autoMemory: boolean2().optional(),
54667
- bundledSkills: boolean2().optional(),
54668
- simple: boolean2().optional(),
54669
- tools: array(string2().min(1)).optional()
54670
- });
54671
- function metaCarriesNoCanonicalFields(data) {
54672
- if (data._meta === undefined) {
54673
- return true;
54674
- }
54675
- for (const banned of CANONICAL_FIELDS_BANNED_IN_META) {
54676
- if (banned in data._meta) {
54677
- return false;
54678
- }
54679
- }
54680
- return true;
54681
- }
54682
- var META_SMUGGLE_MESSAGE = "canonical fields (systemPrompt, additionalDirectories, mcpServers, model, skills, cwd) must be top-level, not nested in _meta";
54683
- var InitializeRequestSchema = object({
54684
- protocolVersion: number2().int().positive(),
54685
- clientCapabilities: record(string2(), unknown()).optional(),
54686
- _meta: MetaSchema.optional()
54687
- });
54688
- var SessionOptionFields = {
54689
- additionalDirectories: array(AbsolutePathSchema).optional(),
54690
- plugins: array(PluginPathSchema).optional(),
54691
- systemPrompt: SystemPromptSchema.optional(),
54692
- model: string2().optional(),
54693
- skills: array(string2()).optional(),
54694
- toolPolicy: ToolPolicySchema.optional(),
54695
- autoCompact: boolean2().optional(),
54696
- permissionTimeoutMs: number2().int().positive().optional(),
54697
- permissionDeferTimeoutMs: number2().int().positive().optional(),
54698
- debug: boolean2().optional(),
54699
- debugCaptureRawSdk: boolean2().optional(),
54700
- debugCaptureRpc: boolean2().optional(),
54701
- heartbeatIntervalMs: number2().int().positive().optional(),
54702
- inactivityThresholdMs: number2().int().positive().optional(),
54703
- settingSources: SettingSourcesSchema.optional(),
54704
- features: FeaturesSchema.optional(),
54705
- _meta: MetaSchema.optional()
54706
- };
54707
- var TIMEOUT_EXCLUSION = {
54708
- message: "permissionTimeoutMs and permissionDeferTimeoutMs are mutually exclusive (pick hard-deny on timeout OR soft-defer on timeout)",
54709
- path: ["permissionDeferTimeoutMs"]
54710
- };
54711
- function timeoutsAreExclusive(data) {
54712
- return !(data.permissionTimeoutMs !== undefined && data.permissionDeferTimeoutMs !== undefined);
54713
- }
54714
- var NewSessionRequestSchema = object({
54715
- cwd: AbsolutePathSchema,
54716
- mcpServers: array(McpServerSchema),
54717
- ...SessionOptionFields
54718
- }).refine(metaCarriesNoCanonicalFields, { message: META_SMUGGLE_MESSAGE, path: ["_meta"] }).refine(timeoutsAreExclusive, TIMEOUT_EXCLUSION);
54719
- var PromptRequestSchema = object({
54720
- sessionId: string2().min(1),
54721
- prompt: array(unknown()),
54722
- model: string2().optional(),
54723
- _meta: MetaSchema.optional()
54724
- }).refine(metaCarriesNoCanonicalFields, { message: META_SMUGGLE_MESSAGE, path: ["_meta"] });
54725
- var CancelRequestSchema = object({
54726
- sessionId: string2().min(1)
54727
- });
54728
- var LoadSessionRequestSchema = object({
54729
- sessionId: string2().min(1),
54730
- cwd: AbsolutePathSchema,
54731
- mcpServers: array(McpServerSchema),
54732
- ...SessionOptionFields
54733
- }).refine(metaCarriesNoCanonicalFields, { message: META_SMUGGLE_MESSAGE, path: ["_meta"] }).refine(timeoutsAreExclusive, TIMEOUT_EXCLUSION);
54734
- var CompactSessionRequestSchema = object({
54735
- sessionId: string2().min(1)
54736
- });
54737
- var ForkSessionRequestSchema = object({
54738
- sourceSessionId: string2().min(1),
54739
- cwd: AbsolutePathSchema,
54740
- mcpServers: array(McpServerSchema),
54741
- systemPrompt: SystemPromptSchema.optional(),
54742
- model: string2().optional(),
54743
- additionalDirectories: array(AbsolutePathSchema).optional(),
54744
- settingSources: SettingSourcesSchema.optional(),
54745
- features: FeaturesSchema.optional(),
54746
- _meta: MetaSchema.optional()
54747
- }).refine(metaCarriesNoCanonicalFields, { message: META_SMUGGLE_MESSAGE, path: ["_meta"] });
54748
-
54749
55526
  // src/server/acp-server.ts
55527
+ init_schemas3();
54750
55528
  init_errors();
54751
55529
  var JsonRpcErrorCode2 = {
54752
55530
  ParseError: -32700,