@kodizm/acp 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +988 -270
- 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.0",
|
|
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(() => {
|
|
@@ -5206,58 +5275,7 @@ var init_events = __esm(() => {
|
|
|
5206
5275
|
]);
|
|
5207
5276
|
});
|
|
5208
5277
|
|
|
5209
|
-
// src/backends/
|
|
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
|
-
}
|
|
5278
|
+
// src/backends/permission-rpc.ts
|
|
5261
5279
|
async function awaitPermissionResponse(server, method, params, options = {}) {
|
|
5262
5280
|
const racers = [server.request(method, params)];
|
|
5263
5281
|
let timeoutHandle;
|
|
@@ -5304,49 +5322,9 @@ async function awaitPermissionResponse(server, method, params, options = {}) {
|
|
|
5304
5322
|
}
|
|
5305
5323
|
}
|
|
5306
5324
|
}
|
|
5307
|
-
|
|
5308
|
-
|
|
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(() => {
|
|
5325
|
+
var DEFERRED_SENTINEL;
|
|
5326
|
+
var init_permission_rpc = __esm(() => {
|
|
5344
5327
|
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
5328
|
DEFERRED_SENTINEL = Symbol.for("kodizm.permission.deferred");
|
|
5351
5329
|
});
|
|
5352
5330
|
|
|
@@ -5399,7 +5377,7 @@ var QuestionsArraySchema;
|
|
|
5399
5377
|
var init_ask_user_question = __esm(() => {
|
|
5400
5378
|
init_zod();
|
|
5401
5379
|
init_events();
|
|
5402
|
-
|
|
5380
|
+
init_permission_rpc();
|
|
5403
5381
|
QuestionsArraySchema = array(KodizmQuestionSchema).min(1).max(4);
|
|
5404
5382
|
});
|
|
5405
5383
|
|
|
@@ -5784,6 +5762,104 @@ function buildSdkServer(server) {
|
|
|
5784
5762
|
return sdkServer;
|
|
5785
5763
|
}
|
|
5786
5764
|
|
|
5765
|
+
// src/backends/claude/permission-bridge.ts
|
|
5766
|
+
function buildCanUseTool(deps) {
|
|
5767
|
+
return async function canUseTool(toolName, input, options) {
|
|
5768
|
+
const payload = {
|
|
5769
|
+
sessionId: deps.sessionId,
|
|
5770
|
+
toolCall: {
|
|
5771
|
+
toolCallId: options.toolUseID,
|
|
5772
|
+
rawInput: input,
|
|
5773
|
+
title: options.title ?? toolName,
|
|
5774
|
+
kind: "tool_use"
|
|
5775
|
+
},
|
|
5776
|
+
options: DEFAULT_OPTIONS
|
|
5777
|
+
};
|
|
5778
|
+
if (options.agentID !== undefined) {
|
|
5779
|
+
payload.agentId = options.agentID;
|
|
5780
|
+
}
|
|
5781
|
+
const event = {
|
|
5782
|
+
sessionId: deps.sessionId,
|
|
5783
|
+
type: "permission_request",
|
|
5784
|
+
toolUseId: options.toolUseID,
|
|
5785
|
+
name: toolName,
|
|
5786
|
+
options: DEFAULT_OPTIONS.map((o) => ({ optionId: o.optionId, label: o.name })),
|
|
5787
|
+
...options.agentID === undefined ? {} : { agentId: options.agentID, parentSessionId: deps.sessionId }
|
|
5788
|
+
};
|
|
5789
|
+
deps.emit.send(event);
|
|
5790
|
+
const raceOptions = {
|
|
5791
|
+
signal: composeSignals(deps.signal, options.signal)
|
|
5792
|
+
};
|
|
5793
|
+
if (deps.permissionTimeoutMs !== undefined) {
|
|
5794
|
+
raceOptions.timeoutMs = deps.permissionTimeoutMs;
|
|
5795
|
+
}
|
|
5796
|
+
if (deps.deferTimeoutMs !== undefined && deps.onDefer !== undefined) {
|
|
5797
|
+
raceOptions.deferTimeoutMs = deps.deferTimeoutMs;
|
|
5798
|
+
}
|
|
5799
|
+
let raced;
|
|
5800
|
+
try {
|
|
5801
|
+
raced = await awaitPermissionResponse(deps.server, "session/request_permission", payload, raceOptions);
|
|
5802
|
+
} catch (error) {
|
|
5803
|
+
if (error instanceof AcpTimeoutError) {
|
|
5804
|
+
return { behavior: "deny", message: "Permission RPC timed out" };
|
|
5805
|
+
}
|
|
5806
|
+
throw error;
|
|
5807
|
+
}
|
|
5808
|
+
if (raced === DEFERRED_SENTINEL) {
|
|
5809
|
+
if (deps.onDefer === undefined) {
|
|
5810
|
+
return { behavior: "deny", message: "Permission deferred (no handler)" };
|
|
5811
|
+
}
|
|
5812
|
+
return await deps.onDefer({ toolName, input, options });
|
|
5813
|
+
}
|
|
5814
|
+
return mapPermissionOutcome(raced, input, options.suggestions);
|
|
5815
|
+
};
|
|
5816
|
+
}
|
|
5817
|
+
function composeSignals(a, b) {
|
|
5818
|
+
if (a.aborted)
|
|
5819
|
+
return a;
|
|
5820
|
+
if (b.aborted)
|
|
5821
|
+
return b;
|
|
5822
|
+
const controller = new AbortController;
|
|
5823
|
+
const onAbort = () => controller.abort();
|
|
5824
|
+
a.addEventListener("abort", onAbort, { once: true });
|
|
5825
|
+
b.addEventListener("abort", onAbort, { once: true });
|
|
5826
|
+
return controller.signal;
|
|
5827
|
+
}
|
|
5828
|
+
function mapPermissionOutcome(response, input, suggestions) {
|
|
5829
|
+
const outcome = response.outcome;
|
|
5830
|
+
if (outcome.outcome === "cancelled") {
|
|
5831
|
+
throw new Error("Tool use aborted");
|
|
5832
|
+
}
|
|
5833
|
+
if (outcome.optionId === "allow") {
|
|
5834
|
+
return { behavior: "allow", updatedInput: input };
|
|
5835
|
+
}
|
|
5836
|
+
if (outcome.optionId === "allow_always") {
|
|
5837
|
+
return {
|
|
5838
|
+
behavior: "allow",
|
|
5839
|
+
updatedInput: input,
|
|
5840
|
+
updatedPermissions: suggestions !== undefined && suggestions.length > 0 ? suggestions : [
|
|
5841
|
+
{
|
|
5842
|
+
type: "addRules",
|
|
5843
|
+
rules: [{ toolName: "unknown" }],
|
|
5844
|
+
behavior: "allow",
|
|
5845
|
+
destination: "session"
|
|
5846
|
+
}
|
|
5847
|
+
]
|
|
5848
|
+
};
|
|
5849
|
+
}
|
|
5850
|
+
return { behavior: "deny", message: "User refused permission to run tool" };
|
|
5851
|
+
}
|
|
5852
|
+
var DEFAULT_OPTIONS;
|
|
5853
|
+
var init_permission_bridge = __esm(() => {
|
|
5854
|
+
init_errors();
|
|
5855
|
+
init_permission_rpc();
|
|
5856
|
+
DEFAULT_OPTIONS = [
|
|
5857
|
+
{ kind: "allow_always", name: "Always allow this tool", optionId: "allow_always" },
|
|
5858
|
+
{ kind: "allow_once", name: "Allow", optionId: "allow" },
|
|
5859
|
+
{ kind: "reject_once", name: "Reject", optionId: "reject" }
|
|
5860
|
+
];
|
|
5861
|
+
});
|
|
5862
|
+
|
|
5787
5863
|
// src/wire/policy.ts
|
|
5788
5864
|
function parseCanonicalPattern(input) {
|
|
5789
5865
|
if (input.length === 0) {
|
|
@@ -51522,7 +51598,7 @@ async function safeReply2(sdk, requestId, body) {
|
|
|
51522
51598
|
}
|
|
51523
51599
|
var CANONICAL_OPTIONS;
|
|
51524
51600
|
var init_permission_bridge2 = __esm(() => {
|
|
51525
|
-
|
|
51601
|
+
init_permission_rpc();
|
|
51526
51602
|
CANONICAL_OPTIONS = [
|
|
51527
51603
|
{ optionId: "allow", label: "Allow" },
|
|
51528
51604
|
{ optionId: "allow_always", label: "Always allow" },
|
|
@@ -52194,11 +52270,17 @@ function apiStopReason(reason) {
|
|
|
52194
52270
|
function turnFailed(hook) {
|
|
52195
52271
|
const error = text(hook.error) ?? "unknown";
|
|
52196
52272
|
const details = text(hook.error_details);
|
|
52273
|
+
return turnFailedWith(failureReason(error), details === undefined ? `StopFailure ${error}` : `StopFailure ${error}: ${details}`.slice(0, TITLE_MAX * 2));
|
|
52274
|
+
}
|
|
52275
|
+
function turnFailedWith(reason, detail) {
|
|
52197
52276
|
return draft("turn_failed", {
|
|
52198
|
-
reason
|
|
52199
|
-
detail
|
|
52277
|
+
reason,
|
|
52278
|
+
detail
|
|
52200
52279
|
});
|
|
52201
52280
|
}
|
|
52281
|
+
function failureReason(error) {
|
|
52282
|
+
return FAILURE_REASONS[error] ?? "api_error";
|
|
52283
|
+
}
|
|
52202
52284
|
function sessionEnded(reason) {
|
|
52203
52285
|
return draft("session_ended", { reason });
|
|
52204
52286
|
}
|
|
@@ -52365,11 +52447,30 @@ async function buildPaneSettings(options) {
|
|
|
52365
52447
|
}
|
|
52366
52448
|
]
|
|
52367
52449
|
]));
|
|
52368
|
-
|
|
52450
|
+
const settings = {
|
|
52369
52451
|
...base,
|
|
52370
52452
|
disableAllHooks: false,
|
|
52453
|
+
autoContinueAtUsageLimit: false,
|
|
52371
52454
|
hooks
|
|
52372
52455
|
};
|
|
52456
|
+
if (options.rules !== undefined) {
|
|
52457
|
+
settings.permissions = mergePermissions(base.permissions, options.rules);
|
|
52458
|
+
}
|
|
52459
|
+
return settings;
|
|
52460
|
+
}
|
|
52461
|
+
function mergePermissions(base, rules) {
|
|
52462
|
+
const permissions = isRecord2(base) ? base : {};
|
|
52463
|
+
const merge = (list) => {
|
|
52464
|
+
const own = permissions[list];
|
|
52465
|
+
const entries = Array.isArray(own) ? own.filter((entry) => typeof entry === "string") : [];
|
|
52466
|
+
return [...new Set([...entries, ...rules[list]])];
|
|
52467
|
+
};
|
|
52468
|
+
return {
|
|
52469
|
+
...permissions,
|
|
52470
|
+
allow: merge("allow"),
|
|
52471
|
+
deny: merge("deny"),
|
|
52472
|
+
ask: merge("ask")
|
|
52473
|
+
};
|
|
52373
52474
|
}
|
|
52374
52475
|
async function writePaneSettings(path, options) {
|
|
52375
52476
|
const settings = await buildPaneSettings(options);
|
|
@@ -52558,8 +52659,9 @@ var init_input = __esm(() => {
|
|
|
52558
52659
|
});
|
|
52559
52660
|
|
|
52560
52661
|
// src/live/pane.ts
|
|
52561
|
-
import {
|
|
52562
|
-
import {
|
|
52662
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
52663
|
+
import { mkdir as mkdir3, readFile as readFile3, realpath as realpath3, rename as rename4, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
52664
|
+
import { dirname as dirname4, join as join6, resolve as resolve5 } from "path";
|
|
52563
52665
|
function supportsPasteBufferS(versionOutput) {
|
|
52564
52666
|
const match = /^tmux (?:next-)?(\d+)\.(\d+)[a-z]?$/.exec(versionOutput.trim());
|
|
52565
52667
|
if (match === null) {
|
|
@@ -52585,7 +52687,7 @@ async function createPane(options) {
|
|
|
52585
52687
|
}
|
|
52586
52688
|
const env = paneEnv(options);
|
|
52587
52689
|
const cwd = await realpath3(options.cwd);
|
|
52588
|
-
await seedTrust(env.CLAUDE_CONFIG_DIR === undefined ? [env.HOME] : [env.HOME, env.CLAUDE_CONFIG_DIR], cwd);
|
|
52690
|
+
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
52691
|
const { PATH: path, TERM: term, ...sessionEnv } = env;
|
|
52590
52692
|
const created = await run(pane.socket, [
|
|
52591
52693
|
"start-server",
|
|
@@ -52674,11 +52776,21 @@ function paneEnv(options) {
|
|
|
52674
52776
|
...env,
|
|
52675
52777
|
HOME: options.home,
|
|
52676
52778
|
CI: "1",
|
|
52677
|
-
TERM: "xterm-256color"
|
|
52779
|
+
TERM: "xterm-256color",
|
|
52780
|
+
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1"
|
|
52678
52781
|
};
|
|
52679
52782
|
}
|
|
52680
52783
|
function launchArgv(options) {
|
|
52681
52784
|
const argv = [options.claudeBin ?? "claude", "--settings", options.settingsPath, "--setting-sources", ""];
|
|
52785
|
+
if (options.permissionMode !== undefined) {
|
|
52786
|
+
argv.push("--permission-mode", options.permissionMode);
|
|
52787
|
+
}
|
|
52788
|
+
if (options.mcpConfig !== undefined) {
|
|
52789
|
+
argv.push("--mcp-config", options.mcpConfig, "--strict-mcp-config");
|
|
52790
|
+
}
|
|
52791
|
+
if (options.systemPromptFile !== undefined) {
|
|
52792
|
+
argv.push("--append-system-prompt-file", options.systemPromptFile);
|
|
52793
|
+
}
|
|
52682
52794
|
if (options.model !== undefined) {
|
|
52683
52795
|
argv.push("--model", options.model);
|
|
52684
52796
|
}
|
|
@@ -52687,26 +52799,129 @@ function launchArgv(options) {
|
|
|
52687
52799
|
}
|
|
52688
52800
|
return argv;
|
|
52689
52801
|
}
|
|
52690
|
-
async function
|
|
52802
|
+
async function trustRoots(cwd) {
|
|
52803
|
+
const roots = new Set([cwd]);
|
|
52804
|
+
const repository = await findGitEntry(cwd);
|
|
52805
|
+
if (repository === null) {
|
|
52806
|
+
return [...roots];
|
|
52807
|
+
}
|
|
52808
|
+
roots.add(repository.root);
|
|
52809
|
+
if (repository.isFile) {
|
|
52810
|
+
const main = await worktreeMainRoot(repository.root);
|
|
52811
|
+
if (main !== null) {
|
|
52812
|
+
roots.add(main);
|
|
52813
|
+
}
|
|
52814
|
+
}
|
|
52815
|
+
return [...roots];
|
|
52816
|
+
}
|
|
52817
|
+
async function findGitEntry(start) {
|
|
52818
|
+
let dir = start;
|
|
52819
|
+
while (true) {
|
|
52820
|
+
const entry = await statIfPresent(join6(dir, ".git"));
|
|
52821
|
+
if (entry !== null) {
|
|
52822
|
+
return {
|
|
52823
|
+
root: dir,
|
|
52824
|
+
isFile: entry.isFile()
|
|
52825
|
+
};
|
|
52826
|
+
}
|
|
52827
|
+
const parent = dirname4(dir);
|
|
52828
|
+
if (parent === dir) {
|
|
52829
|
+
return null;
|
|
52830
|
+
}
|
|
52831
|
+
dir = parent;
|
|
52832
|
+
}
|
|
52833
|
+
}
|
|
52834
|
+
async function worktreeMainRoot(root) {
|
|
52835
|
+
const gitfile = await readTextIfPresent(join6(root, ".git"));
|
|
52836
|
+
const target = gitfile === null ? undefined : /^gitdir:\s*(.+?)\s*$/m.exec(gitfile)?.[1];
|
|
52837
|
+
if (target === undefined) {
|
|
52838
|
+
return null;
|
|
52839
|
+
}
|
|
52840
|
+
const gitdir = resolve5(root, target);
|
|
52841
|
+
const commondir = await readTextIfPresent(join6(gitdir, "commondir"));
|
|
52842
|
+
if (commondir === null) {
|
|
52843
|
+
return null;
|
|
52844
|
+
}
|
|
52845
|
+
return realpathIfPresent(dirname4(resolve5(gitdir, commondir.trim())));
|
|
52846
|
+
}
|
|
52847
|
+
async function statIfPresent(path) {
|
|
52848
|
+
try {
|
|
52849
|
+
return await stat2(path);
|
|
52850
|
+
} catch (error) {
|
|
52851
|
+
if (isAbsent(error)) {
|
|
52852
|
+
return null;
|
|
52853
|
+
}
|
|
52854
|
+
throw error;
|
|
52855
|
+
}
|
|
52856
|
+
}
|
|
52857
|
+
async function readTextIfPresent(path) {
|
|
52858
|
+
try {
|
|
52859
|
+
return await readFile3(path, "utf8");
|
|
52860
|
+
} catch (error) {
|
|
52861
|
+
if (isAbsent(error)) {
|
|
52862
|
+
return null;
|
|
52863
|
+
}
|
|
52864
|
+
throw error;
|
|
52865
|
+
}
|
|
52866
|
+
}
|
|
52867
|
+
async function realpathIfPresent(path) {
|
|
52868
|
+
try {
|
|
52869
|
+
return await realpath3(path);
|
|
52870
|
+
} catch (error) {
|
|
52871
|
+
if (isAbsent(error)) {
|
|
52872
|
+
return null;
|
|
52873
|
+
}
|
|
52874
|
+
throw error;
|
|
52875
|
+
}
|
|
52876
|
+
}
|
|
52877
|
+
function isAbsent(error) {
|
|
52878
|
+
return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
|
|
52879
|
+
}
|
|
52880
|
+
function approvedKeySuffix(apiKey) {
|
|
52881
|
+
return apiKey === undefined || apiKey === "" ? undefined : apiKey.slice(-20);
|
|
52882
|
+
}
|
|
52883
|
+
function approveKey(current, suffix) {
|
|
52884
|
+
const responses = isRecord3(current) ? current : {};
|
|
52885
|
+
const approved = stringList(responses.approved);
|
|
52886
|
+
return {
|
|
52887
|
+
...responses,
|
|
52888
|
+
approved: approved.includes(suffix) ? approved : [...approved, suffix],
|
|
52889
|
+
rejected: stringList(responses.rejected)
|
|
52890
|
+
};
|
|
52891
|
+
}
|
|
52892
|
+
function stringList(value) {
|
|
52893
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
52894
|
+
}
|
|
52895
|
+
function seedTrust(dirs, roots, keySuffix) {
|
|
52896
|
+
const seeded = seeding.then(() => writeTrust(dirs, roots, keySuffix));
|
|
52897
|
+
seeding = seeded.catch(() => {
|
|
52898
|
+
return;
|
|
52899
|
+
});
|
|
52900
|
+
return seeded;
|
|
52901
|
+
}
|
|
52902
|
+
async function writeTrust(dirs, roots, keySuffix) {
|
|
52691
52903
|
for (const dir of dirs) {
|
|
52692
52904
|
const path = join6(dir, ".claude.json");
|
|
52693
52905
|
const config = await readConfig(path);
|
|
52694
52906
|
const projects = isRecord3(config.projects) ? config.projects : {};
|
|
52695
|
-
const
|
|
52696
|
-
|
|
52697
|
-
|
|
52698
|
-
|
|
52699
|
-
|
|
52700
|
-
|
|
52701
|
-
|
|
52907
|
+
for (const root of roots) {
|
|
52908
|
+
const project = isRecord3(projects[root]) ? projects[root] : {};
|
|
52909
|
+
projects[root] = {
|
|
52910
|
+
allowedTools: [],
|
|
52911
|
+
...project,
|
|
52912
|
+
hasTrustDialogAccepted: true,
|
|
52913
|
+
hasCompletedProjectOnboarding: true
|
|
52914
|
+
};
|
|
52915
|
+
}
|
|
52702
52916
|
const seeded = {
|
|
52703
52917
|
theme: "dark",
|
|
52704
52918
|
...config,
|
|
52705
52919
|
hasCompletedOnboarding: true,
|
|
52706
|
-
projects
|
|
52920
|
+
projects,
|
|
52921
|
+
...keySuffix === undefined ? {} : { customApiKeyResponses: approveKey(config.customApiKeyResponses, keySuffix) }
|
|
52707
52922
|
};
|
|
52708
52923
|
await mkdir3(dir, { recursive: true });
|
|
52709
|
-
const staging = `${path}.${
|
|
52924
|
+
const staging = `${path}.${randomUUID5()}.tmp`;
|
|
52710
52925
|
await writeFile2(staging, JSON.stringify(seeded, null, 2), { mode: 384 });
|
|
52711
52926
|
await rename4(staging, path);
|
|
52712
52927
|
}
|
|
@@ -52791,7 +53006,7 @@ async function failOnPane(pane, stderr) {
|
|
|
52791
53006
|
}
|
|
52792
53007
|
throw new PaneError("tmux_failed", stderr);
|
|
52793
53008
|
}
|
|
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;
|
|
53009
|
+
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
53010
|
var init_pane = __esm(() => {
|
|
52796
53011
|
init_input();
|
|
52797
53012
|
SUBMIT_WINDOWS_MS = [328, 656, 1312, 2624];
|
|
@@ -52807,11 +53022,197 @@ var init_pane = __esm(() => {
|
|
|
52807
53022
|
this.reason = reason;
|
|
52808
53023
|
}
|
|
52809
53024
|
};
|
|
53025
|
+
seeding = Promise.resolve();
|
|
52810
53026
|
pasteSFlags = new Map;
|
|
52811
53027
|
});
|
|
52812
53028
|
|
|
53029
|
+
// src/live/policy.ts
|
|
53030
|
+
function parseToolPolicy(text) {
|
|
53031
|
+
const policy = parseJson2(text);
|
|
53032
|
+
if (!isRecord4(policy) || !Object.keys(policy).every((key) => POLICY_KEYS.includes(key))) {
|
|
53033
|
+
return null;
|
|
53034
|
+
}
|
|
53035
|
+
const rules = {
|
|
53036
|
+
allow: [],
|
|
53037
|
+
deny: [],
|
|
53038
|
+
ask: []
|
|
53039
|
+
};
|
|
53040
|
+
for (const list of RULE_LISTS) {
|
|
53041
|
+
const entries = policy[list] === undefined ? [] : policy[list];
|
|
53042
|
+
if (!Array.isArray(entries) || !entries.every((entry) => typeof entry === "string")) {
|
|
53043
|
+
return null;
|
|
53044
|
+
}
|
|
53045
|
+
for (const entry of entries) {
|
|
53046
|
+
const rule = translatePattern(entry, list);
|
|
53047
|
+
if (rule === null) {
|
|
53048
|
+
return null;
|
|
53049
|
+
}
|
|
53050
|
+
if (!rules[list].includes(rule)) {
|
|
53051
|
+
rules[list].push(rule);
|
|
53052
|
+
}
|
|
53053
|
+
}
|
|
53054
|
+
}
|
|
53055
|
+
return rules;
|
|
53056
|
+
}
|
|
53057
|
+
function translatePattern(pattern, list) {
|
|
53058
|
+
let parsed;
|
|
53059
|
+
try {
|
|
53060
|
+
parsed = parseCanonicalPattern(pattern);
|
|
53061
|
+
} catch (error) {
|
|
53062
|
+
if (error instanceof InvalidPatternError) {
|
|
53063
|
+
return null;
|
|
53064
|
+
}
|
|
53065
|
+
throw error;
|
|
53066
|
+
}
|
|
53067
|
+
if (parsed.mcpPath !== undefined) {
|
|
53068
|
+
return mcpRule(parsed.mcpPath);
|
|
53069
|
+
}
|
|
53070
|
+
const { toolName, argPattern } = parsed;
|
|
53071
|
+
if (!TOOL_NAME.test(toolName)) {
|
|
53072
|
+
return null;
|
|
53073
|
+
}
|
|
53074
|
+
if (argPattern === undefined || argPattern === "*") {
|
|
53075
|
+
return toolName;
|
|
53076
|
+
}
|
|
53077
|
+
const fileTool = FILE_TOOLS.get(toolName);
|
|
53078
|
+
if (fileTool !== undefined && fileTool !== toolName && list === "allow") {
|
|
53079
|
+
return null;
|
|
53080
|
+
}
|
|
53081
|
+
const tool = fileTool ?? toolName;
|
|
53082
|
+
const content = ruleContent(tool, argPattern, list);
|
|
53083
|
+
return isEnforceableContent(tool, content) ? `${tool}(${escapeRuleContent(content)})` : null;
|
|
53084
|
+
}
|
|
53085
|
+
function mcpRule(path) {
|
|
53086
|
+
const wildcardMisplaced = path.some((segment, index) => segment.includes("*") && !(index === 1 && segment === "*"));
|
|
53087
|
+
if (path.length > 2 || wildcardMisplaced || path.some((segment) => /[()]/.test(segment))) {
|
|
53088
|
+
return null;
|
|
53089
|
+
}
|
|
53090
|
+
return `mcp__${path.join("__")}`;
|
|
53091
|
+
}
|
|
53092
|
+
function ruleContent(tool, argPattern, list) {
|
|
53093
|
+
if (tool === "Read" || tool === "Edit") {
|
|
53094
|
+
return anchorPath(argPattern);
|
|
53095
|
+
}
|
|
53096
|
+
return tool === "Bash" ? bashArg(argPattern, list) : argPattern;
|
|
53097
|
+
}
|
|
53098
|
+
function anchorPath(path) {
|
|
53099
|
+
return path.startsWith("/") && !path.startsWith("//") ? `/${path}` : path;
|
|
53100
|
+
}
|
|
53101
|
+
function bashArg(argPattern, list) {
|
|
53102
|
+
const prefix = argPattern.slice(0, -1);
|
|
53103
|
+
if (list !== "allow" || !argPattern.endsWith("*") || prefix.includes("*") || /[:\s]$/.test(prefix)) {
|
|
53104
|
+
return argPattern;
|
|
53105
|
+
}
|
|
53106
|
+
return `${prefix}:*`;
|
|
53107
|
+
}
|
|
53108
|
+
function escapeRuleContent(content) {
|
|
53109
|
+
return content.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
|
|
53110
|
+
}
|
|
53111
|
+
function isEnforceableContent(tool, content) {
|
|
53112
|
+
if (tool === "Bash") {
|
|
53113
|
+
return (!content.includes(":*") || content.endsWith(":*")) && content !== ":*";
|
|
53114
|
+
}
|
|
53115
|
+
if (tool === "Read" || tool === "Edit") {
|
|
53116
|
+
return !content.includes(":*") && (!content.includes("*") || /^\*|\*$|\*\*|\/\*|\*\.|\*\)/.test(content));
|
|
53117
|
+
}
|
|
53118
|
+
return true;
|
|
53119
|
+
}
|
|
53120
|
+
function parseJson2(text) {
|
|
53121
|
+
try {
|
|
53122
|
+
return JSON.parse(text);
|
|
53123
|
+
} catch (error) {
|
|
53124
|
+
if (error instanceof SyntaxError) {
|
|
53125
|
+
return;
|
|
53126
|
+
}
|
|
53127
|
+
throw error;
|
|
53128
|
+
}
|
|
53129
|
+
}
|
|
53130
|
+
function isRecord4(value) {
|
|
53131
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
53132
|
+
}
|
|
53133
|
+
var RULE_LISTS, POLICY_KEYS, FILE_TOOLS, TOOL_NAME;
|
|
53134
|
+
var init_policy4 = __esm(() => {
|
|
53135
|
+
init_policy();
|
|
53136
|
+
RULE_LISTS = ["allow", "deny", "ask"];
|
|
53137
|
+
POLICY_KEYS = [...RULE_LISTS, "defaultMode"];
|
|
53138
|
+
FILE_TOOLS = new Map([
|
|
53139
|
+
["Read", "Read"],
|
|
53140
|
+
["Edit", "Edit"],
|
|
53141
|
+
["Write", "Edit"],
|
|
53142
|
+
["MultiEdit", "Edit"],
|
|
53143
|
+
["NotebookEdit", "Edit"],
|
|
53144
|
+
["Glob", "Read"],
|
|
53145
|
+
["Grep", "Read"]
|
|
53146
|
+
]);
|
|
53147
|
+
TOOL_NAME = /^[A-Z][A-Za-z0-9_]*$/;
|
|
53148
|
+
});
|
|
53149
|
+
|
|
53150
|
+
// src/live/start-flags.ts
|
|
53151
|
+
function parseStartExtras(args) {
|
|
53152
|
+
for (const key of Object.keys(args)) {
|
|
53153
|
+
if (!START_FLAGS.has(key)) {
|
|
53154
|
+
return null;
|
|
53155
|
+
}
|
|
53156
|
+
}
|
|
53157
|
+
const permissionMode = parsePermissionMode(args["permission-mode"]);
|
|
53158
|
+
const oneShot = parseOneShot(args["one-shot"]);
|
|
53159
|
+
const deadlineSeconds = parseSeconds(args["deadline-seconds"], 1);
|
|
53160
|
+
const retainFailedSeconds = parseSeconds(args["retain-failed-seconds"], 0);
|
|
53161
|
+
if (permissionMode === null || oneShot === null || deadlineSeconds === null || retainFailedSeconds === null) {
|
|
53162
|
+
return null;
|
|
53163
|
+
}
|
|
53164
|
+
return {
|
|
53165
|
+
permissionMode,
|
|
53166
|
+
oneShot,
|
|
53167
|
+
deadlineSeconds,
|
|
53168
|
+
retainFailedSeconds: retainFailedSeconds ?? 0
|
|
53169
|
+
};
|
|
53170
|
+
}
|
|
53171
|
+
function parsePermissionMode(value) {
|
|
53172
|
+
if (value === undefined) {
|
|
53173
|
+
return;
|
|
53174
|
+
}
|
|
53175
|
+
return value === "default" || value === "dontAsk" ? value : null;
|
|
53176
|
+
}
|
|
53177
|
+
function parseOneShot(value) {
|
|
53178
|
+
if (value === undefined || value === "0") {
|
|
53179
|
+
return false;
|
|
53180
|
+
}
|
|
53181
|
+
return value === "1" ? true : null;
|
|
53182
|
+
}
|
|
53183
|
+
function parseSeconds(value, min) {
|
|
53184
|
+
if (value === undefined) {
|
|
53185
|
+
return;
|
|
53186
|
+
}
|
|
53187
|
+
if (!DIGITS.test(value)) {
|
|
53188
|
+
return null;
|
|
53189
|
+
}
|
|
53190
|
+
const seconds = Number(value);
|
|
53191
|
+
return seconds >= min && seconds <= MAX_SECONDS ? seconds : null;
|
|
53192
|
+
}
|
|
53193
|
+
var START_FLAGS, START_FILE_FLAGS, MAX_SECONDS = 2147483, DIGITS;
|
|
53194
|
+
var init_start_flags = __esm(() => {
|
|
53195
|
+
START_FLAGS = new Set([
|
|
53196
|
+
"pane",
|
|
53197
|
+
"cwd",
|
|
53198
|
+
"settings-base",
|
|
53199
|
+
"env-file",
|
|
53200
|
+
"model",
|
|
53201
|
+
"resume",
|
|
53202
|
+
"mcp-config",
|
|
53203
|
+
"tool-policy",
|
|
53204
|
+
"system-prompt-file",
|
|
53205
|
+
"permission-mode",
|
|
53206
|
+
"one-shot",
|
|
53207
|
+
"deadline-seconds",
|
|
53208
|
+
"retain-failed-seconds"
|
|
53209
|
+
]);
|
|
53210
|
+
START_FILE_FLAGS = ["env-file", "tool-policy", "system-prompt-file", "mcp-config"];
|
|
53211
|
+
DIGITS = /^[0-9]+$/;
|
|
53212
|
+
});
|
|
53213
|
+
|
|
52813
53214
|
// src/live/transcript.ts
|
|
52814
|
-
import { stat as
|
|
53215
|
+
import { stat as stat3 } from "fs/promises";
|
|
52815
53216
|
|
|
52816
53217
|
class TranscriptTail {
|
|
52817
53218
|
path;
|
|
@@ -52875,7 +53276,7 @@ function classifyInterrupt(record) {
|
|
|
52875
53276
|
return null;
|
|
52876
53277
|
}
|
|
52877
53278
|
const [block] = content;
|
|
52878
|
-
if (!
|
|
53279
|
+
if (!isRecord5(block) || block.type !== "text") {
|
|
52879
53280
|
return null;
|
|
52880
53281
|
}
|
|
52881
53282
|
if (block.text === INTERRUPT_MARKER) {
|
|
@@ -52886,6 +53287,29 @@ function classifyInterrupt(record) {
|
|
|
52886
53287
|
}
|
|
52887
53288
|
return null;
|
|
52888
53289
|
}
|
|
53290
|
+
function classifyApiError(record) {
|
|
53291
|
+
if (record.type !== "assistant" || !isMainChain(record) || record.isApiErrorMessage !== true) {
|
|
53292
|
+
return null;
|
|
53293
|
+
}
|
|
53294
|
+
const error = typeof record.error === "string" ? record.error : "unknown";
|
|
53295
|
+
const status = typeof record.apiErrorStatus === "number" ? record.apiErrorStatus : null;
|
|
53296
|
+
const message = assistantBlocks(record).filter((block) => block.kind === "text").map((block) => block.text).join(`
|
|
53297
|
+
`);
|
|
53298
|
+
return {
|
|
53299
|
+
reason: apiErrorReason(error, status, message),
|
|
53300
|
+
detail: `API error ${status ?? error}: ${message.slice(0, API_ERROR_DETAIL_MAX)}`
|
|
53301
|
+
};
|
|
53302
|
+
}
|
|
53303
|
+
function apiErrorReason(error, status, message) {
|
|
53304
|
+
if (error === "rate_limit" && USAGE_LIMIT_TEXT.test(message) && LIMIT_RESETS.test(message) && !NOT_A_USAGE_LIMIT.test(message)) {
|
|
53305
|
+
return "usage_limit";
|
|
53306
|
+
}
|
|
53307
|
+
const reason = failureReason(error);
|
|
53308
|
+
if (reason !== "api_error" || status === null) {
|
|
53309
|
+
return reason;
|
|
53310
|
+
}
|
|
53311
|
+
return STATUS_REASONS[status] ?? reason;
|
|
53312
|
+
}
|
|
52889
53313
|
|
|
52890
53314
|
class UsageLedger {
|
|
52891
53315
|
counted = new Map;
|
|
@@ -52894,7 +53318,7 @@ class UsageLedger {
|
|
|
52894
53318
|
if (record.type !== "assistant" || !isMainChain(record) || message === null) {
|
|
52895
53319
|
return null;
|
|
52896
53320
|
}
|
|
52897
|
-
if (typeof message.id !== "string" || !
|
|
53321
|
+
if (typeof message.id !== "string" || !isRecord5(message.usage)) {
|
|
52898
53322
|
return null;
|
|
52899
53323
|
}
|
|
52900
53324
|
const seen = this.counted.get(message.id) ?? emptyCounts();
|
|
@@ -52921,7 +53345,7 @@ function assistantBlocks(record) {
|
|
|
52921
53345
|
}
|
|
52922
53346
|
const blocks = [];
|
|
52923
53347
|
for (const block of content) {
|
|
52924
|
-
if (!
|
|
53348
|
+
if (!isRecord5(block)) {
|
|
52925
53349
|
continue;
|
|
52926
53350
|
}
|
|
52927
53351
|
if (block.type === "text" && typeof block.text === "string" && block.text !== "") {
|
|
@@ -52941,12 +53365,12 @@ function isMainChain(record) {
|
|
|
52941
53365
|
return record.isSidechain !== true;
|
|
52942
53366
|
}
|
|
52943
53367
|
function messageOf(record) {
|
|
52944
|
-
return
|
|
53368
|
+
return isRecord5(record.message) ? record.message : null;
|
|
52945
53369
|
}
|
|
52946
53370
|
function parseLine(line) {
|
|
52947
53371
|
try {
|
|
52948
53372
|
const value = JSON.parse(line);
|
|
52949
|
-
return
|
|
53373
|
+
return isRecord5(value) ? value : null;
|
|
52950
53374
|
} catch (error) {
|
|
52951
53375
|
if (error instanceof SyntaxError) {
|
|
52952
53376
|
return null;
|
|
@@ -52956,7 +53380,7 @@ function parseLine(line) {
|
|
|
52956
53380
|
}
|
|
52957
53381
|
async function fileSize(path) {
|
|
52958
53382
|
try {
|
|
52959
|
-
return (await
|
|
53383
|
+
return (await stat3(path)).size;
|
|
52960
53384
|
} catch (error) {
|
|
52961
53385
|
if (error.code === "ENOENT") {
|
|
52962
53386
|
return null;
|
|
@@ -52984,17 +53408,27 @@ function emptyCounts() {
|
|
|
52984
53408
|
cacheCreationTokens: 0
|
|
52985
53409
|
};
|
|
52986
53410
|
}
|
|
52987
|
-
function
|
|
53411
|
+
function isRecord5(value) {
|
|
52988
53412
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
52989
53413
|
}
|
|
52990
|
-
var INTERRUPT_MARKER = "[Request interrupted by user]", INTERRUPT_TOOL_MARKER = "[Request interrupted by user for tool use]", NEWLINE = 10;
|
|
52991
|
-
var init_transcript = () => {
|
|
53414
|
+
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;
|
|
53415
|
+
var init_transcript = __esm(() => {
|
|
53416
|
+
init_events2();
|
|
53417
|
+
USAGE_LIMIT_TEXT = /\b(usage|session|weekly) limit\b|\bhit your\b[^.\n]*\blimit\b/i;
|
|
53418
|
+
LIMIT_RESETS = /\bresets?\b/i;
|
|
53419
|
+
NOT_A_USAGE_LIMIT = /\bnot your (usage )?limit\b/i;
|
|
53420
|
+
STATUS_REASONS = {
|
|
53421
|
+
429: "rate_limit",
|
|
53422
|
+
401: "auth_error"
|
|
53423
|
+
};
|
|
53424
|
+
});
|
|
52992
53425
|
|
|
52993
53426
|
// src/live/daemon.ts
|
|
52994
|
-
import { randomUUID as
|
|
52995
|
-
import {
|
|
53427
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
53428
|
+
import { renameSync as renameSync2, rmSync as rmSync2, writeFileSync } from "fs";
|
|
53429
|
+
import { chmod, lstat as lstat2, mkdir as mkdir4, readFile as readFile4, realpath as realpath4, unlink as unlink2 } from "fs/promises";
|
|
52996
53430
|
import { createServer } from "net";
|
|
52997
|
-
import { dirname as
|
|
53431
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join7 } from "path";
|
|
52998
53432
|
function exitCodeFor(code) {
|
|
52999
53433
|
return EXIT_CODES[code];
|
|
53000
53434
|
}
|
|
@@ -53157,6 +53591,9 @@ class LivePane {
|
|
|
53157
53591
|
gen;
|
|
53158
53592
|
ingestUrl;
|
|
53159
53593
|
daemon;
|
|
53594
|
+
extras;
|
|
53595
|
+
mcpConfig;
|
|
53596
|
+
paneDir;
|
|
53160
53597
|
started = new Latch;
|
|
53161
53598
|
submitted = new Latch;
|
|
53162
53599
|
turnClosed = new Latch;
|
|
@@ -53179,17 +53616,29 @@ class LivePane {
|
|
|
53179
53616
|
skippedLines = 0;
|
|
53180
53617
|
lastAssistantText;
|
|
53181
53618
|
lastStopReason = null;
|
|
53619
|
+
apiError;
|
|
53182
53620
|
settled = false;
|
|
53183
53621
|
lastActivityAt = Date.now();
|
|
53184
53622
|
sessionEndReason;
|
|
53185
53623
|
timers = [];
|
|
53624
|
+
heartbeatTimer;
|
|
53625
|
+
deadlineTimer;
|
|
53626
|
+
retentionTimer;
|
|
53627
|
+
interruptTimer;
|
|
53628
|
+
interrupt;
|
|
53629
|
+
isRetaining = false;
|
|
53630
|
+
ending = false;
|
|
53631
|
+
teardown;
|
|
53186
53632
|
monitoring = false;
|
|
53187
53633
|
closed = false;
|
|
53188
|
-
constructor(pane, gen, ingestUrl, token, authValues, daemon) {
|
|
53634
|
+
constructor(pane, gen, ingestUrl, token, authValues, daemon, extras, mcpConfig, paneDir) {
|
|
53189
53635
|
this.pane = pane;
|
|
53190
53636
|
this.gen = gen;
|
|
53191
53637
|
this.ingestUrl = ingestUrl;
|
|
53192
53638
|
this.daemon = daemon;
|
|
53639
|
+
this.extras = extras;
|
|
53640
|
+
this.mcpConfig = mcpConfig;
|
|
53641
|
+
this.paneDir = paneDir;
|
|
53193
53642
|
this.token = token;
|
|
53194
53643
|
this.secrets = [token, ...authValues.filter((value) => value.length >= MIN_SECRET_LENGTH)];
|
|
53195
53644
|
this.queue = new EventQueue({
|
|
@@ -53198,7 +53647,11 @@ class LivePane {
|
|
|
53198
53647
|
bearer: () => this.token,
|
|
53199
53648
|
secrets: () => this.secrets,
|
|
53200
53649
|
timings: () => daemon.timings,
|
|
53201
|
-
onStaleGen: () =>
|
|
53650
|
+
onStaleGen: () => {
|
|
53651
|
+
if (!this.isRetaining) {
|
|
53652
|
+
this.abandon();
|
|
53653
|
+
}
|
|
53654
|
+
},
|
|
53202
53655
|
logger: daemon.logger
|
|
53203
53656
|
});
|
|
53204
53657
|
}
|
|
@@ -53208,13 +53661,16 @@ class LivePane {
|
|
|
53208
53661
|
get busy() {
|
|
53209
53662
|
return this.turn !== undefined || this.compacting;
|
|
53210
53663
|
}
|
|
53664
|
+
get refusesInput() {
|
|
53665
|
+
return this.ending || this.interrupt !== undefined;
|
|
53666
|
+
}
|
|
53211
53667
|
begin() {
|
|
53212
53668
|
const timings = this.daemon.timings;
|
|
53213
53669
|
this.timers = [
|
|
53214
53670
|
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)
|
|
53671
|
+
setInterval(() => void this.guard("monitor", () => this.monitor()), timings.monitorMs)
|
|
53217
53672
|
];
|
|
53673
|
+
this.heartbeatTimer = setInterval(() => void this.guard("heartbeat", () => this.beat()), timings.heartbeatMs);
|
|
53218
53674
|
}
|
|
53219
53675
|
rotate(token) {
|
|
53220
53676
|
this.token = token;
|
|
@@ -53280,24 +53736,28 @@ class LivePane {
|
|
|
53280
53736
|
}
|
|
53281
53737
|
this.closeOpenTools();
|
|
53282
53738
|
this.clearDialog();
|
|
53739
|
+
const message = text(draft.payload.lastAssistantMessage);
|
|
53740
|
+
if (draft.type === "turn_completed" && message !== undefined) {
|
|
53741
|
+
this.persistLastAssistantMessage(message);
|
|
53742
|
+
}
|
|
53283
53743
|
this.queue.push(draft);
|
|
53284
53744
|
this.turn = undefined;
|
|
53745
|
+
clearTimeout(this.deadlineTimer);
|
|
53746
|
+
this.deadlineTimer = undefined;
|
|
53747
|
+
this.lastActivityAt = Date.now();
|
|
53285
53748
|
this.lastCloseCause = cause;
|
|
53286
53749
|
this.turnClosed.fire();
|
|
53750
|
+
this.afterTurn(draft.type);
|
|
53287
53751
|
}
|
|
53288
53752
|
clearDialog() {
|
|
53289
53753
|
this.dialog?.abort.abort();
|
|
53290
53754
|
this.dialog = undefined;
|
|
53291
53755
|
}
|
|
53292
53756
|
interruptedTurn() {
|
|
53293
|
-
return turnCompleted("interrupted", this.lastAssistantText);
|
|
53757
|
+
return turnCompleted("interrupted", this.lastAssistantText ?? "");
|
|
53294
53758
|
}
|
|
53295
|
-
|
|
53296
|
-
this.
|
|
53297
|
-
await killPane(this.pane);
|
|
53298
|
-
this.queue.push(sessionEnded("stopped"));
|
|
53299
|
-
await this.queue.drain(STOP_DRAIN_MS);
|
|
53300
|
-
this.queue.stop();
|
|
53759
|
+
stop(reason = "stopped") {
|
|
53760
|
+
return this.tearDown(() => this.finish(reason, () => killPane(this.pane)));
|
|
53301
53761
|
}
|
|
53302
53762
|
async dispose() {
|
|
53303
53763
|
this.close();
|
|
@@ -53309,7 +53769,130 @@ class LivePane {
|
|
|
53309
53769
|
for (const timer of this.timers.splice(0)) {
|
|
53310
53770
|
clearInterval(timer);
|
|
53311
53771
|
}
|
|
53772
|
+
clearInterval(this.heartbeatTimer);
|
|
53773
|
+
clearTimeout(this.deadlineTimer);
|
|
53774
|
+
clearTimeout(this.retentionTimer);
|
|
53775
|
+
clearTimeout(this.interruptTimer);
|
|
53312
53776
|
this.clearDialog();
|
|
53777
|
+
this.removeMcpConfig();
|
|
53778
|
+
}
|
|
53779
|
+
tearDown(run) {
|
|
53780
|
+
this.teardown ??= run();
|
|
53781
|
+
return this.teardown;
|
|
53782
|
+
}
|
|
53783
|
+
async finish(reason, kill) {
|
|
53784
|
+
try {
|
|
53785
|
+
this.close();
|
|
53786
|
+
await kill();
|
|
53787
|
+
this.queue.push(sessionEnded(reason));
|
|
53788
|
+
await this.queue.drain(STOP_DRAIN_MS);
|
|
53789
|
+
this.queue.stop();
|
|
53790
|
+
} finally {
|
|
53791
|
+
this.daemon.remove(this);
|
|
53792
|
+
}
|
|
53793
|
+
}
|
|
53794
|
+
afterTurn(type) {
|
|
53795
|
+
if (this.closed || this.ending) {
|
|
53796
|
+
return;
|
|
53797
|
+
}
|
|
53798
|
+
const { oneShot, retainFailedSeconds } = this.extras;
|
|
53799
|
+
if (type === "turn_completed" && oneShot) {
|
|
53800
|
+
this.ending = true;
|
|
53801
|
+
this.guard("end", () => this.endAfterDelivery(0));
|
|
53802
|
+
return;
|
|
53803
|
+
}
|
|
53804
|
+
if (type !== "turn_failed" || !oneShot && retainFailedSeconds === 0) {
|
|
53805
|
+
return;
|
|
53806
|
+
}
|
|
53807
|
+
this.ending = true;
|
|
53808
|
+
if (retainFailedSeconds > 0) {
|
|
53809
|
+
this.isRetaining = true;
|
|
53810
|
+
clearInterval(this.heartbeatTimer);
|
|
53811
|
+
}
|
|
53812
|
+
this.guard("end", () => this.endAfterDelivery(retainFailedSeconds * this.daemon.timings.secondMs));
|
|
53813
|
+
}
|
|
53814
|
+
async endAfterDelivery(delayMs) {
|
|
53815
|
+
await this.queue.drain(this.daemon.timings.deliveryDrainMs);
|
|
53816
|
+
if (this.closed) {
|
|
53817
|
+
return;
|
|
53818
|
+
}
|
|
53819
|
+
if (delayMs > 0) {
|
|
53820
|
+
await new Promise((resolve) => {
|
|
53821
|
+
this.retentionTimer = setTimeout(resolve, delayMs);
|
|
53822
|
+
});
|
|
53823
|
+
if (this.closed) {
|
|
53824
|
+
return;
|
|
53825
|
+
}
|
|
53826
|
+
}
|
|
53827
|
+
await this.end("stopped");
|
|
53828
|
+
}
|
|
53829
|
+
end(reason) {
|
|
53830
|
+
return this.tearDown(() => this.finish(reason, () => this.killLogged()));
|
|
53831
|
+
}
|
|
53832
|
+
async onDeadline(promptId, seconds) {
|
|
53833
|
+
if (this.closed || this.turn?.promptId !== promptId) {
|
|
53834
|
+
return;
|
|
53835
|
+
}
|
|
53836
|
+
this.closeTurn(turnFailedWith("deadline", `The turn ran longer than ${seconds} seconds.`), "deadline");
|
|
53837
|
+
const interrupt = {
|
|
53838
|
+
sent: false,
|
|
53839
|
+
marked: false
|
|
53840
|
+
};
|
|
53841
|
+
this.interrupt = interrupt;
|
|
53842
|
+
this.interruptTimer = setTimeout(() => this.endInterrupt(), this.daemon.timings.cancelTimeoutMs);
|
|
53843
|
+
try {
|
|
53844
|
+
await cancelPane(this.pane);
|
|
53845
|
+
interrupt.sent = true;
|
|
53846
|
+
this.settleInterrupt();
|
|
53847
|
+
} catch (error) {
|
|
53848
|
+
if (!(error instanceof PaneError)) {
|
|
53849
|
+
throw error;
|
|
53850
|
+
}
|
|
53851
|
+
this.daemon.logger.warn("tmuxd could not interrupt a turn past its deadline", {
|
|
53852
|
+
pane: this.id,
|
|
53853
|
+
reason: error.reason
|
|
53854
|
+
});
|
|
53855
|
+
}
|
|
53856
|
+
}
|
|
53857
|
+
settleInterrupt() {
|
|
53858
|
+
if (this.interrupt?.sent === true && this.interrupt.marked) {
|
|
53859
|
+
this.endInterrupt();
|
|
53860
|
+
}
|
|
53861
|
+
}
|
|
53862
|
+
endInterrupt() {
|
|
53863
|
+
clearTimeout(this.interruptTimer);
|
|
53864
|
+
this.interruptTimer = undefined;
|
|
53865
|
+
this.interrupt = undefined;
|
|
53866
|
+
}
|
|
53867
|
+
removeMcpConfig() {
|
|
53868
|
+
if (this.mcpConfig === undefined) {
|
|
53869
|
+
return;
|
|
53870
|
+
}
|
|
53871
|
+
try {
|
|
53872
|
+
rmSync2(this.mcpConfig, { force: true });
|
|
53873
|
+
} catch (error) {
|
|
53874
|
+
if (!isErrno(error)) {
|
|
53875
|
+
throw error;
|
|
53876
|
+
}
|
|
53877
|
+
this.daemon.logger.error("tmuxd could not remove the mcp-config", { pane: this.id, code: error.code });
|
|
53878
|
+
}
|
|
53879
|
+
}
|
|
53880
|
+
persistLastAssistantMessage(message) {
|
|
53881
|
+
const path = join7(this.paneDir, LAST_MESSAGE_FILE);
|
|
53882
|
+
const temp = `${path}.${randomUUID6()}.tmp`;
|
|
53883
|
+
try {
|
|
53884
|
+
writeFileSync(temp, redactDeep(message, this.secrets), { mode: 384, flag: "wx" });
|
|
53885
|
+
renameSync2(temp, path);
|
|
53886
|
+
} catch (error) {
|
|
53887
|
+
if (!isErrno(error)) {
|
|
53888
|
+
throw error;
|
|
53889
|
+
}
|
|
53890
|
+
rmSync2(temp, { force: true });
|
|
53891
|
+
this.daemon.logger.error("tmuxd could not persist the last assistant message", {
|
|
53892
|
+
pane: this.id,
|
|
53893
|
+
code: error.code
|
|
53894
|
+
});
|
|
53895
|
+
}
|
|
53313
53896
|
}
|
|
53314
53897
|
async onSessionStart(payload) {
|
|
53315
53898
|
const source = text(payload.source);
|
|
@@ -53333,20 +53916,26 @@ class LivePane {
|
|
|
53333
53916
|
return;
|
|
53334
53917
|
}
|
|
53335
53918
|
this.turn = {
|
|
53336
|
-
promptId: this.pendingPromptId ?? text(payload.prompt_id) ??
|
|
53919
|
+
promptId: this.pendingPromptId ?? text(payload.prompt_id) ?? randomUUID6()
|
|
53337
53920
|
};
|
|
53338
53921
|
this.pendingPromptId = undefined;
|
|
53339
53922
|
this.settled = false;
|
|
53340
53923
|
this.lastAssistantText = undefined;
|
|
53341
53924
|
this.lastStopReason = null;
|
|
53925
|
+
this.apiError = undefined;
|
|
53342
53926
|
this.queue.push(turnStarted(this.turn.promptId));
|
|
53343
53927
|
this.submitted.fire();
|
|
53928
|
+
const seconds = this.extras.deadlineSeconds;
|
|
53929
|
+
if (seconds !== undefined) {
|
|
53930
|
+
const promptId = this.turn.promptId;
|
|
53931
|
+
this.deadlineTimer = setTimeout(() => void this.guard("deadline", () => this.onDeadline(promptId, seconds)), seconds * this.daemon.timings.secondMs);
|
|
53932
|
+
}
|
|
53344
53933
|
}
|
|
53345
53934
|
onPreToolUse(payload) {
|
|
53346
53935
|
if (this.turn === undefined) {
|
|
53347
53936
|
return;
|
|
53348
53937
|
}
|
|
53349
|
-
const toolCallId = text(payload.tool_use_id) ??
|
|
53938
|
+
const toolCallId = text(payload.tool_use_id) ?? randomUUID6();
|
|
53350
53939
|
this.openTools.set(toolCallId, {
|
|
53351
53940
|
name: text(payload.tool_name),
|
|
53352
53941
|
input: payload.tool_input
|
|
@@ -53355,9 +53944,22 @@ class LivePane {
|
|
|
53355
53944
|
this.queue.push(toolCallBegin(payload, toolCallId));
|
|
53356
53945
|
}
|
|
53357
53946
|
async onPermissionRequest(payload, signal) {
|
|
53947
|
+
if (this.extras.permissionMode === "dontAsk") {
|
|
53948
|
+
this.daemon.logger.debug("tmuxd denied a permission request in dontAsk mode", {
|
|
53949
|
+
pane: this.id,
|
|
53950
|
+
tool: text(payload.tool_name)
|
|
53951
|
+
});
|
|
53952
|
+
return {
|
|
53953
|
+
ok: true,
|
|
53954
|
+
decision: {
|
|
53955
|
+
behavior: "deny",
|
|
53956
|
+
message: UNATTENDED_DENIAL
|
|
53957
|
+
}
|
|
53958
|
+
};
|
|
53959
|
+
}
|
|
53358
53960
|
const correlated = this.findOpenTool(payload);
|
|
53359
53961
|
const dialog = {
|
|
53360
|
-
toolCallId: correlated ??
|
|
53962
|
+
toolCallId: correlated ?? randomUUID6(),
|
|
53361
53963
|
correlated: correlated !== undefined,
|
|
53362
53964
|
toolName: text(payload.tool_name),
|
|
53363
53965
|
input: payload.tool_input,
|
|
@@ -53481,7 +54083,10 @@ class LivePane {
|
|
|
53481
54083
|
return;
|
|
53482
54084
|
}
|
|
53483
54085
|
await this.syncTranscript();
|
|
53484
|
-
this.closeTurn(turnCompleted(apiStopReason(this.lastStopReason), text(payload.last_assistant_message) ?? this.lastAssistantText), "stop");
|
|
54086
|
+
this.closeTurn(this.endOfTurn(turnCompleted(apiStopReason(this.lastStopReason), text(payload.last_assistant_message) ?? this.lastAssistantText ?? "")), "stop");
|
|
54087
|
+
}
|
|
54088
|
+
endOfTurn(completed) {
|
|
54089
|
+
return this.apiError === undefined ? completed : turnFailedWith(this.apiError.reason, this.apiError.detail);
|
|
53485
54090
|
}
|
|
53486
54091
|
async onStopFailure(payload) {
|
|
53487
54092
|
if (this.turn === undefined) {
|
|
@@ -53535,13 +54140,19 @@ class LivePane {
|
|
|
53535
54140
|
}
|
|
53536
54141
|
this.lastActivityAt = Date.now();
|
|
53537
54142
|
if (classifyInterrupt(record) !== null) {
|
|
53538
|
-
|
|
54143
|
+
if (this.interrupt === undefined) {
|
|
54144
|
+
this.closeTurn(this.interruptedTurn(), "marker");
|
|
54145
|
+
} else {
|
|
54146
|
+
this.interrupt.marked = true;
|
|
54147
|
+
this.settleInterrupt();
|
|
54148
|
+
}
|
|
53539
54149
|
this.clearDialog();
|
|
53540
54150
|
continue;
|
|
53541
54151
|
}
|
|
54152
|
+
const failure = classifyApiError(record);
|
|
53542
54153
|
for (const block of assistantBlocks(record)) {
|
|
53543
54154
|
this.queue.push(block.kind === "text" ? outputChunk(block.text) : thinkingChunk(block.text));
|
|
53544
|
-
if (block.kind === "text") {
|
|
54155
|
+
if (block.kind === "text" && failure === null) {
|
|
53545
54156
|
this.lastAssistantText = block.text;
|
|
53546
54157
|
}
|
|
53547
54158
|
}
|
|
@@ -53555,6 +54166,14 @@ class LivePane {
|
|
|
53555
54166
|
} else if (record.type === "user") {
|
|
53556
54167
|
this.settled = false;
|
|
53557
54168
|
}
|
|
54169
|
+
if (failure !== null) {
|
|
54170
|
+
this.apiError = failure;
|
|
54171
|
+
if (failure.reason === "usage_limit") {
|
|
54172
|
+
this.closeTurn(turnFailedWith(failure.reason, failure.detail), "api_error");
|
|
54173
|
+
}
|
|
54174
|
+
} else if (record.type === "assistant" || record.type === "user" && record.isMeta !== true) {
|
|
54175
|
+
this.apiError = undefined;
|
|
54176
|
+
}
|
|
53558
54177
|
}
|
|
53559
54178
|
}
|
|
53560
54179
|
async monitor() {
|
|
@@ -53563,21 +54182,33 @@ class LivePane {
|
|
|
53563
54182
|
}
|
|
53564
54183
|
this.monitoring = true;
|
|
53565
54184
|
try {
|
|
53566
|
-
|
|
54185
|
+
const alive = await this.alive();
|
|
54186
|
+
if (this.closed) {
|
|
54187
|
+
return;
|
|
54188
|
+
}
|
|
54189
|
+
if (!alive) {
|
|
53567
54190
|
await this.onDeath();
|
|
53568
54191
|
return;
|
|
53569
54192
|
}
|
|
53570
54193
|
const quiet = Date.now() - this.lastActivityAt >= this.daemon.timings.watchdogQuietMs;
|
|
53571
54194
|
const idle = this.dialog === undefined && this.openTools.size === 0 && !this.compacting && this.settled;
|
|
53572
54195
|
if (this.turn !== undefined && quiet && idle) {
|
|
53573
|
-
this.closeTurn(turnCompleted("watchdog", this.lastAssistantText), "watchdog");
|
|
54196
|
+
this.closeTurn(this.endOfTurn(turnCompleted("watchdog", this.lastAssistantText ?? "")), "watchdog");
|
|
54197
|
+
}
|
|
54198
|
+
const idleTimeoutMs = this.daemon.timings.idleTimeoutMs;
|
|
54199
|
+
const unoccupied = this.turn === undefined && this.pendingPromptId === undefined && this.dialog === undefined && !this.compacting && !this.isRetaining;
|
|
54200
|
+
if (idleTimeoutMs > 0 && unoccupied && Date.now() - this.lastActivityAt >= idleTimeoutMs) {
|
|
54201
|
+
await this.end("idle_timeout");
|
|
53574
54202
|
}
|
|
53575
54203
|
} finally {
|
|
53576
54204
|
this.monitoring = false;
|
|
53577
54205
|
}
|
|
53578
54206
|
}
|
|
53579
54207
|
async beat() {
|
|
53580
|
-
if (
|
|
54208
|
+
if (this.closed || this.isRetaining || !await this.alive()) {
|
|
54209
|
+
return;
|
|
54210
|
+
}
|
|
54211
|
+
if (!this.closed && !this.isRetaining) {
|
|
53581
54212
|
this.queue.push(heartbeat());
|
|
53582
54213
|
}
|
|
53583
54214
|
}
|
|
@@ -53588,28 +54219,32 @@ class LivePane {
|
|
|
53588
54219
|
if (this.closed) {
|
|
53589
54220
|
return;
|
|
53590
54221
|
}
|
|
53591
|
-
|
|
53592
|
-
|
|
53593
|
-
|
|
53594
|
-
this.
|
|
53595
|
-
|
|
53596
|
-
|
|
53597
|
-
|
|
53598
|
-
|
|
53599
|
-
|
|
54222
|
+
await this.tearDown(async () => {
|
|
54223
|
+
const turnRunning = this.turn !== undefined;
|
|
54224
|
+
this.close();
|
|
54225
|
+
if (this.sessionEndReason !== undefined) {
|
|
54226
|
+
this.queue.push(sessionEnded("stopped"));
|
|
54227
|
+
} else {
|
|
54228
|
+
this.queue.push(sessionEnded("crashed"));
|
|
54229
|
+
this.queue.push(processDied());
|
|
54230
|
+
if (turnRunning) {
|
|
54231
|
+
this.queue.push(sessionFailed("process_died", "claude exited while a turn was running."));
|
|
54232
|
+
}
|
|
53600
54233
|
}
|
|
53601
|
-
|
|
53602
|
-
|
|
53603
|
-
|
|
53604
|
-
|
|
54234
|
+
await this.queue.drain(STOP_DRAIN_MS);
|
|
54235
|
+
this.queue.stop();
|
|
54236
|
+
this.daemon.remove(this);
|
|
54237
|
+
});
|
|
53605
54238
|
}
|
|
53606
54239
|
async abandon() {
|
|
53607
54240
|
if (this.closed) {
|
|
53608
54241
|
return;
|
|
53609
54242
|
}
|
|
53610
|
-
this.
|
|
53611
|
-
|
|
53612
|
-
|
|
54243
|
+
await this.tearDown(async () => {
|
|
54244
|
+
this.close();
|
|
54245
|
+
await this.killLogged();
|
|
54246
|
+
this.daemon.remove(this);
|
|
54247
|
+
});
|
|
53613
54248
|
}
|
|
53614
54249
|
async killLogged() {
|
|
53615
54250
|
try {
|
|
@@ -53639,6 +54274,7 @@ class LiveDaemon {
|
|
|
53639
54274
|
logger;
|
|
53640
54275
|
timings;
|
|
53641
54276
|
panes = new Map;
|
|
54277
|
+
starting = new Set;
|
|
53642
54278
|
server;
|
|
53643
54279
|
constructor(options) {
|
|
53644
54280
|
this.options = options;
|
|
@@ -53656,7 +54292,7 @@ class LiveDaemon {
|
|
|
53656
54292
|
}
|
|
53657
54293
|
async listen() {
|
|
53658
54294
|
const { socketPath } = this.options;
|
|
53659
|
-
await mkdir4(
|
|
54295
|
+
await mkdir4(dirname5(socketPath), { recursive: true, mode: 448 });
|
|
53660
54296
|
if (await Bun.file(socketPath).exists()) {
|
|
53661
54297
|
if (await this.answering(socketPath)) {
|
|
53662
54298
|
throw new Error(`a tmuxd daemon already listens on ${socketPath}`);
|
|
@@ -53724,52 +54360,90 @@ class LiveDaemon {
|
|
|
53724
54360
|
}
|
|
53725
54361
|
}
|
|
53726
54362
|
async start(args) {
|
|
53727
|
-
|
|
53728
|
-
if (envFile === undefined || !isAbsolute4(envFile)) {
|
|
53729
|
-
return refuse("usage_error");
|
|
53730
|
-
}
|
|
54363
|
+
let result;
|
|
53731
54364
|
try {
|
|
53732
|
-
|
|
54365
|
+
result = await this.launch(args);
|
|
54366
|
+
return result;
|
|
53733
54367
|
} finally {
|
|
53734
|
-
await
|
|
54368
|
+
await removeHandoff(args, result?.ok === true, this.logger);
|
|
53735
54369
|
}
|
|
53736
54370
|
}
|
|
53737
|
-
async launch(args
|
|
54371
|
+
async launch(args) {
|
|
53738
54372
|
const { pane: id, cwd, model, resume } = args;
|
|
54373
|
+
const envFile = args["env-file"];
|
|
53739
54374
|
const settingsBase = args["settings-base"];
|
|
54375
|
+
const policyFile = args["tool-policy"];
|
|
54376
|
+
const systemPromptFile = args["system-prompt-file"];
|
|
54377
|
+
const mcpConfig = args["mcp-config"];
|
|
53740
54378
|
if (id === undefined || !PANE_ID2.test(id) || cwd === undefined || !isAbsolute4(cwd)) {
|
|
53741
54379
|
return refuse("usage_error");
|
|
53742
54380
|
}
|
|
53743
|
-
if (settingsBase === undefined || !isAbsolute4(settingsBase)) {
|
|
54381
|
+
if (envFile === undefined || !isAbsolute4(envFile) || settingsBase === undefined || !isAbsolute4(settingsBase)) {
|
|
53744
54382
|
return refuse("usage_error");
|
|
53745
54383
|
}
|
|
53746
54384
|
if (model !== undefined && !MODEL_NAME.test(model)) {
|
|
53747
54385
|
return refuse("usage_error");
|
|
53748
54386
|
}
|
|
53749
|
-
|
|
54387
|
+
const extras = parseStartExtras(args);
|
|
54388
|
+
if (extras === null || [policyFile, systemPromptFile, mcpConfig].some((path) => !optionalAbsolute(path))) {
|
|
54389
|
+
return refuse("usage_error");
|
|
54390
|
+
}
|
|
54391
|
+
if (this.panes.has(id) || this.starting.has(id)) {
|
|
53750
54392
|
return refuse("pane_exists");
|
|
53751
54393
|
}
|
|
53752
|
-
if (this.panes.size >= (this.options.maxPanes ?? DEFAULT_MAX_PANES)) {
|
|
54394
|
+
if (this.panes.size + this.starting.size >= (this.options.maxPanes ?? DEFAULT_MAX_PANES)) {
|
|
53753
54395
|
return refuse("pane_cap");
|
|
53754
54396
|
}
|
|
54397
|
+
this.starting.add(id);
|
|
54398
|
+
try {
|
|
54399
|
+
return await this.open({
|
|
54400
|
+
id,
|
|
54401
|
+
cwd,
|
|
54402
|
+
model,
|
|
54403
|
+
resume,
|
|
54404
|
+
envFile,
|
|
54405
|
+
settingsBase,
|
|
54406
|
+
policyFile,
|
|
54407
|
+
systemPromptFile,
|
|
54408
|
+
mcpConfig,
|
|
54409
|
+
extras
|
|
54410
|
+
});
|
|
54411
|
+
} finally {
|
|
54412
|
+
this.starting.delete(id);
|
|
54413
|
+
}
|
|
54414
|
+
}
|
|
54415
|
+
async open(request) {
|
|
54416
|
+
const { id, cwd, model, resume, envFile, settingsBase, policyFile, systemPromptFile, mcpConfig, extras } = request;
|
|
53755
54417
|
const content = await readSecretFile(envFile);
|
|
53756
54418
|
const env = content === null ? null : parseStartEnv(content);
|
|
53757
54419
|
if (env === null) {
|
|
53758
54420
|
return refuse("usage_error");
|
|
53759
54421
|
}
|
|
54422
|
+
for (const path of [systemPromptFile, mcpConfig]) {
|
|
54423
|
+
if (path !== undefined && !await isPrivateFile(path)) {
|
|
54424
|
+
return refuse("usage_error");
|
|
54425
|
+
}
|
|
54426
|
+
}
|
|
54427
|
+
const policy = policyFile === undefined ? undefined : await readToolPolicy(policyFile);
|
|
54428
|
+
if (policy === null) {
|
|
54429
|
+
return refuse("usage_error");
|
|
54430
|
+
}
|
|
54431
|
+
const mcpSpellings = mcpConfig === undefined ? [] : [...new Set([mcpConfig, await realpath4(mcpConfig)])];
|
|
53760
54432
|
const settingsPath = join7(settingsBase, `${id}.settings.json`);
|
|
53761
54433
|
await mkdir4(settingsBase, { recursive: true, mode: 448 });
|
|
53762
54434
|
await writePaneSettings(settingsPath, {
|
|
53763
54435
|
basePath: this.options.baseSettingsPath ?? DEFAULT_BASE_SETTINGS_PATH,
|
|
53764
54436
|
helperArgv: this.options.helperArgv,
|
|
53765
54437
|
socketPath: this.options.socketPath,
|
|
53766
|
-
paneId: id
|
|
54438
|
+
paneId: id,
|
|
54439
|
+
rules: paneRules(policy, mcpSpellings, extras.permissionMode)
|
|
53767
54440
|
});
|
|
53768
54441
|
const pane = {
|
|
53769
54442
|
id,
|
|
53770
54443
|
socket: this.options.tmuxSocket ?? LIVE_SOCKET
|
|
53771
54444
|
};
|
|
53772
|
-
const live = new LivePane(pane, env.gen, env.ingestUrl, env.token, Object.values(env.authEnv), this);
|
|
54445
|
+
const live = new LivePane(pane, env.gen, env.ingestUrl, env.token, Object.values(env.authEnv), this, extras, mcpConfig, settingsBase);
|
|
54446
|
+
this.starting.delete(id);
|
|
53773
54447
|
this.panes.set(id, live);
|
|
53774
54448
|
try {
|
|
53775
54449
|
await createPane({
|
|
@@ -53780,6 +54454,9 @@ class LiveDaemon {
|
|
|
53780
54454
|
env: env.authEnv,
|
|
53781
54455
|
model,
|
|
53782
54456
|
resumeSessionId: resume,
|
|
54457
|
+
mcpConfig,
|
|
54458
|
+
systemPromptFile,
|
|
54459
|
+
permissionMode: extras.permissionMode,
|
|
53783
54460
|
claudeBin: this.options.claudeBin,
|
|
53784
54461
|
socket: pane.socket,
|
|
53785
54462
|
confirmStarted: (timeoutMs) => live.started.wait(timeoutMs)
|
|
@@ -53789,7 +54466,13 @@ class LiveDaemon {
|
|
|
53789
54466
|
live.queue.stop();
|
|
53790
54467
|
return refuse(launchFailure(error));
|
|
53791
54468
|
}
|
|
53792
|
-
|
|
54469
|
+
try {
|
|
54470
|
+
live.pid = await panePid(pane);
|
|
54471
|
+
} catch (error) {
|
|
54472
|
+
this.panes.delete(id);
|
|
54473
|
+
await live.dispose();
|
|
54474
|
+
return refuse(launchFailure(error));
|
|
54475
|
+
}
|
|
53793
54476
|
live.begin();
|
|
53794
54477
|
return accept({
|
|
53795
54478
|
pane: id,
|
|
@@ -53825,7 +54508,7 @@ class LiveDaemon {
|
|
|
53825
54508
|
}
|
|
53826
54509
|
throw error;
|
|
53827
54510
|
}
|
|
53828
|
-
const promptId =
|
|
54511
|
+
const promptId = randomUUID6();
|
|
53829
54512
|
live.pendingPromptId = promptId;
|
|
53830
54513
|
live.submitted.arm();
|
|
53831
54514
|
let outcome;
|
|
@@ -53971,7 +54654,7 @@ class LiveDaemon {
|
|
|
53971
54654
|
if (live.dialog !== undefined) {
|
|
53972
54655
|
return refuse("dialog_open");
|
|
53973
54656
|
}
|
|
53974
|
-
if (live.busy || live.pendingPromptId !== undefined) {
|
|
54657
|
+
if (live.busy || live.refusesInput || live.pendingPromptId !== undefined) {
|
|
53975
54658
|
return refuse("busy");
|
|
53976
54659
|
}
|
|
53977
54660
|
return null;
|
|
@@ -54083,17 +54766,65 @@ function parseStartEnv(content) {
|
|
|
54083
54766
|
authEnv: Object.fromEntries(Object.entries(values).filter(([key]) => !key.startsWith(LIVE_KEY_PREFIX)))
|
|
54084
54767
|
};
|
|
54085
54768
|
}
|
|
54086
|
-
|
|
54087
|
-
const
|
|
54769
|
+
function paneRules(policy, mcpPaths, mode) {
|
|
54770
|
+
const deny = [...mcpPaths.map((path) => `Read(/${path})`), ...mode === "dontAsk" ? ["EnterPlanMode"] : []];
|
|
54771
|
+
if (policy === undefined && deny.length === 0) {
|
|
54772
|
+
return;
|
|
54773
|
+
}
|
|
54774
|
+
return {
|
|
54775
|
+
allow: policy?.allow ?? [],
|
|
54776
|
+
deny: [...policy?.deny ?? [], ...deny],
|
|
54777
|
+
ask: policy?.ask ?? []
|
|
54778
|
+
};
|
|
54779
|
+
}
|
|
54780
|
+
function optionalAbsolute(path) {
|
|
54781
|
+
return path === undefined || isAbsolute4(path);
|
|
54782
|
+
}
|
|
54783
|
+
async function removeHandoff(args, keepMcp, logger) {
|
|
54784
|
+
const files = START_FILE_FLAGS.flatMap((flag) => {
|
|
54785
|
+
const path = args[flag];
|
|
54786
|
+
return path === undefined || !isAbsolute4(path) || keepMcp && flag === "mcp-config" ? [] : [[flag, path]];
|
|
54787
|
+
});
|
|
54788
|
+
for (const failure of await removeHandoffFiles(files)) {
|
|
54789
|
+
logger.error("tmuxd could not remove a hand-off file", { ...failure });
|
|
54790
|
+
}
|
|
54791
|
+
}
|
|
54792
|
+
async function removeHandoffFiles(files) {
|
|
54793
|
+
const ordered = [
|
|
54794
|
+
...files.filter(([flag]) => flag === "mcp-config"),
|
|
54795
|
+
...files.filter(([flag]) => flag !== "mcp-config")
|
|
54796
|
+
];
|
|
54797
|
+
const failures = [];
|
|
54798
|
+
for (const [flag, path] of ordered) {
|
|
54799
|
+
try {
|
|
54800
|
+
await removeFile(path);
|
|
54801
|
+
} catch (error) {
|
|
54802
|
+
if (!isErrno(error)) {
|
|
54803
|
+
throw error;
|
|
54804
|
+
}
|
|
54805
|
+
failures.push({
|
|
54806
|
+
flag,
|
|
54807
|
+
code: error.code
|
|
54808
|
+
});
|
|
54809
|
+
}
|
|
54810
|
+
}
|
|
54811
|
+
return failures;
|
|
54812
|
+
}
|
|
54813
|
+
async function isPrivateFile(path) {
|
|
54814
|
+
const info = await lstat2(path).catch((error) => {
|
|
54088
54815
|
if (error.code === "ENOENT") {
|
|
54089
54816
|
return null;
|
|
54090
54817
|
}
|
|
54091
54818
|
throw error;
|
|
54092
54819
|
});
|
|
54093
|
-
|
|
54094
|
-
|
|
54095
|
-
|
|
54096
|
-
return readFile4(path, "utf8");
|
|
54820
|
+
return info?.isFile() === true && (info.mode & 511) === 384;
|
|
54821
|
+
}
|
|
54822
|
+
async function readSecretFile(path) {
|
|
54823
|
+
return await isPrivateFile(path) ? readFile4(path, "utf8") : null;
|
|
54824
|
+
}
|
|
54825
|
+
async function readToolPolicy(path) {
|
|
54826
|
+
const content = await readSecretFile(path);
|
|
54827
|
+
return content === null ? null : parseToolPolicy(content);
|
|
54097
54828
|
}
|
|
54098
54829
|
async function readFileOrNull(path) {
|
|
54099
54830
|
return readFile4(path, "utf8").catch((error) => {
|
|
@@ -54255,11 +54986,18 @@ function accept(data) {
|
|
|
54255
54986
|
return {
|
|
54256
54987
|
ok: true,
|
|
54257
54988
|
code: "ok",
|
|
54258
|
-
data:
|
|
54989
|
+
data: stamped(data)
|
|
54990
|
+
};
|
|
54991
|
+
}
|
|
54992
|
+
function refuse(code, data = {}) {
|
|
54993
|
+
return {
|
|
54994
|
+
ok: false,
|
|
54995
|
+
code,
|
|
54996
|
+
data: stamped(data)
|
|
54259
54997
|
};
|
|
54260
54998
|
}
|
|
54261
|
-
function
|
|
54262
|
-
return
|
|
54999
|
+
function stamped(data) {
|
|
55000
|
+
return Object.fromEntries(Object.entries({ daemon_version: DAEMON_VERSION, ...data }).filter(([, value]) => value !== undefined));
|
|
54263
55001
|
}
|
|
54264
55002
|
function notRunning(id) {
|
|
54265
55003
|
return refuse("pane_not_running", {
|
|
@@ -54267,14 +55005,18 @@ function notRunning(id) {
|
|
|
54267
55005
|
running: false
|
|
54268
55006
|
});
|
|
54269
55007
|
}
|
|
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;
|
|
55008
|
+
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
55009
|
var init_daemon = __esm(() => {
|
|
55010
|
+
init_package();
|
|
54272
55011
|
init_logger();
|
|
54273
55012
|
init_events2();
|
|
54274
55013
|
init_hooks();
|
|
54275
55014
|
init_input();
|
|
54276
55015
|
init_pane();
|
|
55016
|
+
init_policy4();
|
|
55017
|
+
init_start_flags();
|
|
54277
55018
|
init_transcript();
|
|
55019
|
+
DAEMON_VERSION = package_default.version;
|
|
54278
55020
|
PERMISSION_WAIT_MS = PERMISSION_HOOK_TIMEOUT_S * 1000 - 1e4;
|
|
54279
55021
|
MAX_REQUEST_BYTES = 8 * 1024 * 1024;
|
|
54280
55022
|
PANE_ID2 = /^[A-Za-z0-9_-]{1,64}$/;
|
|
@@ -54308,14 +55050,17 @@ var init_daemon = __esm(() => {
|
|
|
54308
55050
|
monitorMs: 2000,
|
|
54309
55051
|
transcriptPollMs: 250,
|
|
54310
55052
|
retryBaseMs: 250,
|
|
54311
|
-
retryMaxMs: 1e4
|
|
55053
|
+
retryMaxMs: 1e4,
|
|
55054
|
+
secondMs: 1000,
|
|
55055
|
+
idleTimeoutMs: 0,
|
|
55056
|
+
deliveryDrainMs: 120000
|
|
54312
55057
|
};
|
|
54313
55058
|
});
|
|
54314
55059
|
|
|
54315
55060
|
// src/live/ctl.ts
|
|
54316
55061
|
import { spawn as spawn2 } from "child_process";
|
|
54317
55062
|
import { homedir as homedir2, tmpdir } from "os";
|
|
54318
|
-
import { resolve as
|
|
55063
|
+
import { resolve as resolve6 } from "path";
|
|
54319
55064
|
async function runTmuxd(args) {
|
|
54320
55065
|
const [command, ...rest] = args;
|
|
54321
55066
|
switch (command) {
|
|
@@ -54338,9 +55083,9 @@ async function runTmuxd(args) {
|
|
|
54338
55083
|
async function runCtl(args) {
|
|
54339
55084
|
const [verb, ...rest] = args;
|
|
54340
55085
|
const flags = parseFlags(rest);
|
|
54341
|
-
if (verb === undefined || !VERBS.has(verb) || flags === null) {
|
|
55086
|
+
if (verb === undefined || !VERBS.has(verb) || flags === null || verb === "start" && parseStartExtras(flags) === null) {
|
|
54342
55087
|
await discardHandoff(verb, rest);
|
|
54343
|
-
return print(
|
|
55088
|
+
return print(ctlMade("usage_error"));
|
|
54344
55089
|
}
|
|
54345
55090
|
const socketPath = socketPathFromEnv();
|
|
54346
55091
|
const message = {
|
|
@@ -54359,7 +55104,7 @@ async function runCtl(args) {
|
|
|
54359
55104
|
if (error.reason === "unreachable") {
|
|
54360
55105
|
await discardHandoff(verb, rest);
|
|
54361
55106
|
}
|
|
54362
|
-
return print(error.reason === "unreachable" ?
|
|
55107
|
+
return print(error.reason === "unreachable" ? ctlMade("pane_not_running", { pane: flags.pane, running: false }) : ctlMade("timeout"));
|
|
54363
55108
|
}
|
|
54364
55109
|
}
|
|
54365
55110
|
async function ask(socketPath, message, spawnIfMissing) {
|
|
@@ -54426,6 +55171,7 @@ async function runDaemon() {
|
|
|
54426
55171
|
baseSettingsPath: env.KODIZM_LIVE_BASE_SETTINGS ?? DEFAULT_BASE_SETTINGS_PATH,
|
|
54427
55172
|
maxPanes: Number.isInteger(maxPanes) && maxPanes > 0 ? maxPanes : DEFAULT_MAX_PANES,
|
|
54428
55173
|
logger: createLogger({ env }),
|
|
55174
|
+
timings: { idleTimeoutMs: idleTimeoutMs(env) },
|
|
54429
55175
|
onIdle: () => void daemon.close().then(shutdown)
|
|
54430
55176
|
});
|
|
54431
55177
|
await daemon.listen();
|
|
@@ -54435,6 +55181,10 @@ async function runDaemon() {
|
|
|
54435
55181
|
await stopped;
|
|
54436
55182
|
return 0;
|
|
54437
55183
|
}
|
|
55184
|
+
function idleTimeoutMs(env) {
|
|
55185
|
+
const seconds = env.KODIZM_TMUXD_IDLE_TIMEOUT_SECONDS;
|
|
55186
|
+
return seconds !== undefined && /^[1-9]\d*$/.test(seconds) ? Number(seconds) * 1000 : 0;
|
|
55187
|
+
}
|
|
54438
55188
|
function spawnDaemon() {
|
|
54439
55189
|
const child = spawn2(process.execPath, [process.argv[1] ?? "", "tmuxd", "daemon"], {
|
|
54440
55190
|
detached: true,
|
|
@@ -54455,21 +55205,37 @@ function parseFlags(args) {
|
|
|
54455
55205
|
if (!flag.startsWith("--") || name === "" || value === undefined || name in flags) {
|
|
54456
55206
|
return null;
|
|
54457
55207
|
}
|
|
54458
|
-
flags[name] = PATH_FLAGS.has(name) ?
|
|
55208
|
+
flags[name] = PATH_FLAGS.has(name) ? resolve6(value) : value;
|
|
54459
55209
|
}
|
|
54460
55210
|
return flags;
|
|
54461
55211
|
}
|
|
54462
55212
|
async function discardHandoff(verb, args) {
|
|
54463
|
-
const
|
|
54464
|
-
if (
|
|
55213
|
+
const flags = verb === undefined ? undefined : HANDOFF_FLAGS.get(verb);
|
|
55214
|
+
if (flags === undefined) {
|
|
54465
55215
|
return;
|
|
54466
55216
|
}
|
|
55217
|
+
const files = [];
|
|
54467
55218
|
for (let index = 0;index < args.length - 1; index += 1) {
|
|
54468
55219
|
const value = args[index + 1];
|
|
54469
|
-
|
|
54470
|
-
|
|
55220
|
+
const flag = flags.find((name) => args[index] === `--${name}`);
|
|
55221
|
+
if (value !== undefined && flag !== undefined) {
|
|
55222
|
+
files.push([flag, resolve6(value)]);
|
|
54471
55223
|
}
|
|
54472
55224
|
}
|
|
55225
|
+
for (const failure of await removeHandoffFiles(files)) {
|
|
55226
|
+
process.stderr.write(`${JSON.stringify({ level: "error", message: "tmuxd ctl could not remove a hand-off file", ...failure })}
|
|
55227
|
+
`);
|
|
55228
|
+
}
|
|
55229
|
+
}
|
|
55230
|
+
function ctlMade(code, data = {}) {
|
|
55231
|
+
return {
|
|
55232
|
+
ok: false,
|
|
55233
|
+
code,
|
|
55234
|
+
data: {
|
|
55235
|
+
daemon_version: DAEMON_VERSION,
|
|
55236
|
+
...data
|
|
55237
|
+
}
|
|
55238
|
+
};
|
|
54473
55239
|
}
|
|
54474
55240
|
function toResult(response) {
|
|
54475
55241
|
const code = typeof response === "object" && response !== null ? response.code : undefined;
|
|
@@ -54498,11 +55264,15 @@ var init_ctl = __esm(() => {
|
|
|
54498
55264
|
init_daemon();
|
|
54499
55265
|
init_hooks();
|
|
54500
55266
|
init_pane();
|
|
55267
|
+
init_start_flags();
|
|
54501
55268
|
USAGE = `Usage: kodizm-acp tmuxd <command>
|
|
54502
55269
|
|
|
54503
55270
|
Commands:
|
|
54504
55271
|
ctl <verb> [flags] Drive a Live pane through the daemon; prints one JSON line.
|
|
54505
55272
|
Verbs: start, prompt, command, cancel, stop, rotate-token, status.
|
|
55273
|
+
start also takes --permission-mode default|dontAsk, --one-shot 0|1,
|
|
55274
|
+
--deadline-seconds N, --retain-failed-seconds N and
|
|
55275
|
+
--mcp-config/--tool-policy/--system-prompt-file PATH.
|
|
54506
55276
|
hook <Event> --pane <id> --socket <path> Hook helper the pane settings run; reads the payload on stdin.
|
|
54507
55277
|
daemon Run the daemon in the foreground; ctl start spawns it detached.
|
|
54508
55278
|
|
|
@@ -54510,82 +55280,30 @@ Environment:
|
|
|
54510
55280
|
KODIZM_TMUXD_SOCKET daemon socket (default: $XDG_RUNTIME_DIR or the temp dir, kodizm-live-<uid>/tmuxd.sock)
|
|
54511
55281
|
KODIZM_TMUXD_TMUX_SOCKET tmux socket name (default: ${LIVE_SOCKET})
|
|
54512
55282
|
KODIZM_TMUXD_MAX_PANES panes per daemon (default: ${DEFAULT_MAX_PANES})
|
|
55283
|
+
KODIZM_TMUXD_IDLE_TIMEOUT_SECONDS
|
|
55284
|
+
end a pane with no turn, dialog or activity for N seconds (default: off)
|
|
54513
55285
|
KODIZM_LIVE_BASE_SETTINGS base settings with the deny list (default: ${DEFAULT_BASE_SETTINGS_PATH})
|
|
54514
55286
|
CLAUDE_CODE_PATH claude binary (default: claude on PATH)
|
|
54515
55287
|
`;
|
|
54516
55288
|
VERBS = new Set(["start", "prompt", "command", "cancel", "stop", "rotate-token", "status"]);
|
|
54517
|
-
PATH_FLAGS = new Set([
|
|
54518
|
-
|
|
54519
|
-
|
|
54520
|
-
|
|
54521
|
-
"
|
|
54522
|
-
|
|
55289
|
+
PATH_FLAGS = new Set([
|
|
55290
|
+
"settings-base",
|
|
55291
|
+
"env-file",
|
|
55292
|
+
"file",
|
|
55293
|
+
"token-file",
|
|
55294
|
+
"mcp-config",
|
|
55295
|
+
"tool-policy",
|
|
55296
|
+
"system-prompt-file"
|
|
55297
|
+
]);
|
|
55298
|
+
HANDOFF_FLAGS = new Map([
|
|
55299
|
+
["start", START_FILE_FLAGS],
|
|
55300
|
+
["prompt", ["file"]],
|
|
55301
|
+
["rotate-token", ["token-file"]]
|
|
55302
|
+
]);
|
|
54523
55303
|
});
|
|
54524
|
-
|
|
54525
|
-
|
|
54526
|
-
|
|
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
|
-
};
|
|
55304
|
+
|
|
55305
|
+
// src/index.ts
|
|
55306
|
+
init_package();
|
|
54589
55307
|
|
|
54590
55308
|
// src/backends/driver.ts
|
|
54591
55309
|
init_errors();
|