@layorixinc/axterm 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1071 -274
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -31,6 +31,19 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
31
31
|
return to;
|
|
32
32
|
};
|
|
33
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
+
var __returnValue = (v) => v;
|
|
35
|
+
function __exportSetter(name, newValue) {
|
|
36
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
37
|
+
}
|
|
38
|
+
var __export = (target, all) => {
|
|
39
|
+
for (var name in all)
|
|
40
|
+
__defProp(target, name, {
|
|
41
|
+
get: all[name],
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
set: __exportSetter.bind(all, name)
|
|
45
|
+
});
|
|
46
|
+
};
|
|
34
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
35
48
|
|
|
36
49
|
// packages/core/src/mcp-config.ts
|
|
@@ -69,6 +82,12 @@ function uuidFromSessionId(sessionId) {
|
|
|
69
82
|
throw new Error(`session id is not 32 hex characters: ${sessionId}`);
|
|
70
83
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
71
84
|
}
|
|
85
|
+
function sessionIdFromUuid(ref) {
|
|
86
|
+
if (!ref.includes("-"))
|
|
87
|
+
return;
|
|
88
|
+
const stripped = ref.replace(/-/g, "");
|
|
89
|
+
return /^[0-9a-f]{4,32}$/i.test(stripped) ? stripped.toLowerCase() : undefined;
|
|
90
|
+
}
|
|
72
91
|
function withAppServer(argv, url) {
|
|
73
92
|
const out = [];
|
|
74
93
|
for (let i = 0;i < argv.length; i++) {
|
|
@@ -82,6 +101,9 @@ function withAppServer(argv, url) {
|
|
|
82
101
|
return out;
|
|
83
102
|
return [out[0], "--remote", url, ...out.slice(1)];
|
|
84
103
|
}
|
|
104
|
+
function resumesAConversation(args) {
|
|
105
|
+
return args.some((arg) => CLAUDE_RESUME_FLAGS.has(arg) || arg.startsWith("--resume=") || arg.startsWith("--continue="));
|
|
106
|
+
}
|
|
85
107
|
function buildAgentCommand(input) {
|
|
86
108
|
const extra = [...input.extraArgs ?? []];
|
|
87
109
|
if (input.agent === "claude") {
|
|
@@ -92,6 +114,7 @@ function buildAgentCommand(input) {
|
|
|
92
114
|
...input.mcpConfigPath === undefined ? [] : ["--mcp-config", input.mcpConfigPath],
|
|
93
115
|
"--session-id",
|
|
94
116
|
uuidFromSessionId(input.sessionId),
|
|
117
|
+
...resumesAConversation(extra) ? ["--fork-session"] : [],
|
|
95
118
|
...extra
|
|
96
119
|
];
|
|
97
120
|
}
|
|
@@ -136,21 +159,40 @@ function resolveAgentArgs(input) {
|
|
|
136
159
|
});
|
|
137
160
|
return groups.filter((group, index) => group.flag === undefined || lastIndex.get(group.flag) === index).flatMap((group) => group.tokens);
|
|
138
161
|
}
|
|
162
|
+
function operatorArgsFrom(storedArgv) {
|
|
163
|
+
if (storedArgv === undefined || storedArgv.length === 0)
|
|
164
|
+
return [];
|
|
165
|
+
const kept = [];
|
|
166
|
+
for (let i = 1;i < storedArgv.length; i++) {
|
|
167
|
+
const token = storedArgv[i];
|
|
168
|
+
const width = AXTERM_OWNED_FLAGS[token];
|
|
169
|
+
if (width !== undefined) {
|
|
170
|
+
i += width - 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (/^--(resume|continue)=/.test(token))
|
|
174
|
+
continue;
|
|
175
|
+
kept.push(token);
|
|
176
|
+
}
|
|
177
|
+
return kept;
|
|
178
|
+
}
|
|
139
179
|
function buildAgentResumeCommand(input) {
|
|
180
|
+
const operator = input.extraArgs ?? operatorArgsFrom(input.storedArgv);
|
|
140
181
|
if (input.agent === "claude") {
|
|
141
182
|
return [
|
|
142
183
|
"claude",
|
|
143
184
|
"--settings",
|
|
144
185
|
input.claudeSettingsPath,
|
|
186
|
+
...input.mcpConfigPath === undefined ? [] : ["--mcp-config", input.mcpConfigPath],
|
|
187
|
+
...operator,
|
|
145
188
|
"--resume",
|
|
146
|
-
uuidFromSessionId(input.sessionId)
|
|
147
|
-
...input.extraArgs ?? []
|
|
189
|
+
uuidFromSessionId(input.sessionId)
|
|
148
190
|
];
|
|
149
191
|
}
|
|
150
192
|
if (input.agent === "gemini") {
|
|
151
|
-
return ["gemini", "--resume", input.sessionId
|
|
193
|
+
return ["gemini", ...operator, "--resume", input.sessionId];
|
|
152
194
|
}
|
|
153
|
-
return buildAgentCommand(input);
|
|
195
|
+
return buildAgentCommand({ ...input, extraArgs: operator });
|
|
154
196
|
}
|
|
155
197
|
function isAgentId(value) {
|
|
156
198
|
return AGENT_IDS.includes(value);
|
|
@@ -158,8 +200,20 @@ function isAgentId(value) {
|
|
|
158
200
|
function supportedAgents() {
|
|
159
201
|
return AGENT_IDS;
|
|
160
202
|
}
|
|
161
|
-
var AGENT_IDS;
|
|
203
|
+
var CLAUDE_RESUME_FLAGS, AXTERM_OWNED_FLAGS, AGENT_IDS;
|
|
162
204
|
var init_agent_launch = __esm(() => {
|
|
205
|
+
CLAUDE_RESUME_FLAGS = new Set(["--resume", "-r", "--continue", "-c"]);
|
|
206
|
+
AXTERM_OWNED_FLAGS = {
|
|
207
|
+
"--settings": 2,
|
|
208
|
+
"--mcp-config": 2,
|
|
209
|
+
"--session-id": 2,
|
|
210
|
+
"--extension": 2,
|
|
211
|
+
"--fork-session": 1,
|
|
212
|
+
"--resume": 2,
|
|
213
|
+
"-r": 2,
|
|
214
|
+
"--continue": 1,
|
|
215
|
+
"-c": 1
|
|
216
|
+
};
|
|
163
217
|
AGENT_IDS = ["omp", "claude", "codex", "gemini"];
|
|
164
218
|
});
|
|
165
219
|
|
|
@@ -406,6 +460,9 @@ function isRecord(value) {
|
|
|
406
460
|
}
|
|
407
461
|
|
|
408
462
|
// packages/core/src/config-schema.ts
|
|
463
|
+
function isStagingMode(value) {
|
|
464
|
+
return typeof value === "string" && STAGING_MODES.includes(value);
|
|
465
|
+
}
|
|
409
466
|
function resolveStreamConfig(file) {
|
|
410
467
|
return {
|
|
411
468
|
enabled: file?.enabled ?? DEFAULT_STREAM_CONFIG.enabled,
|
|
@@ -499,7 +556,7 @@ function parseConfig(raw) {
|
|
|
499
556
|
}
|
|
500
557
|
const defaults = raw["defaults"];
|
|
501
558
|
if (isRecord(defaults)) {
|
|
502
|
-
checkKeys(defaults, ["agent", "shell", "autoApprove", "agentArgs"], "defaults.", errors);
|
|
559
|
+
checkKeys(defaults, ["agent", "shell", "autoApprove", "agentArgs", "staging"], "defaults.", errors);
|
|
503
560
|
const agent = defaults["agent"];
|
|
504
561
|
const shell = defaults["shell"];
|
|
505
562
|
const rawArgs = defaults["agentArgs"];
|
|
@@ -518,11 +575,16 @@ function parseConfig(raw) {
|
|
|
518
575
|
if (autoApprove !== undefined && typeof autoApprove !== "boolean") {
|
|
519
576
|
errors.push("defaults.autoApprove must be true or false");
|
|
520
577
|
}
|
|
578
|
+
const staging = defaults["staging"];
|
|
579
|
+
if (staging !== undefined && !isStagingMode(staging)) {
|
|
580
|
+
errors.push(`defaults.staging must be one of: ${STAGING_MODES.join(", ")}`);
|
|
581
|
+
}
|
|
521
582
|
config.defaults = {
|
|
522
583
|
...typeof agent === "string" ? { agent } : {},
|
|
523
584
|
...typeof shell === "string" ? { shell } : {},
|
|
524
585
|
...typeof autoApprove === "boolean" ? { autoApprove } : {},
|
|
525
|
-
...Object.keys(agentArgs).length > 0 ? { agentArgs } : {}
|
|
586
|
+
...Object.keys(agentArgs).length > 0 ? { agentArgs } : {},
|
|
587
|
+
...isStagingMode(staging) ? { staging } : {}
|
|
526
588
|
};
|
|
527
589
|
}
|
|
528
590
|
const projects = raw["projects"];
|
|
@@ -631,6 +693,9 @@ function setConfigValue(config, dottedPath, rawValue) {
|
|
|
631
693
|
const shown = Array.isArray(rawValue) ? rawValue.join(" ") : rawValue;
|
|
632
694
|
return { error: `"${shown}" is not a valid ${type} for ${dottedPath}` };
|
|
633
695
|
}
|
|
696
|
+
if (dottedPath === "defaults.staging" && !isStagingMode(value)) {
|
|
697
|
+
return { error: `"${String(value)}" is not a valid location \u2014 use one of: ${STAGING_MODES.join(", ")}` };
|
|
698
|
+
}
|
|
634
699
|
const next = structuredClone(config);
|
|
635
700
|
const segments = dottedPath.split(".");
|
|
636
701
|
let cursor = next;
|
|
@@ -674,10 +739,11 @@ function resolveEnvRefs(config, env) {
|
|
|
674
739
|
}
|
|
675
740
|
return resolved;
|
|
676
741
|
}
|
|
677
|
-
var CONFIG_VERSION = 1, DEFAULT_CONFIG, DEFAULT_STREAM_CONFIG, DEFAULT_INBOUND_CONFIG, FIELDS, SECRET_SUFFIXES, ENV_REF;
|
|
742
|
+
var STAGING_MODES, CONFIG_VERSION = 1, DEFAULT_CONFIG, DEFAULT_STREAM_CONFIG, DEFAULT_INBOUND_CONFIG, FIELDS, SECRET_SUFFIXES, ENV_REF;
|
|
678
743
|
var init_config_schema = __esm(() => {
|
|
679
744
|
init_agent_launch();
|
|
680
745
|
init_channel_spi();
|
|
746
|
+
STAGING_MODES = ["project", "runtime"];
|
|
681
747
|
DEFAULT_CONFIG = {
|
|
682
748
|
version: CONFIG_VERSION,
|
|
683
749
|
projects: [],
|
|
@@ -703,6 +769,7 @@ var init_config_schema = __esm(() => {
|
|
|
703
769
|
"defaults.agent": "string",
|
|
704
770
|
"defaults.shell": "string",
|
|
705
771
|
"defaults.autoApprove": "boolean",
|
|
772
|
+
"defaults.staging": "string",
|
|
706
773
|
"channels.telegram.enabled": "boolean",
|
|
707
774
|
"channels.telegram.groupId": "number",
|
|
708
775
|
"channels.telegram.allowUserIds": "number[]",
|
|
@@ -768,6 +835,7 @@ function resolvePaths(input) {
|
|
|
768
835
|
extDir: join(stateDir, "ext"),
|
|
769
836
|
runtimeFile: join(runtimeDir, "runtime.json"),
|
|
770
837
|
hookEndpointFile: join(runtimeDir, "hook-endpoint.env"),
|
|
838
|
+
ptyHostFile: join(runtimeDir, "pty-host.json"),
|
|
771
839
|
legacyRoots: legacyRoots.filter((dir) => dir !== stateDir)
|
|
772
840
|
};
|
|
773
841
|
}
|
|
@@ -1044,6 +1112,8 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1044
1112
|
"doctor.bridge.missing": "hook bridge: {command} does not exist \u2014 codex and gemini hooks would fail once per tool call",
|
|
1045
1113
|
"doctor.agent.installed": "{agent}: installed ({path})",
|
|
1046
1114
|
"doctor.agent.notInstalled": "{agent}: not installed",
|
|
1115
|
+
"doctor.staging.project": "inbound files: written to <session dir>/.axterm/inbound (hidden from git; set defaults.staging=runtime for a synced folder)",
|
|
1116
|
+
"doctor.staging.runtime": "inbound files: written to axterm's own runtime directory (defaults.staging=runtime)",
|
|
1047
1117
|
"doctor.omp.extensionOk": "omp extension: deployed ({path})",
|
|
1048
1118
|
"doctor.omp.extensionMissing": "omp extension: missing at {path} \u2014 no omp session can report status",
|
|
1049
1119
|
"doctor.gemini.settingsOk": "gemini hooks: defaults file valid ({events} events subscribed)",
|
|
@@ -1082,6 +1152,9 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1082
1152
|
"err.session.dialogOpen": "session {id} is waiting on a dialog ({state}); answer it with the buttons or /esc first",
|
|
1083
1153
|
"err.agent.notInstalled": "the agent command '{command}' was not found on PATH \u2014 install it, or pass --cmd to run something else",
|
|
1084
1154
|
"err.agent.unknown": "unknown agent '{agent}' \u2014 supported: {supported}",
|
|
1155
|
+
"err.staging.dir": "could not create the session's staging directory {dir} ({error}) \u2014 fix the permissions, or set defaults.staging to runtime",
|
|
1156
|
+
"err.staging.notIgnored": "git can see inside {dir}, so a staged file would show up in `git add -A` \u2014 let axterm write its own {file}, or set defaults.staging to runtime",
|
|
1157
|
+
"err.claude.noConversation": "claude has no conversation {id} in its store on this machine \u2014 a conversation lives on the machine it ran on, so this one cannot be resumed here",
|
|
1085
1158
|
"codex.approval.denied": "Denied from the axterm channel",
|
|
1086
1159
|
"err.codex.noAuth": "codex is not logged in ({authFile} not found) \u2014 run `codex login` once, then start the session again",
|
|
1087
1160
|
"err.codex.authLink": "could not link codex credentials into axterm's CODEX_HOME ({reason}) \u2014 refusing to start a logged-out agent",
|
|
@@ -1340,6 +1413,8 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1340
1413
|
"doctor.bridge.missing": "\uD6C5 \uBE0C\uB9AC\uC9C0: {command} \uC5C6\uC74C \u2014 codex\xB7gemini \uD6C5\uC774 \uD234 \uD638\uCD9C\uB9C8\uB2E4 \uC2E4\uD328\uD558\uAC8C \uB429\uB2C8\uB2E4",
|
|
1341
1414
|
"doctor.agent.installed": "{agent}: \uC124\uCE58\uB428 ({path})",
|
|
1342
1415
|
"doctor.agent.notInstalled": "{agent}: \uBBF8\uC124\uCE58",
|
|
1416
|
+
"doctor.staging.project": "\uC778\uBC14\uC6B4\uB4DC \uD30C\uC77C: <\uC138\uC158 \uB514\uB809\uD130\uB9AC>/.axterm/inbound \uC5D0 \uC501\uB2C8\uB2E4 (git\uC5D0\uC11C \uAC10\uCDB0\uC9D0; \uB3D9\uAE30\uD654 \uD3F4\uB354\uB77C\uBA74 defaults.staging=runtime)",
|
|
1417
|
+
"doctor.staging.runtime": "\uC778\uBC14\uC6B4\uB4DC \uD30C\uC77C: axterm \uC790\uCCB4 \uB7F0\uD0C0\uC784 \uB514\uB809\uD130\uB9AC\uC5D0 \uC501\uB2C8\uB2E4 (defaults.staging=runtime)",
|
|
1343
1418
|
"doctor.omp.extensionOk": "omp \uD655\uC7A5: \uBC30\uD3EC\uB428 ({path})",
|
|
1344
1419
|
"doctor.omp.extensionMissing": "omp \uD655\uC7A5: {path} \uC5C6\uC74C \u2014 \uC5B4\uB5A4 omp \uC138\uC158\uB3C4 \uC0C1\uD0DC\uB97C \uBCF4\uACE0\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
1345
1420
|
"doctor.gemini.settingsOk": "gemini \uD6C5: defaults \uD30C\uC77C \uC815\uC0C1 ({events}\uAC1C \uC774\uBCA4\uD2B8 \uAD6C\uB3C5)",
|
|
@@ -1378,6 +1453,9 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1378
1453
|
"err.session.dialogOpen": "\uC138\uC158 {id} \uC774(\uAC00) \uB2E4\uC774\uC5BC\uB85C\uADF8\uB97C \uAE30\uB2E4\uB9AC\uB294 \uC911\uC785\uB2C8\uB2E4 ({state}). \uBC84\uD2BC\uC73C\uB85C \uB2F5\uD558\uAC70\uB098 /esc \uB97C \uBA3C\uC800 \uBCF4\uB0B4\uC138\uC694",
|
|
1379
1454
|
"err.agent.notInstalled": "\uC5D0\uC774\uC804\uD2B8 \uBA85\uB839 '{command}' \uC744(\uB97C) PATH \uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC124\uCE58\uD558\uAC70\uB098 --cmd \uB85C \uB2E4\uB978 \uBA85\uB839\uC744 \uC9C0\uC815\uD558\uC138\uC694",
|
|
1380
1455
|
"err.agent.unknown": "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uC774\uC804\uD2B8 '{agent}' \uC785\uB2C8\uB2E4. \uC9C0\uC6D0: {supported}",
|
|
1456
|
+
"err.staging.dir": "\uC138\uC158\uC758 \uC2A4\uD14C\uC774\uC9D5 \uB514\uB809\uD130\uB9AC {dir} \uC744(\uB97C) \uB9CC\uB4E4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 ({error}). \uAD8C\uD55C\uC744 \uACE0\uCE58\uAC70\uB098 defaults.staging \uC744 runtime \uC73C\uB85C \uC124\uC815\uD558\uC138\uC694",
|
|
1457
|
+
"err.staging.notIgnored": "git\uC774 {dir} \uC548\uC744 \uBCFC \uC218 \uC788\uC5B4\uC11C, \uC2A4\uD14C\uC774\uC9D5\uD55C \uD30C\uC77C\uC774 `git add -A` \uC5D0 \uC7A1\uD799\uB2C8\uB2E4. axterm\uC774 \uC790\uCCB4 {file} \uC744(\uB97C) \uC4F0\uAC8C \uD558\uAC70\uB098 defaults.staging \uC744 runtime \uC73C\uB85C \uC124\uC815\uD558\uC138\uC694",
|
|
1458
|
+
"err.claude.noConversation": "\uC774 \uBA38\uC2E0\uC758 claude \uC800\uC7A5\uC18C\uC5D0 \uB300\uD654 {id} \uC774(\uAC00) \uC5C6\uC2B5\uB2C8\uB2E4. \uB300\uD654\uB294 \uC2E4\uD589\uB41C \uBA38\uC2E0\uC5D0 \uB0A8\uC73C\uBBC0\uB85C \uC5EC\uAE30\uC11C\uB294 \uC774\uC5B4\uAC08 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
1381
1459
|
"codex.approval.denied": "axterm \uCC44\uB110\uC5D0\uC11C \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
1382
1460
|
"err.codex.noAuth": "codex\uC5D0 \uB85C\uADF8\uC778\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 ({authFile} \uC5C6\uC74C). `codex login` \uC744 \uD55C \uBC88 \uC2E4\uD589\uD55C \uB4A4 \uC138\uC158\uC744 \uB2E4\uC2DC \uC2DC\uC791\uD558\uC138\uC694",
|
|
1383
1461
|
"err.codex.authLink": "codex \uC790\uACA9 \uC99D\uBA85\uC744 axterm\uC758 CODEX_HOME\uC5D0 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 ({reason}). \uB85C\uADF8\uC544\uC6C3 \uC0C1\uD0DC\uC758 \uC5D0\uC774\uC804\uD2B8\uB294 \uC2DC\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
@@ -1763,6 +1841,13 @@ function mcpLaunch(input) {
|
|
|
1763
1841
|
function isMcpInvocation(args) {
|
|
1764
1842
|
return args[0] === MCP_ARGV_FLAG;
|
|
1765
1843
|
}
|
|
1844
|
+
function ptyHostLaunch(input) {
|
|
1845
|
+
const base = daemonLaunch(input);
|
|
1846
|
+
return { command: base.command, args: [...base.args.slice(0, -1), PTY_HOST_ARGV_FLAG] };
|
|
1847
|
+
}
|
|
1848
|
+
function isPtyHostInvocation(args) {
|
|
1849
|
+
return args[0] === PTY_HOST_ARGV_FLAG;
|
|
1850
|
+
}
|
|
1766
1851
|
function hookPostTarget(env, agent) {
|
|
1767
1852
|
const port = Number(env["AXTERM_HOOK_PORT"]);
|
|
1768
1853
|
const token = env["AXTERM_HOOK_TOKEN"];
|
|
@@ -1785,7 +1870,7 @@ function hookPostTarget(env, agent) {
|
|
|
1785
1870
|
function isDaemonInvocation(args) {
|
|
1786
1871
|
return args[0] === DAEMON_ARGV_FLAG;
|
|
1787
1872
|
}
|
|
1788
|
-
var DAEMON_ARGV_FLAG = "__daemon", HOOK_ARGV_FLAG = "__hook", MCP_ARGV_FLAG = "__mcp", RUNTIME_NAMES;
|
|
1873
|
+
var DAEMON_ARGV_FLAG = "__daemon", HOOK_ARGV_FLAG = "__hook", MCP_ARGV_FLAG = "__mcp", PTY_HOST_ARGV_FLAG = "__pty-host", RUNTIME_NAMES;
|
|
1789
1874
|
var init_self_spawn = __esm(() => {
|
|
1790
1875
|
RUNTIME_NAMES = new Set(["bun", "bun.exe", "bun-debug", "bun-debug.exe", "node", "node.exe"]);
|
|
1791
1876
|
});
|
|
@@ -2218,7 +2303,91 @@ var init_agent_status = __esm(() => {
|
|
|
2218
2303
|
]);
|
|
2219
2304
|
});
|
|
2220
2305
|
|
|
2306
|
+
// packages/core/src/pty-protocol.ts
|
|
2307
|
+
function encodeFrame(kind, id, payload) {
|
|
2308
|
+
const idBytes = new TextEncoder().encode(id);
|
|
2309
|
+
if (idBytes.length > 255)
|
|
2310
|
+
throw new Error(`session id too long for a frame header: ${idBytes.length} bytes`);
|
|
2311
|
+
const frame = new Uint8Array(HEADER_BYTES + idBytes.length + payload.byteLength);
|
|
2312
|
+
frame[0] = kind;
|
|
2313
|
+
frame[1] = idBytes.length;
|
|
2314
|
+
new DataView(frame.buffer, frame.byteOffset, frame.byteLength).setUint32(2, payload.byteLength, true);
|
|
2315
|
+
frame.set(idBytes, HEADER_BYTES);
|
|
2316
|
+
frame.set(payload, HEADER_BYTES + idBytes.length);
|
|
2317
|
+
return frame;
|
|
2318
|
+
}
|
|
2319
|
+
function isFrameKind(value) {
|
|
2320
|
+
return value === FrameKind.Control || value === FrameKind.Data;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
class FrameDecoder {
|
|
2324
|
+
buffer = new Uint8Array(0);
|
|
2325
|
+
push(chunk) {
|
|
2326
|
+
this.buffer = concat(this.buffer, chunk);
|
|
2327
|
+
const frames = [];
|
|
2328
|
+
for (;; ) {
|
|
2329
|
+
if (this.buffer.byteLength < HEADER_BYTES)
|
|
2330
|
+
return frames;
|
|
2331
|
+
const kind = this.buffer[0];
|
|
2332
|
+
if (!isFrameKind(kind))
|
|
2333
|
+
throw new Error(`unknown frame kind ${kind}`);
|
|
2334
|
+
const idLength = this.buffer[1];
|
|
2335
|
+
const payloadLength = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength).getUint32(2, true);
|
|
2336
|
+
if (payloadLength > MAX_FRAME_BYTES) {
|
|
2337
|
+
throw new Error(`frame too large: ${payloadLength} bytes declared, ceiling is ${MAX_FRAME_BYTES}`);
|
|
2338
|
+
}
|
|
2339
|
+
const total = HEADER_BYTES + idLength + payloadLength;
|
|
2340
|
+
if (this.buffer.byteLength < total)
|
|
2341
|
+
return frames;
|
|
2342
|
+
const id = new TextDecoder().decode(this.buffer.subarray(HEADER_BYTES, HEADER_BYTES + idLength));
|
|
2343
|
+
const payload = this.buffer.slice(HEADER_BYTES + idLength, total);
|
|
2344
|
+
frames.push({ kind, id, payload });
|
|
2345
|
+
this.buffer = this.buffer.slice(total);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
function concat(left, right) {
|
|
2350
|
+
if (left.byteLength === 0)
|
|
2351
|
+
return right.slice();
|
|
2352
|
+
const joined = new Uint8Array(left.byteLength + right.byteLength);
|
|
2353
|
+
joined.set(left, 0);
|
|
2354
|
+
joined.set(right, left.byteLength);
|
|
2355
|
+
return joined;
|
|
2356
|
+
}
|
|
2357
|
+
function parseControl(payload) {
|
|
2358
|
+
let parsed;
|
|
2359
|
+
try {
|
|
2360
|
+
parsed = JSON.parse(new TextDecoder().decode(payload));
|
|
2361
|
+
} catch {
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
2365
|
+
return;
|
|
2366
|
+
const type = parsed.t;
|
|
2367
|
+
if (typeof type !== "string" || !KNOWN_TYPES.has(type))
|
|
2368
|
+
return;
|
|
2369
|
+
return parsed;
|
|
2370
|
+
}
|
|
2371
|
+
function controlFrame(message) {
|
|
2372
|
+
return encodeFrame(FrameKind.Control, "", new TextEncoder().encode(JSON.stringify(message)));
|
|
2373
|
+
}
|
|
2374
|
+
var PTY_PROTOCOL_VERSION = 1, FrameKind, MAX_FRAME_BYTES, HEADER_BYTES = 6, KNOWN_TYPES;
|
|
2375
|
+
var init_pty_protocol = __esm(() => {
|
|
2376
|
+
FrameKind = {
|
|
2377
|
+
Control: 1,
|
|
2378
|
+
Data: 2
|
|
2379
|
+
};
|
|
2380
|
+
MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
2381
|
+
KNOWN_TYPES = new Set(["hello", "spawn", "resize", "kill", "welcome", "spawned", "exit", "error"]);
|
|
2382
|
+
});
|
|
2383
|
+
|
|
2221
2384
|
// packages/core/src/workspace.ts
|
|
2385
|
+
function scratchDir(sessionDir) {
|
|
2386
|
+
return `${sessionDir.replace(/[/\\]+$/, "")}/${SCRATCH_DIR_NAME}`;
|
|
2387
|
+
}
|
|
2388
|
+
function inboundStagingDir(sessionDir) {
|
|
2389
|
+
return `${scratchDir(sessionDir)}/inbound`;
|
|
2390
|
+
}
|
|
2222
2391
|
function planMaterialisedPath(stagingDir, sessionId, remotePath) {
|
|
2223
2392
|
const parts = remotePath.split(/[/\\]/);
|
|
2224
2393
|
let base = "";
|
|
@@ -2239,7 +2408,8 @@ function planMaterialisedPath(stagingDir, sessionId, remotePath) {
|
|
|
2239
2408
|
}
|
|
2240
2409
|
return `${stagingDir}/${sessionId}/${name}`;
|
|
2241
2410
|
}
|
|
2242
|
-
var
|
|
2411
|
+
var SCRATCH_DIR_NAME = ".axterm", SCRATCH_IGNORE_NAME = ".gitignore", SCRATCH_IGNORE_BODY = `*
|
|
2412
|
+
`, MAX_NAME = 120;
|
|
2243
2413
|
|
|
2244
2414
|
// packages/core/src/claude-hooks.ts
|
|
2245
2415
|
function resultText(response) {
|
|
@@ -2430,6 +2600,15 @@ function evaluateVersionDrift(input) {
|
|
|
2430
2600
|
}
|
|
2431
2601
|
];
|
|
2432
2602
|
}
|
|
2603
|
+
function evaluateStagingDoctor(facts) {
|
|
2604
|
+
return [
|
|
2605
|
+
{
|
|
2606
|
+
id: "staging.mode",
|
|
2607
|
+
status: "info",
|
|
2608
|
+
key: facts.mode === "runtime" ? "doctor.staging.runtime" : "doctor.staging.project"
|
|
2609
|
+
}
|
|
2610
|
+
];
|
|
2611
|
+
}
|
|
2433
2612
|
function binaryCheck(agent, binary) {
|
|
2434
2613
|
return binary === undefined ? { id: `${agent}.binary`, status: "info", key: "doctor.agent.notInstalled", params: { agent } } : { id: `${agent}.binary`, status: "ok", key: "doctor.agent.installed", params: { agent, path: binary } };
|
|
2435
2614
|
}
|
|
@@ -3463,6 +3642,7 @@ var init_src = __esm(() => {
|
|
|
3463
3642
|
init_choice();
|
|
3464
3643
|
init_agent_status();
|
|
3465
3644
|
init_activity();
|
|
3645
|
+
init_pty_protocol();
|
|
3466
3646
|
init_claude_hooks();
|
|
3467
3647
|
init_onboarding();
|
|
3468
3648
|
init_doctor();
|
|
@@ -3961,7 +4141,11 @@ class Store {
|
|
|
3961
4141
|
return existed;
|
|
3962
4142
|
}
|
|
3963
4143
|
findByPrefix(idOrPrefix) {
|
|
3964
|
-
const
|
|
4144
|
+
const alternate = sessionIdFromUuid(idOrPrefix) ?? idOrPrefix;
|
|
4145
|
+
const rows = this.db.prepare(`SELECT * FROM sessions
|
|
4146
|
+
WHERE id = ? OR short_id = ? OR id LIKE ?
|
|
4147
|
+
OR id = ? OR short_id = ? OR id LIKE ?
|
|
4148
|
+
ORDER BY created_at DESC`).all(idOrPrefix, idOrPrefix, `${idOrPrefix}%`, alternate, alternate, `${alternate}%`);
|
|
3965
4149
|
const row = rows[0];
|
|
3966
4150
|
return row ? rowToInfo(row) : undefined;
|
|
3967
4151
|
}
|
|
@@ -4023,6 +4207,7 @@ class Store {
|
|
|
4023
4207
|
}
|
|
4024
4208
|
}
|
|
4025
4209
|
var init_store = __esm(() => {
|
|
4210
|
+
init_src();
|
|
4026
4211
|
init_paths();
|
|
4027
4212
|
});
|
|
4028
4213
|
|
|
@@ -5036,6 +5221,12 @@ class LocalWorkspace {
|
|
|
5036
5221
|
materialise(file) {
|
|
5037
5222
|
return Promise.resolve(file);
|
|
5038
5223
|
}
|
|
5224
|
+
async mkdirp(dir, mode) {
|
|
5225
|
+
await fs11.mkdir(dir, mode === undefined ? { recursive: true } : { recursive: true, mode });
|
|
5226
|
+
}
|
|
5227
|
+
async write(file, bytes, mode) {
|
|
5228
|
+
await fs11.writeFile(file, bytes, mode === undefined ? undefined : { mode });
|
|
5229
|
+
}
|
|
5039
5230
|
exec(argv, cwd) {
|
|
5040
5231
|
const [command, ...args] = argv;
|
|
5041
5232
|
if (command === undefined)
|
|
@@ -5782,6 +5973,14 @@ async function untrackedFiles(ws, worktreePath) {
|
|
|
5782
5973
|
return [];
|
|
5783
5974
|
return result.stdout.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
5784
5975
|
}
|
|
5976
|
+
async function ignoreState(ws, cwd, file) {
|
|
5977
|
+
const run = await ws.exec(["git", "check-ignore", "-q", file], cwd);
|
|
5978
|
+
if (run.code === 0)
|
|
5979
|
+
return "ignored";
|
|
5980
|
+
if (run.code === 1)
|
|
5981
|
+
return "visible";
|
|
5982
|
+
return "unknown";
|
|
5983
|
+
}
|
|
5785
5984
|
async function countLines(ws, filePath) {
|
|
5786
5985
|
try {
|
|
5787
5986
|
const stat = await ws.stat(filePath);
|
|
@@ -5835,6 +6034,51 @@ var init_git = __esm(() => {
|
|
|
5835
6034
|
UNTRACKED_LINE_SCAN_LIMIT = 1024 * 1024;
|
|
5836
6035
|
});
|
|
5837
6036
|
|
|
6037
|
+
// packages/daemon/src/claude-store.ts
|
|
6038
|
+
import fs16 from "fs";
|
|
6039
|
+
import os4 from "os";
|
|
6040
|
+
import path16 from "path";
|
|
6041
|
+
function claudeConfigDir(env = process.env) {
|
|
6042
|
+
const override = env["CLAUDE_CONFIG_DIR"];
|
|
6043
|
+
if (override !== undefined && override.length > 0)
|
|
6044
|
+
return override;
|
|
6045
|
+
return path16.join(os4.homedir(), ".claude");
|
|
6046
|
+
}
|
|
6047
|
+
function resumedConversationId(argv) {
|
|
6048
|
+
for (let i = 0;i < argv.length; i++) {
|
|
6049
|
+
const token = argv[i];
|
|
6050
|
+
if (token === "--resume" || token === "-r") {
|
|
6051
|
+
const value = argv[i + 1];
|
|
6052
|
+
return value !== undefined && CONVERSATION_ID.test(value) ? value : undefined;
|
|
6053
|
+
}
|
|
6054
|
+
const inline = /^--resume=(.+)$/.exec(token);
|
|
6055
|
+
if (inline)
|
|
6056
|
+
return CONVERSATION_ID.test(inline[1]) ? inline[1] : undefined;
|
|
6057
|
+
}
|
|
6058
|
+
return;
|
|
6059
|
+
}
|
|
6060
|
+
function claudeConversationExists(configDir, conversationId) {
|
|
6061
|
+
const projects = path16.join(configDir, "projects");
|
|
6062
|
+
let entries;
|
|
6063
|
+
try {
|
|
6064
|
+
entries = fs16.readdirSync(projects);
|
|
6065
|
+
} catch {
|
|
6066
|
+
return;
|
|
6067
|
+
}
|
|
6068
|
+
const file = `${conversationId}.jsonl`;
|
|
6069
|
+
for (const entry of entries) {
|
|
6070
|
+
try {
|
|
6071
|
+
if (fs16.existsSync(path16.join(projects, entry, file)))
|
|
6072
|
+
return true;
|
|
6073
|
+
} catch {}
|
|
6074
|
+
}
|
|
6075
|
+
return false;
|
|
6076
|
+
}
|
|
6077
|
+
var CONVERSATION_ID;
|
|
6078
|
+
var init_claude_store = __esm(() => {
|
|
6079
|
+
CONVERSATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6080
|
+
});
|
|
6081
|
+
|
|
5838
6082
|
// packages/daemon/src/injector.ts
|
|
5839
6083
|
import { setTimeout as delay2 } from "timers/promises";
|
|
5840
6084
|
|
|
@@ -5880,9 +6124,377 @@ var init_injector = __esm(() => {
|
|
|
5880
6124
|
init_paths();
|
|
5881
6125
|
});
|
|
5882
6126
|
|
|
5883
|
-
// packages/daemon/src/
|
|
6127
|
+
// packages/daemon/src/pty-host-client.ts
|
|
6128
|
+
import net3 from "net";
|
|
6129
|
+
import { StringDecoder } from "string_decoder";
|
|
6130
|
+
|
|
6131
|
+
class RemotePty {
|
|
6132
|
+
id;
|
|
6133
|
+
pid;
|
|
6134
|
+
send;
|
|
6135
|
+
dataListeners = [];
|
|
6136
|
+
exitListeners = [];
|
|
6137
|
+
decoder = new StringDecoder("utf8");
|
|
6138
|
+
buffered = "";
|
|
6139
|
+
exitCode;
|
|
6140
|
+
exited = false;
|
|
6141
|
+
constructor(id, pid, send) {
|
|
6142
|
+
this.id = id;
|
|
6143
|
+
this.pid = pid;
|
|
6144
|
+
this.send = send;
|
|
6145
|
+
}
|
|
6146
|
+
emit(text) {
|
|
6147
|
+
if (text.length === 0)
|
|
6148
|
+
return;
|
|
6149
|
+
if (this.dataListeners.length === 0) {
|
|
6150
|
+
this.buffered += text;
|
|
6151
|
+
return;
|
|
6152
|
+
}
|
|
6153
|
+
for (const listener of this.dataListeners)
|
|
6154
|
+
listener(text);
|
|
6155
|
+
}
|
|
6156
|
+
push(payload) {
|
|
6157
|
+
this.emit(this.decoder.write(Buffer.from(payload)));
|
|
6158
|
+
}
|
|
6159
|
+
finish(code) {
|
|
6160
|
+
this.emit(this.decoder.end());
|
|
6161
|
+
this.exited = true;
|
|
6162
|
+
this.exitCode = code;
|
|
6163
|
+
for (const listener of this.exitListeners)
|
|
6164
|
+
listener(code);
|
|
6165
|
+
}
|
|
6166
|
+
onData(listener) {
|
|
6167
|
+
this.dataListeners.push(listener);
|
|
6168
|
+
if (this.buffered.length > 0) {
|
|
6169
|
+
const held = this.buffered;
|
|
6170
|
+
this.buffered = "";
|
|
6171
|
+
listener(held);
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
onExit(listener) {
|
|
6175
|
+
this.exitListeners.push(listener);
|
|
6176
|
+
if (this.exited)
|
|
6177
|
+
listener(this.exitCode ?? null);
|
|
6178
|
+
}
|
|
6179
|
+
write(data) {
|
|
6180
|
+
this.send(encodeFrame(FrameKind.Data, this.id, new TextEncoder().encode(data)));
|
|
6181
|
+
}
|
|
6182
|
+
resize(cols, rows) {
|
|
6183
|
+
this.send(controlFrame({ t: "resize", id: this.id, cols, rows }));
|
|
6184
|
+
}
|
|
6185
|
+
kill() {
|
|
6186
|
+
if (this.exited)
|
|
6187
|
+
return;
|
|
6188
|
+
this.send(controlFrame({ t: "kill", id: this.id }));
|
|
6189
|
+
}
|
|
6190
|
+
}
|
|
6191
|
+
function connectPtyHost(options) {
|
|
6192
|
+
return new Promise((resolve, reject) => {
|
|
6193
|
+
const socket = net3.createConnection(options.endpoint);
|
|
6194
|
+
const decoder = new FrameDecoder;
|
|
6195
|
+
const ptys = new Map;
|
|
6196
|
+
const pending = new Map;
|
|
6197
|
+
const dataListeners = [];
|
|
6198
|
+
const rawDecoders = new Map;
|
|
6199
|
+
let welcomed = false;
|
|
6200
|
+
const send = (frame) => {
|
|
6201
|
+
if (!socket.destroyed)
|
|
6202
|
+
socket.write(frame);
|
|
6203
|
+
};
|
|
6204
|
+
const fail = (error) => {
|
|
6205
|
+
if (!welcomed)
|
|
6206
|
+
reject(error);
|
|
6207
|
+
for (const waiter of pending.values())
|
|
6208
|
+
waiter.reject(error);
|
|
6209
|
+
pending.clear();
|
|
6210
|
+
socket.destroy();
|
|
6211
|
+
};
|
|
6212
|
+
const timer = setTimeout(() => fail(new Error(`pty host did not greet within ${options.timeoutMs ?? 5000}ms`)), options.timeoutMs ?? 5000);
|
|
6213
|
+
timer.unref?.();
|
|
6214
|
+
socket.on("error", (err) => fail(err instanceof Error ? err : new Error(String(err))));
|
|
6215
|
+
socket.on("close", () => {
|
|
6216
|
+
clearTimeout(timer);
|
|
6217
|
+
if (!welcomed)
|
|
6218
|
+
reject(new Error("pty host refused the connection"));
|
|
6219
|
+
});
|
|
6220
|
+
socket.on("connect", () => send(controlFrame({ t: "hello", v: PTY_PROTOCOL_VERSION, token: options.token })));
|
|
6221
|
+
socket.on("data", (chunk) => {
|
|
6222
|
+
let frames;
|
|
6223
|
+
try {
|
|
6224
|
+
frames = decoder.push(new Uint8Array(chunk));
|
|
6225
|
+
} catch (err) {
|
|
6226
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
6227
|
+
return;
|
|
6228
|
+
}
|
|
6229
|
+
for (const frame of frames) {
|
|
6230
|
+
if (frame.kind === FrameKind.Data) {
|
|
6231
|
+
ptys.get(frame.id)?.push(frame.payload);
|
|
6232
|
+
if (dataListeners.length > 0) {
|
|
6233
|
+
let raw = rawDecoders.get(frame.id);
|
|
6234
|
+
if (raw === undefined) {
|
|
6235
|
+
raw = new StringDecoder("utf8");
|
|
6236
|
+
rawDecoders.set(frame.id, raw);
|
|
6237
|
+
}
|
|
6238
|
+
const text = raw.write(Buffer.from(frame.payload));
|
|
6239
|
+
if (text.length > 0)
|
|
6240
|
+
for (const listener of dataListeners)
|
|
6241
|
+
listener(frame.id, text);
|
|
6242
|
+
}
|
|
6243
|
+
continue;
|
|
6244
|
+
}
|
|
6245
|
+
const message = parseControl(frame.payload);
|
|
6246
|
+
if (message === undefined)
|
|
6247
|
+
continue;
|
|
6248
|
+
if (message.t === "welcome") {
|
|
6249
|
+
clearTimeout(timer);
|
|
6250
|
+
welcomed = true;
|
|
6251
|
+
resolve({
|
|
6252
|
+
protocolVersion: message.v,
|
|
6253
|
+
sessions: message.sessions,
|
|
6254
|
+
spawn: (spawnOptions) => new Promise((done, failed) => {
|
|
6255
|
+
const id = spawnOptions.id ?? crypto.randomUUID().replace(/-/g, "");
|
|
6256
|
+
pending.set(id, {
|
|
6257
|
+
resolve: (pid) => {
|
|
6258
|
+
const pty = new RemotePty(id, pid, send);
|
|
6259
|
+
ptys.set(id, pty);
|
|
6260
|
+
done(pty);
|
|
6261
|
+
},
|
|
6262
|
+
reject: failed
|
|
6263
|
+
});
|
|
6264
|
+
send(controlFrame({
|
|
6265
|
+
t: "spawn",
|
|
6266
|
+
id,
|
|
6267
|
+
exe: spawnOptions.exe,
|
|
6268
|
+
argv: spawnOptions.argv,
|
|
6269
|
+
cwd: spawnOptions.cwd,
|
|
6270
|
+
env: Object.fromEntries(Object.entries(spawnOptions.env).filter((entry) => entry[1] !== undefined)),
|
|
6271
|
+
cols: spawnOptions.cols,
|
|
6272
|
+
rows: spawnOptions.rows,
|
|
6273
|
+
term: spawnOptions.term
|
|
6274
|
+
}));
|
|
6275
|
+
}),
|
|
6276
|
+
onData: (listener) => dataListeners.push(listener),
|
|
6277
|
+
close: () => socket.destroy()
|
|
6278
|
+
});
|
|
6279
|
+
continue;
|
|
6280
|
+
}
|
|
6281
|
+
if (message.t === "spawned") {
|
|
6282
|
+
pending.get(message.id)?.resolve(message.pid);
|
|
6283
|
+
pending.delete(message.id);
|
|
6284
|
+
continue;
|
|
6285
|
+
}
|
|
6286
|
+
if (message.t === "error") {
|
|
6287
|
+
pending.get(message.id)?.reject(new Error(message.message));
|
|
6288
|
+
pending.delete(message.id);
|
|
6289
|
+
continue;
|
|
6290
|
+
}
|
|
6291
|
+
if (message.t === "exit") {
|
|
6292
|
+
const pty = ptys.get(message.id);
|
|
6293
|
+
ptys.delete(message.id);
|
|
6294
|
+
rawDecoders.delete(message.id);
|
|
6295
|
+
pty?.finish(message.code);
|
|
6296
|
+
}
|
|
6297
|
+
}
|
|
6298
|
+
});
|
|
6299
|
+
});
|
|
6300
|
+
}
|
|
6301
|
+
var init_pty_host_client = __esm(() => {
|
|
6302
|
+
init_src();
|
|
6303
|
+
});
|
|
6304
|
+
|
|
6305
|
+
// packages/daemon/src/pty-host-launch.ts
|
|
5884
6306
|
import { spawn as spawn3 } from "child_process";
|
|
5885
|
-
import
|
|
6307
|
+
import fs17 from "fs";
|
|
6308
|
+
import os5 from "os";
|
|
6309
|
+
import path17 from "path";
|
|
6310
|
+
function hostFile(runtimeDir) {
|
|
6311
|
+
return path17.join(runtimeDir, "pty-host.json");
|
|
6312
|
+
}
|
|
6313
|
+
function readPtyHostFile(file) {
|
|
6314
|
+
try {
|
|
6315
|
+
const parsed = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
6316
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
6317
|
+
return;
|
|
6318
|
+
const { pid, endpoint, token, version: version2 } = parsed;
|
|
6319
|
+
if (typeof pid !== "number" || typeof endpoint !== "string" || typeof token !== "string")
|
|
6320
|
+
return;
|
|
6321
|
+
return { pid, endpoint, token, version: typeof version2 === "number" ? version2 : PTY_PROTOCOL_VERSION };
|
|
6322
|
+
} catch {
|
|
6323
|
+
return;
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
function isRunning(pid) {
|
|
6327
|
+
try {
|
|
6328
|
+
process.kill(pid, 0);
|
|
6329
|
+
return true;
|
|
6330
|
+
} catch (err) {
|
|
6331
|
+
return err.code === "EPERM";
|
|
6332
|
+
}
|
|
6333
|
+
}
|
|
6334
|
+
function hostEndpoint(runtimeDir) {
|
|
6335
|
+
const base = ipcEndpoint(process.platform, os5.userInfo().username, { runtimeDir });
|
|
6336
|
+
return process.platform === "win32" ? `${base}-pty` : path17.join(runtimeDir, "axterm-pty.sock");
|
|
6337
|
+
}
|
|
6338
|
+
async function ensurePtyHost(location) {
|
|
6339
|
+
const file = hostFile(location.runtimeDir);
|
|
6340
|
+
const existing = readPtyHostFile(file);
|
|
6341
|
+
if (existing !== undefined && isRunning(existing.pid))
|
|
6342
|
+
return existing;
|
|
6343
|
+
if (existing !== undefined)
|
|
6344
|
+
log(`pty host: handoff file names a dead pid ${existing.pid}, starting a new host`);
|
|
6345
|
+
const endpoint = hostEndpoint(location.runtimeDir);
|
|
6346
|
+
if (process.platform !== "win32")
|
|
6347
|
+
fs17.rmSync(endpoint, { force: true });
|
|
6348
|
+
const token = shortHash(`${location.runtimeDir}:${Date.now()}:${Math.random()}`) + shortHash(String(process.pid));
|
|
6349
|
+
const launch = ptyHostLaunch({ execPath: process.execPath, mainScript: location.mainScript ?? Bun.main });
|
|
6350
|
+
const child = spawn3(launch.command, launch.args, {
|
|
6351
|
+
detached: true,
|
|
6352
|
+
stdio: "ignore",
|
|
6353
|
+
windowsHide: true,
|
|
6354
|
+
env: {
|
|
6355
|
+
...process.env,
|
|
6356
|
+
...location.env,
|
|
6357
|
+
AXTERM_PTY_HOST_ENDPOINT: endpoint,
|
|
6358
|
+
AXTERM_PTY_HOST_TOKEN: token,
|
|
6359
|
+
AXTERM_PTY_HOST_FILE: file
|
|
6360
|
+
}
|
|
6361
|
+
});
|
|
6362
|
+
child.unref();
|
|
6363
|
+
if (child.pid === undefined)
|
|
6364
|
+
throw new Error("could not start the terminal host");
|
|
6365
|
+
const deadline = Date.now() + 15000;
|
|
6366
|
+
for (;; ) {
|
|
6367
|
+
const info = readPtyHostFile(file);
|
|
6368
|
+
if (info !== undefined && info.endpoint === endpoint && isRunning(info.pid))
|
|
6369
|
+
return info;
|
|
6370
|
+
if (Date.now() > deadline)
|
|
6371
|
+
throw new Error(`the terminal host did not become ready within 15s (${endpoint})`);
|
|
6372
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
function writePtyHostFile(file, info) {
|
|
6376
|
+
fs17.mkdirSync(path17.dirname(file), { recursive: true, mode: 448 });
|
|
6377
|
+
fs17.writeFileSync(file, `${JSON.stringify(info, null, 2)}
|
|
6378
|
+
`, { mode: 384 });
|
|
6379
|
+
restrictFile(file);
|
|
6380
|
+
}
|
|
6381
|
+
var init_pty_host_launch = __esm(() => {
|
|
6382
|
+
init_src();
|
|
6383
|
+
init_paths();
|
|
6384
|
+
init_shell();
|
|
6385
|
+
});
|
|
6386
|
+
|
|
6387
|
+
// packages/daemon/src/pty.ts
|
|
6388
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
6389
|
+
import path18 from "path";
|
|
6390
|
+
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
6391
|
+
|
|
6392
|
+
class Fanout {
|
|
6393
|
+
listeners = new Set;
|
|
6394
|
+
backlog = [];
|
|
6395
|
+
add(listener) {
|
|
6396
|
+
this.listeners.add(listener);
|
|
6397
|
+
for (const value of this.backlog)
|
|
6398
|
+
listener(value);
|
|
6399
|
+
}
|
|
6400
|
+
emit(value) {
|
|
6401
|
+
if (this.listeners.size === 0)
|
|
6402
|
+
this.backlog.push(value);
|
|
6403
|
+
else
|
|
6404
|
+
for (const listener of this.listeners)
|
|
6405
|
+
listener(value);
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6408
|
+
function killTree(pid, exeName) {
|
|
6409
|
+
if (process.platform === "win32") {
|
|
6410
|
+
const check = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
|
|
6411
|
+
encoding: "utf8",
|
|
6412
|
+
windowsHide: true
|
|
6413
|
+
});
|
|
6414
|
+
const image = check.stdout?.split(",")[0]?.replace(/"/g, "").toLowerCase() ?? "";
|
|
6415
|
+
if (image === exeName) {
|
|
6416
|
+
spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
|
|
6417
|
+
}
|
|
6418
|
+
return;
|
|
6419
|
+
}
|
|
6420
|
+
try {
|
|
6421
|
+
process.kill(-pid, "SIGKILL");
|
|
6422
|
+
} catch {}
|
|
6423
|
+
}
|
|
6424
|
+
|
|
6425
|
+
class BunPtyHandle {
|
|
6426
|
+
proc;
|
|
6427
|
+
exeName;
|
|
6428
|
+
data = new Fanout;
|
|
6429
|
+
exit = new Fanout;
|
|
6430
|
+
decoder = new StringDecoder2("utf8");
|
|
6431
|
+
exited = false;
|
|
6432
|
+
constructor(proc, exeName) {
|
|
6433
|
+
this.proc = proc;
|
|
6434
|
+
this.exeName = exeName;
|
|
6435
|
+
}
|
|
6436
|
+
get pid() {
|
|
6437
|
+
return this.proc.pid;
|
|
6438
|
+
}
|
|
6439
|
+
push(bytes) {
|
|
6440
|
+
const text = this.decoder.write(Buffer.from(bytes));
|
|
6441
|
+
if (text.length > 0)
|
|
6442
|
+
this.data.emit(text);
|
|
6443
|
+
}
|
|
6444
|
+
finish(code) {
|
|
6445
|
+
this.exited = true;
|
|
6446
|
+
const tail = this.decoder.end();
|
|
6447
|
+
if (tail.length > 0)
|
|
6448
|
+
this.data.emit(tail);
|
|
6449
|
+
try {
|
|
6450
|
+
this.proc.terminal.close();
|
|
6451
|
+
} catch {}
|
|
6452
|
+
this.exit.emit(code);
|
|
6453
|
+
}
|
|
6454
|
+
onData(listener) {
|
|
6455
|
+
this.data.add(listener);
|
|
6456
|
+
}
|
|
6457
|
+
onExit(listener) {
|
|
6458
|
+
this.exit.add(listener);
|
|
6459
|
+
}
|
|
6460
|
+
write(data) {
|
|
6461
|
+
this.proc.terminal.write(data);
|
|
6462
|
+
}
|
|
6463
|
+
resize(cols, rows) {
|
|
6464
|
+
this.proc.terminal.resize(cols, rows);
|
|
6465
|
+
}
|
|
6466
|
+
kill() {
|
|
6467
|
+
if (this.exited)
|
|
6468
|
+
return;
|
|
6469
|
+
killTree(this.proc.pid, this.exeName);
|
|
6470
|
+
try {
|
|
6471
|
+
this.proc.kill();
|
|
6472
|
+
} catch {}
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
function spawnPty(options) {
|
|
6476
|
+
let handle;
|
|
6477
|
+
const proc = Bun.spawn([options.exe, ...options.argv], {
|
|
6478
|
+
cwd: options.cwd,
|
|
6479
|
+
env: { ...options.env, TERM: options.term },
|
|
6480
|
+
terminal: {
|
|
6481
|
+
cols: options.cols,
|
|
6482
|
+
rows: options.rows,
|
|
6483
|
+
name: options.term,
|
|
6484
|
+
data(_terminal, bytes) {
|
|
6485
|
+
handle?.push(bytes);
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
});
|
|
6489
|
+
handle = new BunPtyHandle(proc, path18.basename(options.exe).toLowerCase());
|
|
6490
|
+
proc.exited.then((code) => handle?.finish(code ?? null));
|
|
6491
|
+
return handle;
|
|
6492
|
+
}
|
|
6493
|
+
var init_pty = () => {};
|
|
6494
|
+
|
|
6495
|
+
// packages/daemon/src/occupancy.ts
|
|
6496
|
+
import { spawn as spawn4 } from "child_process";
|
|
6497
|
+
import fs18 from "fs/promises";
|
|
5886
6498
|
function shellOccupied(shellPid) {
|
|
5887
6499
|
return process.platform === "win32" ? occupiedFromSnapshot(shellPid) : occupiedFromPidProbe(shellPid);
|
|
5888
6500
|
}
|
|
@@ -5927,11 +6539,11 @@ async function occupiedFromSnapshot(shellPid) {
|
|
|
5927
6539
|
}
|
|
5928
6540
|
async function childrenViaProc(pid) {
|
|
5929
6541
|
try {
|
|
5930
|
-
const children = await
|
|
6542
|
+
const children = await fs18.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
|
|
5931
6543
|
return children.trim().length > 0;
|
|
5932
6544
|
} catch {
|
|
5933
6545
|
try {
|
|
5934
|
-
await
|
|
6546
|
+
await fs18.access(`/proc/${pid}`);
|
|
5935
6547
|
return;
|
|
5936
6548
|
} catch {
|
|
5937
6549
|
return false;
|
|
@@ -5978,7 +6590,7 @@ async function processTable() {
|
|
|
5978
6590
|
}
|
|
5979
6591
|
function capture(exe, argv) {
|
|
5980
6592
|
const { promise, resolve } = Promise.withResolvers();
|
|
5981
|
-
const child =
|
|
6593
|
+
const child = spawn4(exe, argv, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
|
|
5982
6594
|
let stdout3 = "";
|
|
5983
6595
|
child.stdout.setEncoding("utf8");
|
|
5984
6596
|
child.stdout.on("data", (chunk) => {
|
|
@@ -5995,20 +6607,20 @@ var init_occupancy = __esm(() => {
|
|
|
5995
6607
|
});
|
|
5996
6608
|
|
|
5997
6609
|
// packages/daemon/src/agent-store.ts
|
|
5998
|
-
import
|
|
5999
|
-
import
|
|
6000
|
-
import
|
|
6610
|
+
import fs19 from "fs";
|
|
6611
|
+
import os6 from "os";
|
|
6612
|
+
import path19 from "path";
|
|
6001
6613
|
import { Database as Database3 } from "bun:sqlite";
|
|
6002
6614
|
function readTailLines(file, maxBytes) {
|
|
6003
6615
|
let fd;
|
|
6004
6616
|
try {
|
|
6005
|
-
fd =
|
|
6006
|
-
const size =
|
|
6617
|
+
fd = fs19.openSync(file, "r");
|
|
6618
|
+
const size = fs19.fstatSync(fd).size;
|
|
6007
6619
|
if (size === 0)
|
|
6008
6620
|
return [];
|
|
6009
6621
|
const length = Math.min(size, maxBytes);
|
|
6010
6622
|
const buffer = Buffer.allocUnsafe(length);
|
|
6011
|
-
|
|
6623
|
+
fs19.readSync(fd, buffer, 0, length, size - length);
|
|
6012
6624
|
const text = buffer.toString("utf8");
|
|
6013
6625
|
const lines = text.split(`
|
|
6014
6626
|
`);
|
|
@@ -6021,46 +6633,46 @@ function readTailLines(file, maxBytes) {
|
|
|
6021
6633
|
return [];
|
|
6022
6634
|
} finally {
|
|
6023
6635
|
if (fd !== undefined)
|
|
6024
|
-
|
|
6636
|
+
fs19.closeSync(fd);
|
|
6025
6637
|
}
|
|
6026
6638
|
}
|
|
6027
|
-
function
|
|
6028
|
-
return process.env["CLAUDE_CONFIG_DIR"] ??
|
|
6639
|
+
function claudeConfigDir2() {
|
|
6640
|
+
return process.env["CLAUDE_CONFIG_DIR"] ?? path19.join(os6.homedir(), ".claude");
|
|
6029
6641
|
}
|
|
6030
6642
|
function codexHome() {
|
|
6031
|
-
return
|
|
6643
|
+
return path19.join(STATE_DIR, "codex-home");
|
|
6032
6644
|
}
|
|
6033
6645
|
function ompAgentDb() {
|
|
6034
|
-
return
|
|
6646
|
+
return path19.join(os6.homedir(), ".omp", "agent", "agent.db");
|
|
6035
6647
|
}
|
|
6036
6648
|
function findCodexRollout(sessionId) {
|
|
6037
|
-
const root =
|
|
6649
|
+
const root = path19.join(codexHome(), "sessions");
|
|
6038
6650
|
for (const shard2 of codexRolloutShards(sessionId)) {
|
|
6039
|
-
const dir =
|
|
6651
|
+
const dir = path19.join(root, ...shard2.split("/"));
|
|
6040
6652
|
let entries;
|
|
6041
6653
|
try {
|
|
6042
|
-
entries =
|
|
6654
|
+
entries = fs19.readdirSync(dir);
|
|
6043
6655
|
} catch {
|
|
6044
6656
|
continue;
|
|
6045
6657
|
}
|
|
6046
6658
|
const match2 = entries.find((name) => name.endsWith(`-${sessionId}.jsonl`));
|
|
6047
6659
|
if (match2 !== undefined)
|
|
6048
|
-
return
|
|
6660
|
+
return path19.join(dir, match2);
|
|
6049
6661
|
}
|
|
6050
6662
|
return;
|
|
6051
6663
|
}
|
|
6052
6664
|
function newestCodexRollout() {
|
|
6053
|
-
const root =
|
|
6665
|
+
const root = path19.join(codexHome(), "sessions");
|
|
6054
6666
|
let best;
|
|
6055
6667
|
const walk = (dir, depth) => {
|
|
6056
6668
|
let entries;
|
|
6057
6669
|
try {
|
|
6058
|
-
entries =
|
|
6670
|
+
entries = fs19.readdirSync(dir, { withFileTypes: true });
|
|
6059
6671
|
} catch {
|
|
6060
6672
|
return;
|
|
6061
6673
|
}
|
|
6062
6674
|
for (const entry of entries) {
|
|
6063
|
-
const full =
|
|
6675
|
+
const full = path19.join(dir, entry.name);
|
|
6064
6676
|
if (entry.isDirectory()) {
|
|
6065
6677
|
if (depth < 3)
|
|
6066
6678
|
walk(full, depth + 1);
|
|
@@ -6069,7 +6681,7 @@ function newestCodexRollout() {
|
|
|
6069
6681
|
if (!entry.name.endsWith(".jsonl"))
|
|
6070
6682
|
continue;
|
|
6071
6683
|
try {
|
|
6072
|
-
const { mtimeMs } =
|
|
6684
|
+
const { mtimeMs } = fs19.statSync(full);
|
|
6073
6685
|
if (!best || mtimeMs > best.mtimeMs)
|
|
6074
6686
|
best = { file: full, mtimeMs };
|
|
6075
6687
|
} catch {}
|
|
@@ -6080,7 +6692,7 @@ function newestCodexRollout() {
|
|
|
6080
6692
|
}
|
|
6081
6693
|
function ompQuotaFacts() {
|
|
6082
6694
|
const file = ompAgentDb();
|
|
6083
|
-
if (!
|
|
6695
|
+
if (!fs19.existsSync(file))
|
|
6084
6696
|
return { agent: "omp", kind: "gauge" };
|
|
6085
6697
|
let db;
|
|
6086
6698
|
try {
|
|
@@ -6107,12 +6719,12 @@ function codexQuotaFacts() {
|
|
|
6107
6719
|
return reading ? { agent: "codex", kind: "gauge", reading } : { agent: "codex", kind: "gauge" };
|
|
6108
6720
|
}
|
|
6109
6721
|
function claudeQuotaFacts(sessions) {
|
|
6110
|
-
const dir =
|
|
6722
|
+
const dir = claudeConfigDir2();
|
|
6111
6723
|
let newest;
|
|
6112
6724
|
for (const session of sessions) {
|
|
6113
6725
|
if (session.agentSessionId === undefined)
|
|
6114
6726
|
continue;
|
|
6115
|
-
const file =
|
|
6727
|
+
const file = path19.join(dir, claudeTranscriptRelPath(session.cwd, session.agentSessionId));
|
|
6116
6728
|
const event = parseClaudeLimitEvent(readTailLines(file, QUOTA_TAIL_BYTES));
|
|
6117
6729
|
if (event && (!newest || event.at > newest.at))
|
|
6118
6730
|
newest = event;
|
|
@@ -6144,11 +6756,11 @@ function collectQuotaFacts(agents, sessions, onPath) {
|
|
|
6144
6756
|
function storeQuietForMs(agent, cwd, agentSessionId, nowMs) {
|
|
6145
6757
|
if (agentSessionId === undefined)
|
|
6146
6758
|
return;
|
|
6147
|
-
const file = agent === "claude" ?
|
|
6759
|
+
const file = agent === "claude" ? path19.join(claudeConfigDir2(), claudeTranscriptRelPath(cwd, agentSessionId)) : agent === "codex" ? findCodexRollout(agentSessionId) : undefined;
|
|
6148
6760
|
if (file === undefined)
|
|
6149
6761
|
return;
|
|
6150
6762
|
try {
|
|
6151
|
-
return Math.max(0, nowMs -
|
|
6763
|
+
return Math.max(0, nowMs - fs19.statSync(file).mtimeMs);
|
|
6152
6764
|
} catch {
|
|
6153
6765
|
return;
|
|
6154
6766
|
}
|
|
@@ -6158,15 +6770,15 @@ function readFileRange(file, from, to) {
|
|
|
6158
6770
|
return "";
|
|
6159
6771
|
let fd;
|
|
6160
6772
|
try {
|
|
6161
|
-
fd =
|
|
6773
|
+
fd = fs19.openSync(file, "r");
|
|
6162
6774
|
const buffer = Buffer.allocUnsafe(to - from);
|
|
6163
|
-
const read =
|
|
6775
|
+
const read = fs19.readSync(fd, buffer, 0, to - from, from);
|
|
6164
6776
|
return buffer.subarray(0, read).toString("utf8");
|
|
6165
6777
|
} catch {
|
|
6166
6778
|
return "";
|
|
6167
6779
|
} finally {
|
|
6168
6780
|
if (fd !== undefined)
|
|
6169
|
-
|
|
6781
|
+
fs19.closeSync(fd);
|
|
6170
6782
|
}
|
|
6171
6783
|
}
|
|
6172
6784
|
var QUOTA_TAIL_BYTES;
|
|
@@ -6177,10 +6789,10 @@ var init_agent_store = __esm(() => {
|
|
|
6177
6789
|
});
|
|
6178
6790
|
|
|
6179
6791
|
// packages/daemon/src/codex-app-server.ts
|
|
6180
|
-
import
|
|
6792
|
+
import net4 from "net";
|
|
6181
6793
|
async function freeLoopbackPort() {
|
|
6182
6794
|
const { promise, resolve, reject } = Promise.withResolvers();
|
|
6183
|
-
const probe =
|
|
6795
|
+
const probe = net4.createServer();
|
|
6184
6796
|
probe.once("error", reject);
|
|
6185
6797
|
probe.listen({ host: "127.0.0.1", port: 0 }, () => {
|
|
6186
6798
|
const address = probe.address();
|
|
@@ -6193,7 +6805,7 @@ async function waitForPort(port, timeoutMs = READY_TIMEOUT_MS) {
|
|
|
6193
6805
|
const deadline = Date.now() + timeoutMs;
|
|
6194
6806
|
for (;; ) {
|
|
6195
6807
|
const open = await new Promise((resolve) => {
|
|
6196
|
-
const socket =
|
|
6808
|
+
const socket = net4.connect({ host: "127.0.0.1", port });
|
|
6197
6809
|
const done = (value) => {
|
|
6198
6810
|
socket.destroy();
|
|
6199
6811
|
resolve(value);
|
|
@@ -6288,114 +6900,6 @@ var init_codex_app_server = __esm(() => {
|
|
|
6288
6900
|
init_paths();
|
|
6289
6901
|
});
|
|
6290
6902
|
|
|
6291
|
-
// packages/daemon/src/pty.ts
|
|
6292
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
6293
|
-
import path17 from "path";
|
|
6294
|
-
import { StringDecoder } from "string_decoder";
|
|
6295
|
-
|
|
6296
|
-
class Fanout {
|
|
6297
|
-
listeners = new Set;
|
|
6298
|
-
backlog = [];
|
|
6299
|
-
add(listener) {
|
|
6300
|
-
this.listeners.add(listener);
|
|
6301
|
-
for (const value of this.backlog)
|
|
6302
|
-
listener(value);
|
|
6303
|
-
}
|
|
6304
|
-
emit(value) {
|
|
6305
|
-
if (this.listeners.size === 0)
|
|
6306
|
-
this.backlog.push(value);
|
|
6307
|
-
else
|
|
6308
|
-
for (const listener of this.listeners)
|
|
6309
|
-
listener(value);
|
|
6310
|
-
}
|
|
6311
|
-
}
|
|
6312
|
-
function killTree(pid, exeName) {
|
|
6313
|
-
if (process.platform === "win32") {
|
|
6314
|
-
const check = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
|
|
6315
|
-
encoding: "utf8",
|
|
6316
|
-
windowsHide: true
|
|
6317
|
-
});
|
|
6318
|
-
const image = check.stdout?.split(",")[0]?.replace(/"/g, "").toLowerCase() ?? "";
|
|
6319
|
-
if (image === exeName) {
|
|
6320
|
-
spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
|
|
6321
|
-
}
|
|
6322
|
-
return;
|
|
6323
|
-
}
|
|
6324
|
-
try {
|
|
6325
|
-
process.kill(-pid, "SIGKILL");
|
|
6326
|
-
} catch {}
|
|
6327
|
-
}
|
|
6328
|
-
|
|
6329
|
-
class BunPtyHandle {
|
|
6330
|
-
proc;
|
|
6331
|
-
exeName;
|
|
6332
|
-
data = new Fanout;
|
|
6333
|
-
exit = new Fanout;
|
|
6334
|
-
decoder = new StringDecoder("utf8");
|
|
6335
|
-
exited = false;
|
|
6336
|
-
constructor(proc, exeName) {
|
|
6337
|
-
this.proc = proc;
|
|
6338
|
-
this.exeName = exeName;
|
|
6339
|
-
}
|
|
6340
|
-
get pid() {
|
|
6341
|
-
return this.proc.pid;
|
|
6342
|
-
}
|
|
6343
|
-
push(bytes) {
|
|
6344
|
-
const text = this.decoder.write(Buffer.from(bytes));
|
|
6345
|
-
if (text.length > 0)
|
|
6346
|
-
this.data.emit(text);
|
|
6347
|
-
}
|
|
6348
|
-
finish(code) {
|
|
6349
|
-
this.exited = true;
|
|
6350
|
-
const tail = this.decoder.end();
|
|
6351
|
-
if (tail.length > 0)
|
|
6352
|
-
this.data.emit(tail);
|
|
6353
|
-
try {
|
|
6354
|
-
this.proc.terminal.close();
|
|
6355
|
-
} catch {}
|
|
6356
|
-
this.exit.emit(code);
|
|
6357
|
-
}
|
|
6358
|
-
onData(listener) {
|
|
6359
|
-
this.data.add(listener);
|
|
6360
|
-
}
|
|
6361
|
-
onExit(listener) {
|
|
6362
|
-
this.exit.add(listener);
|
|
6363
|
-
}
|
|
6364
|
-
write(data) {
|
|
6365
|
-
this.proc.terminal.write(data);
|
|
6366
|
-
}
|
|
6367
|
-
resize(cols, rows) {
|
|
6368
|
-
this.proc.terminal.resize(cols, rows);
|
|
6369
|
-
}
|
|
6370
|
-
kill() {
|
|
6371
|
-
if (this.exited)
|
|
6372
|
-
return;
|
|
6373
|
-
killTree(this.proc.pid, this.exeName);
|
|
6374
|
-
try {
|
|
6375
|
-
this.proc.kill();
|
|
6376
|
-
} catch {}
|
|
6377
|
-
}
|
|
6378
|
-
}
|
|
6379
|
-
function spawnPty(options) {
|
|
6380
|
-
let handle;
|
|
6381
|
-
const proc = Bun.spawn([options.exe, ...options.argv], {
|
|
6382
|
-
cwd: options.cwd,
|
|
6383
|
-
env: { ...options.env, TERM: options.term },
|
|
6384
|
-
terminal: {
|
|
6385
|
-
cols: options.cols,
|
|
6386
|
-
rows: options.rows,
|
|
6387
|
-
name: options.term,
|
|
6388
|
-
data(_terminal, bytes) {
|
|
6389
|
-
handle?.push(bytes);
|
|
6390
|
-
}
|
|
6391
|
-
}
|
|
6392
|
-
});
|
|
6393
|
-
handle = new BunPtyHandle(proc, path17.basename(options.exe).toLowerCase());
|
|
6394
|
-
proc.exited.then((code) => handle?.finish(code ?? null));
|
|
6395
|
-
return handle;
|
|
6396
|
-
}
|
|
6397
|
-
var init_pty = () => {};
|
|
6398
|
-
|
|
6399
6903
|
// node_modules/.bun/@xterm+headless@5.5.0/node_modules/@xterm/headless/lib-headless/xterm-headless.js
|
|
6400
6904
|
var require_xterm_headless = __commonJS((exports) => {
|
|
6401
6905
|
(() => {
|
|
@@ -10726,8 +11230,8 @@ var require_addon_serialize = __commonJS((exports, module) => {
|
|
|
10726
11230
|
});
|
|
10727
11231
|
|
|
10728
11232
|
// packages/daemon/src/pty-session.ts
|
|
10729
|
-
import
|
|
10730
|
-
import
|
|
11233
|
+
import fs20 from "fs";
|
|
11234
|
+
import path20 from "path";
|
|
10731
11235
|
|
|
10732
11236
|
class PtySession {
|
|
10733
11237
|
info;
|
|
@@ -10742,25 +11246,29 @@ class PtySession {
|
|
|
10742
11246
|
pasteReady = false;
|
|
10743
11247
|
readyScanTail = "";
|
|
10744
11248
|
lastOutputAt = Date.now();
|
|
10745
|
-
|
|
11249
|
+
static async open(info, spawn5, spawner = spawnPty) {
|
|
11250
|
+
const pty = await spawner({
|
|
11251
|
+
exe: spawn5.exe,
|
|
11252
|
+
argv: spawn5.argv,
|
|
11253
|
+
cwd: spawn5.cwd,
|
|
11254
|
+
cols: spawn5.cols,
|
|
11255
|
+
rows: spawn5.rows,
|
|
11256
|
+
env: spawn5.env,
|
|
11257
|
+
term: "xterm-256color"
|
|
11258
|
+
});
|
|
11259
|
+
return new PtySession(info, spawn5, pty);
|
|
11260
|
+
}
|
|
11261
|
+
constructor(info, spawn5, pty) {
|
|
10746
11262
|
this.info = info;
|
|
10747
11263
|
this.term = new import_headless.Terminal({
|
|
10748
|
-
cols:
|
|
10749
|
-
rows:
|
|
11264
|
+
cols: spawn5.cols,
|
|
11265
|
+
rows: spawn5.rows,
|
|
10750
11266
|
scrollback: SCROLLBACK_LINES,
|
|
10751
11267
|
allowProposedApi: true
|
|
10752
11268
|
});
|
|
10753
11269
|
this.serializeAddon = new import_addon_serialize.SerializeAddon;
|
|
10754
11270
|
this.term.loadAddon(this.serializeAddon);
|
|
10755
|
-
this.pty =
|
|
10756
|
-
exe: spawn4.exe,
|
|
10757
|
-
argv: spawn4.argv,
|
|
10758
|
-
cwd: spawn4.cwd,
|
|
10759
|
-
cols: spawn4.cols,
|
|
10760
|
-
rows: spawn4.rows,
|
|
10761
|
-
env: spawn4.env,
|
|
10762
|
-
term: "xterm-256color"
|
|
10763
|
-
});
|
|
11271
|
+
this.pty = pty;
|
|
10764
11272
|
info.ptyPid = this.pty.pid;
|
|
10765
11273
|
this.pty.onData((data) => {
|
|
10766
11274
|
this.seq += 1;
|
|
@@ -10843,10 +11351,10 @@ class PtySession {
|
|
|
10843
11351
|
}
|
|
10844
11352
|
saveScrollback() {
|
|
10845
11353
|
try {
|
|
10846
|
-
const file =
|
|
11354
|
+
const file = path20.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
|
|
10847
11355
|
const tmp = `${file}.tmp`;
|
|
10848
|
-
|
|
10849
|
-
|
|
11356
|
+
fs20.writeFileSync(tmp, this.serializeBuffer(), "utf8");
|
|
11357
|
+
fs20.renameSync(tmp, file);
|
|
10850
11358
|
} catch (err) {
|
|
10851
11359
|
log(`scrollback save failed for ${this.info.shortId}: ${String(err)}`);
|
|
10852
11360
|
}
|
|
@@ -10868,9 +11376,12 @@ var init_pty_session = __esm(() => {
|
|
|
10868
11376
|
|
|
10869
11377
|
// packages/daemon/src/session-manager.ts
|
|
10870
11378
|
import { randomBytes as randomBytes2, randomUUID } from "crypto";
|
|
10871
|
-
import
|
|
10872
|
-
import
|
|
10873
|
-
import
|
|
11379
|
+
import fs21 from "fs";
|
|
11380
|
+
import os7 from "os";
|
|
11381
|
+
import path21 from "path";
|
|
11382
|
+
function sessionScratchRoot(info) {
|
|
11383
|
+
return info.worktreePath ?? info.cwd;
|
|
11384
|
+
}
|
|
10874
11385
|
function unresolvableAgent(commandOverride, agentCommand, lookup) {
|
|
10875
11386
|
if (commandOverride !== undefined)
|
|
10876
11387
|
return;
|
|
@@ -10932,6 +11443,8 @@ class SessionManager {
|
|
|
10932
11443
|
this.sessionWatch.unref();
|
|
10933
11444
|
}
|
|
10934
11445
|
sessionWatch;
|
|
11446
|
+
ptyHost;
|
|
11447
|
+
ptyHostUnavailable = false;
|
|
10935
11448
|
codexServers = new Map;
|
|
10936
11449
|
workspaceFor(_sessionId) {
|
|
10937
11450
|
return this.workspace;
|
|
@@ -11009,8 +11522,8 @@ class SessionManager {
|
|
|
11009
11522
|
};
|
|
11010
11523
|
}
|
|
11011
11524
|
async create(params) {
|
|
11012
|
-
const requestedCwd = params.cwd ?
|
|
11013
|
-
if (!
|
|
11525
|
+
const requestedCwd = params.cwd ? path21.resolve(params.cwd) : os7.homedir();
|
|
11526
|
+
if (!fs21.existsSync(requestedCwd)) {
|
|
11014
11527
|
throw new AxtermError("err.cwd.missing", { cwd: requestedCwd });
|
|
11015
11528
|
}
|
|
11016
11529
|
let worktree;
|
|
@@ -11057,7 +11570,7 @@ class SessionManager {
|
|
|
11057
11570
|
const info = {
|
|
11058
11571
|
id,
|
|
11059
11572
|
shortId: id.slice(0, 6),
|
|
11060
|
-
name: params.name ?? worktree?.branch ??
|
|
11573
|
+
name: params.name ?? worktree?.branch ?? path21.basename(cwd),
|
|
11061
11574
|
cwd,
|
|
11062
11575
|
shell: shell.kind,
|
|
11063
11576
|
agentCommand,
|
|
@@ -11068,7 +11581,7 @@ class SessionManager {
|
|
|
11068
11581
|
...worktree !== undefined ? { worktreePath: worktree.path, branch: worktree.branch, repoRoot: worktree.repoRoot } : {}
|
|
11069
11582
|
};
|
|
11070
11583
|
const appServerUrl = agent === "codex" ? await this.startCodexAppServer(id, this.ensureCodexHome(), cwd) : undefined;
|
|
11071
|
-
this.launch(info, shell, agentCommand, appServerUrl);
|
|
11584
|
+
await this.launch(info, shell, agentCommand, appServerUrl);
|
|
11072
11585
|
this.store.insertSession(info);
|
|
11073
11586
|
log(`session ${info.shortId} created shell=${shell.kind} cmd=${agentCommand.join(" ") || "(shell only)"}`);
|
|
11074
11587
|
this.emit({ t: "session.created", info });
|
|
@@ -11082,7 +11595,7 @@ class SessionManager {
|
|
|
11082
11595
|
launchToken
|
|
11083
11596
|
});
|
|
11084
11597
|
const file = this.claudeSettingsPath(id);
|
|
11085
|
-
|
|
11598
|
+
fs21.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
|
|
11086
11599
|
}
|
|
11087
11600
|
writeGeminiSettings(sessionId) {
|
|
11088
11601
|
try {
|
|
@@ -11095,8 +11608,8 @@ class SessionManager {
|
|
|
11095
11608
|
});
|
|
11096
11609
|
const file = this.geminiSettingsPath();
|
|
11097
11610
|
const tmp = `${file}.tmp`;
|
|
11098
|
-
|
|
11099
|
-
|
|
11611
|
+
fs21.writeFileSync(tmp, JSON.stringify(settings, null, 2));
|
|
11612
|
+
fs21.renameSync(tmp, file);
|
|
11100
11613
|
} catch (err) {
|
|
11101
11614
|
log(`gemini settings write failed; session starts without hooks: ${String(err)}`);
|
|
11102
11615
|
}
|
|
@@ -11142,22 +11655,22 @@ class SessionManager {
|
|
|
11142
11655
|
this.codexServers.delete(id);
|
|
11143
11656
|
}
|
|
11144
11657
|
ensureCodexHome() {
|
|
11145
|
-
const home =
|
|
11146
|
-
|
|
11658
|
+
const home = path21.join(STATE_DIR, "codex-home");
|
|
11659
|
+
fs21.mkdirSync(home, { recursive: true, mode: 448 });
|
|
11147
11660
|
this.linkCodexAuth(home);
|
|
11148
11661
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
|
|
11149
11662
|
const hooksFile = buildCodexHooks({ command: bridge.command, args: bridge.args });
|
|
11150
11663
|
const hooksPath = codexTrustKeyPath(home);
|
|
11151
11664
|
const tmp = `${hooksPath}.tmp`;
|
|
11152
|
-
|
|
11153
|
-
|
|
11665
|
+
fs21.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
|
|
11666
|
+
fs21.renameSync(tmp, hooksPath);
|
|
11154
11667
|
const entries = codexHookTrust(hooksPath, hooksFile);
|
|
11155
|
-
const configPath =
|
|
11156
|
-
const existing =
|
|
11668
|
+
const configPath = path21.join(home, "config.toml");
|
|
11669
|
+
const existing = fs21.existsSync(configPath) ? fs21.readFileSync(configPath, "utf8") : "";
|
|
11157
11670
|
const missing = entries.filter((e) => !existing.includes(`"${e.key.replaceAll("\\", "\\\\")}"`));
|
|
11158
11671
|
if (missing.length > 0) {
|
|
11159
11672
|
const block = codexTrustStateToml(missing);
|
|
11160
|
-
|
|
11673
|
+
fs21.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
|
|
11161
11674
|
`) ? `
|
|
11162
11675
|
${block}` : block);
|
|
11163
11676
|
}
|
|
@@ -11170,18 +11683,18 @@ ${block}` : block);
|
|
|
11170
11683
|
return home;
|
|
11171
11684
|
}
|
|
11172
11685
|
linkCodexAuth(home) {
|
|
11173
|
-
const target =
|
|
11174
|
-
if (
|
|
11686
|
+
const target = path21.join(home, "auth.json");
|
|
11687
|
+
if (fs21.existsSync(target))
|
|
11175
11688
|
return;
|
|
11176
|
-
const operatorHome = process.env["CODEX_HOME"] ??
|
|
11177
|
-
const source =
|
|
11178
|
-
if (!
|
|
11689
|
+
const operatorHome = process.env["CODEX_HOME"] ?? path21.join(os7.homedir(), ".codex");
|
|
11690
|
+
const source = path21.join(operatorHome, "auth.json");
|
|
11691
|
+
if (!fs21.existsSync(source))
|
|
11179
11692
|
throw new AxtermError("err.codex.noAuth", { authFile: source });
|
|
11180
11693
|
try {
|
|
11181
|
-
|
|
11694
|
+
fs21.symlinkSync(source, target, "file");
|
|
11182
11695
|
} catch {
|
|
11183
11696
|
try {
|
|
11184
|
-
|
|
11697
|
+
fs21.linkSync(source, target);
|
|
11185
11698
|
} catch (err) {
|
|
11186
11699
|
throw new AxtermError("err.codex.authLink", { reason: err instanceof Error ? err.message : String(err) });
|
|
11187
11700
|
}
|
|
@@ -11189,35 +11702,36 @@ ${block}` : block);
|
|
|
11189
11702
|
}
|
|
11190
11703
|
doctorReport() {
|
|
11191
11704
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
|
|
11192
|
-
const home =
|
|
11193
|
-
const operatorHome = process.env["CODEX_HOME"] ??
|
|
11194
|
-
const authSource =
|
|
11705
|
+
const home = path21.join(STATE_DIR, "codex-home");
|
|
11706
|
+
const operatorHome = process.env["CODEX_HOME"] ?? path21.join(os7.homedir(), ".codex");
|
|
11707
|
+
const authSource = path21.join(operatorHome, "auth.json");
|
|
11195
11708
|
const geminiSettings = this.geminiSettingsPath();
|
|
11196
|
-
const configPath =
|
|
11709
|
+
const configPath = path21.join(home, "config.toml");
|
|
11197
11710
|
const read = (file) => {
|
|
11198
11711
|
try {
|
|
11199
|
-
return
|
|
11712
|
+
return fs21.existsSync(file) ? fs21.readFileSync(file, "utf8") : undefined;
|
|
11200
11713
|
} catch {
|
|
11201
11714
|
return;
|
|
11202
11715
|
}
|
|
11203
11716
|
};
|
|
11204
11717
|
return [
|
|
11205
|
-
...evaluateBridgeDoctor({ command: bridge.command, exists:
|
|
11718
|
+
...evaluateBridgeDoctor({ command: bridge.command, exists: fs21.existsSync(bridge.command) }),
|
|
11206
11719
|
...evaluateOmpDoctor({
|
|
11207
11720
|
binary: lookupOnPath("omp"),
|
|
11208
11721
|
extensionPath: this.hooks.extensionPath,
|
|
11209
|
-
extensionExists:
|
|
11722
|
+
extensionExists: fs21.existsSync(this.hooks.extensionPath)
|
|
11210
11723
|
}),
|
|
11211
11724
|
...evaluateClaudeDoctor({ binary: lookupOnPath("claude") }),
|
|
11212
11725
|
...evaluateCodexDoctor({
|
|
11213
11726
|
binary: lookupOnPath("codex"),
|
|
11214
11727
|
authSourcePath: authSource,
|
|
11215
|
-
authSourceExists:
|
|
11216
|
-
homeExists:
|
|
11728
|
+
authSourceExists: fs21.existsSync(authSource),
|
|
11729
|
+
homeExists: fs21.existsSync(home),
|
|
11217
11730
|
expected: codexHookTrust(codexTrustKeyPath(home), buildCodexHooks({ command: bridge.command, args: bridge.args })),
|
|
11218
11731
|
configToml: read(configPath)
|
|
11219
11732
|
}),
|
|
11220
|
-
...evaluateGeminiDoctor({ binary: lookupOnPath("gemini"), settingsJson: read(geminiSettings) })
|
|
11733
|
+
...evaluateGeminiDoctor({ binary: lookupOnPath("gemini"), settingsJson: read(geminiSettings) }),
|
|
11734
|
+
...evaluateStagingDoctor({ mode: this.stagingMode() })
|
|
11221
11735
|
];
|
|
11222
11736
|
}
|
|
11223
11737
|
quotaReport() {
|
|
@@ -11337,50 +11851,92 @@ ${block}` : block);
|
|
|
11337
11851
|
}
|
|
11338
11852
|
writeScreenFile(idOrPrefix, lines) {
|
|
11339
11853
|
const session = this.resolveLive(idOrPrefix);
|
|
11340
|
-
const file =
|
|
11341
|
-
|
|
11854
|
+
const file = path21.join(path21.dirname(this.hooks.endpointFile), `screen-${session.info.id}.txt`);
|
|
11855
|
+
fs21.writeFileSync(file, session.screenText(lines), { mode: 384 });
|
|
11342
11856
|
return file;
|
|
11343
11857
|
}
|
|
11344
11858
|
removeScreenFile(id) {
|
|
11345
11859
|
try {
|
|
11346
|
-
|
|
11860
|
+
fs21.rmSync(path21.join(path21.dirname(this.hooks.endpointFile), `screen-${id}.txt`), { force: true });
|
|
11347
11861
|
} catch {}
|
|
11348
11862
|
}
|
|
11349
11863
|
async stageInboundFile(idOrPrefix, suggestedName, bytes) {
|
|
11350
11864
|
const info = this.resolveLive(idOrPrefix).info;
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
|
|
11865
|
+
if (this.stagingMode() === "runtime")
|
|
11866
|
+
return this.stageInRuntimeDir(info, suggestedName, bytes);
|
|
11867
|
+
const ws = this.workspaceFor(info.id);
|
|
11868
|
+
const dir = sessionScratchRoot(info);
|
|
11869
|
+
const file = path21.resolve(planMaterialisedPath(inboundStagingDir(dir), info.id, suggestedName));
|
|
11870
|
+
await this.prepareScratchDir(ws, dir, file);
|
|
11871
|
+
await ws.write(file, bytes, 384);
|
|
11872
|
+
log(`session ${info.shortId} staged ${path21.basename(file)} (${bytes.byteLength} bytes)`);
|
|
11873
|
+
return file;
|
|
11874
|
+
}
|
|
11875
|
+
stagingMode() {
|
|
11876
|
+
return this.configuredDefaults().staging ?? "project";
|
|
11877
|
+
}
|
|
11878
|
+
async stageInRuntimeDir(info, suggestedName, bytes) {
|
|
11879
|
+
const file = path21.resolve(planMaterialisedPath(MATERIALISED_DIR, info.id, suggestedName));
|
|
11880
|
+
await fs21.promises.mkdir(path21.dirname(file), { recursive: true, mode: 448 });
|
|
11881
|
+
await fs21.promises.writeFile(file, bytes, { mode: 384 });
|
|
11882
|
+
log(`session ${info.shortId} staged ${path21.basename(file)} (${bytes.byteLength} bytes, runtime dir)`);
|
|
11355
11883
|
return file;
|
|
11356
11884
|
}
|
|
11357
|
-
|
|
11885
|
+
async prepareScratchDir(ws, sessionDir, file) {
|
|
11886
|
+
const scratch = scratchDir(sessionDir);
|
|
11887
|
+
const ignore = `${scratch}/${SCRATCH_IGNORE_NAME}`;
|
|
11358
11888
|
try {
|
|
11359
|
-
|
|
11889
|
+
await ws.mkdirp(scratch, 448);
|
|
11890
|
+
if (await ws.stat(ignore) === undefined)
|
|
11891
|
+
await ws.write(ignore, new TextEncoder().encode(SCRATCH_IGNORE_BODY));
|
|
11892
|
+
} catch (err) {
|
|
11893
|
+
throw new AxtermError("err.staging.dir", { dir: scratch, error: err instanceof Error ? err.message : String(err) });
|
|
11894
|
+
}
|
|
11895
|
+
if (await ignoreState(ws, sessionDir, file) === "visible") {
|
|
11896
|
+
throw new AxtermError("err.staging.notIgnored", { dir: scratch, file: SCRATCH_IGNORE_NAME });
|
|
11897
|
+
}
|
|
11898
|
+
try {
|
|
11899
|
+
await ws.mkdirp(path21.dirname(file), 448);
|
|
11900
|
+
} catch (err) {
|
|
11901
|
+
throw new AxtermError("err.staging.dir", { dir: scratch, error: err instanceof Error ? err.message : String(err) });
|
|
11902
|
+
}
|
|
11903
|
+
}
|
|
11904
|
+
removeStagedFiles(id, info) {
|
|
11905
|
+
try {
|
|
11906
|
+
fs21.rmSync(path21.join(MATERIALISED_DIR, id), { recursive: true, force: true });
|
|
11907
|
+
} catch {}
|
|
11908
|
+
if (info === undefined)
|
|
11909
|
+
return;
|
|
11910
|
+
const scratch = path21.resolve(scratchDir(sessionScratchRoot(info)));
|
|
11911
|
+
try {
|
|
11912
|
+
fs21.rmSync(path21.resolve(inboundStagingDir(sessionScratchRoot(info)), id), { recursive: true, force: true });
|
|
11913
|
+
fs21.rmdirSync(path21.join(scratch, "inbound"));
|
|
11914
|
+
fs21.rmSync(path21.join(scratch, SCRATCH_IGNORE_NAME), { force: true });
|
|
11915
|
+
fs21.rmdirSync(scratch);
|
|
11360
11916
|
} catch {}
|
|
11361
11917
|
}
|
|
11362
11918
|
removeClaudeSettings(id) {
|
|
11363
11919
|
try {
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11920
|
+
fs21.rmSync(this.claudeSettingsPath(id), { force: true });
|
|
11921
|
+
fs21.rmSync(this.mcpConfigPath(id), { force: true });
|
|
11922
|
+
fs21.rmSync(this.mcpContextPath(id), { force: true });
|
|
11923
|
+
fs21.rmSync(this.geminiTelemetryPath(id), { force: true });
|
|
11368
11924
|
this.stopCodexAppServer(id);
|
|
11369
11925
|
} catch {}
|
|
11370
11926
|
}
|
|
11371
11927
|
claudeSettingsPath(id) {
|
|
11372
|
-
return
|
|
11928
|
+
return path21.join(path21.dirname(this.hooks.endpointFile), `claude-${id}.json`);
|
|
11373
11929
|
}
|
|
11374
11930
|
mcpConfigPath(id) {
|
|
11375
|
-
return
|
|
11931
|
+
return path21.join(path21.dirname(this.hooks.endpointFile), `mcp-${id}.json`);
|
|
11376
11932
|
}
|
|
11377
11933
|
mcpContextPath(id) {
|
|
11378
|
-
return
|
|
11934
|
+
return path21.join(path21.dirname(this.hooks.endpointFile), `mcp-ctx-${id}.json`);
|
|
11379
11935
|
}
|
|
11380
11936
|
writeMcpContext(id, launchToken) {
|
|
11381
11937
|
const file = this.mcpContextPath(id);
|
|
11382
11938
|
try {
|
|
11383
|
-
|
|
11939
|
+
fs21.writeFileSync(file, JSON.stringify({ port: this.hooks.port, token: this.hooks.host.token, sessionId: id, launchToken }), { mode: 384 });
|
|
11384
11940
|
return file;
|
|
11385
11941
|
} catch (err) {
|
|
11386
11942
|
log(`mcp context write failed; session starts without axterm's tools: ${String(err)}`);
|
|
@@ -11393,7 +11949,7 @@ ${block}` : block);
|
|
|
11393
11949
|
writeMcpConfig(id, contextPath) {
|
|
11394
11950
|
const file = this.mcpConfigPath(id);
|
|
11395
11951
|
try {
|
|
11396
|
-
|
|
11952
|
+
fs21.writeFileSync(file, claudeMcpConfig(this.mcpServerLaunch(), contextPath), { mode: 384 });
|
|
11397
11953
|
return file;
|
|
11398
11954
|
} catch (err) {
|
|
11399
11955
|
log(`mcp config write failed; session starts without axterm's tools: ${String(err)}`);
|
|
@@ -11401,12 +11957,38 @@ ${block}` : block);
|
|
|
11401
11957
|
}
|
|
11402
11958
|
}
|
|
11403
11959
|
geminiTelemetryPath(id) {
|
|
11404
|
-
return
|
|
11960
|
+
return path21.join(path21.dirname(this.hooks.endpointFile), `gemini-telemetry-${id}.log`);
|
|
11405
11961
|
}
|
|
11406
11962
|
geminiSettingsPath() {
|
|
11407
|
-
return
|
|
11963
|
+
return path21.join(path21.dirname(this.hooks.endpointFile), "gemini-defaults.json");
|
|
11964
|
+
}
|
|
11965
|
+
async ptySpawner() {
|
|
11966
|
+
if (process.env["AXTERM_PTY_HOST"] !== "1" || this.ptyHostUnavailable)
|
|
11967
|
+
return spawnPty;
|
|
11968
|
+
try {
|
|
11969
|
+
if (this.ptyHost === undefined) {
|
|
11970
|
+
const override = process.env["AXTERM_PTY_HOST_MAIN"];
|
|
11971
|
+
const info = await ensurePtyHost({ runtimeDir: RUNTIME_DIR, ...override ? { mainScript: override } : {} });
|
|
11972
|
+
this.ptyHost = await connectPtyHost({ endpoint: info.endpoint, token: info.token });
|
|
11973
|
+
log(`pty host: connected (pid ${info.pid})`);
|
|
11974
|
+
}
|
|
11975
|
+
const host = this.ptyHost;
|
|
11976
|
+
return (options) => host.spawn(options);
|
|
11977
|
+
} catch (err) {
|
|
11978
|
+
this.ptyHostUnavailable = true;
|
|
11979
|
+
log(`pty host unavailable, owning ptys in this process: ${err instanceof Error ? err.message : String(err)}`);
|
|
11980
|
+
return spawnPty;
|
|
11981
|
+
}
|
|
11408
11982
|
}
|
|
11409
|
-
|
|
11983
|
+
requireClaudeConversation(agentCommand) {
|
|
11984
|
+
const conversation = resumedConversationId(agentCommand);
|
|
11985
|
+
if (conversation === undefined)
|
|
11986
|
+
return;
|
|
11987
|
+
if (claudeConversationExists(claudeConfigDir(), conversation) === false) {
|
|
11988
|
+
throw new AxtermError("err.claude.noConversation", { id: conversation });
|
|
11989
|
+
}
|
|
11990
|
+
}
|
|
11991
|
+
async launch(info, shell, agentCommand, appServerUrl) {
|
|
11410
11992
|
const id = info.id;
|
|
11411
11993
|
const launchToken = randomBytes2(16).toString("hex");
|
|
11412
11994
|
const agentId = agentIdFromCommand(info.agentCommand);
|
|
@@ -11417,7 +11999,9 @@ ${block}` : block);
|
|
|
11417
11999
|
if (agentId === "gemini")
|
|
11418
12000
|
this.writeGeminiSettings(id);
|
|
11419
12001
|
const codexHome2 = agentId === "codex" ? this.ensureCodexHome() : undefined;
|
|
11420
|
-
|
|
12002
|
+
if (agentId === "claude")
|
|
12003
|
+
this.requireClaudeConversation(agentCommand);
|
|
12004
|
+
const session = await PtySession.open(info, {
|
|
11421
12005
|
exe: shell.exe,
|
|
11422
12006
|
argv: buildShellArgv(shell, withAppServer(agentCommand, appServerUrl)),
|
|
11423
12007
|
cwd: info.cwd,
|
|
@@ -11440,7 +12024,7 @@ ${block}` : block);
|
|
|
11440
12024
|
} : {},
|
|
11441
12025
|
...codexHome2 !== undefined ? { CODEX_HOME: codexHome2 } : {}
|
|
11442
12026
|
}
|
|
11443
|
-
});
|
|
12027
|
+
}, await this.ptySpawner());
|
|
11444
12028
|
this.agentRuntime.set(id, { agentExited: false, choiceSeq: 0, dialogOpen: false });
|
|
11445
12029
|
this.hooks.host.registerSession(id, launchToken, (payload) => this.ingestHookEvent(id, payload));
|
|
11446
12030
|
session.onData(() => {
|
|
@@ -11456,7 +12040,7 @@ ${block}` : block);
|
|
|
11456
12040
|
this.hooks.host.unregisterSession(id);
|
|
11457
12041
|
this.removeClaudeSettings(id);
|
|
11458
12042
|
this.removeScreenFile(id);
|
|
11459
|
-
this.removeStagedFiles(id);
|
|
12043
|
+
this.removeStagedFiles(id, info);
|
|
11460
12044
|
clearTimeout(this.idleTimers.get(id));
|
|
11461
12045
|
this.idleTimers.delete(id);
|
|
11462
12046
|
this.sawOutput.delete(id);
|
|
@@ -11615,7 +12199,8 @@ ${block}` : block);
|
|
|
11615
12199
|
const direct = this.live.get(idOrPrefix);
|
|
11616
12200
|
if (direct)
|
|
11617
12201
|
return direct;
|
|
11618
|
-
const
|
|
12202
|
+
const alternate = sessionIdFromUuid(idOrPrefix);
|
|
12203
|
+
const matches = [...this.live.values()].filter((s) => s.info.id.startsWith(idOrPrefix) || s.info.shortId === idOrPrefix || s.info.name === idOrPrefix || alternate !== undefined && s.info.id.startsWith(alternate));
|
|
11619
12204
|
if (matches.length === 1)
|
|
11620
12205
|
return matches[0];
|
|
11621
12206
|
if (matches.length > 1)
|
|
@@ -11641,21 +12226,22 @@ ${block}` : block);
|
|
|
11641
12226
|
allSessions() {
|
|
11642
12227
|
return sessionsByRecency([...this.live.values()].map((s) => s.info), this.store.listSessions());
|
|
11643
12228
|
}
|
|
11644
|
-
resumeNamed(stored, agent) {
|
|
11645
|
-
|
|
11646
|
-
throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
|
|
12229
|
+
async resumeNamed(stored, agent) {
|
|
12230
|
+
const mcpConfigPath = agent === "claude" ? this.writeMcpConfig(stored.id, this.mcpContextPath(stored.id)) : undefined;
|
|
11647
12231
|
const agentCommand = buildAgentResumeCommand({
|
|
11648
12232
|
agent,
|
|
11649
12233
|
sessionId: stored.id,
|
|
11650
12234
|
extensionPath: this.hooks.extensionPath,
|
|
11651
|
-
claudeSettingsPath: this.claudeSettingsPath(stored.id)
|
|
12235
|
+
claudeSettingsPath: this.claudeSettingsPath(stored.id),
|
|
12236
|
+
...mcpConfigPath === undefined ? {} : { mcpConfigPath },
|
|
12237
|
+
storedArgv: stored.agentCommand
|
|
11652
12238
|
});
|
|
11653
12239
|
const info = { ...stored, agentCommand, state: "running" };
|
|
11654
12240
|
delete info.exitCode;
|
|
11655
12241
|
delete info.exitedAt;
|
|
11656
12242
|
delete info.agentState;
|
|
11657
12243
|
delete info.agentStateSource;
|
|
11658
|
-
this.launch(info, resolveShell(info.shell), agentCommand);
|
|
12244
|
+
await this.launch(info, resolveShell(info.shell), agentCommand);
|
|
11659
12245
|
this.store.markResumed(info.id, agentCommand, info.ptyPid);
|
|
11660
12246
|
log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
|
|
11661
12247
|
this.emit({ t: "session.created", info });
|
|
@@ -11682,7 +12268,7 @@ ${block}` : block);
|
|
|
11682
12268
|
if (agentCommand === undefined) {
|
|
11683
12269
|
throw new AxtermError("err.session.noResumeSupport", { agent: profile.displayName });
|
|
11684
12270
|
}
|
|
11685
|
-
if (!
|
|
12271
|
+
if (!fs21.existsSync(stored.cwd))
|
|
11686
12272
|
throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
|
|
11687
12273
|
const info = { ...stored, agentCommand, state: "running" };
|
|
11688
12274
|
delete info.exitCode;
|
|
@@ -11691,7 +12277,7 @@ ${block}` : block);
|
|
|
11691
12277
|
delete info.agentStateSource;
|
|
11692
12278
|
const shell = resolveShell(info.shell);
|
|
11693
12279
|
const resumedAppServer = agentIdFromCommand(agentCommand) === "codex" ? await this.startCodexAppServer(info.id, this.ensureCodexHome(), info.cwd) : undefined;
|
|
11694
|
-
this.launch(info, shell, agentCommand, resumedAppServer);
|
|
12280
|
+
await this.launch(info, shell, agentCommand, resumedAppServer);
|
|
11695
12281
|
this.store.markResumed(info.id, agentCommand, info.ptyPid);
|
|
11696
12282
|
log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
|
|
11697
12283
|
this.emit({ t: "session.created", info });
|
|
@@ -11732,7 +12318,8 @@ ${block}` : block);
|
|
|
11732
12318
|
if (err instanceof AxtermError && err.key === "err.session.ambiguous")
|
|
11733
12319
|
throw err;
|
|
11734
12320
|
}
|
|
11735
|
-
const
|
|
12321
|
+
const info = live?.info ?? this.store.findByPrefix(idOrPrefix);
|
|
12322
|
+
const id = info?.id;
|
|
11736
12323
|
if (id === undefined)
|
|
11737
12324
|
throw new AxtermError("err.session.missing", { id: idOrPrefix });
|
|
11738
12325
|
if (live !== undefined && !live.isExited) {
|
|
@@ -11747,7 +12334,7 @@ ${block}` : block);
|
|
|
11747
12334
|
await promise;
|
|
11748
12335
|
}
|
|
11749
12336
|
this.removeScreenFile(id);
|
|
11750
|
-
this.removeStagedFiles(id);
|
|
12337
|
+
this.removeStagedFiles(id, info);
|
|
11751
12338
|
this.removeClaudeSettings(id);
|
|
11752
12339
|
this.removeScrollback(id);
|
|
11753
12340
|
this.store.deleteSession(id);
|
|
@@ -11756,7 +12343,7 @@ ${block}` : block);
|
|
|
11756
12343
|
removeScrollback(id) {
|
|
11757
12344
|
for (const file of [`${id}.bin`, `${id}.bin.tmp`]) {
|
|
11758
12345
|
try {
|
|
11759
|
-
|
|
12346
|
+
fs21.rmSync(path21.join(SCROLLBACK_DIR, file), { force: true });
|
|
11760
12347
|
} catch {}
|
|
11761
12348
|
}
|
|
11762
12349
|
}
|
|
@@ -11774,7 +12361,7 @@ ${block}` : block);
|
|
|
11774
12361
|
const file = this.geminiTelemetryPath(id);
|
|
11775
12362
|
let size = 0;
|
|
11776
12363
|
try {
|
|
11777
|
-
size =
|
|
12364
|
+
size = fs21.statSync(file).size;
|
|
11778
12365
|
} catch {
|
|
11779
12366
|
continue;
|
|
11780
12367
|
}
|
|
@@ -11850,7 +12437,11 @@ var init_session_manager = __esm(() => {
|
|
|
11850
12437
|
init_codex_home();
|
|
11851
12438
|
init_host_env();
|
|
11852
12439
|
init_git();
|
|
12440
|
+
init_claude_store();
|
|
11853
12441
|
init_injector();
|
|
12442
|
+
init_pty_host_client();
|
|
12443
|
+
init_pty_host_launch();
|
|
12444
|
+
init_pty();
|
|
11854
12445
|
init_occupancy();
|
|
11855
12446
|
init_agent_store();
|
|
11856
12447
|
init_codex_app_server();
|
|
@@ -11859,7 +12450,7 @@ var init_session_manager = __esm(() => {
|
|
|
11859
12450
|
init_pty_session();
|
|
11860
12451
|
init_shell();
|
|
11861
12452
|
init_workspace_local();
|
|
11862
|
-
MATERIALISED_DIR =
|
|
12453
|
+
MATERIALISED_DIR = path21.join(RUNTIME_DIR, "materialised");
|
|
11863
12454
|
PROMPT_OPTIONS = {
|
|
11864
12455
|
hostKey: [{ label: "yes" }, { label: "no" }],
|
|
11865
12456
|
password: [],
|
|
@@ -12008,11 +12599,11 @@ var init_acknowledge = __esm(() => {
|
|
|
12008
12599
|
});
|
|
12009
12600
|
|
|
12010
12601
|
// packages/channel-telegram/src/attach.ts
|
|
12011
|
-
import
|
|
12012
|
-
import
|
|
12602
|
+
import fs22 from "fs";
|
|
12603
|
+
import path22 from "path";
|
|
12013
12604
|
function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELEGRAM_LIMITS) {
|
|
12014
|
-
const name =
|
|
12015
|
-
const bytes =
|
|
12605
|
+
const name = path22.basename(filePath);
|
|
12606
|
+
const bytes = fs22.statSync(filePath).size;
|
|
12016
12607
|
const plan = planAttachment({ name, bytes }, limits);
|
|
12017
12608
|
if (plan.kind === "rejected")
|
|
12018
12609
|
throw new Error(plan.reason);
|
|
@@ -12022,7 +12613,7 @@ function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELE
|
|
|
12022
12613
|
form.append("message_thread_id", String(topicId));
|
|
12023
12614
|
if (caption !== undefined && caption.length > 0)
|
|
12024
12615
|
form.append("caption", caption);
|
|
12025
|
-
form.append(field, new Blob([
|
|
12616
|
+
form.append(field, new Blob([fs22.readFileSync(filePath)]), name);
|
|
12026
12617
|
return { method: plan.kind === "photo" ? "sendPhoto" : "sendDocument", field, form };
|
|
12027
12618
|
}
|
|
12028
12619
|
function isPhotoRejection(error) {
|
|
@@ -12030,13 +12621,13 @@ function isPhotoRejection(error) {
|
|
|
12030
12621
|
return /IMAGE_PROCESS_FAILED|PHOTO_INVALID_DIMENSIONS|PHOTO_EXT_INVALID|PHOTO_SAVE_FILE_INVALID/i.test(message);
|
|
12031
12622
|
}
|
|
12032
12623
|
function asDocumentUpload(filePath, chatId, topicId, caption) {
|
|
12033
|
-
const name =
|
|
12624
|
+
const name = path22.basename(filePath);
|
|
12034
12625
|
const form = new FormData;
|
|
12035
12626
|
form.append("chat_id", String(chatId));
|
|
12036
12627
|
form.append("message_thread_id", String(topicId));
|
|
12037
12628
|
if (caption !== undefined && caption.length > 0)
|
|
12038
12629
|
form.append("caption", caption);
|
|
12039
|
-
form.append("document", new Blob([
|
|
12630
|
+
form.append("document", new Blob([fs22.readFileSync(filePath)]), name);
|
|
12040
12631
|
return { method: "sendDocument", field: "document", form };
|
|
12041
12632
|
}
|
|
12042
12633
|
var TELEGRAM_LIMITS;
|
|
@@ -12622,6 +13213,7 @@ class TelegramChannel {
|
|
|
12622
13213
|
updateOffset = 0;
|
|
12623
13214
|
albums = new Map;
|
|
12624
13215
|
inboundQueues = new Map;
|
|
13216
|
+
refusedAlbums = new Map;
|
|
12625
13217
|
albumDebounceMs;
|
|
12626
13218
|
inboundLimits;
|
|
12627
13219
|
inboundEnabled;
|
|
@@ -13076,6 +13668,10 @@ ${code(prompt.line)}`,
|
|
|
13076
13668
|
if (!msg.from || !this.config.allowedUserIds.includes(msg.from.id))
|
|
13077
13669
|
return;
|
|
13078
13670
|
const binding = msg.message_thread_id ? this.control.findBindingByTopic(msg.chat.id, msg.message_thread_id) : undefined;
|
|
13671
|
+
if (msg.message_thread_id !== undefined && binding === undefined) {
|
|
13672
|
+
this.log(`ignored update in topic ${msg.message_thread_id}: not this machine's`);
|
|
13673
|
+
return;
|
|
13674
|
+
}
|
|
13079
13675
|
const media = readMedia(msg);
|
|
13080
13676
|
if (media) {
|
|
13081
13677
|
await this.handleMedia(msg, binding, media);
|
|
@@ -13108,28 +13704,47 @@ ${code(prompt.line)}`,
|
|
|
13108
13704
|
return;
|
|
13109
13705
|
}
|
|
13110
13706
|
if (!binding) {
|
|
13111
|
-
await this.refuseMedia(msg, tHtml("tg.inbound.noSession"));
|
|
13707
|
+
await this.refuseMedia(msg, tHtml("tg.inbound.noSession"), "no session bound to the topic");
|
|
13112
13708
|
return;
|
|
13113
13709
|
}
|
|
13114
13710
|
const plan = planInboundAttachment(media.candidate, this.inboundLimits);
|
|
13115
13711
|
if (!plan.ok) {
|
|
13116
|
-
|
|
13712
|
+
const text = tHtml(REFUSAL_KEYS[plan.reason], { limit: megabytes2(this.inboundLimits.maxBytes) });
|
|
13713
|
+
await this.refuseMedia(msg, text, plan.reason);
|
|
13117
13714
|
return;
|
|
13118
13715
|
}
|
|
13119
13716
|
await this.react(msg.chat?.id, msg.message_id, "received");
|
|
13120
13717
|
this.bufferMedia(msg, binding.sessionId, { fileId: media.fileId, name: plan.name, kind: plan.kind, messageId: msg.message_id });
|
|
13121
13718
|
}
|
|
13122
|
-
async refuseMedia(msg, text) {
|
|
13719
|
+
async refuseMedia(msg, text, reason) {
|
|
13123
13720
|
const chatId = msg.chat?.id;
|
|
13721
|
+
const where = `chat ${chatId ?? "?"} topic ${msg.message_thread_id ?? "general"}`;
|
|
13722
|
+
const album = msg.media_group_id !== undefined ? ` album ${msg.media_group_id}` : "";
|
|
13723
|
+
this.log(`inbound refused (${reason}) in ${where}${album}, message ${msg.message_id ?? "?"}`);
|
|
13124
13724
|
await this.react(chatId, msg.message_id, "failed");
|
|
13125
13725
|
if (chatId === undefined)
|
|
13126
13726
|
return;
|
|
13727
|
+
if (this.albumAlreadyRefused(msg, reason))
|
|
13728
|
+
return;
|
|
13729
|
+
const attributed = msg.message_thread_id === undefined ? `[${escapeHtml(this.config.machine.name)}] ${text}` : text;
|
|
13127
13730
|
await sendFormatted(this.api, {
|
|
13128
13731
|
chat_id: chatId,
|
|
13129
13732
|
...msg.message_thread_id !== undefined ? { message_thread_id: msg.message_thread_id } : {},
|
|
13130
|
-
text
|
|
13733
|
+
text: attributed
|
|
13131
13734
|
}).catch((err) => this.log(`inbound refusal failed: ${String(err)}`));
|
|
13132
13735
|
}
|
|
13736
|
+
albumAlreadyRefused(msg, reason) {
|
|
13737
|
+
const group = msg.media_group_id;
|
|
13738
|
+
if (group === undefined)
|
|
13739
|
+
return false;
|
|
13740
|
+
const key = `${group}\x00${reason}`;
|
|
13741
|
+
const pending = this.refusedAlbums.get(key);
|
|
13742
|
+
clearTimeout(pending);
|
|
13743
|
+
const timer = setTimeout(() => this.refusedAlbums.delete(key), this.albumDebounceMs);
|
|
13744
|
+
timer.unref?.();
|
|
13745
|
+
this.refusedAlbums.set(key, timer);
|
|
13746
|
+
return pending !== undefined;
|
|
13747
|
+
}
|
|
13133
13748
|
bufferMedia(msg, sessionId, item) {
|
|
13134
13749
|
const group = msg.media_group_id;
|
|
13135
13750
|
const caption = msg.caption?.trim();
|
|
@@ -13177,8 +13792,8 @@ ${code(prompt.line)}`,
|
|
|
13177
13792
|
throw new Error("this Telegram client cannot download files");
|
|
13178
13793
|
const files = [];
|
|
13179
13794
|
for (const item of batch.items) {
|
|
13180
|
-
const
|
|
13181
|
-
files.push({ path:
|
|
13795
|
+
const path23 = await this.control.stageInboundFile(batch.sessionId, item.name, await download2(item.fileId));
|
|
13796
|
+
files.push({ path: path23, kind: item.kind });
|
|
13182
13797
|
}
|
|
13183
13798
|
await this.control.prompt(batch.sessionId, buildAttachmentPrompt(files, batch.caption));
|
|
13184
13799
|
this.log(`staged ${files.length} inbound file(s) for ${batch.sessionId}`);
|
|
@@ -13572,13 +14187,13 @@ var init_src2 = __esm(() => {
|
|
|
13572
14187
|
});
|
|
13573
14188
|
|
|
13574
14189
|
// packages/daemon/src/machine-identity.ts
|
|
13575
|
-
import
|
|
14190
|
+
import os8 from "os";
|
|
13576
14191
|
function resolveMachineIdentity(override) {
|
|
13577
|
-
const hostname =
|
|
14192
|
+
const hostname = os8.hostname();
|
|
13578
14193
|
return {
|
|
13579
14194
|
name: slugifyMachineName(override && override.trim().length > 0 ? override : hostname),
|
|
13580
14195
|
hostname,
|
|
13581
|
-
addresses: collectAddresses(
|
|
14196
|
+
addresses: collectAddresses(os8.networkInterfaces())
|
|
13582
14197
|
};
|
|
13583
14198
|
}
|
|
13584
14199
|
var init_machine_identity = __esm(() => {
|
|
@@ -13808,8 +14423,8 @@ var init_telegram_service = __esm(() => {
|
|
|
13808
14423
|
// packages/daemon/src/index.ts
|
|
13809
14424
|
var exports_src = {};
|
|
13810
14425
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
13811
|
-
import
|
|
13812
|
-
import
|
|
14426
|
+
import fs23 from "fs";
|
|
14427
|
+
import net5 from "net";
|
|
13813
14428
|
function isPidAlive(pid) {
|
|
13814
14429
|
try {
|
|
13815
14430
|
process.kill(pid, 0);
|
|
@@ -13820,9 +14435,9 @@ function isPidAlive(pid) {
|
|
|
13820
14435
|
}
|
|
13821
14436
|
async function main() {
|
|
13822
14437
|
ensureDataDirs();
|
|
13823
|
-
if (
|
|
14438
|
+
if (fs23.existsSync(RUNTIME_FILE2)) {
|
|
13824
14439
|
try {
|
|
13825
|
-
const existing = JSON.parse(
|
|
14440
|
+
const existing = JSON.parse(fs23.readFileSync(RUNTIME_FILE2, "utf8"));
|
|
13826
14441
|
if (existing.pid !== process.pid && isPidAlive(existing.pid)) {
|
|
13827
14442
|
log(`daemon already running (pid ${existing.pid}); exiting`);
|
|
13828
14443
|
process.exit(0);
|
|
@@ -13845,7 +14460,7 @@ async function main() {
|
|
|
13845
14460
|
version: productVersion(),
|
|
13846
14461
|
startedAt: Date.now()
|
|
13847
14462
|
};
|
|
13848
|
-
|
|
14463
|
+
fs23.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
|
|
13849
14464
|
restrictFile(RUNTIME_FILE2);
|
|
13850
14465
|
const config = new ConfigStore(defaultConfigStorePaths(CONFIG_DIR));
|
|
13851
14466
|
if (config.loadError !== undefined)
|
|
@@ -13897,9 +14512,9 @@ async function main() {
|
|
|
13897
14512
|
hookHost.stop();
|
|
13898
14513
|
store.close();
|
|
13899
14514
|
try {
|
|
13900
|
-
const current = JSON.parse(
|
|
14515
|
+
const current = JSON.parse(fs23.readFileSync(RUNTIME_FILE2, "utf8"));
|
|
13901
14516
|
if (current.pid === process.pid)
|
|
13902
|
-
|
|
14517
|
+
fs23.rmSync(RUNTIME_FILE2);
|
|
13903
14518
|
} catch {}
|
|
13904
14519
|
process.exit(0);
|
|
13905
14520
|
};
|
|
@@ -13932,14 +14547,191 @@ var init_src3 = __esm(() => {
|
|
|
13932
14547
|
init_session_manager();
|
|
13933
14548
|
init_telegram_service();
|
|
13934
14549
|
init_store();
|
|
13935
|
-
|
|
14550
|
+
net5.setDefaultAutoSelectFamilyAttemptTimeout(3000);
|
|
13936
14551
|
main();
|
|
13937
14552
|
});
|
|
13938
14553
|
|
|
14554
|
+
// packages/daemon/src/pty-host.ts
|
|
14555
|
+
import net6 from "net";
|
|
14556
|
+
function startPtyHost(options) {
|
|
14557
|
+
const sessions = new Map;
|
|
14558
|
+
const clients = new Set;
|
|
14559
|
+
const send = (socket, reply) => {
|
|
14560
|
+
if (!socket.destroyed)
|
|
14561
|
+
socket.write(controlFrame(reply));
|
|
14562
|
+
};
|
|
14563
|
+
const broadcast = (frame) => {
|
|
14564
|
+
for (const client of clients)
|
|
14565
|
+
if (!client.destroyed)
|
|
14566
|
+
client.write(frame);
|
|
14567
|
+
};
|
|
14568
|
+
const server = net6.createServer((socket) => {
|
|
14569
|
+
const decoder = new FrameDecoder;
|
|
14570
|
+
let greeted = false;
|
|
14571
|
+
const drop = (reason) => {
|
|
14572
|
+
log(`pty host: dropping client (${reason})`);
|
|
14573
|
+
socket.destroy();
|
|
14574
|
+
clients.delete(socket);
|
|
14575
|
+
};
|
|
14576
|
+
socket.on("close", () => clients.delete(socket));
|
|
14577
|
+
socket.on("error", () => clients.delete(socket));
|
|
14578
|
+
socket.on("data", (chunk) => {
|
|
14579
|
+
let frames;
|
|
14580
|
+
try {
|
|
14581
|
+
frames = decoder.push(new Uint8Array(chunk));
|
|
14582
|
+
} catch (err) {
|
|
14583
|
+
drop(err instanceof Error ? err.message : String(err));
|
|
14584
|
+
return;
|
|
14585
|
+
}
|
|
14586
|
+
for (const frame of frames) {
|
|
14587
|
+
if (frame.kind === FrameKind.Data) {
|
|
14588
|
+
if (!greeted)
|
|
14589
|
+
return drop("data before hello");
|
|
14590
|
+
const session = sessions.get(frame.id);
|
|
14591
|
+
if (session && !session.exited)
|
|
14592
|
+
session.handle.write(new TextDecoder().decode(frame.payload));
|
|
14593
|
+
continue;
|
|
14594
|
+
}
|
|
14595
|
+
const message = parseControl(frame.payload);
|
|
14596
|
+
if (message === undefined)
|
|
14597
|
+
continue;
|
|
14598
|
+
if (message.t === "hello") {
|
|
14599
|
+
if (message.token !== options.token)
|
|
14600
|
+
return drop("bad token");
|
|
14601
|
+
greeted = true;
|
|
14602
|
+
clients.add(socket);
|
|
14603
|
+
send(socket, {
|
|
14604
|
+
t: "welcome",
|
|
14605
|
+
v: PTY_PROTOCOL_VERSION,
|
|
14606
|
+
sessions: [...sessions.entries()].filter(([, s]) => !s.exited).map(([id, s]) => ({ id, pid: s.handle.pid }))
|
|
14607
|
+
});
|
|
14608
|
+
continue;
|
|
14609
|
+
}
|
|
14610
|
+
if (!greeted)
|
|
14611
|
+
return drop("control before hello");
|
|
14612
|
+
if (message.t === "spawn") {
|
|
14613
|
+
try {
|
|
14614
|
+
const handle = spawnPty({
|
|
14615
|
+
exe: message.exe,
|
|
14616
|
+
argv: message.argv,
|
|
14617
|
+
cwd: message.cwd,
|
|
14618
|
+
cols: message.cols,
|
|
14619
|
+
rows: message.rows,
|
|
14620
|
+
env: message.env,
|
|
14621
|
+
term: message.term
|
|
14622
|
+
});
|
|
14623
|
+
const session = { handle, cols: message.cols, rows: message.rows, exited: false };
|
|
14624
|
+
sessions.set(message.id, session);
|
|
14625
|
+
handle.onData((text) => broadcast(encodeFrame(FrameKind.Data, message.id, new TextEncoder().encode(text))));
|
|
14626
|
+
handle.onExit((code2) => {
|
|
14627
|
+
session.exited = true;
|
|
14628
|
+
for (const client of clients)
|
|
14629
|
+
send(client, { t: "exit", id: message.id, code: code2 });
|
|
14630
|
+
sessions.delete(message.id);
|
|
14631
|
+
if (sessions.size === 0)
|
|
14632
|
+
options.onIdle?.();
|
|
14633
|
+
});
|
|
14634
|
+
send(socket, { t: "spawned", id: message.id, pid: handle.pid });
|
|
14635
|
+
} catch (err) {
|
|
14636
|
+
send(socket, { t: "error", id: message.id, message: err instanceof Error ? err.message : String(err) });
|
|
14637
|
+
}
|
|
14638
|
+
continue;
|
|
14639
|
+
}
|
|
14640
|
+
if (message.t === "resize") {
|
|
14641
|
+
const session = sessions.get(message.id);
|
|
14642
|
+
if (session === undefined || session.exited)
|
|
14643
|
+
continue;
|
|
14644
|
+
session.cols = message.cols;
|
|
14645
|
+
session.rows = message.rows;
|
|
14646
|
+
session.handle.resize(message.cols, message.rows);
|
|
14647
|
+
continue;
|
|
14648
|
+
}
|
|
14649
|
+
if (message.t === "kill") {
|
|
14650
|
+
sessions.get(message.id)?.handle.kill();
|
|
14651
|
+
}
|
|
14652
|
+
}
|
|
14653
|
+
});
|
|
14654
|
+
});
|
|
14655
|
+
return new Promise((resolve, reject) => {
|
|
14656
|
+
server.once("error", reject);
|
|
14657
|
+
server.listen(options.endpoint, () => {
|
|
14658
|
+
server.removeListener("error", reject);
|
|
14659
|
+
log(`pty host: listening on ${options.endpoint}`);
|
|
14660
|
+
resolve({
|
|
14661
|
+
endpoint: options.endpoint,
|
|
14662
|
+
get sessionCount() {
|
|
14663
|
+
return sessions.size;
|
|
14664
|
+
},
|
|
14665
|
+
close: async () => {
|
|
14666
|
+
for (const client of clients)
|
|
14667
|
+
client.destroy();
|
|
14668
|
+
clients.clear();
|
|
14669
|
+
for (const session of sessions.values())
|
|
14670
|
+
session.handle.kill();
|
|
14671
|
+
await new Promise((done) => server.close(() => done()));
|
|
14672
|
+
}
|
|
14673
|
+
});
|
|
14674
|
+
});
|
|
14675
|
+
});
|
|
14676
|
+
}
|
|
14677
|
+
var init_pty_host = __esm(() => {
|
|
14678
|
+
init_src();
|
|
14679
|
+
init_paths();
|
|
14680
|
+
init_pty();
|
|
14681
|
+
});
|
|
14682
|
+
|
|
14683
|
+
// packages/daemon/src/pty-host-main.ts
|
|
14684
|
+
var exports_pty_host_main = {};
|
|
14685
|
+
__export(exports_pty_host_main, {
|
|
14686
|
+
runPtyHostProcess: () => runPtyHostProcess,
|
|
14687
|
+
PTY_HOST_IDLE_MS: () => PTY_HOST_IDLE_MS
|
|
14688
|
+
});
|
|
14689
|
+
async function runPtyHostProcess() {
|
|
14690
|
+
const endpoint = process.env["AXTERM_PTY_HOST_ENDPOINT"];
|
|
14691
|
+
const token = process.env["AXTERM_PTY_HOST_TOKEN"];
|
|
14692
|
+
const file = process.env["AXTERM_PTY_HOST_FILE"];
|
|
14693
|
+
if (!endpoint || !token || !file) {
|
|
14694
|
+
console.error("axterm __pty-host: AXTERM_PTY_HOST_ENDPOINT, _TOKEN and _FILE are required");
|
|
14695
|
+
process.exitCode = 2;
|
|
14696
|
+
return;
|
|
14697
|
+
}
|
|
14698
|
+
let idleTimer;
|
|
14699
|
+
const server = await startPtyHost({
|
|
14700
|
+
endpoint,
|
|
14701
|
+
token,
|
|
14702
|
+
onIdle: () => armIdleExit()
|
|
14703
|
+
});
|
|
14704
|
+
function armIdleExit() {
|
|
14705
|
+
clearTimeout(idleTimer);
|
|
14706
|
+
idleTimer = setTimeout(() => {
|
|
14707
|
+
if (server.sessionCount > 0)
|
|
14708
|
+
return armIdleExit();
|
|
14709
|
+
log("pty host: idle with no sessions, exiting");
|
|
14710
|
+
server.close().then(() => process.exit(0));
|
|
14711
|
+
}, PTY_HOST_IDLE_MS);
|
|
14712
|
+
idleTimer.unref?.();
|
|
14713
|
+
}
|
|
14714
|
+
writePtyHostFile(file, { pid: process.pid, endpoint, token, version: PTY_PROTOCOL_VERSION });
|
|
14715
|
+
log(`pty host: ready on ${endpoint} (pid ${process.pid})`);
|
|
14716
|
+
armIdleExit();
|
|
14717
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
14718
|
+
process.on(signal, () => {
|
|
14719
|
+
server.close().then(() => process.exit(0));
|
|
14720
|
+
});
|
|
14721
|
+
}
|
|
14722
|
+
}
|
|
14723
|
+
var PTY_HOST_IDLE_MS = 60000;
|
|
14724
|
+
var init_pty_host_main = __esm(() => {
|
|
14725
|
+
init_src();
|
|
14726
|
+
init_paths();
|
|
14727
|
+
init_pty_host_launch();
|
|
14728
|
+
init_pty_host();
|
|
14729
|
+
});
|
|
14730
|
+
|
|
13939
14731
|
// packages/cli/src/index.ts
|
|
13940
14732
|
init_src();
|
|
13941
|
-
import
|
|
13942
|
-
import
|
|
14733
|
+
import fs24 from "fs";
|
|
14734
|
+
import path23 from "path";
|
|
13943
14735
|
|
|
13944
14736
|
// packages/cli/src/attach.ts
|
|
13945
14737
|
import fs from "fs";
|
|
@@ -14861,7 +15653,7 @@ import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
|
14861
15653
|
// packages/cli/package.json
|
|
14862
15654
|
var package_default = {
|
|
14863
15655
|
name: "@layorixinc/axterm",
|
|
14864
|
-
version: "0.
|
|
15656
|
+
version: "0.5.0",
|
|
14865
15657
|
type: "module",
|
|
14866
15658
|
bin: {
|
|
14867
15659
|
axterm: "dist/cli.js"
|
|
@@ -15183,7 +15975,7 @@ function printSessionTable(sessions) {
|
|
|
15183
15975
|
console.log(row.map((cell, col) => cell.padEnd(widths[col])).join(" "));
|
|
15184
15976
|
}
|
|
15185
15977
|
}
|
|
15186
|
-
var realPath = (target) =>
|
|
15978
|
+
var realPath = (target) => fs24.realpathSync.native(target);
|
|
15187
15979
|
function scopedHere(sessions) {
|
|
15188
15980
|
return sessionsInScope(sessions, process.cwd(), realPath);
|
|
15189
15981
|
}
|
|
@@ -15196,7 +15988,7 @@ var help = () => t("cli.help");
|
|
|
15196
15988
|
function readDotEnv(file = ".env") {
|
|
15197
15989
|
const out = {};
|
|
15198
15990
|
try {
|
|
15199
|
-
for (const line of
|
|
15991
|
+
for (const line of fs24.readFileSync(file, "utf8").split(/\r?\n/)) {
|
|
15200
15992
|
const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
15201
15993
|
if (m)
|
|
15202
15994
|
out[m[1]] = m[2].trim();
|
|
@@ -15207,7 +15999,7 @@ function readDotEnv(file = ".env") {
|
|
|
15207
15999
|
function configuredLocale() {
|
|
15208
16000
|
try {
|
|
15209
16001
|
const file = resolvePaths(currentPathEnv()).configFile;
|
|
15210
|
-
const raw = JSON.parse(
|
|
16002
|
+
const raw = JSON.parse(fs24.readFileSync(file, "utf8"));
|
|
15211
16003
|
return typeof raw.locale === "string" ? raw.locale : undefined;
|
|
15212
16004
|
} catch {
|
|
15213
16005
|
return;
|
|
@@ -15228,6 +16020,11 @@ async function main2() {
|
|
|
15228
16020
|
runMcpServer();
|
|
15229
16021
|
return;
|
|
15230
16022
|
}
|
|
16023
|
+
if (isPtyHostInvocation(argv)) {
|
|
16024
|
+
const { runPtyHostProcess: runPtyHostProcess2 } = await Promise.resolve().then(() => (init_pty_host_main(), exports_pty_host_main));
|
|
16025
|
+
await runPtyHostProcess2();
|
|
16026
|
+
return;
|
|
16027
|
+
}
|
|
15231
16028
|
const [command, ...rest] = argv;
|
|
15232
16029
|
setLocale(resolveLocale({ configured: configuredLocale(), env: process.env }));
|
|
15233
16030
|
const { flags, positional, passthrough } = parseArgs(rest);
|
|
@@ -15429,8 +16226,8 @@ async function main2() {
|
|
|
15429
16226
|
case "project": {
|
|
15430
16227
|
const sub = positional[0] ?? "list";
|
|
15431
16228
|
if (sub === "add") {
|
|
15432
|
-
const dir = positional[1] ?
|
|
15433
|
-
const alias = positional[2] ??
|
|
16229
|
+
const dir = positional[1] ? path23.resolve(positional[1]) : process.cwd();
|
|
16230
|
+
const alias = positional[2] ?? path23.basename(dir);
|
|
15434
16231
|
const projects = await client.call("project.add", {
|
|
15435
16232
|
alias,
|
|
15436
16233
|
path: dir
|