@layorixinc/axterm 0.4.0 → 0.6.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 +1875 -681
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -31,8 +31,169 @@ 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
|
|
|
49
|
+
// packages/core/src/agent-registry.ts
|
|
50
|
+
function agentProfile(id) {
|
|
51
|
+
const known = PROFILES[id];
|
|
52
|
+
if (known)
|
|
53
|
+
return known;
|
|
54
|
+
return {
|
|
55
|
+
id,
|
|
56
|
+
displayName: id,
|
|
57
|
+
title: {
|
|
58
|
+
workingLabel: id,
|
|
59
|
+
permissionLabel: `${id} - action required`,
|
|
60
|
+
idleLabel: `${id} ready`
|
|
61
|
+
},
|
|
62
|
+
hostHookSource: id
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function renderAgentTitle(profile, state, frame) {
|
|
66
|
+
if (state === undefined)
|
|
67
|
+
return;
|
|
68
|
+
const { title } = profile;
|
|
69
|
+
if (state === "working") {
|
|
70
|
+
if (title.synthesizeWorking === false)
|
|
71
|
+
return;
|
|
72
|
+
const spinner = SPINNER_FRAMES[(frame % SPINNER_FRAMES.length + SPINNER_FRAMES.length) % SPINNER_FRAMES.length];
|
|
73
|
+
return `${spinner} ${title.workingLabel}`;
|
|
74
|
+
}
|
|
75
|
+
if (title.synthesizeTerminal === false)
|
|
76
|
+
return;
|
|
77
|
+
return state === "done" ? title.idleLabel : title.permissionLabel;
|
|
78
|
+
}
|
|
79
|
+
function oscTitle(title) {
|
|
80
|
+
const safe = title.replace(/[\x00-\x1f\x7f]/g, "").slice(0, MAX_TITLE_LENGTH);
|
|
81
|
+
return `\x1B]0;${safe}\x07`;
|
|
82
|
+
}
|
|
83
|
+
var SPINNER_FRAMES, MENU_APPROVE = "\r", PROFILES, REGISTERED_AGENTS, MAX_TITLE_LENGTH = 200;
|
|
84
|
+
var init_agent_registry = __esm(() => {
|
|
85
|
+
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
86
|
+
PROFILES = {
|
|
87
|
+
omp: {
|
|
88
|
+
id: "omp",
|
|
89
|
+
displayName: "OMP",
|
|
90
|
+
title: { workingLabel: "OMP", permissionLabel: "OMP - action required", idleLabel: "OMP ready" },
|
|
91
|
+
hostHookSource: "omp",
|
|
92
|
+
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
93
|
+
quota: "gauge",
|
|
94
|
+
resume: { argv: ["--resume", "{ref}"], ref: "sessionFile" }
|
|
95
|
+
},
|
|
96
|
+
pi: {
|
|
97
|
+
id: "pi",
|
|
98
|
+
displayName: "Pi",
|
|
99
|
+
title: { workingLabel: "Pi", permissionLabel: "Pi - action required", idleLabel: "Pi ready" },
|
|
100
|
+
hostHookSource: "pi",
|
|
101
|
+
resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
|
|
102
|
+
},
|
|
103
|
+
codex: {
|
|
104
|
+
id: "codex",
|
|
105
|
+
displayName: "Codex",
|
|
106
|
+
title: {
|
|
107
|
+
workingLabel: "Codex",
|
|
108
|
+
permissionLabel: "Codex - action required",
|
|
109
|
+
idleLabel: "Codex ready",
|
|
110
|
+
synthesizeWorking: false
|
|
111
|
+
},
|
|
112
|
+
hostHookSource: "codex",
|
|
113
|
+
quota: "gauge",
|
|
114
|
+
approvalBypassArgs: ["--dangerously-bypass-approvals-and-sandbox"],
|
|
115
|
+
structuredApprovals: true,
|
|
116
|
+
resume: { argv: ["resume", "{ref}"], ref: "sessionId", form: "subcommand" }
|
|
117
|
+
},
|
|
118
|
+
opencode: {
|
|
119
|
+
id: "opencode",
|
|
120
|
+
displayName: "OpenCode",
|
|
121
|
+
title: {
|
|
122
|
+
workingLabel: "OpenCode",
|
|
123
|
+
permissionLabel: "OpenCode - action required",
|
|
124
|
+
idleLabel: "OpenCode ready",
|
|
125
|
+
synthesizeTerminal: false
|
|
126
|
+
},
|
|
127
|
+
hostHookSource: "opencode",
|
|
128
|
+
resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
|
|
129
|
+
},
|
|
130
|
+
cursor: {
|
|
131
|
+
id: "cursor",
|
|
132
|
+
displayName: "Cursor Agent",
|
|
133
|
+
title: {
|
|
134
|
+
workingLabel: "Cursor Agent",
|
|
135
|
+
permissionLabel: "Cursor - action required",
|
|
136
|
+
idleLabel: "Cursor ready"
|
|
137
|
+
},
|
|
138
|
+
hostHookSource: "cursor",
|
|
139
|
+
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
140
|
+
},
|
|
141
|
+
droid: {
|
|
142
|
+
id: "droid",
|
|
143
|
+
displayName: "Droid",
|
|
144
|
+
title: { workingLabel: "Droid", permissionLabel: "Droid - action required", idleLabel: "Droid ready" },
|
|
145
|
+
hostHookSource: "droid",
|
|
146
|
+
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
147
|
+
},
|
|
148
|
+
claude: {
|
|
149
|
+
id: "claude",
|
|
150
|
+
displayName: "Claude Code",
|
|
151
|
+
title: {
|
|
152
|
+
workingLabel: "Claude",
|
|
153
|
+
permissionLabel: "Claude - action required",
|
|
154
|
+
idleLabel: "Claude ready",
|
|
155
|
+
synthesizeWorking: false,
|
|
156
|
+
synthesizeTerminal: false
|
|
157
|
+
},
|
|
158
|
+
hostHookSource: "claude",
|
|
159
|
+
quota: "event",
|
|
160
|
+
approvalBypassArgs: ["--permission-mode", "bypassPermissions"],
|
|
161
|
+
resume: { argv: ["--resume", "{ref}"], ref: "sessionId", selfNamed: "uuid" }
|
|
162
|
+
},
|
|
163
|
+
gemini: {
|
|
164
|
+
id: "gemini",
|
|
165
|
+
displayName: "Gemini CLI",
|
|
166
|
+
title: {
|
|
167
|
+
workingLabel: "Gemini",
|
|
168
|
+
permissionLabel: "Gemini - action required",
|
|
169
|
+
idleLabel: "Gemini ready",
|
|
170
|
+
synthesizeWorking: false,
|
|
171
|
+
synthesizeTerminal: false
|
|
172
|
+
},
|
|
173
|
+
hostHookSource: "gemini",
|
|
174
|
+
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
175
|
+
hookAck: "{}",
|
|
176
|
+
approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
|
|
177
|
+
resume: { argv: ["--resume", "{ref}"], ref: "sessionId", selfNamed: "bare" }
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
REGISTERED_AGENTS = Object.keys(PROFILES);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// packages/core/src/session-id.ts
|
|
184
|
+
function uuidFromSessionId(sessionId) {
|
|
185
|
+
const hex = sessionId.replace(/-/g, "");
|
|
186
|
+
if (hex.length !== 32)
|
|
187
|
+
throw new Error(`session id is not 32 hex characters: ${sessionId}`);
|
|
188
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
189
|
+
}
|
|
190
|
+
function sessionIdFromUuid(ref) {
|
|
191
|
+
if (!ref.includes("-"))
|
|
192
|
+
return;
|
|
193
|
+
const stripped = ref.replace(/-/g, "");
|
|
194
|
+
return /^[0-9a-f]{4,32}$/i.test(stripped) ? stripped.toLowerCase() : undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
36
197
|
// packages/core/src/mcp-config.ts
|
|
37
198
|
function mcpServerEntry(launch, contextPath) {
|
|
38
199
|
return {
|
|
@@ -63,12 +224,6 @@ function codexMcpArgs(launch, contextPath) {
|
|
|
63
224
|
var MCP_SERVER_NAME = "axterm", MCP_CONTEXT_ENV = "AXTERM_MCP_CONTEXT";
|
|
64
225
|
|
|
65
226
|
// packages/core/src/agent-launch.ts
|
|
66
|
-
function uuidFromSessionId(sessionId) {
|
|
67
|
-
const hex = sessionId.replace(/-/g, "");
|
|
68
|
-
if (hex.length !== 32)
|
|
69
|
-
throw new Error(`session id is not 32 hex characters: ${sessionId}`);
|
|
70
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
71
|
-
}
|
|
72
227
|
function withAppServer(argv, url) {
|
|
73
228
|
const out = [];
|
|
74
229
|
for (let i = 0;i < argv.length; i++) {
|
|
@@ -82,6 +237,15 @@ function withAppServer(argv, url) {
|
|
|
82
237
|
return out;
|
|
83
238
|
return [out[0], "--remote", url, ...out.slice(1)];
|
|
84
239
|
}
|
|
240
|
+
function resumesAConversation(args) {
|
|
241
|
+
return args.some((arg) => RESUME_FLAGS.has(arg) || arg.startsWith("--resume=") || arg.startsWith("--continue="));
|
|
242
|
+
}
|
|
243
|
+
function namingFlag(agent, sessionId, extra) {
|
|
244
|
+
const named = agentProfile(agent).resume?.selfNamed;
|
|
245
|
+
if (named === undefined || resumesAConversation(extra))
|
|
246
|
+
return [];
|
|
247
|
+
return ["--session-id", named === "uuid" ? uuidFromSessionId(sessionId) : sessionId];
|
|
248
|
+
}
|
|
85
249
|
function buildAgentCommand(input) {
|
|
86
250
|
const extra = [...input.extraArgs ?? []];
|
|
87
251
|
if (input.agent === "claude") {
|
|
@@ -90,16 +254,14 @@ function buildAgentCommand(input) {
|
|
|
90
254
|
"--settings",
|
|
91
255
|
input.claudeSettingsPath,
|
|
92
256
|
...input.mcpConfigPath === undefined ? [] : ["--mcp-config", input.mcpConfigPath],
|
|
93
|
-
"
|
|
94
|
-
uuidFromSessionId(input.sessionId),
|
|
257
|
+
...namingFlag("claude", input.sessionId, extra),
|
|
95
258
|
...extra
|
|
96
259
|
];
|
|
97
260
|
}
|
|
98
261
|
if (input.agent === "gemini") {
|
|
99
262
|
return [
|
|
100
263
|
"gemini",
|
|
101
|
-
"
|
|
102
|
-
input.sessionId,
|
|
264
|
+
...namingFlag("gemini", input.sessionId, extra),
|
|
103
265
|
...extra
|
|
104
266
|
];
|
|
105
267
|
}
|
|
@@ -136,30 +298,16 @@ function resolveAgentArgs(input) {
|
|
|
136
298
|
});
|
|
137
299
|
return groups.filter((group, index) => group.flag === undefined || lastIndex.get(group.flag) === index).flatMap((group) => group.tokens);
|
|
138
300
|
}
|
|
139
|
-
function buildAgentResumeCommand(input) {
|
|
140
|
-
if (input.agent === "claude") {
|
|
141
|
-
return [
|
|
142
|
-
"claude",
|
|
143
|
-
"--settings",
|
|
144
|
-
input.claudeSettingsPath,
|
|
145
|
-
"--resume",
|
|
146
|
-
uuidFromSessionId(input.sessionId),
|
|
147
|
-
...input.extraArgs ?? []
|
|
148
|
-
];
|
|
149
|
-
}
|
|
150
|
-
if (input.agent === "gemini") {
|
|
151
|
-
return ["gemini", "--resume", input.sessionId, ...input.extraArgs ?? []];
|
|
152
|
-
}
|
|
153
|
-
return buildAgentCommand(input);
|
|
154
|
-
}
|
|
155
301
|
function isAgentId(value) {
|
|
156
302
|
return AGENT_IDS.includes(value);
|
|
157
303
|
}
|
|
158
304
|
function supportedAgents() {
|
|
159
305
|
return AGENT_IDS;
|
|
160
306
|
}
|
|
161
|
-
var AGENT_IDS;
|
|
307
|
+
var RESUME_FLAGS, AGENT_IDS;
|
|
162
308
|
var init_agent_launch = __esm(() => {
|
|
309
|
+
init_agent_registry();
|
|
310
|
+
RESUME_FLAGS = new Set(["--resume", "-r", "--continue", "-c"]);
|
|
163
311
|
AGENT_IDS = ["omp", "claude", "codex", "gemini"];
|
|
164
312
|
});
|
|
165
313
|
|
|
@@ -406,6 +554,9 @@ function isRecord(value) {
|
|
|
406
554
|
}
|
|
407
555
|
|
|
408
556
|
// packages/core/src/config-schema.ts
|
|
557
|
+
function isStagingMode(value) {
|
|
558
|
+
return typeof value === "string" && STAGING_MODES.includes(value);
|
|
559
|
+
}
|
|
409
560
|
function resolveStreamConfig(file) {
|
|
410
561
|
return {
|
|
411
562
|
enabled: file?.enabled ?? DEFAULT_STREAM_CONFIG.enabled,
|
|
@@ -499,7 +650,7 @@ function parseConfig(raw) {
|
|
|
499
650
|
}
|
|
500
651
|
const defaults = raw["defaults"];
|
|
501
652
|
if (isRecord(defaults)) {
|
|
502
|
-
checkKeys(defaults, ["agent", "shell", "autoApprove", "agentArgs"], "defaults.", errors);
|
|
653
|
+
checkKeys(defaults, ["agent", "shell", "autoApprove", "agentArgs", "staging"], "defaults.", errors);
|
|
503
654
|
const agent = defaults["agent"];
|
|
504
655
|
const shell = defaults["shell"];
|
|
505
656
|
const rawArgs = defaults["agentArgs"];
|
|
@@ -518,11 +669,16 @@ function parseConfig(raw) {
|
|
|
518
669
|
if (autoApprove !== undefined && typeof autoApprove !== "boolean") {
|
|
519
670
|
errors.push("defaults.autoApprove must be true or false");
|
|
520
671
|
}
|
|
672
|
+
const staging = defaults["staging"];
|
|
673
|
+
if (staging !== undefined && !isStagingMode(staging)) {
|
|
674
|
+
errors.push(`defaults.staging must be one of: ${STAGING_MODES.join(", ")}`);
|
|
675
|
+
}
|
|
521
676
|
config.defaults = {
|
|
522
677
|
...typeof agent === "string" ? { agent } : {},
|
|
523
678
|
...typeof shell === "string" ? { shell } : {},
|
|
524
679
|
...typeof autoApprove === "boolean" ? { autoApprove } : {},
|
|
525
|
-
...Object.keys(agentArgs).length > 0 ? { agentArgs } : {}
|
|
680
|
+
...Object.keys(agentArgs).length > 0 ? { agentArgs } : {},
|
|
681
|
+
...isStagingMode(staging) ? { staging } : {}
|
|
526
682
|
};
|
|
527
683
|
}
|
|
528
684
|
const projects = raw["projects"];
|
|
@@ -631,6 +787,9 @@ function setConfigValue(config, dottedPath, rawValue) {
|
|
|
631
787
|
const shown = Array.isArray(rawValue) ? rawValue.join(" ") : rawValue;
|
|
632
788
|
return { error: `"${shown}" is not a valid ${type} for ${dottedPath}` };
|
|
633
789
|
}
|
|
790
|
+
if (dottedPath === "defaults.staging" && !isStagingMode(value)) {
|
|
791
|
+
return { error: `"${String(value)}" is not a valid location \u2014 use one of: ${STAGING_MODES.join(", ")}` };
|
|
792
|
+
}
|
|
634
793
|
const next = structuredClone(config);
|
|
635
794
|
const segments = dottedPath.split(".");
|
|
636
795
|
let cursor = next;
|
|
@@ -674,10 +833,11 @@ function resolveEnvRefs(config, env) {
|
|
|
674
833
|
}
|
|
675
834
|
return resolved;
|
|
676
835
|
}
|
|
677
|
-
var CONFIG_VERSION = 1, DEFAULT_CONFIG, DEFAULT_STREAM_CONFIG, DEFAULT_INBOUND_CONFIG, FIELDS, SECRET_SUFFIXES, ENV_REF;
|
|
836
|
+
var STAGING_MODES, CONFIG_VERSION = 1, DEFAULT_CONFIG, DEFAULT_STREAM_CONFIG, DEFAULT_INBOUND_CONFIG, FIELDS, SECRET_SUFFIXES, ENV_REF;
|
|
678
837
|
var init_config_schema = __esm(() => {
|
|
679
838
|
init_agent_launch();
|
|
680
839
|
init_channel_spi();
|
|
840
|
+
STAGING_MODES = ["project", "runtime"];
|
|
681
841
|
DEFAULT_CONFIG = {
|
|
682
842
|
version: CONFIG_VERSION,
|
|
683
843
|
projects: [],
|
|
@@ -703,6 +863,7 @@ var init_config_schema = __esm(() => {
|
|
|
703
863
|
"defaults.agent": "string",
|
|
704
864
|
"defaults.shell": "string",
|
|
705
865
|
"defaults.autoApprove": "boolean",
|
|
866
|
+
"defaults.staging": "string",
|
|
706
867
|
"channels.telegram.enabled": "boolean",
|
|
707
868
|
"channels.telegram.groupId": "number",
|
|
708
869
|
"channels.telegram.allowUserIds": "number[]",
|
|
@@ -768,6 +929,7 @@ function resolvePaths(input) {
|
|
|
768
929
|
extDir: join(stateDir, "ext"),
|
|
769
930
|
runtimeFile: join(runtimeDir, "runtime.json"),
|
|
770
931
|
hookEndpointFile: join(runtimeDir, "hook-endpoint.env"),
|
|
932
|
+
ptyHostFile: join(runtimeDir, "pty-host.json"),
|
|
771
933
|
legacyRoots: legacyRoots.filter((dir) => dir !== stateDir)
|
|
772
934
|
};
|
|
773
935
|
}
|
|
@@ -876,6 +1038,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
876
1038
|
"cli.session.sent": "sent",
|
|
877
1039
|
"cli.session.sentKey": "sent {key}",
|
|
878
1040
|
"cli.session.selected": "selected option {index}",
|
|
1041
|
+
"cli.session.selectedDialog": "answered the agent's first-run dialog",
|
|
879
1042
|
"cli.resume.done": "resumed session {id} ({command})",
|
|
880
1043
|
"cli.update.current": "axterm {version} is the latest version",
|
|
881
1044
|
"cli.update.available": "axterm {latest} is available (you have {current})",
|
|
@@ -945,7 +1108,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
945
1108
|
"err.telegram.noToken": "no bot token is configured \u2014 run `axterm setup` or pass --token",
|
|
946
1109
|
"tg.help.general.header": "axterm commands (General topic):",
|
|
947
1110
|
"tg.help.general.projects": "/projects \u2014 registered projects",
|
|
948
|
-
"tg.help.general.new": "/new <project> [name] \u2014 new
|
|
1111
|
+
"tg.help.general.new": "/new <project> [name] [-- agent flags] \u2014 new session (e.g. --resume <conversation>)",
|
|
949
1112
|
"tg.help.general.wt": "/wt <project> <branch> \u2014 worktree session",
|
|
950
1113
|
"tg.help.general.fanout": "/fanout <project> <count|branches> <prompt> \u2014 run in parallel",
|
|
951
1114
|
"tg.help.general.worktrees": "/worktrees <project> \xB7 /rm <project> <branch> [force]",
|
|
@@ -967,6 +1130,9 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
967
1130
|
"tg.prompt.confirm": "the session is waiting on a confirmation:",
|
|
968
1131
|
"tg.prompt.secretAsk": "the session is asking for a secret. Reply with it and the message will be deleted:",
|
|
969
1132
|
"tg.prompt.secretDisabled": "the session is waiting for a password. Answering from chat is off (channels.telegram.secretPrompts); attach and type it, or use an SSH key",
|
|
1133
|
+
"tg.prompt.secretSubmitted": "the secret reached the session",
|
|
1134
|
+
"tg.prompt.secretStale": "that prompt is no longer waiting, so nothing was typed into the session",
|
|
1135
|
+
"tg.prompt.secretUndeleted": "\u26A0\uFE0F Telegram refused to delete that message \u2014 the secret is still in the chat; delete it yourself",
|
|
970
1136
|
"tg.session.screenFile": "terminal screen",
|
|
971
1137
|
"tg.session.none": "no sessions",
|
|
972
1138
|
"tg.prompt.failed": "\u26A0\uFE0F Delivery failed: {error}",
|
|
@@ -1044,6 +1210,8 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1044
1210
|
"doctor.bridge.missing": "hook bridge: {command} does not exist \u2014 codex and gemini hooks would fail once per tool call",
|
|
1045
1211
|
"doctor.agent.installed": "{agent}: installed ({path})",
|
|
1046
1212
|
"doctor.agent.notInstalled": "{agent}: not installed",
|
|
1213
|
+
"doctor.staging.project": "inbound files: written to <session dir>/.axterm/inbound (hidden from git; set defaults.staging=runtime for a synced folder)",
|
|
1214
|
+
"doctor.staging.runtime": "inbound files: written to axterm's own runtime directory (defaults.staging=runtime)",
|
|
1047
1215
|
"doctor.omp.extensionOk": "omp extension: deployed ({path})",
|
|
1048
1216
|
"doctor.omp.extensionMissing": "omp extension: missing at {path} \u2014 no omp session can report status",
|
|
1049
1217
|
"doctor.gemini.settingsOk": "gemini hooks: defaults file valid ({events} events subscribed)",
|
|
@@ -1082,6 +1250,10 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1082
1250
|
"err.session.dialogOpen": "session {id} is waiting on a dialog ({state}); answer it with the buttons or /esc first",
|
|
1083
1251
|
"err.agent.notInstalled": "the agent command '{command}' was not found on PATH \u2014 install it, or pass --cmd to run something else",
|
|
1084
1252
|
"err.agent.unknown": "unknown agent '{agent}' \u2014 supported: {supported}",
|
|
1253
|
+
"err.staging.dir": "could not create the session's staging directory {dir} ({error}) \u2014 fix the permissions, or set defaults.staging to runtime",
|
|
1254
|
+
"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",
|
|
1255
|
+
"err.claude.conversationBusy": "session {session} is already in conversation {id} \u2014 two sessions writing one transcript would corrupt it; kill that one first",
|
|
1256
|
+
"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
1257
|
"codex.approval.denied": "Denied from the axterm channel",
|
|
1086
1258
|
"err.codex.noAuth": "codex is not logged in ({authFile} not found) \u2014 run `codex login` once, then start the session again",
|
|
1087
1259
|
"err.codex.authLink": "could not link codex credentials into axterm's CODEX_HOME ({reason}) \u2014 refusing to start a logged-out agent",
|
|
@@ -1172,6 +1344,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1172
1344
|
"cli.session.sent": "\uC804\uB2EC\uB428",
|
|
1173
1345
|
"cli.session.sentKey": "{key} \uC804\uB2EC\uB428",
|
|
1174
1346
|
"cli.session.selected": "{index}\uBC88 \uD56D\uBAA9 \uC120\uD0DD\uB428",
|
|
1347
|
+
"cli.session.selectedDialog": "\uC5D0\uC774\uC804\uD2B8\uC758 \uCCAB \uC2E4\uD589 \uB300\uD654\uC0C1\uC790\uC5D0 \uC751\uB2F5\uD588\uC2B5\uB2C8\uB2E4",
|
|
1175
1348
|
"cli.resume.done": "\uC138\uC158 {id} \uC7AC\uAC1C\uB428 ({command})",
|
|
1176
1349
|
"cli.update.current": "axterm {version} \uC774 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4",
|
|
1177
1350
|
"cli.update.available": "axterm {latest} \uC744(\uB97C) \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (\uD604\uC7AC {current})",
|
|
@@ -1234,7 +1407,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1234
1407
|
"cli.telegram.missingSettings": "telegram link: \uD1A0\uD070/\uADF8\uB8F9/\uC0AC\uC6A9\uC790 \uC815\uBCF4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --token/--group/--user \uD50C\uB798\uADF8, TELEGRAM_* \uD658\uACBD\uBCC0\uC218, \uB610\uB294 \uD604\uC7AC \uD3F4\uB354\uC758 .env \uD30C\uC77C \uC911 \uD558\uB098\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
1235
1408
|
"tg.help.general.header": "axterm \uBA85\uB839 (General \uD1A0\uD53D):",
|
|
1236
1409
|
"tg.help.general.projects": "/projects \u2014 \uB4F1\uB85D\uB41C \uD504\uB85C\uC81D\uD2B8",
|
|
1237
|
-
"tg.help.general.new": "/new <\uD504\uB85C\uC81D\uD2B8> [\uC774\uB984] \u2014 \uC0C8
|
|
1410
|
+
"tg.help.general.new": "/new <\uD504\uB85C\uC81D\uD2B8> [\uC774\uB984] [\uC5D0\uC774\uC804\uD2B8 \uD50C\uB798\uADF8] \u2014 \uC0C8 \uC138\uC158 (\uC608: --resume <\uB300\uD654>)",
|
|
1238
1411
|
"tg.help.general.wt": "/wt <\uD504\uB85C\uC81D\uD2B8> <\uBE0C\uB79C\uCE58> \u2014 worktree \uC138\uC158",
|
|
1239
1412
|
"tg.help.general.fanout": "/fanout <\uD504\uB85C\uC81D\uD2B8> <\uAC1C\uC218|\uBE0C\uB79C\uCE58\uB4E4> <\uD504\uB86C\uD504\uD2B8> \u2014 \uBCD1\uB82C \uC2E4\uD589",
|
|
1240
1413
|
"tg.help.general.worktrees": "/worktrees <\uD504\uB85C\uC81D\uD2B8> \xB7 /rm <\uD504\uB85C\uC81D\uD2B8> <\uBE0C\uB79C\uCE58> [force]",
|
|
@@ -1263,6 +1436,9 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1263
1436
|
"tg.prompt.confirm": "\uC138\uC158\uC774 \uD655\uC778\uC744 \uAE30\uB2E4\uB9AC\uACE0 \uC788\uC2B5\uB2C8\uB2E4:",
|
|
1264
1437
|
"tg.prompt.secretAsk": "\uC138\uC158\uC774 \uBE44\uBC00\uAC12\uC744 \uC694\uAD6C\uD569\uB2C8\uB2E4. \uB2F5\uC7A5\uD558\uBA74 \uADF8 \uBA54\uC2DC\uC9C0\uB294 \uC0AD\uC81C\uB429\uB2C8\uB2E4:",
|
|
1265
1438
|
"tg.prompt.secretDisabled": "\uC138\uC158\uC774 \uBE44\uBC00\uBC88\uD638\uB97C \uAE30\uB2E4\uB9AC\uACE0 \uC788\uC2B5\uB2C8\uB2E4. \uCC44\uD305 \uC785\uB825\uC740 \uAEBC\uC838 \uC788\uC2B5\uB2C8\uB2E4 (channels.telegram.secretPrompts). attach \uD574\uC11C \uC785\uB825\uD558\uAC70\uB098 SSH \uD0A4\uB97C \uC4F0\uC138\uC694",
|
|
1439
|
+
"tg.prompt.secretSubmitted": "\uBE44\uBC00\uAC12\uC774 \uC138\uC158\uC5D0 \uC804\uB2EC\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
1440
|
+
"tg.prompt.secretStale": "\uADF8 \uD504\uB86C\uD504\uD2B8\uB294 \uB354 \uC774\uC0C1 \uAE30\uB2E4\uB9AC\uACE0 \uC788\uC9C0 \uC54A\uC544 \uC138\uC158\uC5D0 \uC544\uBB34\uAC83\uB3C4 \uC785\uB825\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4",
|
|
1441
|
+
"tg.prompt.secretUndeleted": "\u26A0\uFE0F Telegram\uC774 \uBA54\uC2DC\uC9C0 \uC0AD\uC81C\uB97C \uAC70\uBD80\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uBE44\uBC00\uAC12\uC774 \uC544\uC9C1 \uCC44\uD305\uC5D0 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4. \uC9C1\uC811 \uC0AD\uC81C\uD558\uC138\uC694",
|
|
1266
1442
|
"tg.session.screenFile": "\uD130\uBBF8\uB110 \uD654\uBA74",
|
|
1267
1443
|
"tg.session.none": "\uC138\uC158 \uC5C6\uC74C",
|
|
1268
1444
|
"tg.prompt.failed": "\u26A0\uFE0F \uC804\uB2EC \uC2E4\uD328: {error}",
|
|
@@ -1340,6 +1516,8 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1340
1516
|
"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
1517
|
"doctor.agent.installed": "{agent}: \uC124\uCE58\uB428 ({path})",
|
|
1342
1518
|
"doctor.agent.notInstalled": "{agent}: \uBBF8\uC124\uCE58",
|
|
1519
|
+
"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)",
|
|
1520
|
+
"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
1521
|
"doctor.omp.extensionOk": "omp \uD655\uC7A5: \uBC30\uD3EC\uB428 ({path})",
|
|
1344
1522
|
"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
1523
|
"doctor.gemini.settingsOk": "gemini \uD6C5: defaults \uD30C\uC77C \uC815\uC0C1 ({events}\uAC1C \uC774\uBCA4\uD2B8 \uAD6C\uB3C5)",
|
|
@@ -1378,6 +1556,10 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1378
1556
|
"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
1557
|
"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
1558
|
"err.agent.unknown": "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uC774\uC804\uD2B8 '{agent}' \uC785\uB2C8\uB2E4. \uC9C0\uC6D0: {supported}",
|
|
1559
|
+
"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",
|
|
1560
|
+
"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",
|
|
1561
|
+
"err.claude.conversationBusy": "\uC138\uC158 {session} \uC774(\uAC00) \uC774\uBBF8 \uB300\uD654 {id} \uC5D0 \uB4E4\uC5B4\uAC00 \uC788\uC2B5\uB2C8\uB2E4. \uD55C transcript\uC5D0 \uB450 \uC138\uC158\uC774 \uC4F0\uBA74 \uC190\uC0C1\uB418\uBBC0\uB85C \uBA3C\uC800 \uADF8 \uC138\uC158\uC744 \uC885\uB8CC\uD558\uC138\uC694",
|
|
1562
|
+
"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
1563
|
"codex.approval.denied": "axterm \uCC44\uB110\uC5D0\uC11C \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
1382
1564
|
"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
1565
|
"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",
|
|
@@ -1567,144 +1749,16 @@ var init_setup_wizard = __esm(() => {
|
|
|
1567
1749
|
wizardConfig = new WeakMap;
|
|
1568
1750
|
});
|
|
1569
1751
|
|
|
1570
|
-
// packages/core/src/agent-registry.ts
|
|
1571
|
-
function agentProfile(id) {
|
|
1572
|
-
const known = PROFILES[id];
|
|
1573
|
-
if (known)
|
|
1574
|
-
return known;
|
|
1575
|
-
return {
|
|
1576
|
-
id,
|
|
1577
|
-
displayName: id,
|
|
1578
|
-
title: {
|
|
1579
|
-
workingLabel: id,
|
|
1580
|
-
permissionLabel: `${id} - action required`,
|
|
1581
|
-
idleLabel: `${id} ready`
|
|
1582
|
-
},
|
|
1583
|
-
hostHookSource: id
|
|
1584
|
-
};
|
|
1585
|
-
}
|
|
1586
|
-
function renderAgentTitle(profile, state, frame) {
|
|
1587
|
-
if (state === undefined)
|
|
1588
|
-
return;
|
|
1589
|
-
const { title } = profile;
|
|
1590
|
-
if (state === "working") {
|
|
1591
|
-
if (title.synthesizeWorking === false)
|
|
1592
|
-
return;
|
|
1593
|
-
const spinner = SPINNER_FRAMES[(frame % SPINNER_FRAMES.length + SPINNER_FRAMES.length) % SPINNER_FRAMES.length];
|
|
1594
|
-
return `${spinner} ${title.workingLabel}`;
|
|
1595
|
-
}
|
|
1596
|
-
if (title.synthesizeTerminal === false)
|
|
1597
|
-
return;
|
|
1598
|
-
return state === "done" ? title.idleLabel : title.permissionLabel;
|
|
1599
|
-
}
|
|
1600
|
-
function oscTitle(title) {
|
|
1601
|
-
const safe = title.replace(/[\x00-\x1f\x7f]/g, "").slice(0, MAX_TITLE_LENGTH);
|
|
1602
|
-
return `\x1B]0;${safe}\x07`;
|
|
1603
|
-
}
|
|
1604
|
-
var SPINNER_FRAMES, MENU_APPROVE = "\r", PROFILES, REGISTERED_AGENTS, MAX_TITLE_LENGTH = 200;
|
|
1605
|
-
var init_agent_registry = __esm(() => {
|
|
1606
|
-
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1607
|
-
PROFILES = {
|
|
1608
|
-
omp: {
|
|
1609
|
-
id: "omp",
|
|
1610
|
-
displayName: "OMP",
|
|
1611
|
-
title: { workingLabel: "OMP", permissionLabel: "OMP - action required", idleLabel: "OMP ready" },
|
|
1612
|
-
hostHookSource: "omp",
|
|
1613
|
-
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
1614
|
-
quota: "gauge",
|
|
1615
|
-
resume: { argv: ["--resume", "{ref}"], ref: "sessionFile" }
|
|
1616
|
-
},
|
|
1617
|
-
pi: {
|
|
1618
|
-
id: "pi",
|
|
1619
|
-
displayName: "Pi",
|
|
1620
|
-
title: { workingLabel: "Pi", permissionLabel: "Pi - action required", idleLabel: "Pi ready" },
|
|
1621
|
-
hostHookSource: "pi",
|
|
1622
|
-
resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
|
|
1623
|
-
},
|
|
1624
|
-
codex: {
|
|
1625
|
-
id: "codex",
|
|
1626
|
-
displayName: "Codex",
|
|
1627
|
-
title: {
|
|
1628
|
-
workingLabel: "Codex",
|
|
1629
|
-
permissionLabel: "Codex - action required",
|
|
1630
|
-
idleLabel: "Codex ready",
|
|
1631
|
-
synthesizeWorking: false
|
|
1632
|
-
},
|
|
1633
|
-
hostHookSource: "codex",
|
|
1634
|
-
quota: "gauge",
|
|
1635
|
-
approvalBypassArgs: ["--dangerously-bypass-approvals-and-sandbox"],
|
|
1636
|
-
structuredApprovals: true,
|
|
1637
|
-
resume: { argv: ["resume", "{ref}"], ref: "sessionId", form: "subcommand" }
|
|
1638
|
-
},
|
|
1639
|
-
opencode: {
|
|
1640
|
-
id: "opencode",
|
|
1641
|
-
displayName: "OpenCode",
|
|
1642
|
-
title: {
|
|
1643
|
-
workingLabel: "OpenCode",
|
|
1644
|
-
permissionLabel: "OpenCode - action required",
|
|
1645
|
-
idleLabel: "OpenCode ready",
|
|
1646
|
-
synthesizeTerminal: false
|
|
1647
|
-
},
|
|
1648
|
-
hostHookSource: "opencode",
|
|
1649
|
-
resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
|
|
1650
|
-
},
|
|
1651
|
-
cursor: {
|
|
1652
|
-
id: "cursor",
|
|
1653
|
-
displayName: "Cursor Agent",
|
|
1654
|
-
title: {
|
|
1655
|
-
workingLabel: "Cursor Agent",
|
|
1656
|
-
permissionLabel: "Cursor - action required",
|
|
1657
|
-
idleLabel: "Cursor ready"
|
|
1658
|
-
},
|
|
1659
|
-
hostHookSource: "cursor",
|
|
1660
|
-
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1661
|
-
},
|
|
1662
|
-
droid: {
|
|
1663
|
-
id: "droid",
|
|
1664
|
-
displayName: "Droid",
|
|
1665
|
-
title: { workingLabel: "Droid", permissionLabel: "Droid - action required", idleLabel: "Droid ready" },
|
|
1666
|
-
hostHookSource: "droid",
|
|
1667
|
-
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1668
|
-
},
|
|
1669
|
-
claude: {
|
|
1670
|
-
id: "claude",
|
|
1671
|
-
displayName: "Claude Code",
|
|
1672
|
-
title: {
|
|
1673
|
-
workingLabel: "Claude",
|
|
1674
|
-
permissionLabel: "Claude - action required",
|
|
1675
|
-
idleLabel: "Claude ready",
|
|
1676
|
-
synthesizeWorking: false,
|
|
1677
|
-
synthesizeTerminal: false
|
|
1678
|
-
},
|
|
1679
|
-
hostHookSource: "claude",
|
|
1680
|
-
quota: "event",
|
|
1681
|
-
approvalBypassArgs: ["--permission-mode", "bypassPermissions"],
|
|
1682
|
-
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1683
|
-
},
|
|
1684
|
-
gemini: {
|
|
1685
|
-
id: "gemini",
|
|
1686
|
-
displayName: "Gemini CLI",
|
|
1687
|
-
title: {
|
|
1688
|
-
workingLabel: "Gemini",
|
|
1689
|
-
permissionLabel: "Gemini - action required",
|
|
1690
|
-
idleLabel: "Gemini ready",
|
|
1691
|
-
synthesizeWorking: false,
|
|
1692
|
-
synthesizeTerminal: false
|
|
1693
|
-
},
|
|
1694
|
-
hostHookSource: "gemini",
|
|
1695
|
-
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
1696
|
-
hookAck: "{}",
|
|
1697
|
-
approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
|
|
1698
|
-
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1699
|
-
}
|
|
1700
|
-
};
|
|
1701
|
-
REGISTERED_AGENTS = Object.keys(PROFILES);
|
|
1702
|
-
});
|
|
1703
|
-
|
|
1704
1752
|
// packages/core/src/agent-resume.ts
|
|
1705
1753
|
function resumeReference(profile, source) {
|
|
1706
|
-
const
|
|
1707
|
-
|
|
1754
|
+
const reported = profile.resume?.ref === "sessionId" ? source.agentSessionId : source.agentSessionFile;
|
|
1755
|
+
if (reported !== undefined && reported.length > 0)
|
|
1756
|
+
return reported;
|
|
1757
|
+
const named = profile.resume?.selfNamed;
|
|
1758
|
+
const own = source.ownSessionId;
|
|
1759
|
+
if (named === undefined || own === undefined || own.length === 0)
|
|
1760
|
+
return;
|
|
1761
|
+
return named === "uuid" ? uuidFromSessionId(own) : own;
|
|
1708
1762
|
}
|
|
1709
1763
|
function agentIdFromCommand(command) {
|
|
1710
1764
|
const head = command[0] ?? "";
|
|
@@ -1731,17 +1785,23 @@ function withoutExistingResume(command, fragment) {
|
|
|
1731
1785
|
}
|
|
1732
1786
|
return command;
|
|
1733
1787
|
}
|
|
1788
|
+
function withoutNamingFlag(command) {
|
|
1789
|
+
const at = command.indexOf("--session-id");
|
|
1790
|
+
return at < 0 ? command : [...command.slice(0, at), ...command.slice(at + 2)];
|
|
1791
|
+
}
|
|
1734
1792
|
function buildResumeCommand(command, profile, reference) {
|
|
1735
1793
|
const resume = profile.resume;
|
|
1736
1794
|
const binary = command[0];
|
|
1737
1795
|
if (!resume || binary === undefined || reference.length === 0)
|
|
1738
1796
|
return;
|
|
1739
1797
|
const template = resume.argv;
|
|
1740
|
-
const
|
|
1798
|
+
const stripped = withoutExistingResume(command, template);
|
|
1799
|
+
const base = resume.selfNamed === undefined ? stripped : withoutNamingFlag(stripped);
|
|
1741
1800
|
const fragment = template.map((token) => token.replaceAll(REF, reference));
|
|
1742
1801
|
return resume.form === "subcommand" ? [base[0], ...fragment, ...base.slice(1)] : [...base, ...fragment];
|
|
1743
1802
|
}
|
|
1744
1803
|
var REF = "{ref}";
|
|
1804
|
+
var init_agent_resume = () => {};
|
|
1745
1805
|
|
|
1746
1806
|
// packages/core/src/self-spawn.ts
|
|
1747
1807
|
function daemonLaunch(input) {
|
|
@@ -1763,9 +1823,16 @@ function mcpLaunch(input) {
|
|
|
1763
1823
|
function isMcpInvocation(args) {
|
|
1764
1824
|
return args[0] === MCP_ARGV_FLAG;
|
|
1765
1825
|
}
|
|
1766
|
-
function
|
|
1767
|
-
const
|
|
1768
|
-
|
|
1826
|
+
function ptyHostLaunch(input) {
|
|
1827
|
+
const base = daemonLaunch(input);
|
|
1828
|
+
return { command: base.command, args: [...base.args.slice(0, -1), PTY_HOST_ARGV_FLAG] };
|
|
1829
|
+
}
|
|
1830
|
+
function isPtyHostInvocation(args) {
|
|
1831
|
+
return args[0] === PTY_HOST_ARGV_FLAG;
|
|
1832
|
+
}
|
|
1833
|
+
function hookPostTarget(env, agent, override) {
|
|
1834
|
+
const port = override?.port ?? Number(env["AXTERM_HOOK_PORT"]);
|
|
1835
|
+
const token = override?.token ?? env["AXTERM_HOOK_TOKEN"];
|
|
1769
1836
|
const sessionId = env["AXTERM_SESSION_ID"];
|
|
1770
1837
|
const launchToken = env["AXTERM_LAUNCH_TOKEN"];
|
|
1771
1838
|
if (!Number.isSafeInteger(port) || port <= 0)
|
|
@@ -1782,10 +1849,17 @@ function hookPostTarget(env, agent) {
|
|
|
1782
1849
|
}
|
|
1783
1850
|
};
|
|
1784
1851
|
}
|
|
1852
|
+
function parseHookEndpoint(text) {
|
|
1853
|
+
const port = Number(/^AXTERM_HOOK_PORT=(\d+)$/m.exec(text)?.[1]);
|
|
1854
|
+
const token = /^AXTERM_HOOK_TOKEN=(\S+)$/m.exec(text)?.[1];
|
|
1855
|
+
if (!Number.isSafeInteger(port) || port <= 0 || !token)
|
|
1856
|
+
return;
|
|
1857
|
+
return { port, token };
|
|
1858
|
+
}
|
|
1785
1859
|
function isDaemonInvocation(args) {
|
|
1786
1860
|
return args[0] === DAEMON_ARGV_FLAG;
|
|
1787
1861
|
}
|
|
1788
|
-
var DAEMON_ARGV_FLAG = "__daemon", HOOK_ARGV_FLAG = "__hook", MCP_ARGV_FLAG = "__mcp", RUNTIME_NAMES;
|
|
1862
|
+
var DAEMON_ARGV_FLAG = "__daemon", HOOK_ARGV_FLAG = "__hook", MCP_ARGV_FLAG = "__mcp", PTY_HOST_ARGV_FLAG = "__pty-host", RUNTIME_NAMES;
|
|
1789
1863
|
var init_self_spawn = __esm(() => {
|
|
1790
1864
|
RUNTIME_NAMES = new Set(["bun", "bun.exe", "bun-debug", "bun-debug.exe", "node", "node.exe"]);
|
|
1791
1865
|
});
|
|
@@ -1907,6 +1981,7 @@ function planUpdateRestart(input) {
|
|
|
1907
1981
|
var SEMVER;
|
|
1908
1982
|
var init_update = __esm(() => {
|
|
1909
1983
|
init_agent_registry();
|
|
1984
|
+
init_agent_resume();
|
|
1910
1985
|
SEMVER = /^\d+\.\d+\.\d+/;
|
|
1911
1986
|
});
|
|
1912
1987
|
|
|
@@ -2107,6 +2182,30 @@ function buildMenuSelectKeys(index, normalizePresses = MENU_NORMALIZE_PRESSES) {
|
|
|
2107
2182
|
}
|
|
2108
2183
|
var MENU_UP = "\x1B[A", MENU_DOWN = "\x1B[B", MENU_SELECT = "\r", MENU_NORMALIZE_PRESSES = 24;
|
|
2109
2184
|
|
|
2185
|
+
// packages/core/src/adopt.ts
|
|
2186
|
+
function resolveSessionCwd(input) {
|
|
2187
|
+
if (!input.preferConversation)
|
|
2188
|
+
return input.requested;
|
|
2189
|
+
const own = input.conversationCwd;
|
|
2190
|
+
return own !== undefined && own.length > 0 ? own : input.requested;
|
|
2191
|
+
}
|
|
2192
|
+
function conversationHeldBy(sessions, conversationId) {
|
|
2193
|
+
if (conversationId.length === 0)
|
|
2194
|
+
return;
|
|
2195
|
+
return sessions.find((session) => {
|
|
2196
|
+
if (session.state !== "running")
|
|
2197
|
+
return false;
|
|
2198
|
+
if (session.agentSessionId === conversationId)
|
|
2199
|
+
return true;
|
|
2200
|
+
const argv = session.agentCommand;
|
|
2201
|
+
return argv.some((token, index) => {
|
|
2202
|
+
if (token === "--resume" || token === "-r" || token === "--session-id")
|
|
2203
|
+
return argv[index + 1] === conversationId;
|
|
2204
|
+
return token === `--resume=${conversationId}`;
|
|
2205
|
+
});
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2110
2209
|
// packages/core/src/agent-status.ts
|
|
2111
2210
|
function next(prev, state, now) {
|
|
2112
2211
|
return {
|
|
@@ -2116,17 +2215,29 @@ function next(prev, state, now) {
|
|
|
2116
2215
|
stateStartedAt: prev?.state === state ? prev.stateStartedAt : now
|
|
2117
2216
|
};
|
|
2118
2217
|
}
|
|
2218
|
+
function withConversationId(events, identity) {
|
|
2219
|
+
const id = identity.sessionId;
|
|
2220
|
+
const subagent = identity.subagentId;
|
|
2221
|
+
if (typeof id !== "string" || id === "" || typeof subagent === "string" && subagent !== "") {
|
|
2222
|
+
return [...events];
|
|
2223
|
+
}
|
|
2224
|
+
return events.map((event) => ({ session_id: id, ...event }));
|
|
2225
|
+
}
|
|
2119
2226
|
function reduceAgentState(prev, payload, now) {
|
|
2227
|
+
const result = reduceState(prev, payload, now);
|
|
2228
|
+
const identity = {
|
|
2229
|
+
...payload.session_id !== undefined ? { agentSessionId: payload.session_id } : {},
|
|
2230
|
+
...payload.session_file !== undefined ? { agentSessionFile: payload.session_file } : {},
|
|
2231
|
+
...payload.session_name !== undefined ? { sessionName: payload.session_name } : {}
|
|
2232
|
+
};
|
|
2233
|
+
if (Object.keys(identity).length === 0)
|
|
2234
|
+
return result;
|
|
2235
|
+
return { ...result, meta: { ...identity, ...result.meta } };
|
|
2236
|
+
}
|
|
2237
|
+
function reduceState(prev, payload, now) {
|
|
2120
2238
|
switch (payload.event) {
|
|
2121
2239
|
case "session_start":
|
|
2122
|
-
return {
|
|
2123
|
-
entry: prev,
|
|
2124
|
-
meta: {
|
|
2125
|
-
agentSessionId: payload.session_id,
|
|
2126
|
-
agentSessionFile: payload.session_file,
|
|
2127
|
-
sessionName: payload.session_name
|
|
2128
|
-
}
|
|
2129
|
-
};
|
|
2240
|
+
return { entry: prev };
|
|
2130
2241
|
case "before_agent_start": {
|
|
2131
2242
|
const entry = next(prev, prev?.state ?? "working", now);
|
|
2132
2243
|
entry.prompt = payload.prompt ?? "";
|
|
@@ -2218,7 +2329,102 @@ var init_agent_status = __esm(() => {
|
|
|
2218
2329
|
]);
|
|
2219
2330
|
});
|
|
2220
2331
|
|
|
2332
|
+
// packages/core/src/pty-protocol.ts
|
|
2333
|
+
function encodeFrame(kind, id, payload) {
|
|
2334
|
+
const idBytes = new TextEncoder().encode(id);
|
|
2335
|
+
if (idBytes.length > 255)
|
|
2336
|
+
throw new Error(`session id too long for a frame header: ${idBytes.length} bytes`);
|
|
2337
|
+
const frame = new Uint8Array(HEADER_BYTES + idBytes.length + payload.byteLength);
|
|
2338
|
+
frame[0] = kind;
|
|
2339
|
+
frame[1] = idBytes.length;
|
|
2340
|
+
new DataView(frame.buffer, frame.byteOffset, frame.byteLength).setUint32(2, payload.byteLength, true);
|
|
2341
|
+
frame.set(idBytes, HEADER_BYTES);
|
|
2342
|
+
frame.set(payload, HEADER_BYTES + idBytes.length);
|
|
2343
|
+
return frame;
|
|
2344
|
+
}
|
|
2345
|
+
function isFrameKind(value) {
|
|
2346
|
+
return value === FrameKind.Control || value === FrameKind.Data;
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
class FrameDecoder {
|
|
2350
|
+
buffer = new Uint8Array(0);
|
|
2351
|
+
push(chunk) {
|
|
2352
|
+
this.buffer = concat(this.buffer, chunk);
|
|
2353
|
+
const frames = [];
|
|
2354
|
+
for (;; ) {
|
|
2355
|
+
if (this.buffer.byteLength < HEADER_BYTES)
|
|
2356
|
+
return frames;
|
|
2357
|
+
const kind = this.buffer[0];
|
|
2358
|
+
if (!isFrameKind(kind))
|
|
2359
|
+
throw new Error(`unknown frame kind ${kind}`);
|
|
2360
|
+
const idLength = this.buffer[1];
|
|
2361
|
+
const payloadLength = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength).getUint32(2, true);
|
|
2362
|
+
if (payloadLength > MAX_FRAME_BYTES) {
|
|
2363
|
+
throw new Error(`frame too large: ${payloadLength} bytes declared, ceiling is ${MAX_FRAME_BYTES}`);
|
|
2364
|
+
}
|
|
2365
|
+
const total = HEADER_BYTES + idLength + payloadLength;
|
|
2366
|
+
if (this.buffer.byteLength < total)
|
|
2367
|
+
return frames;
|
|
2368
|
+
const id = new TextDecoder().decode(this.buffer.subarray(HEADER_BYTES, HEADER_BYTES + idLength));
|
|
2369
|
+
const payload = this.buffer.slice(HEADER_BYTES + idLength, total);
|
|
2370
|
+
frames.push({ kind, id, payload });
|
|
2371
|
+
this.buffer = this.buffer.slice(total);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
function concat(left, right) {
|
|
2376
|
+
if (left.byteLength === 0)
|
|
2377
|
+
return right.slice();
|
|
2378
|
+
const joined = new Uint8Array(left.byteLength + right.byteLength);
|
|
2379
|
+
joined.set(left, 0);
|
|
2380
|
+
joined.set(right, left.byteLength);
|
|
2381
|
+
return joined;
|
|
2382
|
+
}
|
|
2383
|
+
function parseControl(payload) {
|
|
2384
|
+
let parsed;
|
|
2385
|
+
try {
|
|
2386
|
+
parsed = JSON.parse(new TextDecoder().decode(payload));
|
|
2387
|
+
} catch {
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
2391
|
+
return;
|
|
2392
|
+
const type = parsed.t;
|
|
2393
|
+
if (typeof type !== "string" || !KNOWN_TYPES.has(type))
|
|
2394
|
+
return;
|
|
2395
|
+
return parsed;
|
|
2396
|
+
}
|
|
2397
|
+
function controlFrame(message) {
|
|
2398
|
+
return encodeFrame(FrameKind.Control, "", new TextEncoder().encode(JSON.stringify(message)));
|
|
2399
|
+
}
|
|
2400
|
+
var PTY_PROTOCOL_VERSION = 1, FrameKind, MAX_FRAME_BYTES, HEADER_BYTES = 6, KNOWN_TYPES;
|
|
2401
|
+
var init_pty_protocol = __esm(() => {
|
|
2402
|
+
FrameKind = {
|
|
2403
|
+
Control: 1,
|
|
2404
|
+
Data: 2
|
|
2405
|
+
};
|
|
2406
|
+
MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
2407
|
+
KNOWN_TYPES = new Set([
|
|
2408
|
+
"hello",
|
|
2409
|
+
"spawn",
|
|
2410
|
+
"subscribe",
|
|
2411
|
+
"resize",
|
|
2412
|
+
"kill",
|
|
2413
|
+
"welcome",
|
|
2414
|
+
"spawned",
|
|
2415
|
+
"subscribed",
|
|
2416
|
+
"exit",
|
|
2417
|
+
"error"
|
|
2418
|
+
]);
|
|
2419
|
+
});
|
|
2420
|
+
|
|
2221
2421
|
// packages/core/src/workspace.ts
|
|
2422
|
+
function scratchDir(sessionDir) {
|
|
2423
|
+
return `${sessionDir.replace(/[/\\]+$/, "")}/${SCRATCH_DIR_NAME}`;
|
|
2424
|
+
}
|
|
2425
|
+
function inboundStagingDir(sessionDir) {
|
|
2426
|
+
return `${scratchDir(sessionDir)}/inbound`;
|
|
2427
|
+
}
|
|
2222
2428
|
function planMaterialisedPath(stagingDir, sessionId, remotePath) {
|
|
2223
2429
|
const parts = remotePath.split(/[/\\]/);
|
|
2224
2430
|
let base = "";
|
|
@@ -2239,7 +2445,8 @@ function planMaterialisedPath(stagingDir, sessionId, remotePath) {
|
|
|
2239
2445
|
}
|
|
2240
2446
|
return `${stagingDir}/${sessionId}/${name}`;
|
|
2241
2447
|
}
|
|
2242
|
-
var
|
|
2448
|
+
var SCRATCH_DIR_NAME = ".axterm", SCRATCH_IGNORE_NAME = ".gitignore", SCRATCH_IGNORE_BODY = `*
|
|
2449
|
+
`, MAX_NAME = 120;
|
|
2243
2450
|
|
|
2244
2451
|
// packages/core/src/claude-hooks.ts
|
|
2245
2452
|
function resultText(response) {
|
|
@@ -2403,6 +2610,8 @@ var init_onboarding = __esm(() => {
|
|
|
2403
2610
|
{ kind: "authMethod", question: "How would you like to authenticate for this project?" }
|
|
2404
2611
|
],
|
|
2405
2612
|
claude: [
|
|
2613
|
+
{ kind: "folderTrust", question: "Quick safety check: Is this a project you created or one you trust?" },
|
|
2614
|
+
{ kind: "autoMode", question: "Set up auto mode for your environment?" },
|
|
2406
2615
|
{ kind: "apiKey", question: "Do you want to use this API key?" },
|
|
2407
2616
|
{ kind: "loginMethod", question: "Select login method:" }
|
|
2408
2617
|
],
|
|
@@ -2430,6 +2639,15 @@ function evaluateVersionDrift(input) {
|
|
|
2430
2639
|
}
|
|
2431
2640
|
];
|
|
2432
2641
|
}
|
|
2642
|
+
function evaluateStagingDoctor(facts) {
|
|
2643
|
+
return [
|
|
2644
|
+
{
|
|
2645
|
+
id: "staging.mode",
|
|
2646
|
+
status: "info",
|
|
2647
|
+
key: facts.mode === "runtime" ? "doctor.staging.runtime" : "doctor.staging.project"
|
|
2648
|
+
}
|
|
2649
|
+
];
|
|
2650
|
+
}
|
|
2433
2651
|
function binaryCheck(agent, binary) {
|
|
2434
2652
|
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
2653
|
}
|
|
@@ -3435,6 +3653,9 @@ function sessionsByRecency(live, stored) {
|
|
|
3435
3653
|
var asGiven = (target) => target;
|
|
3436
3654
|
var init_session_scope = () => {};
|
|
3437
3655
|
|
|
3656
|
+
// packages/core/src/test-scratch.ts
|
|
3657
|
+
var init_test_scratch = () => {};
|
|
3658
|
+
|
|
3438
3659
|
// packages/core/src/index.ts
|
|
3439
3660
|
function isRpcEvent(f) {
|
|
3440
3661
|
return typeof f.ev === "string";
|
|
@@ -3454,6 +3675,7 @@ var init_src = __esm(() => {
|
|
|
3454
3675
|
init_config_paths();
|
|
3455
3676
|
init_setup_wizard();
|
|
3456
3677
|
init_agent_registry();
|
|
3678
|
+
init_agent_resume();
|
|
3457
3679
|
init_self_spawn();
|
|
3458
3680
|
init_channel_spi();
|
|
3459
3681
|
init_update();
|
|
@@ -3463,6 +3685,7 @@ var init_src = __esm(() => {
|
|
|
3463
3685
|
init_choice();
|
|
3464
3686
|
init_agent_status();
|
|
3465
3687
|
init_activity();
|
|
3688
|
+
init_pty_protocol();
|
|
3466
3689
|
init_claude_hooks();
|
|
3467
3690
|
init_onboarding();
|
|
3468
3691
|
init_doctor();
|
|
@@ -3480,19 +3703,20 @@ var init_src = __esm(() => {
|
|
|
3480
3703
|
init_dotenv_scrub();
|
|
3481
3704
|
init_prompt_detect();
|
|
3482
3705
|
init_session_scope();
|
|
3706
|
+
init_test_scratch();
|
|
3483
3707
|
SUBMIT_DELAY_MS = process.platform === "win32" ? 1500 : 500;
|
|
3484
3708
|
PASTE_CHUNK_BYTES = 16 * 1024;
|
|
3485
3709
|
});
|
|
3486
3710
|
|
|
3487
3711
|
// packages/daemon/src/config-store.ts
|
|
3488
|
-
import
|
|
3712
|
+
import fs6 from "fs";
|
|
3489
3713
|
import path6 from "path";
|
|
3490
3714
|
function writeAtomic(target, contents, mode) {
|
|
3491
3715
|
const tmp = `${target}.tmp`;
|
|
3492
|
-
|
|
3493
|
-
|
|
3716
|
+
fs6.writeFileSync(tmp, contents, { mode });
|
|
3717
|
+
fs6.renameSync(tmp, target);
|
|
3494
3718
|
if (process.platform !== "win32")
|
|
3495
|
-
|
|
3719
|
+
fs6.chmodSync(target, mode);
|
|
3496
3720
|
}
|
|
3497
3721
|
|
|
3498
3722
|
class ConfigStore {
|
|
@@ -3508,16 +3732,16 @@ class ConfigStore {
|
|
|
3508
3732
|
}
|
|
3509
3733
|
load() {
|
|
3510
3734
|
this.loadError = undefined;
|
|
3511
|
-
|
|
3735
|
+
fs6.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
|
|
3512
3736
|
if (process.platform !== "win32") {
|
|
3513
3737
|
try {
|
|
3514
|
-
|
|
3738
|
+
fs6.chmodSync(this.paths.configDir, DIR_MODE);
|
|
3515
3739
|
} catch {}
|
|
3516
3740
|
}
|
|
3517
3741
|
this.config = structuredClone(DEFAULT_CONFIG);
|
|
3518
|
-
if (
|
|
3742
|
+
if (fs6.existsSync(this.paths.configFile)) {
|
|
3519
3743
|
try {
|
|
3520
|
-
const parsed = parseConfig(JSON.parse(
|
|
3744
|
+
const parsed = parseConfig(JSON.parse(fs6.readFileSync(this.paths.configFile, "utf8")));
|
|
3521
3745
|
if ("errors" in parsed)
|
|
3522
3746
|
this.loadError = parsed.errors.join("; ");
|
|
3523
3747
|
else
|
|
@@ -3527,10 +3751,10 @@ class ConfigStore {
|
|
|
3527
3751
|
}
|
|
3528
3752
|
}
|
|
3529
3753
|
this.secrets = {};
|
|
3530
|
-
if (
|
|
3754
|
+
if (fs6.existsSync(this.paths.secretsFile)) {
|
|
3531
3755
|
this.tightenPermissions(this.paths.secretsFile);
|
|
3532
3756
|
try {
|
|
3533
|
-
const raw = JSON.parse(
|
|
3757
|
+
const raw = JSON.parse(fs6.readFileSync(this.paths.secretsFile, "utf8"));
|
|
3534
3758
|
if (raw !== null && typeof raw === "object") {
|
|
3535
3759
|
for (const [key, value] of Object.entries(raw)) {
|
|
3536
3760
|
if (typeof value === "string")
|
|
@@ -3546,8 +3770,8 @@ class ConfigStore {
|
|
|
3546
3770
|
if (process.platform === "win32")
|
|
3547
3771
|
return;
|
|
3548
3772
|
try {
|
|
3549
|
-
if ((
|
|
3550
|
-
|
|
3773
|
+
if ((fs6.statSync(file).mode & 63) !== 0)
|
|
3774
|
+
fs6.chmodSync(file, FILE_MODE);
|
|
3551
3775
|
} catch {}
|
|
3552
3776
|
}
|
|
3553
3777
|
get() {
|
|
@@ -3555,7 +3779,7 @@ class ConfigStore {
|
|
|
3555
3779
|
}
|
|
3556
3780
|
health() {
|
|
3557
3781
|
return configHealth({
|
|
3558
|
-
fileExists:
|
|
3782
|
+
fileExists: fs6.existsSync(this.paths.configFile),
|
|
3559
3783
|
parseError: this.loadError,
|
|
3560
3784
|
config: this.resolved()
|
|
3561
3785
|
});
|
|
@@ -3600,7 +3824,7 @@ class ConfigStore {
|
|
|
3600
3824
|
delete this.secrets[dottedPath];
|
|
3601
3825
|
else
|
|
3602
3826
|
this.secrets[dottedPath] = value;
|
|
3603
|
-
if (!
|
|
3827
|
+
if (!fs6.existsSync(this.paths.configFile)) {
|
|
3604
3828
|
writeAtomic(this.paths.configFile, `${JSON.stringify(this.config, null, 2)}
|
|
3605
3829
|
`, FILE_MODE);
|
|
3606
3830
|
}
|
|
@@ -3630,7 +3854,7 @@ class ConfigStore {
|
|
|
3630
3854
|
fingerprint() {
|
|
3631
3855
|
const stamp = (file) => {
|
|
3632
3856
|
try {
|
|
3633
|
-
return String(
|
|
3857
|
+
return String(fs6.statSync(file).mtimeMs);
|
|
3634
3858
|
} catch {
|
|
3635
3859
|
return "-";
|
|
3636
3860
|
}
|
|
@@ -3672,7 +3896,7 @@ var init_config_store = __esm(() => {
|
|
|
3672
3896
|
});
|
|
3673
3897
|
|
|
3674
3898
|
// packages/daemon/src/shell.ts
|
|
3675
|
-
import
|
|
3899
|
+
import fs7 from "fs";
|
|
3676
3900
|
import path7 from "path";
|
|
3677
3901
|
function findExecutable(name, env) {
|
|
3678
3902
|
const isWindows = env.platform === "win32";
|
|
@@ -3696,7 +3920,7 @@ function lookupOnPath(name) {
|
|
|
3696
3920
|
platform: process.platform,
|
|
3697
3921
|
pathEnv: process.env["PATH"] ?? "",
|
|
3698
3922
|
pathExt: process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD",
|
|
3699
|
-
exists: (candidate) =>
|
|
3923
|
+
exists: (candidate) => fs7.existsSync(candidate)
|
|
3700
3924
|
});
|
|
3701
3925
|
}
|
|
3702
3926
|
function resolveShell(kind) {
|
|
@@ -3733,7 +3957,7 @@ function resolvePosixShell(kind) {
|
|
|
3733
3957
|
candidates.push({ kind: "bash", exe: login });
|
|
3734
3958
|
candidates.push({ kind: "bash", exe: "/bin/bash" }, { kind: "bash", exe: "/usr/bin/bash" });
|
|
3735
3959
|
}
|
|
3736
|
-
const found = candidates.find((c) =>
|
|
3960
|
+
const found = candidates.find((c) => fs7.existsSync(c.exe));
|
|
3737
3961
|
if (found)
|
|
3738
3962
|
return found;
|
|
3739
3963
|
return { kind: "sh", exe: "/bin/sh" };
|
|
@@ -3784,18 +4008,18 @@ var cachedPwsh;
|
|
|
3784
4008
|
var init_shell = () => {};
|
|
3785
4009
|
|
|
3786
4010
|
// packages/daemon/src/paths.ts
|
|
3787
|
-
import
|
|
4011
|
+
import fs8 from "fs";
|
|
3788
4012
|
import os3 from "os";
|
|
3789
4013
|
import path8 from "path";
|
|
3790
4014
|
function ensureDataDirs() {
|
|
3791
4015
|
for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir, SCROLLBACK_DIR, LOG_DIR, EXT_DIR]) {
|
|
3792
|
-
|
|
4016
|
+
fs8.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
|
|
3793
4017
|
}
|
|
3794
4018
|
if (process.platform === "win32")
|
|
3795
4019
|
return;
|
|
3796
4020
|
for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir]) {
|
|
3797
4021
|
try {
|
|
3798
|
-
|
|
4022
|
+
fs8.chmodSync(dir, DIR_MODE2);
|
|
3799
4023
|
} catch {}
|
|
3800
4024
|
}
|
|
3801
4025
|
}
|
|
@@ -3803,14 +4027,14 @@ function restrictFile(file) {
|
|
|
3803
4027
|
if (process.platform === "win32")
|
|
3804
4028
|
return;
|
|
3805
4029
|
try {
|
|
3806
|
-
|
|
4030
|
+
fs8.chmodSync(file, 384);
|
|
3807
4031
|
} catch {}
|
|
3808
4032
|
}
|
|
3809
4033
|
function log(message) {
|
|
3810
4034
|
const line = `${new Date().toISOString()} ${message}
|
|
3811
4035
|
`;
|
|
3812
4036
|
try {
|
|
3813
|
-
|
|
4037
|
+
fs8.appendFileSync(LOG_FILE, line);
|
|
3814
4038
|
} catch {}
|
|
3815
4039
|
}
|
|
3816
4040
|
var PATHS, CONFIG_DIR, STATE_DIR, RUNTIME_DIR, SCROLLBACK_DIR, LOG_DIR, EXT_DIR, DB_PATH, RUNTIME_FILE2, HOOK_ENDPOINT_FILE, DIR_MODE2 = 448, PIPE_PATH, LOG_FILE;
|
|
@@ -3834,7 +4058,7 @@ var init_paths = __esm(() => {
|
|
|
3834
4058
|
});
|
|
3835
4059
|
|
|
3836
4060
|
// packages/daemon/src/store.ts
|
|
3837
|
-
import
|
|
4061
|
+
import fs9 from "fs";
|
|
3838
4062
|
import path9 from "path";
|
|
3839
4063
|
import { Database } from "bun:sqlite";
|
|
3840
4064
|
function rowToInfo(row) {
|
|
@@ -3866,7 +4090,7 @@ function rowToInfo(row) {
|
|
|
3866
4090
|
class Store {
|
|
3867
4091
|
db;
|
|
3868
4092
|
constructor(dbPath = DB_PATH) {
|
|
3869
|
-
|
|
4093
|
+
fs9.mkdirSync(path9.dirname(dbPath), { recursive: true });
|
|
3870
4094
|
this.db = new Database(dbPath, { strict: true });
|
|
3871
4095
|
this.db.run("PRAGMA journal_mode = WAL");
|
|
3872
4096
|
this.db.run(`
|
|
@@ -3945,8 +4169,10 @@ class Store {
|
|
|
3945
4169
|
this.db.prepare(`UPDATE sessions SET state = 'running', exit_code = NULL, exited_at = NULL,
|
|
3946
4170
|
agent_command = ?, pty_pid = ? WHERE id = ?`).run(JSON.stringify(agentCommand), ptyPid ?? null, id);
|
|
3947
4171
|
}
|
|
3948
|
-
markStaleRunningAsExited() {
|
|
3949
|
-
|
|
4172
|
+
markStaleRunningAsExited(keep = []) {
|
|
4173
|
+
const holes = keep.map(() => "?").join(",");
|
|
4174
|
+
const sql = keep.length === 0 ? `UPDATE sessions SET state = 'exited', exited_at = ? WHERE state != 'exited'` : `UPDATE sessions SET state = 'exited', exited_at = ? WHERE state != 'exited' AND id NOT IN (${holes})`;
|
|
4175
|
+
return this.db.prepare(sql).run(Date.now(), ...keep).changes;
|
|
3950
4176
|
}
|
|
3951
4177
|
listSessions() {
|
|
3952
4178
|
const rows = this.db.prepare(`SELECT * FROM sessions ORDER BY created_at DESC`).all();
|
|
@@ -3961,7 +4187,11 @@ class Store {
|
|
|
3961
4187
|
return existed;
|
|
3962
4188
|
}
|
|
3963
4189
|
findByPrefix(idOrPrefix) {
|
|
3964
|
-
const
|
|
4190
|
+
const alternate = sessionIdFromUuid(idOrPrefix) ?? idOrPrefix;
|
|
4191
|
+
const rows = this.db.prepare(`SELECT * FROM sessions
|
|
4192
|
+
WHERE id = ? OR short_id = ? OR id LIKE ?
|
|
4193
|
+
OR id = ? OR short_id = ? OR id LIKE ?
|
|
4194
|
+
ORDER BY created_at DESC`).all(idOrPrefix, idOrPrefix, `${idOrPrefix}%`, alternate, alternate, `${alternate}%`);
|
|
3965
4195
|
const row = rows[0];
|
|
3966
4196
|
return row ? rowToInfo(row) : undefined;
|
|
3967
4197
|
}
|
|
@@ -4023,15 +4253,16 @@ class Store {
|
|
|
4023
4253
|
}
|
|
4024
4254
|
}
|
|
4025
4255
|
var init_store = __esm(() => {
|
|
4256
|
+
init_src();
|
|
4026
4257
|
init_paths();
|
|
4027
4258
|
});
|
|
4028
4259
|
|
|
4029
4260
|
// packages/daemon/src/config-migrate.ts
|
|
4030
|
-
import
|
|
4261
|
+
import fs10 from "fs";
|
|
4031
4262
|
import path10 from "path";
|
|
4032
4263
|
import { Database as Database2 } from "bun:sqlite";
|
|
4033
4264
|
function checkpointWal(dbFile) {
|
|
4034
|
-
if (!
|
|
4265
|
+
if (!fs10.existsSync(dbFile))
|
|
4035
4266
|
return;
|
|
4036
4267
|
try {
|
|
4037
4268
|
const db = new Database2(dbFile);
|
|
@@ -4041,30 +4272,30 @@ function checkpointWal(dbFile) {
|
|
|
4041
4272
|
}
|
|
4042
4273
|
function relocate(source, target) {
|
|
4043
4274
|
try {
|
|
4044
|
-
|
|
4275
|
+
fs10.renameSync(source, target);
|
|
4045
4276
|
} catch {
|
|
4046
|
-
|
|
4277
|
+
fs10.cpSync(source, target, { recursive: true });
|
|
4047
4278
|
}
|
|
4048
4279
|
}
|
|
4049
4280
|
function moveInto(from, to, notes) {
|
|
4050
4281
|
checkpointWal(path10.join(from, "axterm.db"));
|
|
4051
|
-
|
|
4282
|
+
fs10.mkdirSync(to, { recursive: true });
|
|
4052
4283
|
for (const entry of STATE_ENTRIES) {
|
|
4053
4284
|
const source = path10.join(from, entry);
|
|
4054
4285
|
const target = path10.join(to, entry);
|
|
4055
|
-
if (!
|
|
4286
|
+
if (!fs10.existsSync(source))
|
|
4056
4287
|
continue;
|
|
4057
|
-
if (!
|
|
4288
|
+
if (!fs10.existsSync(target)) {
|
|
4058
4289
|
relocate(source, target);
|
|
4059
4290
|
notes.push(`moved ${entry}`);
|
|
4060
4291
|
continue;
|
|
4061
4292
|
}
|
|
4062
|
-
if (!
|
|
4293
|
+
if (!fs10.statSync(source).isDirectory() || !fs10.statSync(target).isDirectory())
|
|
4063
4294
|
continue;
|
|
4064
4295
|
let moved = 0;
|
|
4065
|
-
for (const child of
|
|
4296
|
+
for (const child of fs10.readdirSync(source)) {
|
|
4066
4297
|
const childTarget = path10.join(target, child);
|
|
4067
|
-
if (
|
|
4298
|
+
if (fs10.existsSync(childTarget))
|
|
4068
4299
|
continue;
|
|
4069
4300
|
relocate(path10.join(source, child), childTarget);
|
|
4070
4301
|
moved += 1;
|
|
@@ -4076,29 +4307,29 @@ function moveInto(from, to, notes) {
|
|
|
4076
4307
|
function migrateLegacyConfig(input) {
|
|
4077
4308
|
const notes = [];
|
|
4078
4309
|
const storePaths = defaultConfigStorePaths(input.configDir);
|
|
4079
|
-
if (
|
|
4310
|
+
if (fs10.existsSync(storePaths.configFile))
|
|
4080
4311
|
return { migrated: false, notes };
|
|
4081
|
-
const configSource = input.legacyRoots.find((dir) =>
|
|
4312
|
+
const configSource = input.legacyRoots.find((dir) => fs10.existsSync(path10.join(dir, "config.json")));
|
|
4082
4313
|
if (configSource !== undefined) {
|
|
4083
|
-
|
|
4084
|
-
|
|
4314
|
+
fs10.mkdirSync(input.configDir, { recursive: true });
|
|
4315
|
+
fs10.copyFileSync(path10.join(configSource, "config.json"), storePaths.configFile);
|
|
4085
4316
|
const secrets = path10.join(configSource, "secrets.json");
|
|
4086
|
-
if (
|
|
4087
|
-
|
|
4317
|
+
if (fs10.existsSync(secrets)) {
|
|
4318
|
+
fs10.copyFileSync(secrets, storePaths.secretsFile);
|
|
4088
4319
|
try {
|
|
4089
|
-
|
|
4320
|
+
fs10.chmodSync(storePaths.secretsFile, 384);
|
|
4090
4321
|
} catch {}
|
|
4091
4322
|
}
|
|
4092
|
-
|
|
4323
|
+
fs10.writeFileSync(path10.join(configSource, "MIGRATED.txt"), `axterm now reads its configuration from:
|
|
4093
4324
|
${input.configDir}
|
|
4094
4325
|
` + `These files are no longer used. secrets.json still holds your bot token \u2014 delete this directory once you are satisfied.
|
|
4095
4326
|
`);
|
|
4096
4327
|
notes.push(`adopted configuration from ${configSource}`);
|
|
4097
4328
|
}
|
|
4098
|
-
const legacy = input.legacyRoots.find((dir) =>
|
|
4329
|
+
const legacy = input.legacyRoots.find((dir) => fs10.existsSync(path10.join(dir, "axterm.db")));
|
|
4099
4330
|
if (legacy !== undefined) {
|
|
4100
4331
|
moveInto(legacy, input.stateDir, notes);
|
|
4101
|
-
|
|
4332
|
+
fs10.writeFileSync(path10.join(legacy, "MIGRATED.txt"), `axterm now stores state in:
|
|
4102
4333
|
${input.stateDir}
|
|
4103
4334
|
and configuration in:
|
|
4104
4335
|
${input.configDir}
|
|
@@ -4108,7 +4339,7 @@ and configuration in:
|
|
|
4108
4339
|
if (configSource !== undefined)
|
|
4109
4340
|
return { migrated: true, ...legacy ? { from: legacy } : {}, notes };
|
|
4110
4341
|
const dbFile = path10.join(input.stateDir, "axterm.db");
|
|
4111
|
-
if (!
|
|
4342
|
+
if (!fs10.existsSync(dbFile))
|
|
4112
4343
|
return { migrated: notes.length > 0, ...legacy ? { from: legacy } : {}, notes };
|
|
4113
4344
|
const store = new Store(dbFile);
|
|
4114
4345
|
const machine2 = store.getConfig("axterm_machine");
|
|
@@ -4946,13 +5177,13 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
4946
5177
|
var init_axterm_agent_status = () => {};
|
|
4947
5178
|
|
|
4948
5179
|
// packages/daemon/src/ext-install.ts
|
|
4949
|
-
import
|
|
5180
|
+
import fs11 from "fs";
|
|
4950
5181
|
import path11 from "path";
|
|
4951
5182
|
function installExtensionAsset(targetDir) {
|
|
4952
|
-
|
|
5183
|
+
fs11.mkdirSync(targetDir, { recursive: true });
|
|
4953
5184
|
const target = path11.join(targetDir, "axterm-agent-status.mjs");
|
|
4954
5185
|
const js = new Bun.Transpiler({ loader: "ts" }).transformSync(extensionSource);
|
|
4955
|
-
|
|
5186
|
+
fs11.writeFileSync(target, MANAGED_HEADER + js);
|
|
4956
5187
|
return target;
|
|
4957
5188
|
}
|
|
4958
5189
|
var extensionSource, MANAGED_HEADER = `// @axterm-managed-omp-extension
|
|
@@ -4965,7 +5196,7 @@ var init_ext_install = __esm(() => {
|
|
|
4965
5196
|
|
|
4966
5197
|
// packages/daemon/src/workspace-local.ts
|
|
4967
5198
|
import { spawn as spawn2 } from "child_process";
|
|
4968
|
-
import
|
|
5199
|
+
import fs12 from "fs/promises";
|
|
4969
5200
|
import path12 from "path";
|
|
4970
5201
|
function isAbsent(error) {
|
|
4971
5202
|
const code = error?.code;
|
|
@@ -4979,10 +5210,10 @@ class LocalWorkspace {
|
|
|
4979
5210
|
this.stagingDir = stagingDir;
|
|
4980
5211
|
}
|
|
4981
5212
|
async list(dir) {
|
|
4982
|
-
const dirents = await
|
|
5213
|
+
const dirents = await fs12.readdir(dir, { withFileTypes: true });
|
|
4983
5214
|
const stats = await Promise.all(dirents.map(async (dirent) => {
|
|
4984
5215
|
try {
|
|
4985
|
-
return await
|
|
5216
|
+
return await fs12.lstat(path12.join(dir, dirent.name));
|
|
4986
5217
|
} catch (error) {
|
|
4987
5218
|
if (isAbsent(error))
|
|
4988
5219
|
return;
|
|
@@ -5006,7 +5237,7 @@ class LocalWorkspace {
|
|
|
5006
5237
|
}
|
|
5007
5238
|
async stat(target) {
|
|
5008
5239
|
try {
|
|
5009
|
-
const stat = await
|
|
5240
|
+
const stat = await fs12.stat(target);
|
|
5010
5241
|
const kind = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "other";
|
|
5011
5242
|
return { kind, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
5012
5243
|
} catch (error) {
|
|
@@ -5017,8 +5248,8 @@ class LocalWorkspace {
|
|
|
5017
5248
|
}
|
|
5018
5249
|
async read(file, limit) {
|
|
5019
5250
|
if (limit === undefined)
|
|
5020
|
-
return await
|
|
5021
|
-
const handle = await
|
|
5251
|
+
return await fs12.readFile(file);
|
|
5252
|
+
const handle = await fs12.open(file, "r");
|
|
5022
5253
|
try {
|
|
5023
5254
|
const buffer = Buffer.allocUnsafe(Math.min(limit, (await handle.stat()).size));
|
|
5024
5255
|
let filled = 0;
|
|
@@ -5036,6 +5267,12 @@ class LocalWorkspace {
|
|
|
5036
5267
|
materialise(file) {
|
|
5037
5268
|
return Promise.resolve(file);
|
|
5038
5269
|
}
|
|
5270
|
+
async mkdirp(dir, mode) {
|
|
5271
|
+
await fs12.mkdir(dir, mode === undefined ? { recursive: true } : { recursive: true, mode });
|
|
5272
|
+
}
|
|
5273
|
+
async write(file, bytes, mode) {
|
|
5274
|
+
await fs12.writeFile(file, bytes, mode === undefined ? undefined : { mode });
|
|
5275
|
+
}
|
|
5039
5276
|
exec(argv, cwd) {
|
|
5040
5277
|
const [command, ...args] = argv;
|
|
5041
5278
|
if (command === undefined)
|
|
@@ -5063,7 +5300,7 @@ var init_workspace_local = () => {};
|
|
|
5063
5300
|
|
|
5064
5301
|
// packages/daemon/src/hook-host.ts
|
|
5065
5302
|
import { randomBytes } from "crypto";
|
|
5066
|
-
import
|
|
5303
|
+
import fs13 from "fs";
|
|
5067
5304
|
import http from "http";
|
|
5068
5305
|
import path13 from "path";
|
|
5069
5306
|
function header(req, name) {
|
|
@@ -5079,6 +5316,7 @@ class HookHost {
|
|
|
5079
5316
|
}
|
|
5080
5317
|
sessions = new Map;
|
|
5081
5318
|
pendingAsks = new Map;
|
|
5319
|
+
refusedHooks = new Set;
|
|
5082
5320
|
endpointFile;
|
|
5083
5321
|
server;
|
|
5084
5322
|
askOpenHandler;
|
|
@@ -5105,16 +5343,19 @@ class HookHost {
|
|
|
5105
5343
|
return promise;
|
|
5106
5344
|
}
|
|
5107
5345
|
writeEndpointFile(port) {
|
|
5108
|
-
|
|
5346
|
+
fs13.mkdirSync(path13.dirname(this.endpointFile), { recursive: true });
|
|
5109
5347
|
const tmp = `${this.endpointFile}.tmp`;
|
|
5110
|
-
|
|
5348
|
+
fs13.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
|
|
5111
5349
|
AXTERM_HOOK_TOKEN=${this.token}
|
|
5112
5350
|
`);
|
|
5113
|
-
|
|
5351
|
+
fs13.renameSync(tmp, this.endpointFile);
|
|
5114
5352
|
}
|
|
5115
5353
|
registerSession(sessionId, launchToken, onEvent) {
|
|
5116
5354
|
this.sessions.set(sessionId, { launchToken, onEvent });
|
|
5117
5355
|
}
|
|
5356
|
+
launchMatches(session, presented) {
|
|
5357
|
+
return session.launchToken === undefined || session.launchToken === presented;
|
|
5358
|
+
}
|
|
5118
5359
|
unregisterSession(sessionId) {
|
|
5119
5360
|
this.sessions.delete(sessionId);
|
|
5120
5361
|
for (const [askId, pending] of this.pendingAsks) {
|
|
@@ -5203,7 +5444,7 @@ AXTERM_HOOK_TOKEN=${this.token}
|
|
|
5203
5444
|
res.writeHead(404).end();
|
|
5204
5445
|
return;
|
|
5205
5446
|
}
|
|
5206
|
-
if (
|
|
5447
|
+
if (!this.launchMatches(session, body.launchToken)) {
|
|
5207
5448
|
res.writeHead(403).end();
|
|
5208
5449
|
return;
|
|
5209
5450
|
}
|
|
@@ -5238,7 +5479,9 @@ AXTERM_HOOK_TOKEN=${this.token}
|
|
|
5238
5479
|
return;
|
|
5239
5480
|
}
|
|
5240
5481
|
res.writeHead(200).end();
|
|
5241
|
-
|
|
5482
|
+
const named = input;
|
|
5483
|
+
const events = withConversationId(map(input), { sessionId: named.session_id, subagentId: named.agent_id });
|
|
5484
|
+
this.dispatchHookPayloads(auth.session, events, agent);
|
|
5242
5485
|
}
|
|
5243
5486
|
handleCodex(req, raw, res) {
|
|
5244
5487
|
const auth = this.agentHookSession(req);
|
|
@@ -5294,8 +5537,15 @@ AXTERM_HOOK_TOKEN=${this.token}
|
|
|
5294
5537
|
const sessionId = header(req, "x-axterm-session");
|
|
5295
5538
|
const launchToken = header(req, "x-axterm-launch");
|
|
5296
5539
|
const session = sessionId === undefined ? undefined : this.sessions.get(sessionId);
|
|
5297
|
-
if (sessionId === undefined || !session || session
|
|
5540
|
+
if (sessionId === undefined || !session || !this.launchMatches(session, launchToken)) {
|
|
5541
|
+
const why = sessionId === undefined ? "no session header" : !session ? "unknown session" : "launch token mismatch";
|
|
5542
|
+
const key = `${req.url}\x00${sessionId ?? ""}\x00${why}`;
|
|
5543
|
+
if (!this.refusedHooks.has(key)) {
|
|
5544
|
+
this.refusedHooks.add(key);
|
|
5545
|
+
log(`refused hook post to ${req.url}: ${why} (session ${sessionId ?? "-"}) \u2014 reported once per session`);
|
|
5546
|
+
}
|
|
5298
5547
|
return;
|
|
5548
|
+
}
|
|
5299
5549
|
return { sessionId, session };
|
|
5300
5550
|
}
|
|
5301
5551
|
dispatchHookPayloads(session, payloads, agent) {
|
|
@@ -5385,7 +5635,7 @@ var init_hook_host = __esm(() => {
|
|
|
5385
5635
|
});
|
|
5386
5636
|
|
|
5387
5637
|
// packages/daemon/src/rpc-server.ts
|
|
5388
|
-
import
|
|
5638
|
+
import fs14 from "fs";
|
|
5389
5639
|
import net2 from "net";
|
|
5390
5640
|
function writeFrame(socket, frame) {
|
|
5391
5641
|
if (!socket.destroyed)
|
|
@@ -5495,6 +5745,8 @@ function startRpcServer(options) {
|
|
|
5495
5745
|
session.write(Buffer.from(p["dataB64"], "base64").toString("utf8"));
|
|
5496
5746
|
return {};
|
|
5497
5747
|
}
|
|
5748
|
+
case "session.choose":
|
|
5749
|
+
return { answered: manager.answerScreenChoice(p["id"], p["index"]) };
|
|
5498
5750
|
case "attach": {
|
|
5499
5751
|
const session = manager.resolveLive(p["id"]);
|
|
5500
5752
|
cleanup();
|
|
@@ -5621,7 +5873,7 @@ function startRpcServer(options) {
|
|
|
5621
5873
|
});
|
|
5622
5874
|
if (process.platform !== "win32") {
|
|
5623
5875
|
try {
|
|
5624
|
-
|
|
5876
|
+
fs14.unlinkSync(pipePath);
|
|
5625
5877
|
} catch {}
|
|
5626
5878
|
}
|
|
5627
5879
|
server.listen(pipePath, () => {
|
|
@@ -5636,18 +5888,35 @@ var init_rpc_server = __esm(() => {
|
|
|
5636
5888
|
});
|
|
5637
5889
|
|
|
5638
5890
|
// packages/daemon/src/codex-home.ts
|
|
5639
|
-
import
|
|
5891
|
+
import fs15 from "fs";
|
|
5640
5892
|
import path14 from "path";
|
|
5641
5893
|
function codexTrustKeyPath(home) {
|
|
5642
5894
|
const resolved = (() => {
|
|
5643
5895
|
try {
|
|
5644
|
-
return
|
|
5896
|
+
return fs15.realpathSync.native(home);
|
|
5645
5897
|
} catch {
|
|
5646
5898
|
return path14.resolve(home);
|
|
5647
5899
|
}
|
|
5648
5900
|
})();
|
|
5649
5901
|
return path14.join(resolved, "hooks.json");
|
|
5650
5902
|
}
|
|
5903
|
+
function referenceCredential(source, target, ops) {
|
|
5904
|
+
const symlink = ops?.symlink ?? ((s, t2) => fs15.symlinkSync(s, t2, "file"));
|
|
5905
|
+
const link = ops?.link ?? fs15.linkSync;
|
|
5906
|
+
try {
|
|
5907
|
+
symlink(source, target);
|
|
5908
|
+
return "symlink";
|
|
5909
|
+
} catch {
|
|
5910
|
+
try {
|
|
5911
|
+
link(source, target);
|
|
5912
|
+
return "hardlink";
|
|
5913
|
+
} catch {
|
|
5914
|
+
fs15.copyFileSync(source, target);
|
|
5915
|
+
fs15.chmodSync(target, 384);
|
|
5916
|
+
return "copy";
|
|
5917
|
+
}
|
|
5918
|
+
}
|
|
5919
|
+
}
|
|
5651
5920
|
var init_codex_home = () => {};
|
|
5652
5921
|
|
|
5653
5922
|
// packages/daemon/src/host-env.ts
|
|
@@ -5662,15 +5931,36 @@ function stripHostAttribution(env) {
|
|
|
5662
5931
|
}
|
|
5663
5932
|
var ATTRIBUTION_PREFIXES, ATTRIBUTION_KEEP;
|
|
5664
5933
|
var init_host_env = __esm(() => {
|
|
5665
|
-
ATTRIBUTION_PREFIXES = ["ORCA_"];
|
|
5934
|
+
ATTRIBUTION_PREFIXES = ["ORCA_", "AXTERM_"];
|
|
5666
5935
|
ATTRIBUTION_KEEP = new Set([
|
|
5667
5936
|
"ORCA_CODEX_HOME",
|
|
5668
5937
|
"ORCA_OPENCODE_CONFIG_DIR",
|
|
5669
5938
|
"ORCA_USER_DATA_PATH",
|
|
5670
|
-
"ORCA_APP_VERSION"
|
|
5939
|
+
"ORCA_APP_VERSION",
|
|
5940
|
+
"AXTERM_CONFIG_DIR",
|
|
5941
|
+
"AXTERM_STATE_DIR",
|
|
5942
|
+
"AXTERM_RUNTIME_DIR",
|
|
5943
|
+
"AXTERM_LANG",
|
|
5944
|
+
"AXTERM_PTY_HOST",
|
|
5945
|
+
"AXTERM_PTY_HOST_MAIN"
|
|
5671
5946
|
]);
|
|
5672
5947
|
});
|
|
5673
5948
|
|
|
5949
|
+
// packages/daemon/src/session-env.ts
|
|
5950
|
+
function sessionEnv(base, identity) {
|
|
5951
|
+
return {
|
|
5952
|
+
...stripHostAttribution(base),
|
|
5953
|
+
AXTERM_SESSION_ID: identity.sessionId,
|
|
5954
|
+
AXTERM_LAUNCH_TOKEN: identity.launchToken,
|
|
5955
|
+
AXTERM_HOOK_PORT: String(identity.hookPort),
|
|
5956
|
+
AXTERM_HOOK_TOKEN: identity.hookToken,
|
|
5957
|
+
AXTERM_HOOK_ENDPOINT: identity.endpointFile
|
|
5958
|
+
};
|
|
5959
|
+
}
|
|
5960
|
+
var init_session_env = __esm(() => {
|
|
5961
|
+
init_host_env();
|
|
5962
|
+
});
|
|
5963
|
+
|
|
5674
5964
|
// packages/daemon/src/git-porcelain.ts
|
|
5675
5965
|
function parseWorktreeList(porcelain) {
|
|
5676
5966
|
const entries = [];
|
|
@@ -5709,7 +5999,7 @@ function slugifyBranch(branch) {
|
|
|
5709
5999
|
var MAX_SLUG_LENGTH = 60;
|
|
5710
6000
|
|
|
5711
6001
|
// packages/daemon/src/git.ts
|
|
5712
|
-
import
|
|
6002
|
+
import fs16 from "fs";
|
|
5713
6003
|
import path15 from "path";
|
|
5714
6004
|
async function git(ws, cwd, args) {
|
|
5715
6005
|
const run = await ws.exec(["git", ...args], cwd);
|
|
@@ -5729,7 +6019,7 @@ async function gitOrThrow(ws, cwd, args) {
|
|
|
5729
6019
|
function canonicalPath(target) {
|
|
5730
6020
|
let resolved = path15.resolve(target);
|
|
5731
6021
|
try {
|
|
5732
|
-
resolved =
|
|
6022
|
+
resolved = fs16.realpathSync.native(resolved);
|
|
5733
6023
|
} catch {}
|
|
5734
6024
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
5735
6025
|
}
|
|
@@ -5753,7 +6043,7 @@ async function createWorktree(ws, options) {
|
|
|
5753
6043
|
const existing = (await listWorktrees(ws, repoRoot)).find((w) => canonicalPath(w.path) === canonicalPath(target));
|
|
5754
6044
|
if (existing)
|
|
5755
6045
|
return { path: target, branch, createdBranch: false };
|
|
5756
|
-
|
|
6046
|
+
fs16.mkdirSync(path15.dirname(target), { recursive: true });
|
|
5757
6047
|
const exists = (await git(ws, repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`])).ok;
|
|
5758
6048
|
const args = exists ? ["worktree", "add", target, branch] : ["worktree", "add", "-b", branch, target, ...base !== undefined ? [base] : []];
|
|
5759
6049
|
await gitOrThrow(ws, repoRoot, args);
|
|
@@ -5782,6 +6072,14 @@ async function untrackedFiles(ws, worktreePath) {
|
|
|
5782
6072
|
return [];
|
|
5783
6073
|
return result.stdout.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
5784
6074
|
}
|
|
6075
|
+
async function ignoreState(ws, cwd, file) {
|
|
6076
|
+
const run = await ws.exec(["git", "check-ignore", "-q", file], cwd);
|
|
6077
|
+
if (run.code === 0)
|
|
6078
|
+
return "ignored";
|
|
6079
|
+
if (run.code === 1)
|
|
6080
|
+
return "visible";
|
|
6081
|
+
return "unknown";
|
|
6082
|
+
}
|
|
5785
6083
|
async function countLines(ws, filePath) {
|
|
5786
6084
|
try {
|
|
5787
6085
|
const stat = await ws.stat(filePath);
|
|
@@ -5825,64 +6123,536 @@ async function changedFiles(ws, worktreePath, base, limit = 20) {
|
|
|
5825
6123
|
names.add(line.trim());
|
|
5826
6124
|
}
|
|
5827
6125
|
}
|
|
5828
|
-
for (const file of await untrackedFiles(ws, worktreePath))
|
|
5829
|
-
names.add(file);
|
|
5830
|
-
return [...names].slice(0, limit);
|
|
6126
|
+
for (const file of await untrackedFiles(ws, worktreePath))
|
|
6127
|
+
names.add(file);
|
|
6128
|
+
return [...names].slice(0, limit);
|
|
6129
|
+
}
|
|
6130
|
+
var UNTRACKED_LINE_SCAN_LIMIT;
|
|
6131
|
+
var init_git = __esm(() => {
|
|
6132
|
+
init_paths();
|
|
6133
|
+
UNTRACKED_LINE_SCAN_LIMIT = 1024 * 1024;
|
|
6134
|
+
});
|
|
6135
|
+
|
|
6136
|
+
// packages/daemon/src/claude-store.ts
|
|
6137
|
+
import fs17 from "fs";
|
|
6138
|
+
import os4 from "os";
|
|
6139
|
+
import path16 from "path";
|
|
6140
|
+
function claudeConfigDir(env = process.env) {
|
|
6141
|
+
const override = env["CLAUDE_CONFIG_DIR"];
|
|
6142
|
+
if (override !== undefined && override.length > 0)
|
|
6143
|
+
return override;
|
|
6144
|
+
return path16.join(os4.homedir(), ".claude");
|
|
6145
|
+
}
|
|
6146
|
+
function resumedConversationId(argv) {
|
|
6147
|
+
for (let i = 0;i < argv.length; i++) {
|
|
6148
|
+
const token = argv[i];
|
|
6149
|
+
if (token === "--resume" || token === "-r") {
|
|
6150
|
+
const value = argv[i + 1];
|
|
6151
|
+
return value !== undefined && CONVERSATION_ID.test(value) ? value : undefined;
|
|
6152
|
+
}
|
|
6153
|
+
const inline = /^--resume=(.+)$/.exec(token);
|
|
6154
|
+
if (inline)
|
|
6155
|
+
return CONVERSATION_ID.test(inline[1]) ? inline[1] : undefined;
|
|
6156
|
+
}
|
|
6157
|
+
return;
|
|
6158
|
+
}
|
|
6159
|
+
function claudeConversationExists(configDir, conversationId) {
|
|
6160
|
+
const projects = path16.join(configDir, "projects");
|
|
6161
|
+
let entries;
|
|
6162
|
+
try {
|
|
6163
|
+
entries = fs17.readdirSync(projects);
|
|
6164
|
+
} catch {
|
|
6165
|
+
return;
|
|
6166
|
+
}
|
|
6167
|
+
const file = `${conversationId}.jsonl`;
|
|
6168
|
+
for (const entry of entries) {
|
|
6169
|
+
try {
|
|
6170
|
+
if (fs17.existsSync(path16.join(projects, entry, file)))
|
|
6171
|
+
return true;
|
|
6172
|
+
} catch {}
|
|
6173
|
+
}
|
|
6174
|
+
return false;
|
|
6175
|
+
}
|
|
6176
|
+
function claudeConversationCwd(configDir, conversationId) {
|
|
6177
|
+
const projects = path16.join(configDir, "projects");
|
|
6178
|
+
let entries;
|
|
6179
|
+
try {
|
|
6180
|
+
entries = fs17.readdirSync(projects);
|
|
6181
|
+
} catch {
|
|
6182
|
+
return;
|
|
6183
|
+
}
|
|
6184
|
+
for (const entry of entries) {
|
|
6185
|
+
const file = path16.join(projects, entry, `${conversationId}.jsonl`);
|
|
6186
|
+
let head;
|
|
6187
|
+
try {
|
|
6188
|
+
const handle = fs17.openSync(file, "r");
|
|
6189
|
+
try {
|
|
6190
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
6191
|
+
const read = fs17.readSync(handle, buffer, 0, buffer.byteLength, 0);
|
|
6192
|
+
head = buffer.subarray(0, read).toString("utf8");
|
|
6193
|
+
} finally {
|
|
6194
|
+
fs17.closeSync(handle);
|
|
6195
|
+
}
|
|
6196
|
+
} catch {
|
|
6197
|
+
continue;
|
|
6198
|
+
}
|
|
6199
|
+
for (const line of head.split(/\r?\n/)) {
|
|
6200
|
+
if (line.length === 0)
|
|
6201
|
+
continue;
|
|
6202
|
+
try {
|
|
6203
|
+
const record = JSON.parse(line);
|
|
6204
|
+
const cwd = record.cwd;
|
|
6205
|
+
if (typeof cwd === "string" && cwd.length > 0)
|
|
6206
|
+
return cwd;
|
|
6207
|
+
} catch {}
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
return;
|
|
6211
|
+
}
|
|
6212
|
+
var CONVERSATION_ID;
|
|
6213
|
+
var init_claude_store = __esm(() => {
|
|
6214
|
+
CONVERSATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6215
|
+
});
|
|
6216
|
+
|
|
6217
|
+
// packages/daemon/src/injector.ts
|
|
6218
|
+
import { setTimeout as delay2 } from "timers/promises";
|
|
6219
|
+
|
|
6220
|
+
class Injector {
|
|
6221
|
+
queues = new Map;
|
|
6222
|
+
enqueue(sessionId, task) {
|
|
6223
|
+
const previous = this.queues.get(sessionId) ?? Promise.resolve();
|
|
6224
|
+
const next2 = previous.then(task, task);
|
|
6225
|
+
this.queues.set(sessionId, next2);
|
|
6226
|
+
return next2;
|
|
6227
|
+
}
|
|
6228
|
+
prompt(session, text) {
|
|
6229
|
+
return this.enqueue(session.info.id, async () => {
|
|
6230
|
+
try {
|
|
6231
|
+
const deadline = Date.now() + 5000;
|
|
6232
|
+
while (!session.pasteReady && !session.isExited && Date.now() < deadline) {
|
|
6233
|
+
await delay2(100);
|
|
6234
|
+
}
|
|
6235
|
+
session.write(CTRL_U);
|
|
6236
|
+
await delay2(CLEAR_SETTLE_MS);
|
|
6237
|
+
for (const chunk of iteratePasteChunks(text)) {
|
|
6238
|
+
session.write(chunk);
|
|
6239
|
+
}
|
|
6240
|
+
await delay2(SUBMIT_DELAY_MS);
|
|
6241
|
+
session.write(SUBMIT);
|
|
6242
|
+
} catch (err) {
|
|
6243
|
+
session.write(BRACKETED_PASTE_END);
|
|
6244
|
+
log(`prompt injection failed for ${session.info.shortId}: ${String(err)}`);
|
|
6245
|
+
throw err;
|
|
6246
|
+
}
|
|
6247
|
+
});
|
|
6248
|
+
}
|
|
6249
|
+
interrupt(session) {
|
|
6250
|
+
return this.enqueue(session.info.id, async () => {
|
|
6251
|
+
session.write(ESC);
|
|
6252
|
+
await delay2(500);
|
|
6253
|
+
});
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6256
|
+
var CLEAR_SETTLE_MS = 150;
|
|
6257
|
+
var init_injector = __esm(() => {
|
|
6258
|
+
init_src();
|
|
6259
|
+
init_paths();
|
|
6260
|
+
});
|
|
6261
|
+
|
|
6262
|
+
// packages/daemon/src/pty-host-client.ts
|
|
6263
|
+
import net3 from "net";
|
|
6264
|
+
import { StringDecoder } from "string_decoder";
|
|
6265
|
+
|
|
6266
|
+
class RemotePty {
|
|
6267
|
+
id;
|
|
6268
|
+
pid;
|
|
6269
|
+
send;
|
|
6270
|
+
dataListeners = [];
|
|
6271
|
+
exitListeners = [];
|
|
6272
|
+
decoder = new StringDecoder("utf8");
|
|
6273
|
+
buffered = "";
|
|
6274
|
+
exitCode;
|
|
6275
|
+
exited = false;
|
|
6276
|
+
constructor(id, pid, send) {
|
|
6277
|
+
this.id = id;
|
|
6278
|
+
this.pid = pid;
|
|
6279
|
+
this.send = send;
|
|
6280
|
+
}
|
|
6281
|
+
emit(text) {
|
|
6282
|
+
if (text.length === 0)
|
|
6283
|
+
return;
|
|
6284
|
+
if (this.dataListeners.length === 0) {
|
|
6285
|
+
this.buffered += text;
|
|
6286
|
+
return;
|
|
6287
|
+
}
|
|
6288
|
+
for (const listener of this.dataListeners)
|
|
6289
|
+
listener(text);
|
|
6290
|
+
}
|
|
6291
|
+
push(payload) {
|
|
6292
|
+
this.emit(this.decoder.write(Buffer.from(payload)));
|
|
6293
|
+
}
|
|
6294
|
+
finish(code) {
|
|
6295
|
+
this.emit(this.decoder.end());
|
|
6296
|
+
this.exited = true;
|
|
6297
|
+
this.exitCode = code;
|
|
6298
|
+
for (const listener of this.exitListeners)
|
|
6299
|
+
listener(code);
|
|
6300
|
+
}
|
|
6301
|
+
onData(listener) {
|
|
6302
|
+
this.dataListeners.push(listener);
|
|
6303
|
+
if (this.buffered.length > 0) {
|
|
6304
|
+
const held = this.buffered;
|
|
6305
|
+
this.buffered = "";
|
|
6306
|
+
listener(held);
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
onExit(listener) {
|
|
6310
|
+
this.exitListeners.push(listener);
|
|
6311
|
+
if (this.exited)
|
|
6312
|
+
listener(this.exitCode ?? null);
|
|
6313
|
+
}
|
|
6314
|
+
write(data) {
|
|
6315
|
+
this.send(encodeFrame(FrameKind.Data, this.id, new TextEncoder().encode(data)));
|
|
6316
|
+
}
|
|
6317
|
+
resize(cols, rows) {
|
|
6318
|
+
this.send(controlFrame({ t: "resize", id: this.id, cols, rows }));
|
|
6319
|
+
}
|
|
6320
|
+
kill() {
|
|
6321
|
+
if (this.exited)
|
|
6322
|
+
return;
|
|
6323
|
+
this.send(controlFrame({ t: "kill", id: this.id }));
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
function connectPtyHost(options) {
|
|
6327
|
+
return new Promise((resolve, reject) => {
|
|
6328
|
+
const socket = net3.createConnection(options.endpoint);
|
|
6329
|
+
const decoder = new FrameDecoder;
|
|
6330
|
+
const ptys = new Map;
|
|
6331
|
+
const pending = new Map;
|
|
6332
|
+
const subscriptions = new Map;
|
|
6333
|
+
const dataListeners = [];
|
|
6334
|
+
const rawDecoders = new Map;
|
|
6335
|
+
let welcomed = false;
|
|
6336
|
+
const send = (frame) => {
|
|
6337
|
+
if (!socket.destroyed)
|
|
6338
|
+
socket.write(frame);
|
|
6339
|
+
};
|
|
6340
|
+
const fail = (error) => {
|
|
6341
|
+
if (!welcomed)
|
|
6342
|
+
reject(error);
|
|
6343
|
+
for (const waiter of pending.values())
|
|
6344
|
+
waiter.reject(error);
|
|
6345
|
+
pending.clear();
|
|
6346
|
+
for (const waiter of subscriptions.values())
|
|
6347
|
+
waiter.reject(error);
|
|
6348
|
+
subscriptions.clear();
|
|
6349
|
+
socket.destroy();
|
|
6350
|
+
};
|
|
6351
|
+
const timer = setTimeout(() => fail(new Error(`pty host did not greet within ${options.timeoutMs ?? 5000}ms`)), options.timeoutMs ?? 5000);
|
|
6352
|
+
timer.unref?.();
|
|
6353
|
+
socket.on("error", (err) => fail(err instanceof Error ? err : new Error(String(err))));
|
|
6354
|
+
socket.on("close", () => {
|
|
6355
|
+
clearTimeout(timer);
|
|
6356
|
+
if (!welcomed)
|
|
6357
|
+
reject(new Error("pty host refused the connection"));
|
|
6358
|
+
});
|
|
6359
|
+
socket.on("connect", () => send(controlFrame({ t: "hello", v: PTY_PROTOCOL_VERSION, token: options.token })));
|
|
6360
|
+
socket.on("data", (chunk) => {
|
|
6361
|
+
let frames;
|
|
6362
|
+
try {
|
|
6363
|
+
frames = decoder.push(new Uint8Array(chunk));
|
|
6364
|
+
} catch (err) {
|
|
6365
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
6366
|
+
return;
|
|
6367
|
+
}
|
|
6368
|
+
for (const frame of frames) {
|
|
6369
|
+
if (frame.kind === FrameKind.Data) {
|
|
6370
|
+
ptys.get(frame.id)?.push(frame.payload);
|
|
6371
|
+
if (dataListeners.length > 0) {
|
|
6372
|
+
let raw = rawDecoders.get(frame.id);
|
|
6373
|
+
if (raw === undefined) {
|
|
6374
|
+
raw = new StringDecoder("utf8");
|
|
6375
|
+
rawDecoders.set(frame.id, raw);
|
|
6376
|
+
}
|
|
6377
|
+
const text = raw.write(Buffer.from(frame.payload));
|
|
6378
|
+
if (text.length > 0)
|
|
6379
|
+
for (const listener of dataListeners)
|
|
6380
|
+
listener(frame.id, text);
|
|
6381
|
+
}
|
|
6382
|
+
continue;
|
|
6383
|
+
}
|
|
6384
|
+
const message = parseControl(frame.payload);
|
|
6385
|
+
if (message === undefined)
|
|
6386
|
+
continue;
|
|
6387
|
+
if (message.t === "welcome") {
|
|
6388
|
+
clearTimeout(timer);
|
|
6389
|
+
welcomed = true;
|
|
6390
|
+
resolve({
|
|
6391
|
+
protocolVersion: message.v,
|
|
6392
|
+
sessions: message.sessions,
|
|
6393
|
+
spawn: (spawnOptions) => new Promise((done, failed) => {
|
|
6394
|
+
const id = spawnOptions.id ?? crypto.randomUUID().replace(/-/g, "");
|
|
6395
|
+
pending.set(id, {
|
|
6396
|
+
resolve: (pid) => {
|
|
6397
|
+
const pty = new RemotePty(id, pid, send);
|
|
6398
|
+
ptys.set(id, pty);
|
|
6399
|
+
done(pty);
|
|
6400
|
+
},
|
|
6401
|
+
reject: failed
|
|
6402
|
+
});
|
|
6403
|
+
send(controlFrame({
|
|
6404
|
+
t: "spawn",
|
|
6405
|
+
id,
|
|
6406
|
+
exe: spawnOptions.exe,
|
|
6407
|
+
argv: spawnOptions.argv,
|
|
6408
|
+
cwd: spawnOptions.cwd,
|
|
6409
|
+
env: Object.fromEntries(Object.entries(spawnOptions.env).filter((entry) => entry[1] !== undefined)),
|
|
6410
|
+
cols: spawnOptions.cols,
|
|
6411
|
+
rows: spawnOptions.rows,
|
|
6412
|
+
term: spawnOptions.term
|
|
6413
|
+
}));
|
|
6414
|
+
}),
|
|
6415
|
+
adopt: (session) => new Promise((done, failed) => {
|
|
6416
|
+
const pty = ptys.get(session.id) ?? new RemotePty(session.id, session.pid, send);
|
|
6417
|
+
ptys.set(session.id, pty);
|
|
6418
|
+
subscriptions.set(session.id, {
|
|
6419
|
+
resolve: () => done(pty),
|
|
6420
|
+
reject: failed
|
|
6421
|
+
});
|
|
6422
|
+
send(controlFrame({ t: "subscribe", id: session.id, replay: true }));
|
|
6423
|
+
}),
|
|
6424
|
+
onData: (listener) => dataListeners.push(listener),
|
|
6425
|
+
close: () => socket.destroy()
|
|
6426
|
+
});
|
|
6427
|
+
continue;
|
|
6428
|
+
}
|
|
6429
|
+
if (message.t === "spawned") {
|
|
6430
|
+
pending.get(message.id)?.resolve(message.pid);
|
|
6431
|
+
pending.delete(message.id);
|
|
6432
|
+
continue;
|
|
6433
|
+
}
|
|
6434
|
+
if (message.t === "error") {
|
|
6435
|
+
pending.get(message.id)?.reject(new Error(message.message));
|
|
6436
|
+
pending.delete(message.id);
|
|
6437
|
+
continue;
|
|
6438
|
+
}
|
|
6439
|
+
if (message.t === "subscribed") {
|
|
6440
|
+
subscriptions.get(message.id)?.resolve();
|
|
6441
|
+
subscriptions.delete(message.id);
|
|
6442
|
+
continue;
|
|
6443
|
+
}
|
|
6444
|
+
if (message.t === "exit") {
|
|
6445
|
+
const pty = ptys.get(message.id);
|
|
6446
|
+
ptys.delete(message.id);
|
|
6447
|
+
rawDecoders.delete(message.id);
|
|
6448
|
+
subscriptions.get(message.id)?.reject(new Error(`session ${message.id} exited`));
|
|
6449
|
+
subscriptions.delete(message.id);
|
|
6450
|
+
pty?.finish(message.code);
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
});
|
|
6454
|
+
});
|
|
6455
|
+
}
|
|
6456
|
+
var init_pty_host_client = __esm(() => {
|
|
6457
|
+
init_src();
|
|
6458
|
+
});
|
|
6459
|
+
|
|
6460
|
+
// packages/daemon/src/pty-host-launch.ts
|
|
6461
|
+
import { spawn as spawn3 } from "child_process";
|
|
6462
|
+
import fs18 from "fs";
|
|
6463
|
+
import os5 from "os";
|
|
6464
|
+
import path17 from "path";
|
|
6465
|
+
function hostFile(runtimeDir) {
|
|
6466
|
+
return path17.join(runtimeDir, "pty-host.json");
|
|
6467
|
+
}
|
|
6468
|
+
function readPtyHostIn(runtimeDir) {
|
|
6469
|
+
return readPtyHostFile(hostFile(runtimeDir));
|
|
6470
|
+
}
|
|
6471
|
+
function readPtyHostFile(file) {
|
|
6472
|
+
try {
|
|
6473
|
+
const parsed = JSON.parse(fs18.readFileSync(file, "utf8"));
|
|
6474
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
6475
|
+
return;
|
|
6476
|
+
const { pid, endpoint, token, version: version2 } = parsed;
|
|
6477
|
+
if (typeof pid !== "number" || typeof endpoint !== "string" || typeof token !== "string")
|
|
6478
|
+
return;
|
|
6479
|
+
return { pid, endpoint, token, version: typeof version2 === "number" ? version2 : PTY_PROTOCOL_VERSION };
|
|
6480
|
+
} catch {
|
|
6481
|
+
return;
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
function isRunning(pid) {
|
|
6485
|
+
try {
|
|
6486
|
+
process.kill(pid, 0);
|
|
6487
|
+
return true;
|
|
6488
|
+
} catch (err) {
|
|
6489
|
+
return err.code === "EPERM";
|
|
6490
|
+
}
|
|
6491
|
+
}
|
|
6492
|
+
function hostEndpoint(runtimeDir) {
|
|
6493
|
+
const base = ipcEndpoint(process.platform, os5.userInfo().username, { runtimeDir });
|
|
6494
|
+
return process.platform === "win32" ? `${base}-pty` : path17.join(runtimeDir, "axterm-pty.sock");
|
|
6495
|
+
}
|
|
6496
|
+
async function ensurePtyHost(location) {
|
|
6497
|
+
const file = hostFile(location.runtimeDir);
|
|
6498
|
+
const existing = readPtyHostFile(file);
|
|
6499
|
+
if (existing !== undefined && isRunning(existing.pid))
|
|
6500
|
+
return existing;
|
|
6501
|
+
if (existing !== undefined)
|
|
6502
|
+
log(`pty host: handoff file names a dead pid ${existing.pid}, starting a new host`);
|
|
6503
|
+
const endpoint = hostEndpoint(location.runtimeDir);
|
|
6504
|
+
if (process.platform !== "win32")
|
|
6505
|
+
fs18.rmSync(endpoint, { force: true });
|
|
6506
|
+
const token = shortHash(`${location.runtimeDir}:${Date.now()}:${Math.random()}`) + shortHash(String(process.pid));
|
|
6507
|
+
const launch = ptyHostLaunch({ execPath: process.execPath, mainScript: location.mainScript ?? Bun.main });
|
|
6508
|
+
const child = spawn3(launch.command, launch.args, {
|
|
6509
|
+
detached: true,
|
|
6510
|
+
stdio: "ignore",
|
|
6511
|
+
windowsHide: true,
|
|
6512
|
+
env: {
|
|
6513
|
+
...process.env,
|
|
6514
|
+
...location.env,
|
|
6515
|
+
AXTERM_PTY_HOST_ENDPOINT: endpoint,
|
|
6516
|
+
AXTERM_PTY_HOST_TOKEN: token,
|
|
6517
|
+
AXTERM_PTY_HOST_FILE: file
|
|
6518
|
+
}
|
|
6519
|
+
});
|
|
6520
|
+
child.unref();
|
|
6521
|
+
if (child.pid === undefined)
|
|
6522
|
+
throw new Error("could not start the terminal host");
|
|
6523
|
+
const deadline = Date.now() + 15000;
|
|
6524
|
+
for (;; ) {
|
|
6525
|
+
const info = readPtyHostFile(file);
|
|
6526
|
+
if (info !== undefined && info.endpoint === endpoint && isRunning(info.pid))
|
|
6527
|
+
return info;
|
|
6528
|
+
if (Date.now() > deadline)
|
|
6529
|
+
throw new Error(`the terminal host did not become ready within 15s (${endpoint})`);
|
|
6530
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
function writePtyHostFile(file, info) {
|
|
6534
|
+
fs18.mkdirSync(path17.dirname(file), { recursive: true, mode: 448 });
|
|
6535
|
+
fs18.writeFileSync(file, `${JSON.stringify(info, null, 2)}
|
|
6536
|
+
`, { mode: 384 });
|
|
6537
|
+
restrictFile(file);
|
|
6538
|
+
}
|
|
6539
|
+
var init_pty_host_launch = __esm(() => {
|
|
6540
|
+
init_src();
|
|
6541
|
+
init_paths();
|
|
6542
|
+
init_shell();
|
|
6543
|
+
});
|
|
6544
|
+
|
|
6545
|
+
// packages/daemon/src/pty.ts
|
|
6546
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
6547
|
+
import path18 from "path";
|
|
6548
|
+
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
6549
|
+
|
|
6550
|
+
class Fanout {
|
|
6551
|
+
listeners = new Set;
|
|
6552
|
+
backlog = [];
|
|
6553
|
+
add(listener) {
|
|
6554
|
+
this.listeners.add(listener);
|
|
6555
|
+
for (const value of this.backlog)
|
|
6556
|
+
listener(value);
|
|
6557
|
+
}
|
|
6558
|
+
emit(value) {
|
|
6559
|
+
if (this.listeners.size === 0)
|
|
6560
|
+
this.backlog.push(value);
|
|
6561
|
+
else
|
|
6562
|
+
for (const listener of this.listeners)
|
|
6563
|
+
listener(value);
|
|
6564
|
+
}
|
|
6565
|
+
}
|
|
6566
|
+
function killTree(pid, exeName) {
|
|
6567
|
+
if (process.platform === "win32") {
|
|
6568
|
+
const check = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
|
|
6569
|
+
encoding: "utf8",
|
|
6570
|
+
windowsHide: true
|
|
6571
|
+
});
|
|
6572
|
+
const image = check.stdout?.split(",")[0]?.replace(/"/g, "").toLowerCase() ?? "";
|
|
6573
|
+
if (image === exeName) {
|
|
6574
|
+
spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
|
|
6575
|
+
}
|
|
6576
|
+
return;
|
|
6577
|
+
}
|
|
6578
|
+
try {
|
|
6579
|
+
process.kill(-pid, "SIGKILL");
|
|
6580
|
+
} catch {}
|
|
5831
6581
|
}
|
|
5832
|
-
var UNTRACKED_LINE_SCAN_LIMIT;
|
|
5833
|
-
var init_git = __esm(() => {
|
|
5834
|
-
init_paths();
|
|
5835
|
-
UNTRACKED_LINE_SCAN_LIMIT = 1024 * 1024;
|
|
5836
|
-
});
|
|
5837
6582
|
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
this.
|
|
5847
|
-
|
|
6583
|
+
class BunPtyHandle {
|
|
6584
|
+
proc;
|
|
6585
|
+
exeName;
|
|
6586
|
+
data = new Fanout;
|
|
6587
|
+
exit = new Fanout;
|
|
6588
|
+
decoder = new StringDecoder2("utf8");
|
|
6589
|
+
exited = false;
|
|
6590
|
+
constructor(proc, exeName) {
|
|
6591
|
+
this.proc = proc;
|
|
6592
|
+
this.exeName = exeName;
|
|
5848
6593
|
}
|
|
5849
|
-
|
|
5850
|
-
return this.
|
|
5851
|
-
try {
|
|
5852
|
-
const deadline = Date.now() + 5000;
|
|
5853
|
-
while (!session.pasteReady && !session.isExited && Date.now() < deadline) {
|
|
5854
|
-
await delay2(100);
|
|
5855
|
-
}
|
|
5856
|
-
session.write(CTRL_U);
|
|
5857
|
-
await delay2(CLEAR_SETTLE_MS);
|
|
5858
|
-
for (const chunk of iteratePasteChunks(text)) {
|
|
5859
|
-
session.write(chunk);
|
|
5860
|
-
}
|
|
5861
|
-
await delay2(SUBMIT_DELAY_MS);
|
|
5862
|
-
session.write(SUBMIT);
|
|
5863
|
-
} catch (err) {
|
|
5864
|
-
session.write(BRACKETED_PASTE_END);
|
|
5865
|
-
log(`prompt injection failed for ${session.info.shortId}: ${String(err)}`);
|
|
5866
|
-
throw err;
|
|
5867
|
-
}
|
|
5868
|
-
});
|
|
6594
|
+
get pid() {
|
|
6595
|
+
return this.proc.pid;
|
|
5869
6596
|
}
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
6597
|
+
push(bytes) {
|
|
6598
|
+
const text = this.decoder.write(Buffer.from(bytes));
|
|
6599
|
+
if (text.length > 0)
|
|
6600
|
+
this.data.emit(text);
|
|
6601
|
+
}
|
|
6602
|
+
finish(code) {
|
|
6603
|
+
this.exited = true;
|
|
6604
|
+
const tail = this.decoder.end();
|
|
6605
|
+
if (tail.length > 0)
|
|
6606
|
+
this.data.emit(tail);
|
|
6607
|
+
try {
|
|
6608
|
+
this.proc.terminal.close();
|
|
6609
|
+
} catch {}
|
|
6610
|
+
this.exit.emit(code);
|
|
6611
|
+
}
|
|
6612
|
+
onData(listener) {
|
|
6613
|
+
this.data.add(listener);
|
|
6614
|
+
}
|
|
6615
|
+
onExit(listener) {
|
|
6616
|
+
this.exit.add(listener);
|
|
6617
|
+
}
|
|
6618
|
+
write(data) {
|
|
6619
|
+
this.proc.terminal.write(data);
|
|
6620
|
+
}
|
|
6621
|
+
resize(cols, rows) {
|
|
6622
|
+
this.proc.terminal.resize(cols, rows);
|
|
6623
|
+
}
|
|
6624
|
+
kill() {
|
|
6625
|
+
if (this.exited)
|
|
6626
|
+
return;
|
|
6627
|
+
killTree(this.proc.pid, this.exeName);
|
|
6628
|
+
try {
|
|
6629
|
+
this.proc.kill();
|
|
6630
|
+
} catch {}
|
|
5875
6631
|
}
|
|
5876
6632
|
}
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
}
|
|
6633
|
+
function spawnPty(options) {
|
|
6634
|
+
let handle;
|
|
6635
|
+
const proc = Bun.spawn([options.exe, ...options.argv], {
|
|
6636
|
+
cwd: options.cwd,
|
|
6637
|
+
env: { ...options.env, TERM: options.term },
|
|
6638
|
+
terminal: {
|
|
6639
|
+
cols: options.cols,
|
|
6640
|
+
rows: options.rows,
|
|
6641
|
+
name: options.term,
|
|
6642
|
+
data(_terminal, bytes) {
|
|
6643
|
+
handle?.push(bytes);
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
});
|
|
6647
|
+
handle = new BunPtyHandle(proc, path18.basename(options.exe).toLowerCase());
|
|
6648
|
+
proc.exited.then((code) => handle?.finish(code ?? null));
|
|
6649
|
+
return handle;
|
|
6650
|
+
}
|
|
6651
|
+
var init_pty = () => {};
|
|
5882
6652
|
|
|
5883
6653
|
// packages/daemon/src/occupancy.ts
|
|
5884
|
-
import { spawn as
|
|
5885
|
-
import
|
|
6654
|
+
import { spawn as spawn4 } from "child_process";
|
|
6655
|
+
import fs19 from "fs/promises";
|
|
5886
6656
|
function shellOccupied(shellPid) {
|
|
5887
6657
|
return process.platform === "win32" ? occupiedFromSnapshot(shellPid) : occupiedFromPidProbe(shellPid);
|
|
5888
6658
|
}
|
|
@@ -5927,11 +6697,11 @@ async function occupiedFromSnapshot(shellPid) {
|
|
|
5927
6697
|
}
|
|
5928
6698
|
async function childrenViaProc(pid) {
|
|
5929
6699
|
try {
|
|
5930
|
-
const children = await
|
|
6700
|
+
const children = await fs19.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
|
|
5931
6701
|
return children.trim().length > 0;
|
|
5932
6702
|
} catch {
|
|
5933
6703
|
try {
|
|
5934
|
-
await
|
|
6704
|
+
await fs19.access(`/proc/${pid}`);
|
|
5935
6705
|
return;
|
|
5936
6706
|
} catch {
|
|
5937
6707
|
return false;
|
|
@@ -5978,7 +6748,7 @@ async function processTable() {
|
|
|
5978
6748
|
}
|
|
5979
6749
|
function capture(exe, argv) {
|
|
5980
6750
|
const { promise, resolve } = Promise.withResolvers();
|
|
5981
|
-
const child =
|
|
6751
|
+
const child = spawn4(exe, argv, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
|
|
5982
6752
|
let stdout3 = "";
|
|
5983
6753
|
child.stdout.setEncoding("utf8");
|
|
5984
6754
|
child.stdout.on("data", (chunk) => {
|
|
@@ -5995,20 +6765,20 @@ var init_occupancy = __esm(() => {
|
|
|
5995
6765
|
});
|
|
5996
6766
|
|
|
5997
6767
|
// packages/daemon/src/agent-store.ts
|
|
5998
|
-
import
|
|
5999
|
-
import
|
|
6000
|
-
import
|
|
6768
|
+
import fs20 from "fs";
|
|
6769
|
+
import os6 from "os";
|
|
6770
|
+
import path19 from "path";
|
|
6001
6771
|
import { Database as Database3 } from "bun:sqlite";
|
|
6002
6772
|
function readTailLines(file, maxBytes) {
|
|
6003
6773
|
let fd;
|
|
6004
6774
|
try {
|
|
6005
|
-
fd =
|
|
6006
|
-
const size =
|
|
6775
|
+
fd = fs20.openSync(file, "r");
|
|
6776
|
+
const size = fs20.fstatSync(fd).size;
|
|
6007
6777
|
if (size === 0)
|
|
6008
6778
|
return [];
|
|
6009
6779
|
const length = Math.min(size, maxBytes);
|
|
6010
6780
|
const buffer = Buffer.allocUnsafe(length);
|
|
6011
|
-
|
|
6781
|
+
fs20.readSync(fd, buffer, 0, length, size - length);
|
|
6012
6782
|
const text = buffer.toString("utf8");
|
|
6013
6783
|
const lines = text.split(`
|
|
6014
6784
|
`);
|
|
@@ -6021,46 +6791,46 @@ function readTailLines(file, maxBytes) {
|
|
|
6021
6791
|
return [];
|
|
6022
6792
|
} finally {
|
|
6023
6793
|
if (fd !== undefined)
|
|
6024
|
-
|
|
6794
|
+
fs20.closeSync(fd);
|
|
6025
6795
|
}
|
|
6026
6796
|
}
|
|
6027
|
-
function
|
|
6028
|
-
return process.env["CLAUDE_CONFIG_DIR"] ??
|
|
6797
|
+
function claudeConfigDir2() {
|
|
6798
|
+
return process.env["CLAUDE_CONFIG_DIR"] ?? path19.join(os6.homedir(), ".claude");
|
|
6029
6799
|
}
|
|
6030
6800
|
function codexHome() {
|
|
6031
|
-
return
|
|
6801
|
+
return path19.join(STATE_DIR, "codex-home");
|
|
6032
6802
|
}
|
|
6033
6803
|
function ompAgentDb() {
|
|
6034
|
-
return
|
|
6804
|
+
return path19.join(os6.homedir(), ".omp", "agent", "agent.db");
|
|
6035
6805
|
}
|
|
6036
6806
|
function findCodexRollout(sessionId) {
|
|
6037
|
-
const root =
|
|
6807
|
+
const root = path19.join(codexHome(), "sessions");
|
|
6038
6808
|
for (const shard2 of codexRolloutShards(sessionId)) {
|
|
6039
|
-
const dir =
|
|
6809
|
+
const dir = path19.join(root, ...shard2.split("/"));
|
|
6040
6810
|
let entries;
|
|
6041
6811
|
try {
|
|
6042
|
-
entries =
|
|
6812
|
+
entries = fs20.readdirSync(dir);
|
|
6043
6813
|
} catch {
|
|
6044
6814
|
continue;
|
|
6045
6815
|
}
|
|
6046
6816
|
const match2 = entries.find((name) => name.endsWith(`-${sessionId}.jsonl`));
|
|
6047
6817
|
if (match2 !== undefined)
|
|
6048
|
-
return
|
|
6818
|
+
return path19.join(dir, match2);
|
|
6049
6819
|
}
|
|
6050
6820
|
return;
|
|
6051
6821
|
}
|
|
6052
6822
|
function newestCodexRollout() {
|
|
6053
|
-
const root =
|
|
6823
|
+
const root = path19.join(codexHome(), "sessions");
|
|
6054
6824
|
let best;
|
|
6055
6825
|
const walk = (dir, depth) => {
|
|
6056
6826
|
let entries;
|
|
6057
6827
|
try {
|
|
6058
|
-
entries =
|
|
6828
|
+
entries = fs20.readdirSync(dir, { withFileTypes: true });
|
|
6059
6829
|
} catch {
|
|
6060
6830
|
return;
|
|
6061
6831
|
}
|
|
6062
6832
|
for (const entry of entries) {
|
|
6063
|
-
const full =
|
|
6833
|
+
const full = path19.join(dir, entry.name);
|
|
6064
6834
|
if (entry.isDirectory()) {
|
|
6065
6835
|
if (depth < 3)
|
|
6066
6836
|
walk(full, depth + 1);
|
|
@@ -6069,7 +6839,7 @@ function newestCodexRollout() {
|
|
|
6069
6839
|
if (!entry.name.endsWith(".jsonl"))
|
|
6070
6840
|
continue;
|
|
6071
6841
|
try {
|
|
6072
|
-
const { mtimeMs } =
|
|
6842
|
+
const { mtimeMs } = fs20.statSync(full);
|
|
6073
6843
|
if (!best || mtimeMs > best.mtimeMs)
|
|
6074
6844
|
best = { file: full, mtimeMs };
|
|
6075
6845
|
} catch {}
|
|
@@ -6080,7 +6850,7 @@ function newestCodexRollout() {
|
|
|
6080
6850
|
}
|
|
6081
6851
|
function ompQuotaFacts() {
|
|
6082
6852
|
const file = ompAgentDb();
|
|
6083
|
-
if (!
|
|
6853
|
+
if (!fs20.existsSync(file))
|
|
6084
6854
|
return { agent: "omp", kind: "gauge" };
|
|
6085
6855
|
let db;
|
|
6086
6856
|
try {
|
|
@@ -6107,12 +6877,12 @@ function codexQuotaFacts() {
|
|
|
6107
6877
|
return reading ? { agent: "codex", kind: "gauge", reading } : { agent: "codex", kind: "gauge" };
|
|
6108
6878
|
}
|
|
6109
6879
|
function claudeQuotaFacts(sessions) {
|
|
6110
|
-
const dir =
|
|
6880
|
+
const dir = claudeConfigDir2();
|
|
6111
6881
|
let newest;
|
|
6112
6882
|
for (const session of sessions) {
|
|
6113
6883
|
if (session.agentSessionId === undefined)
|
|
6114
6884
|
continue;
|
|
6115
|
-
const file =
|
|
6885
|
+
const file = path19.join(dir, claudeTranscriptRelPath(session.cwd, session.agentSessionId));
|
|
6116
6886
|
const event = parseClaudeLimitEvent(readTailLines(file, QUOTA_TAIL_BYTES));
|
|
6117
6887
|
if (event && (!newest || event.at > newest.at))
|
|
6118
6888
|
newest = event;
|
|
@@ -6144,11 +6914,11 @@ function collectQuotaFacts(agents, sessions, onPath) {
|
|
|
6144
6914
|
function storeQuietForMs(agent, cwd, agentSessionId, nowMs) {
|
|
6145
6915
|
if (agentSessionId === undefined)
|
|
6146
6916
|
return;
|
|
6147
|
-
const file = agent === "claude" ?
|
|
6917
|
+
const file = agent === "claude" ? path19.join(claudeConfigDir2(), claudeTranscriptRelPath(cwd, agentSessionId)) : agent === "codex" ? findCodexRollout(agentSessionId) : undefined;
|
|
6148
6918
|
if (file === undefined)
|
|
6149
6919
|
return;
|
|
6150
6920
|
try {
|
|
6151
|
-
return Math.max(0, nowMs -
|
|
6921
|
+
return Math.max(0, nowMs - fs20.statSync(file).mtimeMs);
|
|
6152
6922
|
} catch {
|
|
6153
6923
|
return;
|
|
6154
6924
|
}
|
|
@@ -6158,15 +6928,15 @@ function readFileRange(file, from, to) {
|
|
|
6158
6928
|
return "";
|
|
6159
6929
|
let fd;
|
|
6160
6930
|
try {
|
|
6161
|
-
fd =
|
|
6931
|
+
fd = fs20.openSync(file, "r");
|
|
6162
6932
|
const buffer = Buffer.allocUnsafe(to - from);
|
|
6163
|
-
const read =
|
|
6933
|
+
const read = fs20.readSync(fd, buffer, 0, to - from, from);
|
|
6164
6934
|
return buffer.subarray(0, read).toString("utf8");
|
|
6165
6935
|
} catch {
|
|
6166
6936
|
return "";
|
|
6167
6937
|
} finally {
|
|
6168
6938
|
if (fd !== undefined)
|
|
6169
|
-
|
|
6939
|
+
fs20.closeSync(fd);
|
|
6170
6940
|
}
|
|
6171
6941
|
}
|
|
6172
6942
|
var QUOTA_TAIL_BYTES;
|
|
@@ -6177,10 +6947,10 @@ var init_agent_store = __esm(() => {
|
|
|
6177
6947
|
});
|
|
6178
6948
|
|
|
6179
6949
|
// packages/daemon/src/codex-app-server.ts
|
|
6180
|
-
import
|
|
6950
|
+
import net4 from "net";
|
|
6181
6951
|
async function freeLoopbackPort() {
|
|
6182
6952
|
const { promise, resolve, reject } = Promise.withResolvers();
|
|
6183
|
-
const probe =
|
|
6953
|
+
const probe = net4.createServer();
|
|
6184
6954
|
probe.once("error", reject);
|
|
6185
6955
|
probe.listen({ host: "127.0.0.1", port: 0 }, () => {
|
|
6186
6956
|
const address = probe.address();
|
|
@@ -6193,7 +6963,7 @@ async function waitForPort(port, timeoutMs = READY_TIMEOUT_MS) {
|
|
|
6193
6963
|
const deadline = Date.now() + timeoutMs;
|
|
6194
6964
|
for (;; ) {
|
|
6195
6965
|
const open = await new Promise((resolve) => {
|
|
6196
|
-
const socket =
|
|
6966
|
+
const socket = net4.connect({ host: "127.0.0.1", port });
|
|
6197
6967
|
const done = (value) => {
|
|
6198
6968
|
socket.destroy();
|
|
6199
6969
|
resolve(value);
|
|
@@ -6288,114 +7058,6 @@ var init_codex_app_server = __esm(() => {
|
|
|
6288
7058
|
init_paths();
|
|
6289
7059
|
});
|
|
6290
7060
|
|
|
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
7061
|
// node_modules/.bun/@xterm+headless@5.5.0/node_modules/@xterm/headless/lib-headless/xterm-headless.js
|
|
6400
7062
|
var require_xterm_headless = __commonJS((exports) => {
|
|
6401
7063
|
(() => {
|
|
@@ -10725,15 +11387,55 @@ var require_addon_serialize = __commonJS((exports, module) => {
|
|
|
10725
11387
|
})());
|
|
10726
11388
|
});
|
|
10727
11389
|
|
|
11390
|
+
// packages/daemon/src/terminal-mirror.ts
|
|
11391
|
+
class TerminalMirror {
|
|
11392
|
+
scrollbackLines;
|
|
11393
|
+
term;
|
|
11394
|
+
serializeAddon;
|
|
11395
|
+
constructor(cols, rows, scrollbackLines) {
|
|
11396
|
+
this.scrollbackLines = scrollbackLines;
|
|
11397
|
+
this.term = new import_headless.Terminal({ cols, rows, scrollback: scrollbackLines, allowProposedApi: true });
|
|
11398
|
+
this.serializeAddon = new import_addon_serialize.SerializeAddon;
|
|
11399
|
+
this.term.loadAddon(this.serializeAddon);
|
|
11400
|
+
}
|
|
11401
|
+
write(data) {
|
|
11402
|
+
this.term.write(data);
|
|
11403
|
+
}
|
|
11404
|
+
resize(cols, rows) {
|
|
11405
|
+
this.term.resize(cols, rows);
|
|
11406
|
+
}
|
|
11407
|
+
serialize(maxChars = Number.POSITIVE_INFINITY) {
|
|
11408
|
+
const ansi = this.serializeAddon.serialize({ scrollback: this.scrollbackLines });
|
|
11409
|
+
return ansi.length > maxChars ? ansi.slice(-maxChars) : ansi;
|
|
11410
|
+
}
|
|
11411
|
+
text(maxLines) {
|
|
11412
|
+
const buffer = this.term.buffer.active;
|
|
11413
|
+
let last = buffer.length - 1;
|
|
11414
|
+
while (last >= 0 && (buffer.getLine(last)?.translateToString(true).trim() ?? "") === "")
|
|
11415
|
+
last--;
|
|
11416
|
+
const start = Math.max(0, last - maxLines + 1);
|
|
11417
|
+
const lines = [];
|
|
11418
|
+
for (let y = start;y <= last; y++) {
|
|
11419
|
+
lines.push(buffer.getLine(y)?.translateToString(true).trimEnd() ?? "");
|
|
11420
|
+
}
|
|
11421
|
+
return lines.join(`
|
|
11422
|
+
`);
|
|
11423
|
+
}
|
|
11424
|
+
}
|
|
11425
|
+
var import_headless, import_addon_serialize;
|
|
11426
|
+
var init_terminal_mirror = __esm(() => {
|
|
11427
|
+
import_headless = __toESM(require_xterm_headless(), 1);
|
|
11428
|
+
import_addon_serialize = __toESM(require_addon_serialize(), 1);
|
|
11429
|
+
});
|
|
11430
|
+
|
|
10728
11431
|
// packages/daemon/src/pty-session.ts
|
|
10729
|
-
import
|
|
10730
|
-
import
|
|
11432
|
+
import fs21 from "fs";
|
|
11433
|
+
import path20 from "path";
|
|
10731
11434
|
|
|
10732
11435
|
class PtySession {
|
|
10733
11436
|
info;
|
|
10734
11437
|
pty;
|
|
10735
|
-
|
|
10736
|
-
serializeAddon;
|
|
11438
|
+
mirror;
|
|
10737
11439
|
dataListeners = new Set;
|
|
10738
11440
|
exitListeners = new Set;
|
|
10739
11441
|
saveTimer;
|
|
@@ -10742,30 +11444,31 @@ class PtySession {
|
|
|
10742
11444
|
pasteReady = false;
|
|
10743
11445
|
readyScanTail = "";
|
|
10744
11446
|
lastOutputAt = Date.now();
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
this.pty = spawnPty({
|
|
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"
|
|
11447
|
+
static async open(info, spawn5, spawner = spawnPty) {
|
|
11448
|
+
const pty = await spawner({
|
|
11449
|
+
exe: spawn5.exe,
|
|
11450
|
+
argv: spawn5.argv,
|
|
11451
|
+
cwd: spawn5.cwd,
|
|
11452
|
+
cols: spawn5.cols,
|
|
11453
|
+
rows: spawn5.rows,
|
|
11454
|
+
env: spawn5.env,
|
|
11455
|
+
term: "xterm-256color",
|
|
11456
|
+
id: info.id
|
|
10763
11457
|
});
|
|
11458
|
+
return new PtySession(info, spawn5, pty);
|
|
11459
|
+
}
|
|
11460
|
+
static adopt(info, pty, cols, rows) {
|
|
11461
|
+
return new PtySession(info, { exe: "", argv: [], cwd: info.cwd, cols, rows, env: {} }, pty);
|
|
11462
|
+
}
|
|
11463
|
+
constructor(info, spawn5, pty) {
|
|
11464
|
+
this.info = info;
|
|
11465
|
+
this.mirror = new TerminalMirror(spawn5.cols, spawn5.rows, SCROLLBACK_LINES);
|
|
11466
|
+
this.pty = pty;
|
|
10764
11467
|
info.ptyPid = this.pty.pid;
|
|
10765
11468
|
this.pty.onData((data) => {
|
|
10766
11469
|
this.seq += 1;
|
|
10767
11470
|
this.lastOutputAt = Date.now();
|
|
10768
|
-
this.
|
|
11471
|
+
this.mirror.write(data);
|
|
10769
11472
|
if (!this.pasteReady) {
|
|
10770
11473
|
const window2 = this.readyScanTail + data;
|
|
10771
11474
|
if (window2.includes("\x1B[?2004h"))
|
|
@@ -10810,27 +11513,16 @@ class PtySession {
|
|
|
10810
11513
|
this.info.rows = rows;
|
|
10811
11514
|
try {
|
|
10812
11515
|
this.pty.resize(cols, rows);
|
|
10813
|
-
this.
|
|
11516
|
+
this.mirror.resize(cols, rows);
|
|
10814
11517
|
} catch (err) {
|
|
10815
11518
|
log(`resize failed for ${this.info.shortId}: ${String(err)}`);
|
|
10816
11519
|
}
|
|
10817
11520
|
}
|
|
10818
11521
|
screenText(maxLines = 60) {
|
|
10819
|
-
|
|
10820
|
-
let last = buffer.length - 1;
|
|
10821
|
-
while (last >= 0 && (buffer.getLine(last)?.translateToString(true).trim() ?? "") === "")
|
|
10822
|
-
last--;
|
|
10823
|
-
const start = Math.max(0, last - maxLines + 1);
|
|
10824
|
-
const lines = [];
|
|
10825
|
-
for (let y = start;y <= last; y++) {
|
|
10826
|
-
lines.push(buffer.getLine(y)?.translateToString(true).trimEnd() ?? "");
|
|
10827
|
-
}
|
|
10828
|
-
return lines.join(`
|
|
10829
|
-
`);
|
|
11522
|
+
return this.mirror.text(maxLines);
|
|
10830
11523
|
}
|
|
10831
11524
|
serializeBuffer() {
|
|
10832
|
-
|
|
10833
|
-
return ansi.length > SCROLLBACK_MAX_CHARS ? ansi.slice(-SCROLLBACK_MAX_CHARS) : ansi;
|
|
11525
|
+
return this.mirror.serialize(SCROLLBACK_MAX_CHARS);
|
|
10834
11526
|
}
|
|
10835
11527
|
scheduleSave() {
|
|
10836
11528
|
if (this.exited || this.saveTimer)
|
|
@@ -10843,10 +11535,10 @@ class PtySession {
|
|
|
10843
11535
|
}
|
|
10844
11536
|
saveScrollback() {
|
|
10845
11537
|
try {
|
|
10846
|
-
const file =
|
|
11538
|
+
const file = path20.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
|
|
10847
11539
|
const tmp = `${file}.tmp`;
|
|
10848
|
-
|
|
10849
|
-
|
|
11540
|
+
fs21.writeFileSync(tmp, this.serializeBuffer(), "utf8");
|
|
11541
|
+
fs21.renameSync(tmp, file);
|
|
10850
11542
|
} catch (err) {
|
|
10851
11543
|
log(`scrollback save failed for ${this.info.shortId}: ${String(err)}`);
|
|
10852
11544
|
}
|
|
@@ -10857,20 +11549,23 @@ class PtySession {
|
|
|
10857
11549
|
this.pty.kill();
|
|
10858
11550
|
}
|
|
10859
11551
|
}
|
|
10860
|
-
var
|
|
11552
|
+
var SCROLLBACK_LINES = 5000, SAVE_DEBOUNCE_MS = 5000, SCROLLBACK_MAX_CHARS;
|
|
10861
11553
|
var init_pty_session = __esm(() => {
|
|
10862
11554
|
init_paths();
|
|
10863
11555
|
init_pty();
|
|
10864
|
-
|
|
10865
|
-
import_addon_serialize = __toESM(require_addon_serialize(), 1);
|
|
11556
|
+
init_terminal_mirror();
|
|
10866
11557
|
SCROLLBACK_MAX_CHARS = 2 * 1024 * 1024;
|
|
10867
11558
|
});
|
|
10868
11559
|
|
|
10869
11560
|
// packages/daemon/src/session-manager.ts
|
|
10870
11561
|
import { randomBytes as randomBytes2, randomUUID } from "crypto";
|
|
10871
|
-
import
|
|
10872
|
-
import
|
|
10873
|
-
import
|
|
11562
|
+
import { spawn as spawn5 } from "child_process";
|
|
11563
|
+
import fs22 from "fs";
|
|
11564
|
+
import os7 from "os";
|
|
11565
|
+
import path21 from "path";
|
|
11566
|
+
function sessionScratchRoot(info) {
|
|
11567
|
+
return info.worktreePath ?? info.cwd;
|
|
11568
|
+
}
|
|
10874
11569
|
function unresolvableAgent(commandOverride, agentCommand, lookup) {
|
|
10875
11570
|
if (commandOverride !== undefined)
|
|
10876
11571
|
return;
|
|
@@ -10909,6 +11604,14 @@ async function sendableNow(runtime, info, probe, now) {
|
|
|
10909
11604
|
occupied
|
|
10910
11605
|
}, now);
|
|
10911
11606
|
}
|
|
11607
|
+
function pidIsRunning(pid) {
|
|
11608
|
+
try {
|
|
11609
|
+
process.kill(pid, 0);
|
|
11610
|
+
return true;
|
|
11611
|
+
} catch (err) {
|
|
11612
|
+
return err.code === "EPERM";
|
|
11613
|
+
}
|
|
11614
|
+
}
|
|
10912
11615
|
|
|
10913
11616
|
class SessionManager {
|
|
10914
11617
|
store;
|
|
@@ -10925,13 +11628,59 @@ class SessionManager {
|
|
|
10925
11628
|
this.store = store;
|
|
10926
11629
|
this.hooks = hooks;
|
|
10927
11630
|
this.configuredDefaults = configuredDefaults;
|
|
10928
|
-
const stale = store.markStaleRunningAsExited();
|
|
10929
|
-
if (stale > 0)
|
|
10930
|
-
log(`boot: marked ${stale} stale session(s) as exited`);
|
|
10931
11631
|
this.sessionWatch = setInterval(() => this.tickSessions(), SESSION_TICK_MS);
|
|
10932
11632
|
this.sessionWatch.unref();
|
|
10933
11633
|
}
|
|
11634
|
+
async recover() {
|
|
11635
|
+
const running = this.store.listSessions().filter((info) => info.state !== "exited");
|
|
11636
|
+
const held = await this.hostSessions();
|
|
11637
|
+
const adoptedIds = [];
|
|
11638
|
+
for (const info of running) {
|
|
11639
|
+
const description = held.get(info.id);
|
|
11640
|
+
if (description === undefined)
|
|
11641
|
+
continue;
|
|
11642
|
+
try {
|
|
11643
|
+
const handle = await this.ptyHost.adopt(description);
|
|
11644
|
+
info.state = "running";
|
|
11645
|
+
delete info.exitCode;
|
|
11646
|
+
delete info.exitedAt;
|
|
11647
|
+
const session = PtySession.adopt(info, handle, description.cols, description.rows);
|
|
11648
|
+
this.wire(session, undefined);
|
|
11649
|
+
this.hostOwned.add(info.id);
|
|
11650
|
+
this.store.markResumed(info.id, info.agentCommand, info.ptyPid);
|
|
11651
|
+
if (agentIdFromCommand(info.agentCommand) === "codex")
|
|
11652
|
+
this.adoptCodexAppServer(info.id);
|
|
11653
|
+
if (agentIdFromCommand(info.agentCommand) === "gemini")
|
|
11654
|
+
this.skipGeminiTelemetryHistory(info.id);
|
|
11655
|
+
adoptedIds.push(info.id);
|
|
11656
|
+
log(`boot: adopted session ${info.shortId} (pty ${description.pid}) from the terminal host`);
|
|
11657
|
+
} catch (err) {
|
|
11658
|
+
log(`boot: could not adopt ${info.shortId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
11659
|
+
}
|
|
11660
|
+
}
|
|
11661
|
+
const sweptExited = this.store.markStaleRunningAsExited(adoptedIds);
|
|
11662
|
+
if (sweptExited > 0)
|
|
11663
|
+
log(`boot: marked ${sweptExited} stale session(s) as exited`);
|
|
11664
|
+
return { adopted: adoptedIds.length, sweptExited };
|
|
11665
|
+
}
|
|
11666
|
+
async hostSessions() {
|
|
11667
|
+
if (process.env["AXTERM_PTY_HOST"] !== "1")
|
|
11668
|
+
return new Map;
|
|
11669
|
+
const existing = readPtyHostIn(RUNTIME_DIR);
|
|
11670
|
+
if (existing === undefined)
|
|
11671
|
+
return new Map;
|
|
11672
|
+
try {
|
|
11673
|
+
this.ptyHost ??= await connectPtyHost({ endpoint: existing.endpoint, token: existing.token });
|
|
11674
|
+
return new Map(this.ptyHost.sessions.map((s) => [s.id, s]));
|
|
11675
|
+
} catch (err) {
|
|
11676
|
+
log(`boot: the terminal host named in pty-host.json did not answer: ${err instanceof Error ? err.message : String(err)}`);
|
|
11677
|
+
return new Map;
|
|
11678
|
+
}
|
|
11679
|
+
}
|
|
10934
11680
|
sessionWatch;
|
|
11681
|
+
ptyHost;
|
|
11682
|
+
hostOwned = new Set;
|
|
11683
|
+
ptyHostUnavailable = false;
|
|
10935
11684
|
codexServers = new Map;
|
|
10936
11685
|
workspaceFor(_sessionId) {
|
|
10937
11686
|
return this.workspace;
|
|
@@ -11009,8 +11758,8 @@ class SessionManager {
|
|
|
11009
11758
|
};
|
|
11010
11759
|
}
|
|
11011
11760
|
async create(params) {
|
|
11012
|
-
const requestedCwd = params.cwd ?
|
|
11013
|
-
if (!
|
|
11761
|
+
const requestedCwd = params.cwd ? path21.resolve(params.cwd) : os7.homedir();
|
|
11762
|
+
if (!fs22.existsSync(requestedCwd)) {
|
|
11014
11763
|
throw new AxtermError("err.cwd.missing", { cwd: requestedCwd });
|
|
11015
11764
|
}
|
|
11016
11765
|
let worktree;
|
|
@@ -11054,11 +11803,27 @@ class SessionManager {
|
|
|
11054
11803
|
const missing = unresolvableAgent(params.commandOverride, agentCommand, lookupOnPath);
|
|
11055
11804
|
if (missing !== undefined)
|
|
11056
11805
|
throw new AxtermError("err.agent.notInstalled", { command: missing });
|
|
11806
|
+
const adopted = agent === "claude" ? resumedConversationId(agentCommand) : undefined;
|
|
11807
|
+
let sessionCwd = cwd;
|
|
11808
|
+
if (adopted !== undefined) {
|
|
11809
|
+
const holder = conversationHeldBy(this.allSessions(), adopted);
|
|
11810
|
+
if (holder !== undefined) {
|
|
11811
|
+
throw new AxtermError("err.claude.conversationBusy", { id: adopted, session: holder.shortId });
|
|
11812
|
+
}
|
|
11813
|
+
if (worktree === undefined) {
|
|
11814
|
+
const recorded = claudeConversationCwd(claudeConfigDir(), adopted);
|
|
11815
|
+
sessionCwd = resolveSessionCwd({
|
|
11816
|
+
requested: cwd,
|
|
11817
|
+
preferConversation: params.preferConversationCwd === true,
|
|
11818
|
+
...recorded !== undefined && fs22.existsSync(recorded) ? { conversationCwd: recorded } : {}
|
|
11819
|
+
});
|
|
11820
|
+
}
|
|
11821
|
+
}
|
|
11057
11822
|
const info = {
|
|
11058
11823
|
id,
|
|
11059
11824
|
shortId: id.slice(0, 6),
|
|
11060
|
-
name: params.name ?? worktree?.branch ??
|
|
11061
|
-
cwd,
|
|
11825
|
+
name: params.name ?? worktree?.branch ?? path21.basename(sessionCwd),
|
|
11826
|
+
cwd: sessionCwd,
|
|
11062
11827
|
shell: shell.kind,
|
|
11063
11828
|
agentCommand,
|
|
11064
11829
|
state: "running",
|
|
@@ -11067,8 +11832,7 @@ class SessionManager {
|
|
|
11067
11832
|
createdAt: Date.now(),
|
|
11068
11833
|
...worktree !== undefined ? { worktreePath: worktree.path, branch: worktree.branch, repoRoot: worktree.repoRoot } : {}
|
|
11069
11834
|
};
|
|
11070
|
-
|
|
11071
|
-
this.launch(info, shell, agentCommand, appServerUrl);
|
|
11835
|
+
await this.launch(info, shell, agentCommand);
|
|
11072
11836
|
this.store.insertSession(info);
|
|
11073
11837
|
log(`session ${info.shortId} created shell=${shell.kind} cmd=${agentCommand.join(" ") || "(shell only)"}`);
|
|
11074
11838
|
this.emit({ t: "session.created", info });
|
|
@@ -11082,7 +11846,7 @@ class SessionManager {
|
|
|
11082
11846
|
launchToken
|
|
11083
11847
|
});
|
|
11084
11848
|
const file = this.claudeSettingsPath(id);
|
|
11085
|
-
|
|
11849
|
+
fs22.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
|
|
11086
11850
|
}
|
|
11087
11851
|
writeGeminiSettings(sessionId) {
|
|
11088
11852
|
try {
|
|
@@ -11095,69 +11859,104 @@ class SessionManager {
|
|
|
11095
11859
|
});
|
|
11096
11860
|
const file = this.geminiSettingsPath();
|
|
11097
11861
|
const tmp = `${file}.tmp`;
|
|
11098
|
-
|
|
11099
|
-
|
|
11862
|
+
fs22.writeFileSync(tmp, JSON.stringify(settings, null, 2));
|
|
11863
|
+
fs22.renameSync(tmp, file);
|
|
11100
11864
|
} catch (err) {
|
|
11101
11865
|
log(`gemini settings write failed; session starts without hooks: ${String(err)}`);
|
|
11102
11866
|
}
|
|
11103
11867
|
}
|
|
11104
|
-
async startCodexAppServer(id, codexHome2, cwd) {
|
|
11868
|
+
async startCodexAppServer(id, codexHome2, cwd, identity) {
|
|
11105
11869
|
try {
|
|
11870
|
+
const inherited = this.adoptCodexAppServer(id);
|
|
11871
|
+
if (inherited !== undefined)
|
|
11872
|
+
return inherited;
|
|
11106
11873
|
const port = await freeLoopbackPort();
|
|
11107
11874
|
const url = `ws://127.0.0.1:${port}`;
|
|
11108
|
-
const
|
|
11875
|
+
const child = spawn5("codex", ["app-server", "--listen", url], {
|
|
11109
11876
|
cwd,
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
|
|
11877
|
+
detached: true,
|
|
11878
|
+
windowsHide: true,
|
|
11879
|
+
stdio: "ignore",
|
|
11880
|
+
env: { ...sessionEnv(process.env, identity), CODEX_HOME: codexHome2 }
|
|
11114
11881
|
});
|
|
11882
|
+
child.unref();
|
|
11883
|
+
const pid = child.pid;
|
|
11884
|
+
if (pid === undefined)
|
|
11885
|
+
throw new Error("the app-server was not given a pid");
|
|
11115
11886
|
if (!await waitForPort(port)) {
|
|
11116
|
-
|
|
11887
|
+
this.killCodexAppServer(pid);
|
|
11117
11888
|
log(`codex app-server did not open ${port}; session ${id.slice(0, 6)} keeps its hooks`);
|
|
11118
11889
|
return;
|
|
11119
11890
|
}
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
stop: () => {
|
|
11124
|
-
client.close();
|
|
11125
|
-
try {
|
|
11126
|
-
killTree(proc.pid, "codex.exe");
|
|
11127
|
-
} catch {}
|
|
11128
|
-
try {
|
|
11129
|
-
proc.kill();
|
|
11130
|
-
} catch {}
|
|
11131
|
-
}
|
|
11132
|
-
});
|
|
11133
|
-
log(`codex app-server on ${url} for session ${id.slice(0, 6)} in ${cwd}`);
|
|
11891
|
+
this.watchCodexAppServer(id, url, pid);
|
|
11892
|
+
fs22.writeFileSync(this.codexAppServerFile(id), JSON.stringify({ pid, url }), { mode: 384 });
|
|
11893
|
+
log(`codex app-server on ${url} (pid ${pid}) for session ${id.slice(0, 6)} in ${cwd}`);
|
|
11134
11894
|
return url;
|
|
11135
11895
|
} catch (err) {
|
|
11136
11896
|
log(`codex app-server unavailable (${String(err)}); session ${id.slice(0, 6)} keeps its hooks`);
|
|
11137
11897
|
return;
|
|
11138
11898
|
}
|
|
11139
11899
|
}
|
|
11900
|
+
codexAppServerFile(id) {
|
|
11901
|
+
return path21.join(RUNTIME_DIR, `codex-app-${id}.json`);
|
|
11902
|
+
}
|
|
11903
|
+
adoptCodexAppServer(id) {
|
|
11904
|
+
const file = this.codexAppServerFile(id);
|
|
11905
|
+
let record;
|
|
11906
|
+
try {
|
|
11907
|
+
record = JSON.parse(fs22.readFileSync(file, "utf8"));
|
|
11908
|
+
} catch {
|
|
11909
|
+
return;
|
|
11910
|
+
}
|
|
11911
|
+
const pid = record.pid;
|
|
11912
|
+
const url = record.url;
|
|
11913
|
+
if (typeof pid !== "number" || typeof url !== "string")
|
|
11914
|
+
return;
|
|
11915
|
+
if (!pidIsRunning(pid)) {
|
|
11916
|
+
fs22.rmSync(file, { force: true });
|
|
11917
|
+
return;
|
|
11918
|
+
}
|
|
11919
|
+
this.watchCodexAppServer(id, url, pid);
|
|
11920
|
+
log(`codex app-server: reconnected to ${url} (pid ${pid}) for session ${id.slice(0, 6)}`);
|
|
11921
|
+
return url;
|
|
11922
|
+
}
|
|
11923
|
+
watchCodexAppServer(id, url, pid) {
|
|
11924
|
+
const client = new CodexAppServerClient(url, (payload) => this.ingestHookEvent(id, payload));
|
|
11925
|
+
client.connect();
|
|
11926
|
+
this.codexServers.set(id, {
|
|
11927
|
+
stop: () => {
|
|
11928
|
+
client.close();
|
|
11929
|
+
this.killCodexAppServer(pid);
|
|
11930
|
+
fs22.rmSync(this.codexAppServerFile(id), { force: true });
|
|
11931
|
+
}
|
|
11932
|
+
});
|
|
11933
|
+
}
|
|
11934
|
+
killCodexAppServer(pid) {
|
|
11935
|
+
try {
|
|
11936
|
+
killTree(pid, "codex.exe");
|
|
11937
|
+
} catch {}
|
|
11938
|
+
}
|
|
11140
11939
|
stopCodexAppServer(id) {
|
|
11141
11940
|
this.codexServers.get(id)?.stop();
|
|
11142
11941
|
this.codexServers.delete(id);
|
|
11143
11942
|
}
|
|
11144
11943
|
ensureCodexHome() {
|
|
11145
|
-
const home =
|
|
11146
|
-
|
|
11944
|
+
const home = path21.join(STATE_DIR, "codex-home");
|
|
11945
|
+
fs22.mkdirSync(home, { recursive: true, mode: 448 });
|
|
11147
11946
|
this.linkCodexAuth(home);
|
|
11148
11947
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
|
|
11149
11948
|
const hooksFile = buildCodexHooks({ command: bridge.command, args: bridge.args });
|
|
11150
11949
|
const hooksPath = codexTrustKeyPath(home);
|
|
11151
11950
|
const tmp = `${hooksPath}.tmp`;
|
|
11152
|
-
|
|
11153
|
-
|
|
11951
|
+
fs22.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
|
|
11952
|
+
fs22.renameSync(tmp, hooksPath);
|
|
11154
11953
|
const entries = codexHookTrust(hooksPath, hooksFile);
|
|
11155
|
-
const configPath =
|
|
11156
|
-
const existing =
|
|
11954
|
+
const configPath = path21.join(home, "config.toml");
|
|
11955
|
+
const existing = fs22.existsSync(configPath) ? fs22.readFileSync(configPath, "utf8") : "";
|
|
11157
11956
|
const missing = entries.filter((e) => !existing.includes(`"${e.key.replaceAll("\\", "\\\\")}"`));
|
|
11158
11957
|
if (missing.length > 0) {
|
|
11159
11958
|
const block = codexTrustStateToml(missing);
|
|
11160
|
-
|
|
11959
|
+
fs22.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
|
|
11161
11960
|
`) ? `
|
|
11162
11961
|
${block}` : block);
|
|
11163
11962
|
}
|
|
@@ -11170,54 +11969,55 @@ ${block}` : block);
|
|
|
11170
11969
|
return home;
|
|
11171
11970
|
}
|
|
11172
11971
|
linkCodexAuth(home) {
|
|
11173
|
-
const target =
|
|
11174
|
-
if (
|
|
11972
|
+
const target = path21.join(home, "auth.json");
|
|
11973
|
+
if (fs22.existsSync(target))
|
|
11175
11974
|
return;
|
|
11176
|
-
const operatorHome = process.env["CODEX_HOME"] ??
|
|
11177
|
-
const source =
|
|
11178
|
-
if (!
|
|
11975
|
+
const operatorHome = process.env["CODEX_HOME"] ?? path21.join(os7.homedir(), ".codex");
|
|
11976
|
+
const source = path21.join(operatorHome, "auth.json");
|
|
11977
|
+
if (!fs22.existsSync(source))
|
|
11179
11978
|
throw new AxtermError("err.codex.noAuth", { authFile: source });
|
|
11979
|
+
let how;
|
|
11180
11980
|
try {
|
|
11181
|
-
|
|
11182
|
-
} catch {
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
|
|
11187
|
-
}
|
|
11981
|
+
how = referenceCredential(source, target);
|
|
11982
|
+
} catch (err) {
|
|
11983
|
+
throw new AxtermError("err.codex.authLink", { reason: err instanceof Error ? err.message : String(err) });
|
|
11984
|
+
}
|
|
11985
|
+
if (how === "copy") {
|
|
11986
|
+
log(`codex auth: neither a symlink nor a hard link to ${source} was possible (state dir on another volume?) \u2014 ` + `copied instead, so a token refresh will not follow; delete ${target} to re-reference it`);
|
|
11188
11987
|
}
|
|
11189
11988
|
}
|
|
11190
11989
|
doctorReport() {
|
|
11191
11990
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
|
|
11192
|
-
const home =
|
|
11193
|
-
const operatorHome = process.env["CODEX_HOME"] ??
|
|
11194
|
-
const authSource =
|
|
11991
|
+
const home = path21.join(STATE_DIR, "codex-home");
|
|
11992
|
+
const operatorHome = process.env["CODEX_HOME"] ?? path21.join(os7.homedir(), ".codex");
|
|
11993
|
+
const authSource = path21.join(operatorHome, "auth.json");
|
|
11195
11994
|
const geminiSettings = this.geminiSettingsPath();
|
|
11196
|
-
const configPath =
|
|
11995
|
+
const configPath = path21.join(home, "config.toml");
|
|
11197
11996
|
const read = (file) => {
|
|
11198
11997
|
try {
|
|
11199
|
-
return
|
|
11998
|
+
return fs22.existsSync(file) ? fs22.readFileSync(file, "utf8") : undefined;
|
|
11200
11999
|
} catch {
|
|
11201
12000
|
return;
|
|
11202
12001
|
}
|
|
11203
12002
|
};
|
|
11204
12003
|
return [
|
|
11205
|
-
...evaluateBridgeDoctor({ command: bridge.command, exists:
|
|
12004
|
+
...evaluateBridgeDoctor({ command: bridge.command, exists: fs22.existsSync(bridge.command) }),
|
|
11206
12005
|
...evaluateOmpDoctor({
|
|
11207
12006
|
binary: lookupOnPath("omp"),
|
|
11208
12007
|
extensionPath: this.hooks.extensionPath,
|
|
11209
|
-
extensionExists:
|
|
12008
|
+
extensionExists: fs22.existsSync(this.hooks.extensionPath)
|
|
11210
12009
|
}),
|
|
11211
12010
|
...evaluateClaudeDoctor({ binary: lookupOnPath("claude") }),
|
|
11212
12011
|
...evaluateCodexDoctor({
|
|
11213
12012
|
binary: lookupOnPath("codex"),
|
|
11214
12013
|
authSourcePath: authSource,
|
|
11215
|
-
authSourceExists:
|
|
11216
|
-
homeExists:
|
|
12014
|
+
authSourceExists: fs22.existsSync(authSource),
|
|
12015
|
+
homeExists: fs22.existsSync(home),
|
|
11217
12016
|
expected: codexHookTrust(codexTrustKeyPath(home), buildCodexHooks({ command: bridge.command, args: bridge.args })),
|
|
11218
12017
|
configToml: read(configPath)
|
|
11219
12018
|
}),
|
|
11220
|
-
...evaluateGeminiDoctor({ binary: lookupOnPath("gemini"), settingsJson: read(geminiSettings) })
|
|
12019
|
+
...evaluateGeminiDoctor({ binary: lookupOnPath("gemini"), settingsJson: read(geminiSettings) }),
|
|
12020
|
+
...evaluateStagingDoctor({ mode: this.stagingMode() })
|
|
11221
12021
|
];
|
|
11222
12022
|
}
|
|
11223
12023
|
quotaReport() {
|
|
@@ -11311,6 +12111,18 @@ ${block}` : block);
|
|
|
11311
12111
|
session.write(digit);
|
|
11312
12112
|
return true;
|
|
11313
12113
|
}
|
|
12114
|
+
answerScreenChoice(idOrPrefix, optionIndex) {
|
|
12115
|
+
const session = this.resolveLive(idOrPrefix);
|
|
12116
|
+
const dialog = detectOnboarding(agentIdFromCommand(session.info.agentCommand), session.screenText(30));
|
|
12117
|
+
const digit = dialog?.options[optionIndex]?.digit;
|
|
12118
|
+
if (digit === undefined) {
|
|
12119
|
+
session.write(buildMenuSelectKeys(optionIndex));
|
|
12120
|
+
return "menu";
|
|
12121
|
+
}
|
|
12122
|
+
this.pendingPrompt.delete(session.info.id);
|
|
12123
|
+
session.write(digit);
|
|
12124
|
+
return "onboarding";
|
|
12125
|
+
}
|
|
11314
12126
|
sinceLastCapture(id) {
|
|
11315
12127
|
const at = this.lastCaptureAt.get(id);
|
|
11316
12128
|
return at === undefined ? undefined : Date.now() - at;
|
|
@@ -11337,50 +12149,92 @@ ${block}` : block);
|
|
|
11337
12149
|
}
|
|
11338
12150
|
writeScreenFile(idOrPrefix, lines) {
|
|
11339
12151
|
const session = this.resolveLive(idOrPrefix);
|
|
11340
|
-
const file =
|
|
11341
|
-
|
|
12152
|
+
const file = path21.join(path21.dirname(this.hooks.endpointFile), `screen-${session.info.id}.txt`);
|
|
12153
|
+
fs22.writeFileSync(file, session.screenText(lines), { mode: 384 });
|
|
11342
12154
|
return file;
|
|
11343
12155
|
}
|
|
11344
12156
|
removeScreenFile(id) {
|
|
11345
12157
|
try {
|
|
11346
|
-
|
|
12158
|
+
fs22.rmSync(path21.join(path21.dirname(this.hooks.endpointFile), `screen-${id}.txt`), { force: true });
|
|
11347
12159
|
} catch {}
|
|
11348
12160
|
}
|
|
11349
12161
|
async stageInboundFile(idOrPrefix, suggestedName, bytes) {
|
|
11350
12162
|
const info = this.resolveLive(idOrPrefix).info;
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
|
|
12163
|
+
if (this.stagingMode() === "runtime")
|
|
12164
|
+
return this.stageInRuntimeDir(info, suggestedName, bytes);
|
|
12165
|
+
const ws = this.workspaceFor(info.id);
|
|
12166
|
+
const dir = sessionScratchRoot(info);
|
|
12167
|
+
const file = path21.resolve(planMaterialisedPath(inboundStagingDir(dir), info.id, suggestedName));
|
|
12168
|
+
await this.prepareScratchDir(ws, dir, file);
|
|
12169
|
+
await ws.write(file, bytes, 384);
|
|
12170
|
+
log(`session ${info.shortId} staged ${path21.basename(file)} (${bytes.byteLength} bytes)`);
|
|
12171
|
+
return file;
|
|
12172
|
+
}
|
|
12173
|
+
stagingMode() {
|
|
12174
|
+
return this.configuredDefaults().staging ?? "project";
|
|
12175
|
+
}
|
|
12176
|
+
async stageInRuntimeDir(info, suggestedName, bytes) {
|
|
12177
|
+
const file = path21.resolve(planMaterialisedPath(MATERIALISED_DIR, info.id, suggestedName));
|
|
12178
|
+
await fs22.promises.mkdir(path21.dirname(file), { recursive: true, mode: 448 });
|
|
12179
|
+
await fs22.promises.writeFile(file, bytes, { mode: 384 });
|
|
12180
|
+
log(`session ${info.shortId} staged ${path21.basename(file)} (${bytes.byteLength} bytes, runtime dir)`);
|
|
11355
12181
|
return file;
|
|
11356
12182
|
}
|
|
11357
|
-
|
|
12183
|
+
async prepareScratchDir(ws, sessionDir, file) {
|
|
12184
|
+
const scratch = scratchDir(sessionDir);
|
|
12185
|
+
const ignore = `${scratch}/${SCRATCH_IGNORE_NAME}`;
|
|
11358
12186
|
try {
|
|
11359
|
-
|
|
12187
|
+
await ws.mkdirp(scratch, 448);
|
|
12188
|
+
if (await ws.stat(ignore) === undefined)
|
|
12189
|
+
await ws.write(ignore, new TextEncoder().encode(SCRATCH_IGNORE_BODY));
|
|
12190
|
+
} catch (err) {
|
|
12191
|
+
throw new AxtermError("err.staging.dir", { dir: scratch, error: err instanceof Error ? err.message : String(err) });
|
|
12192
|
+
}
|
|
12193
|
+
if (await ignoreState(ws, sessionDir, file) === "visible") {
|
|
12194
|
+
throw new AxtermError("err.staging.notIgnored", { dir: scratch, file: SCRATCH_IGNORE_NAME });
|
|
12195
|
+
}
|
|
12196
|
+
try {
|
|
12197
|
+
await ws.mkdirp(path21.dirname(file), 448);
|
|
12198
|
+
} catch (err) {
|
|
12199
|
+
throw new AxtermError("err.staging.dir", { dir: scratch, error: err instanceof Error ? err.message : String(err) });
|
|
12200
|
+
}
|
|
12201
|
+
}
|
|
12202
|
+
removeStagedFiles(id, info) {
|
|
12203
|
+
try {
|
|
12204
|
+
fs22.rmSync(path21.join(MATERIALISED_DIR, id), { recursive: true, force: true });
|
|
12205
|
+
} catch {}
|
|
12206
|
+
if (info === undefined)
|
|
12207
|
+
return;
|
|
12208
|
+
const scratch = path21.resolve(scratchDir(sessionScratchRoot(info)));
|
|
12209
|
+
try {
|
|
12210
|
+
fs22.rmSync(path21.resolve(inboundStagingDir(sessionScratchRoot(info)), id), { recursive: true, force: true });
|
|
12211
|
+
fs22.rmdirSync(path21.join(scratch, "inbound"));
|
|
12212
|
+
fs22.rmSync(path21.join(scratch, SCRATCH_IGNORE_NAME), { force: true });
|
|
12213
|
+
fs22.rmdirSync(scratch);
|
|
11360
12214
|
} catch {}
|
|
11361
12215
|
}
|
|
11362
12216
|
removeClaudeSettings(id) {
|
|
11363
12217
|
try {
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
12218
|
+
fs22.rmSync(this.claudeSettingsPath(id), { force: true });
|
|
12219
|
+
fs22.rmSync(this.mcpConfigPath(id), { force: true });
|
|
12220
|
+
fs22.rmSync(this.mcpContextPath(id), { force: true });
|
|
12221
|
+
fs22.rmSync(this.geminiTelemetryPath(id), { force: true });
|
|
11368
12222
|
this.stopCodexAppServer(id);
|
|
11369
12223
|
} catch {}
|
|
11370
12224
|
}
|
|
11371
12225
|
claudeSettingsPath(id) {
|
|
11372
|
-
return
|
|
12226
|
+
return path21.join(path21.dirname(this.hooks.endpointFile), `claude-${id}.json`);
|
|
11373
12227
|
}
|
|
11374
12228
|
mcpConfigPath(id) {
|
|
11375
|
-
return
|
|
12229
|
+
return path21.join(path21.dirname(this.hooks.endpointFile), `mcp-${id}.json`);
|
|
11376
12230
|
}
|
|
11377
12231
|
mcpContextPath(id) {
|
|
11378
|
-
return
|
|
12232
|
+
return path21.join(path21.dirname(this.hooks.endpointFile), `mcp-ctx-${id}.json`);
|
|
11379
12233
|
}
|
|
11380
12234
|
writeMcpContext(id, launchToken) {
|
|
11381
12235
|
const file = this.mcpContextPath(id);
|
|
11382
12236
|
try {
|
|
11383
|
-
|
|
12237
|
+
fs22.writeFileSync(file, JSON.stringify({ port: this.hooks.port, token: this.hooks.host.token, sessionId: id, launchToken }), { mode: 384 });
|
|
11384
12238
|
return file;
|
|
11385
12239
|
} catch (err) {
|
|
11386
12240
|
log(`mcp context write failed; session starts without axterm's tools: ${String(err)}`);
|
|
@@ -11393,7 +12247,7 @@ ${block}` : block);
|
|
|
11393
12247
|
writeMcpConfig(id, contextPath) {
|
|
11394
12248
|
const file = this.mcpConfigPath(id);
|
|
11395
12249
|
try {
|
|
11396
|
-
|
|
12250
|
+
fs22.writeFileSync(file, claudeMcpConfig(this.mcpServerLaunch(), contextPath), { mode: 384 });
|
|
11397
12251
|
return file;
|
|
11398
12252
|
} catch (err) {
|
|
11399
12253
|
log(`mcp config write failed; session starts without axterm's tools: ${String(err)}`);
|
|
@@ -11401,12 +12255,38 @@ ${block}` : block);
|
|
|
11401
12255
|
}
|
|
11402
12256
|
}
|
|
11403
12257
|
geminiTelemetryPath(id) {
|
|
11404
|
-
return
|
|
12258
|
+
return path21.join(path21.dirname(this.hooks.endpointFile), `gemini-telemetry-${id}.log`);
|
|
11405
12259
|
}
|
|
11406
12260
|
geminiSettingsPath() {
|
|
11407
|
-
return
|
|
12261
|
+
return path21.join(path21.dirname(this.hooks.endpointFile), "gemini-defaults.json");
|
|
12262
|
+
}
|
|
12263
|
+
async ptySpawner() {
|
|
12264
|
+
if (process.env["AXTERM_PTY_HOST"] !== "1" || this.ptyHostUnavailable)
|
|
12265
|
+
return { spawn: spawnPty, hostOwned: false };
|
|
12266
|
+
try {
|
|
12267
|
+
if (this.ptyHost === undefined) {
|
|
12268
|
+
const override = process.env["AXTERM_PTY_HOST_MAIN"];
|
|
12269
|
+
const info = await ensurePtyHost({ runtimeDir: RUNTIME_DIR, ...override ? { mainScript: override } : {} });
|
|
12270
|
+
this.ptyHost = await connectPtyHost({ endpoint: info.endpoint, token: info.token });
|
|
12271
|
+
log(`pty host: connected (pid ${info.pid})`);
|
|
12272
|
+
}
|
|
12273
|
+
const host = this.ptyHost;
|
|
12274
|
+
return { spawn: (options) => host.spawn(options), hostOwned: true };
|
|
12275
|
+
} catch (err) {
|
|
12276
|
+
this.ptyHostUnavailable = true;
|
|
12277
|
+
log(`pty host unavailable, owning ptys in this process: ${err instanceof Error ? err.message : String(err)}`);
|
|
12278
|
+
return { spawn: spawnPty, hostOwned: false };
|
|
12279
|
+
}
|
|
11408
12280
|
}
|
|
11409
|
-
|
|
12281
|
+
requireClaudeConversation(agentCommand) {
|
|
12282
|
+
const conversation = resumedConversationId(agentCommand);
|
|
12283
|
+
if (conversation === undefined)
|
|
12284
|
+
return;
|
|
12285
|
+
if (claudeConversationExists(claudeConfigDir(), conversation) === false) {
|
|
12286
|
+
throw new AxtermError("err.claude.noConversation", { id: conversation });
|
|
12287
|
+
}
|
|
12288
|
+
}
|
|
12289
|
+
async launch(info, shell, agentCommand) {
|
|
11410
12290
|
const id = info.id;
|
|
11411
12291
|
const launchToken = randomBytes2(16).toString("hex");
|
|
11412
12292
|
const agentId = agentIdFromCommand(info.agentCommand);
|
|
@@ -11414,22 +12294,30 @@ ${block}` : block);
|
|
|
11414
12294
|
this.writeClaudeSettings(id, launchToken);
|
|
11415
12295
|
if (agentId !== "omp")
|
|
11416
12296
|
this.writeMcpContext(id, launchToken);
|
|
12297
|
+
if (agentId === "claude")
|
|
12298
|
+
this.writeMcpConfig(id, this.mcpContextPath(id));
|
|
11417
12299
|
if (agentId === "gemini")
|
|
11418
12300
|
this.writeGeminiSettings(id);
|
|
11419
12301
|
const codexHome2 = agentId === "codex" ? this.ensureCodexHome() : undefined;
|
|
11420
|
-
|
|
12302
|
+
if (agentId === "claude")
|
|
12303
|
+
this.requireClaudeConversation(agentCommand);
|
|
12304
|
+
const identity = {
|
|
12305
|
+
sessionId: id,
|
|
12306
|
+
launchToken,
|
|
12307
|
+
hookPort: this.hooks.port,
|
|
12308
|
+
hookToken: this.hooks.host.token,
|
|
12309
|
+
endpointFile: this.hooks.endpointFile
|
|
12310
|
+
};
|
|
12311
|
+
const appServerUrl = codexHome2 === undefined ? undefined : await this.startCodexAppServer(id, codexHome2, info.cwd, identity);
|
|
12312
|
+
const spawner = await this.ptySpawner();
|
|
12313
|
+
const session = await PtySession.open(info, {
|
|
11421
12314
|
exe: shell.exe,
|
|
11422
12315
|
argv: buildShellArgv(shell, withAppServer(agentCommand, appServerUrl)),
|
|
11423
12316
|
cwd: info.cwd,
|
|
11424
12317
|
cols: info.cols,
|
|
11425
12318
|
rows: info.rows,
|
|
11426
12319
|
env: {
|
|
11427
|
-
...
|
|
11428
|
-
AXTERM_SESSION_ID: id,
|
|
11429
|
-
AXTERM_LAUNCH_TOKEN: launchToken,
|
|
11430
|
-
AXTERM_HOOK_PORT: String(this.hooks.port),
|
|
11431
|
-
AXTERM_HOOK_TOKEN: this.hooks.host.token,
|
|
11432
|
-
AXTERM_HOOK_ENDPOINT: this.hooks.endpointFile,
|
|
12320
|
+
...sessionEnv(process.env, identity),
|
|
11433
12321
|
...agentId === "gemini" ? {
|
|
11434
12322
|
GEMINI_CLI_SYSTEM_DEFAULTS_PATH: this.geminiSettingsPath(),
|
|
11435
12323
|
GEMINI_TELEMETRY_ENABLED: "true",
|
|
@@ -11440,7 +12328,15 @@ ${block}` : block);
|
|
|
11440
12328
|
} : {},
|
|
11441
12329
|
...codexHome2 !== undefined ? { CODEX_HOME: codexHome2 } : {}
|
|
11442
12330
|
}
|
|
11443
|
-
});
|
|
12331
|
+
}, spawner.spawn);
|
|
12332
|
+
if (spawner.hostOwned)
|
|
12333
|
+
this.hostOwned.add(id);
|
|
12334
|
+
this.wire(session, launchToken);
|
|
12335
|
+
return session;
|
|
12336
|
+
}
|
|
12337
|
+
wire(session, launchToken) {
|
|
12338
|
+
const id = session.info.id;
|
|
12339
|
+
const info = session.info;
|
|
11444
12340
|
this.agentRuntime.set(id, { agentExited: false, choiceSeq: 0, dialogOpen: false });
|
|
11445
12341
|
this.hooks.host.registerSession(id, launchToken, (payload) => this.ingestHookEvent(id, payload));
|
|
11446
12342
|
session.onData(() => {
|
|
@@ -11456,7 +12352,7 @@ ${block}` : block);
|
|
|
11456
12352
|
this.hooks.host.unregisterSession(id);
|
|
11457
12353
|
this.removeClaudeSettings(id);
|
|
11458
12354
|
this.removeScreenFile(id);
|
|
11459
|
-
this.removeStagedFiles(id);
|
|
12355
|
+
this.removeStagedFiles(id, info);
|
|
11460
12356
|
clearTimeout(this.idleTimers.get(id));
|
|
11461
12357
|
this.idleTimers.delete(id);
|
|
11462
12358
|
this.sawOutput.delete(id);
|
|
@@ -11467,11 +12363,11 @@ ${block}` : block);
|
|
|
11467
12363
|
this.pendingPrompt.delete(id);
|
|
11468
12364
|
this.live.delete(id);
|
|
11469
12365
|
this.agentRuntime.delete(id);
|
|
12366
|
+
this.hostOwned.delete(id);
|
|
11470
12367
|
log(`session ${info.shortId} exited code=${code}`);
|
|
11471
12368
|
this.emit({ t: "session.exit", sessionId: id, code });
|
|
11472
12369
|
});
|
|
11473
12370
|
this.live.set(id, session);
|
|
11474
|
-
return session;
|
|
11475
12371
|
}
|
|
11476
12372
|
emitActivity(id, payload) {
|
|
11477
12373
|
const dropped = payload.dropped;
|
|
@@ -11615,7 +12511,8 @@ ${block}` : block);
|
|
|
11615
12511
|
const direct = this.live.get(idOrPrefix);
|
|
11616
12512
|
if (direct)
|
|
11617
12513
|
return direct;
|
|
11618
|
-
const
|
|
12514
|
+
const alternate = sessionIdFromUuid(idOrPrefix);
|
|
12515
|
+
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
12516
|
if (matches.length === 1)
|
|
11620
12517
|
return matches[0];
|
|
11621
12518
|
if (matches.length > 1)
|
|
@@ -11641,37 +12538,14 @@ ${block}` : block);
|
|
|
11641
12538
|
allSessions() {
|
|
11642
12539
|
return sessionsByRecency([...this.live.values()].map((s) => s.info), this.store.listSessions());
|
|
11643
12540
|
}
|
|
11644
|
-
resumeNamed(stored, agent) {
|
|
11645
|
-
if (!fs19.existsSync(stored.cwd))
|
|
11646
|
-
throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
|
|
11647
|
-
const agentCommand = buildAgentResumeCommand({
|
|
11648
|
-
agent,
|
|
11649
|
-
sessionId: stored.id,
|
|
11650
|
-
extensionPath: this.hooks.extensionPath,
|
|
11651
|
-
claudeSettingsPath: this.claudeSettingsPath(stored.id)
|
|
11652
|
-
});
|
|
11653
|
-
const info = { ...stored, agentCommand, state: "running" };
|
|
11654
|
-
delete info.exitCode;
|
|
11655
|
-
delete info.exitedAt;
|
|
11656
|
-
delete info.agentState;
|
|
11657
|
-
delete info.agentStateSource;
|
|
11658
|
-
this.launch(info, resolveShell(info.shell), agentCommand);
|
|
11659
|
-
this.store.markResumed(info.id, agentCommand, info.ptyPid);
|
|
11660
|
-
log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
|
|
11661
|
-
this.emit({ t: "session.created", info });
|
|
11662
|
-
return info;
|
|
11663
|
-
}
|
|
11664
12541
|
async resume(idOrPrefix) {
|
|
11665
12542
|
const stored = this.store.findByPrefix(idOrPrefix);
|
|
11666
12543
|
if (!stored)
|
|
11667
12544
|
throw new AxtermError("err.session.unknown", { id: idOrPrefix });
|
|
11668
12545
|
if (this.live.has(stored.id))
|
|
11669
12546
|
throw new AxtermError("err.session.running", { id: stored.shortId });
|
|
11670
|
-
const storedAgent = agentIdFromCommand(stored.agentCommand);
|
|
11671
|
-
if (storedAgent === "claude" || storedAgent === "gemini")
|
|
11672
|
-
return this.resumeNamed(stored, storedAgent);
|
|
11673
12547
|
const profile = agentProfile(agentIdFromCommand(stored.agentCommand));
|
|
11674
|
-
const reference = resumeReference(profile, stored);
|
|
12548
|
+
const reference = resumeReference(profile, { ...stored, ownSessionId: stored.id });
|
|
11675
12549
|
if (reference === undefined) {
|
|
11676
12550
|
throw new AxtermError("err.session.noConversation", {
|
|
11677
12551
|
id: stored.shortId,
|
|
@@ -11682,7 +12556,7 @@ ${block}` : block);
|
|
|
11682
12556
|
if (agentCommand === undefined) {
|
|
11683
12557
|
throw new AxtermError("err.session.noResumeSupport", { agent: profile.displayName });
|
|
11684
12558
|
}
|
|
11685
|
-
if (!
|
|
12559
|
+
if (!fs22.existsSync(stored.cwd))
|
|
11686
12560
|
throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
|
|
11687
12561
|
const info = { ...stored, agentCommand, state: "running" };
|
|
11688
12562
|
delete info.exitCode;
|
|
@@ -11690,8 +12564,7 @@ ${block}` : block);
|
|
|
11690
12564
|
delete info.agentState;
|
|
11691
12565
|
delete info.agentStateSource;
|
|
11692
12566
|
const shell = resolveShell(info.shell);
|
|
11693
|
-
|
|
11694
|
-
this.launch(info, shell, agentCommand, resumedAppServer);
|
|
12567
|
+
await this.launch(info, shell, agentCommand);
|
|
11695
12568
|
this.store.markResumed(info.id, agentCommand, info.ptyPid);
|
|
11696
12569
|
log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
|
|
11697
12570
|
this.emit({ t: "session.created", info });
|
|
@@ -11732,7 +12605,8 @@ ${block}` : block);
|
|
|
11732
12605
|
if (err instanceof AxtermError && err.key === "err.session.ambiguous")
|
|
11733
12606
|
throw err;
|
|
11734
12607
|
}
|
|
11735
|
-
const
|
|
12608
|
+
const info = live?.info ?? this.store.findByPrefix(idOrPrefix);
|
|
12609
|
+
const id = info?.id;
|
|
11736
12610
|
if (id === undefined)
|
|
11737
12611
|
throw new AxtermError("err.session.missing", { id: idOrPrefix });
|
|
11738
12612
|
if (live !== undefined && !live.isExited) {
|
|
@@ -11747,7 +12621,7 @@ ${block}` : block);
|
|
|
11747
12621
|
await promise;
|
|
11748
12622
|
}
|
|
11749
12623
|
this.removeScreenFile(id);
|
|
11750
|
-
this.removeStagedFiles(id);
|
|
12624
|
+
this.removeStagedFiles(id, info);
|
|
11751
12625
|
this.removeClaudeSettings(id);
|
|
11752
12626
|
this.removeScrollback(id);
|
|
11753
12627
|
this.store.deleteSession(id);
|
|
@@ -11756,7 +12630,7 @@ ${block}` : block);
|
|
|
11756
12630
|
removeScrollback(id) {
|
|
11757
12631
|
for (const file of [`${id}.bin`, `${id}.bin.tmp`]) {
|
|
11758
12632
|
try {
|
|
11759
|
-
|
|
12633
|
+
fs22.rmSync(path21.join(SCROLLBACK_DIR, file), { force: true });
|
|
11760
12634
|
} catch {}
|
|
11761
12635
|
}
|
|
11762
12636
|
}
|
|
@@ -11764,6 +12638,14 @@ ${block}` : block);
|
|
|
11764
12638
|
this.drainGeminiTelemetry();
|
|
11765
12639
|
this.watchForStalls();
|
|
11766
12640
|
}
|
|
12641
|
+
skipGeminiTelemetryHistory(id) {
|
|
12642
|
+
const runtime = this.agentRuntime.get(id);
|
|
12643
|
+
if (runtime === undefined)
|
|
12644
|
+
return;
|
|
12645
|
+
try {
|
|
12646
|
+
runtime.telemetryOffset = fs22.statSync(this.geminiTelemetryPath(id)).size;
|
|
12647
|
+
} catch {}
|
|
12648
|
+
}
|
|
11767
12649
|
drainGeminiTelemetry() {
|
|
11768
12650
|
for (const [id, session] of this.live) {
|
|
11769
12651
|
if (agentIdFromCommand(session.info.agentCommand) !== "gemini")
|
|
@@ -11774,7 +12656,7 @@ ${block}` : block);
|
|
|
11774
12656
|
const file = this.geminiTelemetryPath(id);
|
|
11775
12657
|
let size = 0;
|
|
11776
12658
|
try {
|
|
11777
|
-
size =
|
|
12659
|
+
size = fs22.statSync(file).size;
|
|
11778
12660
|
} catch {
|
|
11779
12661
|
continue;
|
|
11780
12662
|
}
|
|
@@ -11836,10 +12718,17 @@ ${block}` : block);
|
|
|
11836
12718
|
clearInterval(this.sessionWatch);
|
|
11837
12719
|
for (const id of [...this.codexServers.keys()])
|
|
11838
12720
|
this.stopCodexAppServer(id);
|
|
11839
|
-
|
|
12721
|
+
let handedOn = 0;
|
|
12722
|
+
for (const [id, session] of this.live) {
|
|
11840
12723
|
session.saveScrollback();
|
|
12724
|
+
if (this.hostOwned.has(id)) {
|
|
12725
|
+
handedOn += 1;
|
|
12726
|
+
continue;
|
|
12727
|
+
}
|
|
11841
12728
|
session.kill();
|
|
11842
12729
|
}
|
|
12730
|
+
if (handedOn > 0)
|
|
12731
|
+
log(`left ${handedOn} session(s) running in the terminal host for the next daemon`);
|
|
11843
12732
|
this.live.clear();
|
|
11844
12733
|
this.agentRuntime.clear();
|
|
11845
12734
|
}
|
|
@@ -11848,9 +12737,13 @@ var DEFAULT_COLS = 120, DEFAULT_ROWS = 30, MATERIALISED_DIR, ACTIVITY_ARG_CHARS
|
|
|
11848
12737
|
var init_session_manager = __esm(() => {
|
|
11849
12738
|
init_src();
|
|
11850
12739
|
init_codex_home();
|
|
11851
|
-
|
|
12740
|
+
init_session_env();
|
|
11852
12741
|
init_git();
|
|
12742
|
+
init_claude_store();
|
|
11853
12743
|
init_injector();
|
|
12744
|
+
init_pty_host_client();
|
|
12745
|
+
init_pty_host_launch();
|
|
12746
|
+
init_pty();
|
|
11854
12747
|
init_occupancy();
|
|
11855
12748
|
init_agent_store();
|
|
11856
12749
|
init_codex_app_server();
|
|
@@ -11859,7 +12752,7 @@ var init_session_manager = __esm(() => {
|
|
|
11859
12752
|
init_pty_session();
|
|
11860
12753
|
init_shell();
|
|
11861
12754
|
init_workspace_local();
|
|
11862
|
-
MATERIALISED_DIR =
|
|
12755
|
+
MATERIALISED_DIR = path21.join(RUNTIME_DIR, "materialised");
|
|
11863
12756
|
PROMPT_OPTIONS = {
|
|
11864
12757
|
hostKey: [{ label: "yes" }, { label: "no" }],
|
|
11865
12758
|
password: [],
|
|
@@ -11890,6 +12783,9 @@ class FetchTelegramApi {
|
|
|
11890
12783
|
throw new Error(`telegram file download failed: ${res.status}`);
|
|
11891
12784
|
return new Uint8Array(await res.arrayBuffer());
|
|
11892
12785
|
}
|
|
12786
|
+
async deleteMessage(chatId, messageId) {
|
|
12787
|
+
await this.call("deleteMessage", { chat_id: chatId, message_id: messageId });
|
|
12788
|
+
}
|
|
11893
12789
|
async call(method, params = {}) {
|
|
11894
12790
|
const attempt = async () => {
|
|
11895
12791
|
const res = await fetch(`https://api.telegram.org/bot${this.botToken}/${method}`, {
|
|
@@ -12008,11 +12904,11 @@ var init_acknowledge = __esm(() => {
|
|
|
12008
12904
|
});
|
|
12009
12905
|
|
|
12010
12906
|
// packages/channel-telegram/src/attach.ts
|
|
12011
|
-
import
|
|
12012
|
-
import
|
|
12907
|
+
import fs23 from "fs";
|
|
12908
|
+
import path22 from "path";
|
|
12013
12909
|
function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELEGRAM_LIMITS) {
|
|
12014
|
-
const name =
|
|
12015
|
-
const bytes =
|
|
12910
|
+
const name = path22.basename(filePath);
|
|
12911
|
+
const bytes = fs23.statSync(filePath).size;
|
|
12016
12912
|
const plan = planAttachment({ name, bytes }, limits);
|
|
12017
12913
|
if (plan.kind === "rejected")
|
|
12018
12914
|
throw new Error(plan.reason);
|
|
@@ -12022,7 +12918,7 @@ function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELE
|
|
|
12022
12918
|
form.append("message_thread_id", String(topicId));
|
|
12023
12919
|
if (caption !== undefined && caption.length > 0)
|
|
12024
12920
|
form.append("caption", caption);
|
|
12025
|
-
form.append(field, new Blob([
|
|
12921
|
+
form.append(field, new Blob([fs23.readFileSync(filePath)]), name);
|
|
12026
12922
|
return { method: plan.kind === "photo" ? "sendPhoto" : "sendDocument", field, form };
|
|
12027
12923
|
}
|
|
12028
12924
|
function isPhotoRejection(error) {
|
|
@@ -12030,13 +12926,13 @@ function isPhotoRejection(error) {
|
|
|
12030
12926
|
return /IMAGE_PROCESS_FAILED|PHOTO_INVALID_DIMENSIONS|PHOTO_EXT_INVALID|PHOTO_SAVE_FILE_INVALID/i.test(message);
|
|
12031
12927
|
}
|
|
12032
12928
|
function asDocumentUpload(filePath, chatId, topicId, caption) {
|
|
12033
|
-
const name =
|
|
12929
|
+
const name = path22.basename(filePath);
|
|
12034
12930
|
const form = new FormData;
|
|
12035
12931
|
form.append("chat_id", String(chatId));
|
|
12036
12932
|
form.append("message_thread_id", String(topicId));
|
|
12037
12933
|
if (caption !== undefined && caption.length > 0)
|
|
12038
12934
|
form.append("caption", caption);
|
|
12039
|
-
form.append("document", new Blob([
|
|
12935
|
+
form.append("document", new Blob([fs23.readFileSync(filePath)]), name);
|
|
12040
12936
|
return { method: "sendDocument", field: "document", form };
|
|
12041
12937
|
}
|
|
12042
12938
|
var TELEGRAM_LIMITS;
|
|
@@ -12622,6 +13518,7 @@ class TelegramChannel {
|
|
|
12622
13518
|
updateOffset = 0;
|
|
12623
13519
|
albums = new Map;
|
|
12624
13520
|
inboundQueues = new Map;
|
|
13521
|
+
refusedAlbums = new Map;
|
|
12625
13522
|
albumDebounceMs;
|
|
12626
13523
|
inboundLimits;
|
|
12627
13524
|
inboundEnabled;
|
|
@@ -12931,6 +13828,26 @@ ${code(prompt.line)}`,
|
|
|
12931
13828
|
});
|
|
12932
13829
|
}
|
|
12933
13830
|
awaitingSecret = new Map;
|
|
13831
|
+
async captureSecret(sessionId, msg, text) {
|
|
13832
|
+
const seq = this.awaitingSecret.get(sessionId);
|
|
13833
|
+
if (seq === undefined)
|
|
13834
|
+
return false;
|
|
13835
|
+
this.awaitingSecret.delete(sessionId);
|
|
13836
|
+
const accepted = this.control.answerShellPrompt(sessionId, seq, text);
|
|
13837
|
+
let deleted = false;
|
|
13838
|
+
if (this.api.deleteMessage && msg.chat && msg.message_id !== undefined) {
|
|
13839
|
+
try {
|
|
13840
|
+
await this.api.deleteMessage(msg.chat.id, msg.message_id);
|
|
13841
|
+
deleted = true;
|
|
13842
|
+
} catch (err) {
|
|
13843
|
+
this.log(`secret reply message ${msg.message_id} not deleted: ${String(err)}`);
|
|
13844
|
+
}
|
|
13845
|
+
}
|
|
13846
|
+
await this.sendToSession(sessionId, tHtml(accepted ? "tg.prompt.secretSubmitted" : "tg.prompt.secretStale"));
|
|
13847
|
+
if (!deleted)
|
|
13848
|
+
await this.sendToSession(sessionId, tHtml("tg.prompt.secretUndeleted"));
|
|
13849
|
+
return true;
|
|
13850
|
+
}
|
|
12934
13851
|
async onChoiceRequest(sessionId, seq, questions, askId, onboarding2 = false) {
|
|
12935
13852
|
const binding = this.control.findBindingBySession(sessionId);
|
|
12936
13853
|
if (!binding || questions.length === 0)
|
|
@@ -13076,6 +13993,10 @@ ${code(prompt.line)}`,
|
|
|
13076
13993
|
if (!msg.from || !this.config.allowedUserIds.includes(msg.from.id))
|
|
13077
13994
|
return;
|
|
13078
13995
|
const binding = msg.message_thread_id ? this.control.findBindingByTopic(msg.chat.id, msg.message_thread_id) : undefined;
|
|
13996
|
+
if (msg.message_thread_id !== undefined && binding === undefined) {
|
|
13997
|
+
this.log(`ignored update in topic ${msg.message_thread_id}: not this machine's`);
|
|
13998
|
+
return;
|
|
13999
|
+
}
|
|
13079
14000
|
const media = readMedia(msg);
|
|
13080
14001
|
if (media) {
|
|
13081
14002
|
await this.handleMedia(msg, binding, media);
|
|
@@ -13090,6 +14011,8 @@ ${code(prompt.line)}`,
|
|
|
13090
14011
|
}
|
|
13091
14012
|
if (!binding)
|
|
13092
14013
|
return;
|
|
14014
|
+
if (await this.captureSecret(binding.sessionId, msg, text))
|
|
14015
|
+
return;
|
|
13093
14016
|
if (await this.captureFreeText(binding.sessionId, text))
|
|
13094
14017
|
return;
|
|
13095
14018
|
const chatId = msg.chat.id;
|
|
@@ -13108,28 +14031,47 @@ ${code(prompt.line)}`,
|
|
|
13108
14031
|
return;
|
|
13109
14032
|
}
|
|
13110
14033
|
if (!binding) {
|
|
13111
|
-
await this.refuseMedia(msg, tHtml("tg.inbound.noSession"));
|
|
14034
|
+
await this.refuseMedia(msg, tHtml("tg.inbound.noSession"), "no session bound to the topic");
|
|
13112
14035
|
return;
|
|
13113
14036
|
}
|
|
13114
14037
|
const plan = planInboundAttachment(media.candidate, this.inboundLimits);
|
|
13115
14038
|
if (!plan.ok) {
|
|
13116
|
-
|
|
14039
|
+
const text = tHtml(REFUSAL_KEYS[plan.reason], { limit: megabytes2(this.inboundLimits.maxBytes) });
|
|
14040
|
+
await this.refuseMedia(msg, text, plan.reason);
|
|
13117
14041
|
return;
|
|
13118
14042
|
}
|
|
13119
14043
|
await this.react(msg.chat?.id, msg.message_id, "received");
|
|
13120
14044
|
this.bufferMedia(msg, binding.sessionId, { fileId: media.fileId, name: plan.name, kind: plan.kind, messageId: msg.message_id });
|
|
13121
14045
|
}
|
|
13122
|
-
async refuseMedia(msg, text) {
|
|
14046
|
+
async refuseMedia(msg, text, reason) {
|
|
13123
14047
|
const chatId = msg.chat?.id;
|
|
14048
|
+
const where = `chat ${chatId ?? "?"} topic ${msg.message_thread_id ?? "general"}`;
|
|
14049
|
+
const album = msg.media_group_id !== undefined ? ` album ${msg.media_group_id}` : "";
|
|
14050
|
+
this.log(`inbound refused (${reason}) in ${where}${album}, message ${msg.message_id ?? "?"}`);
|
|
13124
14051
|
await this.react(chatId, msg.message_id, "failed");
|
|
13125
14052
|
if (chatId === undefined)
|
|
13126
14053
|
return;
|
|
14054
|
+
if (this.albumAlreadyRefused(msg, reason))
|
|
14055
|
+
return;
|
|
14056
|
+
const attributed = msg.message_thread_id === undefined ? `[${escapeHtml(this.config.machine.name)}] ${text}` : text;
|
|
13127
14057
|
await sendFormatted(this.api, {
|
|
13128
14058
|
chat_id: chatId,
|
|
13129
14059
|
...msg.message_thread_id !== undefined ? { message_thread_id: msg.message_thread_id } : {},
|
|
13130
|
-
text
|
|
14060
|
+
text: attributed
|
|
13131
14061
|
}).catch((err) => this.log(`inbound refusal failed: ${String(err)}`));
|
|
13132
14062
|
}
|
|
14063
|
+
albumAlreadyRefused(msg, reason) {
|
|
14064
|
+
const group = msg.media_group_id;
|
|
14065
|
+
if (group === undefined)
|
|
14066
|
+
return false;
|
|
14067
|
+
const key = `${group}\x00${reason}`;
|
|
14068
|
+
const pending = this.refusedAlbums.get(key);
|
|
14069
|
+
clearTimeout(pending);
|
|
14070
|
+
const timer = setTimeout(() => this.refusedAlbums.delete(key), this.albumDebounceMs);
|
|
14071
|
+
timer.unref?.();
|
|
14072
|
+
this.refusedAlbums.set(key, timer);
|
|
14073
|
+
return pending !== undefined;
|
|
14074
|
+
}
|
|
13133
14075
|
bufferMedia(msg, sessionId, item) {
|
|
13134
14076
|
const group = msg.media_group_id;
|
|
13135
14077
|
const caption = msg.caption?.trim();
|
|
@@ -13177,8 +14119,8 @@ ${code(prompt.line)}`,
|
|
|
13177
14119
|
throw new Error("this Telegram client cannot download files");
|
|
13178
14120
|
const files = [];
|
|
13179
14121
|
for (const item of batch.items) {
|
|
13180
|
-
const
|
|
13181
|
-
files.push({ path:
|
|
14122
|
+
const path23 = await this.control.stageInboundFile(batch.sessionId, item.name, await download2(item.fileId));
|
|
14123
|
+
files.push({ path: path23, kind: item.kind });
|
|
13182
14124
|
}
|
|
13183
14125
|
await this.control.prompt(batch.sessionId, buildAttachmentPrompt(files, batch.caption));
|
|
13184
14126
|
this.log(`staged ${files.length} inbound file(s) for ${batch.sessionId}`);
|
|
@@ -13301,8 +14243,15 @@ ${code(prompt.line)}`,
|
|
|
13301
14243
|
const project = await resolveProject(args[0]);
|
|
13302
14244
|
if (!project)
|
|
13303
14245
|
return;
|
|
13304
|
-
const
|
|
13305
|
-
|
|
14246
|
+
const tail = args.slice(1);
|
|
14247
|
+
const flagAt = tail.findIndex((token) => token.startsWith("-"));
|
|
14248
|
+
const name = (flagAt < 0 ? tail : tail.slice(0, flagAt)).join(" ") || undefined;
|
|
14249
|
+
const agentArgs = flagAt < 0 ? [] : tail.slice(flagAt);
|
|
14250
|
+
await this.control.createSession({
|
|
14251
|
+
cwd: project.path,
|
|
14252
|
+
...name !== undefined ? { name } : {},
|
|
14253
|
+
...agentArgs.length > 0 ? { agentArgs } : {}
|
|
14254
|
+
});
|
|
13306
14255
|
return;
|
|
13307
14256
|
}
|
|
13308
14257
|
case "/wt": {
|
|
@@ -13572,13 +14521,13 @@ var init_src2 = __esm(() => {
|
|
|
13572
14521
|
});
|
|
13573
14522
|
|
|
13574
14523
|
// packages/daemon/src/machine-identity.ts
|
|
13575
|
-
import
|
|
14524
|
+
import os8 from "os";
|
|
13576
14525
|
function resolveMachineIdentity(override) {
|
|
13577
|
-
const hostname =
|
|
14526
|
+
const hostname = os8.hostname();
|
|
13578
14527
|
return {
|
|
13579
14528
|
name: slugifyMachineName(override && override.trim().length > 0 ? override : hostname),
|
|
13580
14529
|
hostname,
|
|
13581
|
-
addresses: collectAddresses(
|
|
14530
|
+
addresses: collectAddresses(os8.networkInterfaces())
|
|
13582
14531
|
};
|
|
13583
14532
|
}
|
|
13584
14533
|
var init_machine_identity = __esm(() => {
|
|
@@ -13590,13 +14539,15 @@ class TelegramService {
|
|
|
13590
14539
|
config;
|
|
13591
14540
|
store;
|
|
13592
14541
|
manager;
|
|
14542
|
+
apiFactory;
|
|
13593
14543
|
channel;
|
|
13594
14544
|
unsubscribe;
|
|
13595
14545
|
runningConfig;
|
|
13596
|
-
constructor(config, store, manager) {
|
|
14546
|
+
constructor(config, store, manager, apiFactory = (token) => new FetchTelegramApi(token)) {
|
|
13597
14547
|
this.config = config;
|
|
13598
14548
|
this.store = store;
|
|
13599
14549
|
this.manager = manager;
|
|
14550
|
+
this.apiFactory = apiFactory;
|
|
13600
14551
|
}
|
|
13601
14552
|
readSettings() {
|
|
13602
14553
|
const telegram = this.config.resolved().channels.telegram;
|
|
@@ -13645,7 +14596,7 @@ class TelegramService {
|
|
|
13645
14596
|
const token = botToken ?? this.readSettings().botToken;
|
|
13646
14597
|
if (!token)
|
|
13647
14598
|
throw new AxtermError("err.telegram.noToken");
|
|
13648
|
-
const api =
|
|
14599
|
+
const api = this.apiFactory(token);
|
|
13649
14600
|
const updates = await api.call("getUpdates", { timeout: 0 });
|
|
13650
14601
|
return discoverTelegramTargets(updates);
|
|
13651
14602
|
}
|
|
@@ -13660,7 +14611,7 @@ class TelegramService {
|
|
|
13660
14611
|
log("telegram: not configured, channel not started");
|
|
13661
14612
|
return false;
|
|
13662
14613
|
}
|
|
13663
|
-
const api =
|
|
14614
|
+
const api = this.apiFactory(s.botToken);
|
|
13664
14615
|
const config = {
|
|
13665
14616
|
groupId: s.groupId,
|
|
13666
14617
|
allowedUserIds: [s.allowUserId],
|
|
@@ -13808,8 +14759,8 @@ var init_telegram_service = __esm(() => {
|
|
|
13808
14759
|
// packages/daemon/src/index.ts
|
|
13809
14760
|
var exports_src = {};
|
|
13810
14761
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
13811
|
-
import
|
|
13812
|
-
import
|
|
14762
|
+
import fs24 from "fs";
|
|
14763
|
+
import net5 from "net";
|
|
13813
14764
|
function isPidAlive(pid) {
|
|
13814
14765
|
try {
|
|
13815
14766
|
process.kill(pid, 0);
|
|
@@ -13820,9 +14771,9 @@ function isPidAlive(pid) {
|
|
|
13820
14771
|
}
|
|
13821
14772
|
async function main() {
|
|
13822
14773
|
ensureDataDirs();
|
|
13823
|
-
if (
|
|
14774
|
+
if (fs24.existsSync(RUNTIME_FILE2)) {
|
|
13824
14775
|
try {
|
|
13825
|
-
const existing = JSON.parse(
|
|
14776
|
+
const existing = JSON.parse(fs24.readFileSync(RUNTIME_FILE2, "utf8"));
|
|
13826
14777
|
if (existing.pid !== process.pid && isPidAlive(existing.pid)) {
|
|
13827
14778
|
log(`daemon already running (pid ${existing.pid}); exiting`);
|
|
13828
14779
|
process.exit(0);
|
|
@@ -13845,7 +14796,7 @@ async function main() {
|
|
|
13845
14796
|
version: productVersion(),
|
|
13846
14797
|
startedAt: Date.now()
|
|
13847
14798
|
};
|
|
13848
|
-
|
|
14799
|
+
fs24.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
|
|
13849
14800
|
restrictFile(RUNTIME_FILE2);
|
|
13850
14801
|
const config = new ConfigStore(defaultConfigStorePaths(CONFIG_DIR));
|
|
13851
14802
|
if (config.loadError !== undefined)
|
|
@@ -13897,12 +14848,15 @@ async function main() {
|
|
|
13897
14848
|
hookHost.stop();
|
|
13898
14849
|
store.close();
|
|
13899
14850
|
try {
|
|
13900
|
-
const current = JSON.parse(
|
|
14851
|
+
const current = JSON.parse(fs24.readFileSync(RUNTIME_FILE2, "utf8"));
|
|
13901
14852
|
if (current.pid === process.pid)
|
|
13902
|
-
|
|
14853
|
+
fs24.rmSync(RUNTIME_FILE2);
|
|
13903
14854
|
} catch {}
|
|
13904
14855
|
process.exit(0);
|
|
13905
14856
|
};
|
|
14857
|
+
const recovered = await manager.recover();
|
|
14858
|
+
if (recovered.adopted > 0)
|
|
14859
|
+
log(`boot: ${recovered.adopted} session(s) adopted from the terminal host`);
|
|
13906
14860
|
startRpcServer({
|
|
13907
14861
|
pipePath: PIPE_PATH,
|
|
13908
14862
|
token,
|
|
@@ -13932,14 +14886,219 @@ var init_src3 = __esm(() => {
|
|
|
13932
14886
|
init_session_manager();
|
|
13933
14887
|
init_telegram_service();
|
|
13934
14888
|
init_store();
|
|
13935
|
-
|
|
14889
|
+
net5.setDefaultAutoSelectFamilyAttemptTimeout(3000);
|
|
13936
14890
|
main();
|
|
13937
14891
|
});
|
|
13938
14892
|
|
|
14893
|
+
// packages/daemon/src/pty-host.ts
|
|
14894
|
+
import net6 from "net";
|
|
14895
|
+
function startPtyHost(options) {
|
|
14896
|
+
const sessions = new Map;
|
|
14897
|
+
const clients = new Set;
|
|
14898
|
+
const send = (socket, reply) => {
|
|
14899
|
+
if (!socket.destroyed)
|
|
14900
|
+
socket.write(controlFrame(reply));
|
|
14901
|
+
};
|
|
14902
|
+
const broadcast = (frame) => {
|
|
14903
|
+
for (const client of clients)
|
|
14904
|
+
if (!client.destroyed)
|
|
14905
|
+
client.write(frame);
|
|
14906
|
+
};
|
|
14907
|
+
const server = net6.createServer((socket) => {
|
|
14908
|
+
const decoder = new FrameDecoder;
|
|
14909
|
+
let greeted = false;
|
|
14910
|
+
const drop = (reason) => {
|
|
14911
|
+
log(`pty host: dropping client (${reason})`);
|
|
14912
|
+
socket.destroy();
|
|
14913
|
+
clients.delete(socket);
|
|
14914
|
+
};
|
|
14915
|
+
socket.on("close", () => clients.delete(socket));
|
|
14916
|
+
socket.on("error", () => clients.delete(socket));
|
|
14917
|
+
socket.on("data", (chunk) => {
|
|
14918
|
+
let frames;
|
|
14919
|
+
try {
|
|
14920
|
+
frames = decoder.push(new Uint8Array(chunk));
|
|
14921
|
+
} catch (err) {
|
|
14922
|
+
drop(err instanceof Error ? err.message : String(err));
|
|
14923
|
+
return;
|
|
14924
|
+
}
|
|
14925
|
+
for (const frame of frames) {
|
|
14926
|
+
if (frame.kind === FrameKind.Data) {
|
|
14927
|
+
if (!greeted)
|
|
14928
|
+
return drop("data before hello");
|
|
14929
|
+
const session = sessions.get(frame.id);
|
|
14930
|
+
if (session && !session.exited)
|
|
14931
|
+
session.handle.write(new TextDecoder().decode(frame.payload));
|
|
14932
|
+
continue;
|
|
14933
|
+
}
|
|
14934
|
+
const message = parseControl(frame.payload);
|
|
14935
|
+
if (message === undefined)
|
|
14936
|
+
continue;
|
|
14937
|
+
if (message.t === "hello") {
|
|
14938
|
+
if (message.token !== options.token)
|
|
14939
|
+
return drop("bad token");
|
|
14940
|
+
greeted = true;
|
|
14941
|
+
clients.add(socket);
|
|
14942
|
+
send(socket, {
|
|
14943
|
+
t: "welcome",
|
|
14944
|
+
v: PTY_PROTOCOL_VERSION,
|
|
14945
|
+
sessions: [...sessions.entries()].filter(([, s]) => !s.exited).map(([id, s]) => ({ id, pid: s.handle.pid, cols: s.cols, rows: s.rows }))
|
|
14946
|
+
});
|
|
14947
|
+
continue;
|
|
14948
|
+
}
|
|
14949
|
+
if (!greeted)
|
|
14950
|
+
return drop("control before hello");
|
|
14951
|
+
if (message.t === "spawn") {
|
|
14952
|
+
try {
|
|
14953
|
+
const handle = spawnPty({
|
|
14954
|
+
exe: message.exe,
|
|
14955
|
+
argv: message.argv,
|
|
14956
|
+
cwd: message.cwd,
|
|
14957
|
+
cols: message.cols,
|
|
14958
|
+
rows: message.rows,
|
|
14959
|
+
env: message.env,
|
|
14960
|
+
term: message.term
|
|
14961
|
+
});
|
|
14962
|
+
const session = {
|
|
14963
|
+
handle,
|
|
14964
|
+
cols: message.cols,
|
|
14965
|
+
rows: message.rows,
|
|
14966
|
+
exited: false,
|
|
14967
|
+
mirror: new TerminalMirror(message.cols, message.rows, REPLAY_SCROLLBACK_LINES)
|
|
14968
|
+
};
|
|
14969
|
+
sessions.set(message.id, session);
|
|
14970
|
+
handle.onData((text) => {
|
|
14971
|
+
session.mirror.write(text);
|
|
14972
|
+
broadcast(encodeFrame(FrameKind.Data, message.id, new TextEncoder().encode(text)));
|
|
14973
|
+
});
|
|
14974
|
+
handle.onExit((code2) => {
|
|
14975
|
+
session.exited = true;
|
|
14976
|
+
for (const client of clients)
|
|
14977
|
+
send(client, { t: "exit", id: message.id, code: code2 });
|
|
14978
|
+
sessions.delete(message.id);
|
|
14979
|
+
if (sessions.size === 0)
|
|
14980
|
+
options.onIdle?.();
|
|
14981
|
+
});
|
|
14982
|
+
send(socket, { t: "spawned", id: message.id, pid: handle.pid });
|
|
14983
|
+
} catch (err) {
|
|
14984
|
+
send(socket, { t: "error", id: message.id, message: err instanceof Error ? err.message : String(err) });
|
|
14985
|
+
}
|
|
14986
|
+
continue;
|
|
14987
|
+
}
|
|
14988
|
+
if (message.t === "subscribe") {
|
|
14989
|
+
const session = sessions.get(message.id);
|
|
14990
|
+
if (session === undefined) {
|
|
14991
|
+
send(socket, { t: "error", id: message.id, message: "no such session" });
|
|
14992
|
+
continue;
|
|
14993
|
+
}
|
|
14994
|
+
if (message.replay) {
|
|
14995
|
+
const ansi = session.mirror.serialize(REPLAY_MAX_CHARS);
|
|
14996
|
+
if (ansi.length > 0 && !socket.destroyed) {
|
|
14997
|
+
socket.write(encodeFrame(FrameKind.Data, message.id, new TextEncoder().encode(ansi)));
|
|
14998
|
+
}
|
|
14999
|
+
}
|
|
15000
|
+
send(socket, { t: "subscribed", id: message.id });
|
|
15001
|
+
continue;
|
|
15002
|
+
}
|
|
15003
|
+
if (message.t === "resize") {
|
|
15004
|
+
const session = sessions.get(message.id);
|
|
15005
|
+
if (session === undefined || session.exited)
|
|
15006
|
+
continue;
|
|
15007
|
+
session.cols = message.cols;
|
|
15008
|
+
session.rows = message.rows;
|
|
15009
|
+
session.handle.resize(message.cols, message.rows);
|
|
15010
|
+
session.mirror.resize(message.cols, message.rows);
|
|
15011
|
+
continue;
|
|
15012
|
+
}
|
|
15013
|
+
if (message.t === "kill") {
|
|
15014
|
+
sessions.get(message.id)?.handle.kill();
|
|
15015
|
+
}
|
|
15016
|
+
}
|
|
15017
|
+
});
|
|
15018
|
+
});
|
|
15019
|
+
return new Promise((resolve, reject) => {
|
|
15020
|
+
server.once("error", reject);
|
|
15021
|
+
server.listen(options.endpoint, () => {
|
|
15022
|
+
server.removeListener("error", reject);
|
|
15023
|
+
log(`pty host: listening on ${options.endpoint}`);
|
|
15024
|
+
resolve({
|
|
15025
|
+
endpoint: options.endpoint,
|
|
15026
|
+
get sessionCount() {
|
|
15027
|
+
return sessions.size;
|
|
15028
|
+
},
|
|
15029
|
+
close: async () => {
|
|
15030
|
+
for (const client of clients)
|
|
15031
|
+
client.destroy();
|
|
15032
|
+
clients.clear();
|
|
15033
|
+
for (const session of sessions.values())
|
|
15034
|
+
session.handle.kill();
|
|
15035
|
+
await new Promise((done) => server.close(() => done()));
|
|
15036
|
+
}
|
|
15037
|
+
});
|
|
15038
|
+
});
|
|
15039
|
+
});
|
|
15040
|
+
}
|
|
15041
|
+
var REPLAY_MAX_CHARS, REPLAY_SCROLLBACK_LINES = 5000;
|
|
15042
|
+
var init_pty_host = __esm(() => {
|
|
15043
|
+
init_src();
|
|
15044
|
+
init_paths();
|
|
15045
|
+
init_pty();
|
|
15046
|
+
init_terminal_mirror();
|
|
15047
|
+
REPLAY_MAX_CHARS = 2 * 1024 * 1024;
|
|
15048
|
+
});
|
|
15049
|
+
|
|
15050
|
+
// packages/daemon/src/pty-host-main.ts
|
|
15051
|
+
var exports_pty_host_main = {};
|
|
15052
|
+
__export(exports_pty_host_main, {
|
|
15053
|
+
runPtyHostProcess: () => runPtyHostProcess,
|
|
15054
|
+
PTY_HOST_IDLE_MS: () => PTY_HOST_IDLE_MS
|
|
15055
|
+
});
|
|
15056
|
+
async function runPtyHostProcess() {
|
|
15057
|
+
const endpoint = process.env["AXTERM_PTY_HOST_ENDPOINT"];
|
|
15058
|
+
const token = process.env["AXTERM_PTY_HOST_TOKEN"];
|
|
15059
|
+
const file = process.env["AXTERM_PTY_HOST_FILE"];
|
|
15060
|
+
if (!endpoint || !token || !file) {
|
|
15061
|
+
console.error("axterm __pty-host: AXTERM_PTY_HOST_ENDPOINT, _TOKEN and _FILE are required");
|
|
15062
|
+
process.exitCode = 2;
|
|
15063
|
+
return;
|
|
15064
|
+
}
|
|
15065
|
+
let idleTimer;
|
|
15066
|
+
const server = await startPtyHost({
|
|
15067
|
+
endpoint,
|
|
15068
|
+
token,
|
|
15069
|
+
onIdle: () => armIdleExit()
|
|
15070
|
+
});
|
|
15071
|
+
function armIdleExit() {
|
|
15072
|
+
clearTimeout(idleTimer);
|
|
15073
|
+
idleTimer = setTimeout(() => {
|
|
15074
|
+
if (server.sessionCount > 0)
|
|
15075
|
+
return armIdleExit();
|
|
15076
|
+
log("pty host: idle with no sessions, exiting");
|
|
15077
|
+
server.close().then(() => process.exit(0));
|
|
15078
|
+
}, PTY_HOST_IDLE_MS);
|
|
15079
|
+
idleTimer.unref?.();
|
|
15080
|
+
}
|
|
15081
|
+
writePtyHostFile(file, { pid: process.pid, endpoint, token, version: PTY_PROTOCOL_VERSION });
|
|
15082
|
+
log(`pty host: ready on ${endpoint} (pid ${process.pid})`);
|
|
15083
|
+
armIdleExit();
|
|
15084
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
15085
|
+
process.on(signal, () => {
|
|
15086
|
+
server.close().then(() => process.exit(0));
|
|
15087
|
+
});
|
|
15088
|
+
}
|
|
15089
|
+
}
|
|
15090
|
+
var PTY_HOST_IDLE_MS = 60000;
|
|
15091
|
+
var init_pty_host_main = __esm(() => {
|
|
15092
|
+
init_src();
|
|
15093
|
+
init_paths();
|
|
15094
|
+
init_pty_host_launch();
|
|
15095
|
+
init_pty_host();
|
|
15096
|
+
});
|
|
15097
|
+
|
|
13939
15098
|
// packages/cli/src/index.ts
|
|
13940
15099
|
init_src();
|
|
13941
|
-
import
|
|
13942
|
-
import
|
|
15100
|
+
import fs25 from "fs";
|
|
15101
|
+
import path23 from "path";
|
|
13943
15102
|
|
|
13944
15103
|
// packages/cli/src/attach.ts
|
|
13945
15104
|
import fs from "fs";
|
|
@@ -14541,6 +15700,7 @@ async function ensureDaemon() {
|
|
|
14541
15700
|
|
|
14542
15701
|
// packages/cli/src/hook-bridge.ts
|
|
14543
15702
|
init_src();
|
|
15703
|
+
import fs3 from "fs";
|
|
14544
15704
|
var POST_TIMEOUT_MS = 1500;
|
|
14545
15705
|
function isCodexApproval(agent, body) {
|
|
14546
15706
|
if (agent !== "codex")
|
|
@@ -14565,23 +15725,43 @@ async function relayHookEvent(agent) {
|
|
|
14565
15725
|
if (body === "")
|
|
14566
15726
|
return;
|
|
14567
15727
|
const approval = isCodexApproval(agent, body);
|
|
14568
|
-
const
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
}
|
|
14574
|
-
if (
|
|
15728
|
+
const timeout = approval ? CODEX_APPROVAL_POST_TIMEOUT_MS : POST_TIMEOUT_MS;
|
|
15729
|
+
const post = (to) => fetch(to.url, { method: "POST", headers: to.headers, body, signal: AbortSignal.timeout(timeout) });
|
|
15730
|
+
let res;
|
|
15731
|
+
try {
|
|
15732
|
+
res = await post(target);
|
|
15733
|
+
} catch {}
|
|
15734
|
+
if (res === undefined || res.status === 401 || res.status === 404) {
|
|
15735
|
+
if (approval && res === undefined)
|
|
15736
|
+
return;
|
|
15737
|
+
const fresh = currentEndpoint();
|
|
15738
|
+
const retarget = fresh === undefined ? undefined : hookPostTarget(process.env, agent, fresh);
|
|
15739
|
+
if (retarget !== undefined && retarget.url !== target.url)
|
|
15740
|
+
res = await post(retarget);
|
|
15741
|
+
else if (res === undefined)
|
|
15742
|
+
return;
|
|
15743
|
+
}
|
|
15744
|
+
if (approval && res !== undefined && res.ok) {
|
|
14575
15745
|
const decision = await res.text();
|
|
14576
15746
|
if (decision !== "")
|
|
14577
15747
|
process.stdout.write(decision);
|
|
14578
15748
|
}
|
|
14579
15749
|
} catch {}
|
|
14580
15750
|
}
|
|
15751
|
+
function currentEndpoint() {
|
|
15752
|
+
const file = process.env["AXTERM_HOOK_ENDPOINT"];
|
|
15753
|
+
if (file === undefined)
|
|
15754
|
+
return;
|
|
15755
|
+
try {
|
|
15756
|
+
return parseHookEndpoint(fs3.readFileSync(file, "utf8"));
|
|
15757
|
+
} catch {
|
|
15758
|
+
return;
|
|
15759
|
+
}
|
|
15760
|
+
}
|
|
14581
15761
|
|
|
14582
15762
|
// packages/cli/src/mcp-server.ts
|
|
14583
15763
|
init_src();
|
|
14584
|
-
import
|
|
15764
|
+
import fs4 from "fs";
|
|
14585
15765
|
import path4 from "path";
|
|
14586
15766
|
var ASK_TIMEOUT_MS = 10 * 60000;
|
|
14587
15767
|
var POST_TIMEOUT_MS2 = 5000;
|
|
@@ -14698,7 +15878,7 @@ class McpServer {
|
|
|
14698
15878
|
const contextFile = this.io.env[MCP_CONTEXT_ENV];
|
|
14699
15879
|
if (contextFile !== undefined) {
|
|
14700
15880
|
try {
|
|
14701
|
-
const context = JSON.parse(
|
|
15881
|
+
const context = JSON.parse(fs4.readFileSync(contextFile, "utf8"));
|
|
14702
15882
|
if (context.port && context.token && context.sessionId) {
|
|
14703
15883
|
return {
|
|
14704
15884
|
url: `http://127.0.0.1:${context.port}`,
|
|
@@ -14855,13 +16035,13 @@ function runMcpServer() {
|
|
|
14855
16035
|
init_src();
|
|
14856
16036
|
import { spawnSync } from "child_process";
|
|
14857
16037
|
import { createHash as createHash2 } from "crypto";
|
|
14858
|
-
import
|
|
16038
|
+
import fs5 from "fs";
|
|
14859
16039
|
import path5 from "path";
|
|
14860
16040
|
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
14861
16041
|
// packages/cli/package.json
|
|
14862
16042
|
var package_default = {
|
|
14863
16043
|
name: "@layorixinc/axterm",
|
|
14864
|
-
version: "0.
|
|
16044
|
+
version: "0.6.0",
|
|
14865
16045
|
type: "module",
|
|
14866
16046
|
bin: {
|
|
14867
16047
|
axterm: "dist/cli.js"
|
|
@@ -14935,7 +16115,7 @@ function assetName(platform, arch, isMusl) {
|
|
|
14935
16115
|
function usesMusl() {
|
|
14936
16116
|
if (process.platform !== "linux")
|
|
14937
16117
|
return false;
|
|
14938
|
-
if (
|
|
16118
|
+
if (fs5.existsSync("/etc/alpine-release"))
|
|
14939
16119
|
return true;
|
|
14940
16120
|
const ldd = spawnSync("ldd", ["--version"], { encoding: "utf8" });
|
|
14941
16121
|
return `${ldd.stdout ?? ""}${ldd.stderr ?? ""}`.toLowerCase().includes("musl");
|
|
@@ -14949,7 +16129,7 @@ function firstLine(command, args) {
|
|
|
14949
16129
|
function detectOrigin(execPath) {
|
|
14950
16130
|
const resolved = (() => {
|
|
14951
16131
|
try {
|
|
14952
|
-
return
|
|
16132
|
+
return fs5.realpathSync(execPath);
|
|
14953
16133
|
} catch {
|
|
14954
16134
|
return execPath;
|
|
14955
16135
|
}
|
|
@@ -14991,7 +16171,7 @@ function checksumFor(sums, asset) {
|
|
|
14991
16171
|
return;
|
|
14992
16172
|
}
|
|
14993
16173
|
function sha256(file) {
|
|
14994
|
-
return createHash2("sha256").update(
|
|
16174
|
+
return createHash2("sha256").update(fs5.readFileSync(file)).digest("hex");
|
|
14995
16175
|
}
|
|
14996
16176
|
function ask2(question) {
|
|
14997
16177
|
if (stdin2.isTTY !== true)
|
|
@@ -15038,7 +16218,7 @@ async function runUpdate(client, current, options) {
|
|
|
15038
16218
|
}
|
|
15039
16219
|
const asset = assetName(process.platform, process.arch, usesMusl());
|
|
15040
16220
|
const base = `${RELEASES}/v${latest.version}`;
|
|
15041
|
-
const staging =
|
|
16221
|
+
const staging = fs5.mkdtempSync(`${process.execPath}.update-`);
|
|
15042
16222
|
const downloaded = path5.join(staging, asset);
|
|
15043
16223
|
try {
|
|
15044
16224
|
console.log(t("cli.update.downloading", { asset }));
|
|
@@ -15052,7 +16232,7 @@ async function runUpdate(client, current, options) {
|
|
|
15052
16232
|
return;
|
|
15053
16233
|
}
|
|
15054
16234
|
if (process.platform !== "win32")
|
|
15055
|
-
|
|
16235
|
+
fs5.chmodSync(downloaded, 493);
|
|
15056
16236
|
const reported = spawnSync(downloaded, ["--version"], { encoding: "utf8" }).stdout?.trim();
|
|
15057
16237
|
if (reported !== latest.version) {
|
|
15058
16238
|
console.error(t("cli.update.wrongVersion", { got: reported ?? "nothing", want: latest.version }));
|
|
@@ -15092,7 +16272,7 @@ async function runUpdate(client, current, options) {
|
|
|
15092
16272
|
console.log(t("cli.update.restoreFailed", { id, reason: (result.stderr ?? "").trim() || "unknown" }));
|
|
15093
16273
|
}
|
|
15094
16274
|
} finally {
|
|
15095
|
-
|
|
16275
|
+
fs5.rmSync(staging, { recursive: true, force: true });
|
|
15096
16276
|
}
|
|
15097
16277
|
}
|
|
15098
16278
|
function applySwap(target, downloaded) {
|
|
@@ -15101,16 +16281,16 @@ function applySwap(target, downloaded) {
|
|
|
15101
16281
|
try {
|
|
15102
16282
|
for (const step of steps) {
|
|
15103
16283
|
if (step.op === "chmod")
|
|
15104
|
-
|
|
16284
|
+
fs5.chmodSync(step.from, 493);
|
|
15105
16285
|
else if (step.op === "backup") {
|
|
15106
|
-
|
|
16286
|
+
fs5.renameSync(step.from, step.to);
|
|
15107
16287
|
backup = step.to;
|
|
15108
16288
|
} else
|
|
15109
|
-
|
|
16289
|
+
fs5.renameSync(step.from, step.to);
|
|
15110
16290
|
}
|
|
15111
16291
|
} catch (err) {
|
|
15112
|
-
if (backup !== undefined && !
|
|
15113
|
-
|
|
16292
|
+
if (backup !== undefined && !fs5.existsSync(target))
|
|
16293
|
+
fs5.renameSync(backup, target);
|
|
15114
16294
|
throw err;
|
|
15115
16295
|
}
|
|
15116
16296
|
}
|
|
@@ -15183,7 +16363,7 @@ function printSessionTable(sessions) {
|
|
|
15183
16363
|
console.log(row.map((cell, col) => cell.padEnd(widths[col])).join(" "));
|
|
15184
16364
|
}
|
|
15185
16365
|
}
|
|
15186
|
-
var realPath = (target) =>
|
|
16366
|
+
var realPath = (target) => fs25.realpathSync.native(target);
|
|
15187
16367
|
function scopedHere(sessions) {
|
|
15188
16368
|
return sessionsInScope(sessions, process.cwd(), realPath);
|
|
15189
16369
|
}
|
|
@@ -15196,7 +16376,7 @@ var help = () => t("cli.help");
|
|
|
15196
16376
|
function readDotEnv(file = ".env") {
|
|
15197
16377
|
const out = {};
|
|
15198
16378
|
try {
|
|
15199
|
-
for (const line of
|
|
16379
|
+
for (const line of fs25.readFileSync(file, "utf8").split(/\r?\n/)) {
|
|
15200
16380
|
const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
15201
16381
|
if (m)
|
|
15202
16382
|
out[m[1]] = m[2].trim();
|
|
@@ -15207,7 +16387,7 @@ function readDotEnv(file = ".env") {
|
|
|
15207
16387
|
function configuredLocale() {
|
|
15208
16388
|
try {
|
|
15209
16389
|
const file = resolvePaths(currentPathEnv()).configFile;
|
|
15210
|
-
const raw = JSON.parse(
|
|
16390
|
+
const raw = JSON.parse(fs25.readFileSync(file, "utf8"));
|
|
15211
16391
|
return typeof raw.locale === "string" ? raw.locale : undefined;
|
|
15212
16392
|
} catch {
|
|
15213
16393
|
return;
|
|
@@ -15228,6 +16408,11 @@ async function main2() {
|
|
|
15228
16408
|
runMcpServer();
|
|
15229
16409
|
return;
|
|
15230
16410
|
}
|
|
16411
|
+
if (isPtyHostInvocation(argv)) {
|
|
16412
|
+
const { runPtyHostProcess: runPtyHostProcess2 } = await Promise.resolve().then(() => (init_pty_host_main(), exports_pty_host_main));
|
|
16413
|
+
await runPtyHostProcess2();
|
|
16414
|
+
return;
|
|
16415
|
+
}
|
|
15231
16416
|
const [command, ...rest] = argv;
|
|
15232
16417
|
setLocale(resolveLocale({ configured: configuredLocale(), env: process.env }));
|
|
15233
16418
|
const { flags, positional, passthrough } = parseArgs(rest);
|
|
@@ -15258,6 +16443,7 @@ async function main2() {
|
|
|
15258
16443
|
const commandOverride = typeof flags.get("cmd") === "string" ? flags.get("cmd").split(/\s+/).filter(Boolean) : undefined;
|
|
15259
16444
|
const info = await client.call("session.create", {
|
|
15260
16445
|
cwd: flags.get("cwd") ?? process.cwd(),
|
|
16446
|
+
preferConversationCwd: flags.get("cwd") === undefined,
|
|
15261
16447
|
name: flags.get("name"),
|
|
15262
16448
|
shell: flags.get("shell"),
|
|
15263
16449
|
agent: flags.get("agent"),
|
|
@@ -15351,9 +16537,17 @@ async function main2() {
|
|
|
15351
16537
|
const [id, indexArg] = positional;
|
|
15352
16538
|
if (!id || indexArg === undefined)
|
|
15353
16539
|
throw new Error(t("cli.usage.choose"));
|
|
15354
|
-
const
|
|
15355
|
-
await client.call("
|
|
16540
|
+
const index = Number(indexArg);
|
|
16541
|
+
const answered = await client.call("session.choose", { id, index }).catch(async (err) => {
|
|
16542
|
+
if (!/unknown method/i.test(String(err)))
|
|
16543
|
+
throw err;
|
|
16544
|
+
const keys2 = buildMenuSelectKeys(index);
|
|
16545
|
+
await client.call("terminal.write", { id, dataB64: Buffer.from(keys2, "utf8").toString("base64") });
|
|
16546
|
+
return { answered: "menu" };
|
|
16547
|
+
});
|
|
15356
16548
|
console.log(t("cli.session.selected", { index: indexArg }));
|
|
16549
|
+
if (answered.answered === "onboarding")
|
|
16550
|
+
console.log(t("cli.session.selectedDialog"));
|
|
15357
16551
|
break;
|
|
15358
16552
|
}
|
|
15359
16553
|
case "setup": {
|
|
@@ -15429,8 +16623,8 @@ async function main2() {
|
|
|
15429
16623
|
case "project": {
|
|
15430
16624
|
const sub = positional[0] ?? "list";
|
|
15431
16625
|
if (sub === "add") {
|
|
15432
|
-
const dir = positional[1] ?
|
|
15433
|
-
const alias = positional[2] ??
|
|
16626
|
+
const dir = positional[1] ? path23.resolve(positional[1]) : process.cwd();
|
|
16627
|
+
const alias = positional[2] ?? path23.basename(dir);
|
|
15434
16628
|
const projects = await client.call("project.add", {
|
|
15435
16629
|
alias,
|
|
15436
16630
|
path: dir
|