@layorixinc/axterm 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2085 -721
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -46,6 +46,179 @@ var __export = (target, all) => {
|
|
|
46
46
|
};
|
|
47
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
48
48
|
|
|
49
|
+
// packages/core/src/menu-keys.ts
|
|
50
|
+
function buildMenuSelectKeys(index, normalizePresses = MENU_NORMALIZE_PRESSES) {
|
|
51
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
52
|
+
throw new RangeError(`menu index must be a non-negative integer, got ${index}`);
|
|
53
|
+
}
|
|
54
|
+
return MENU_UP.repeat(normalizePresses) + MENU_DOWN.repeat(index) + MENU_SELECT;
|
|
55
|
+
}
|
|
56
|
+
var MENU_UP = "\x1B[A", MENU_DOWN = "\x1B[B", MENU_SELECT = "\r", MENU_NORMALIZE_PRESSES = 24;
|
|
57
|
+
|
|
58
|
+
// packages/core/src/agent-registry.ts
|
|
59
|
+
function agentProfile(id) {
|
|
60
|
+
const known = PROFILES[id];
|
|
61
|
+
if (known)
|
|
62
|
+
return known;
|
|
63
|
+
return {
|
|
64
|
+
id,
|
|
65
|
+
displayName: id,
|
|
66
|
+
title: {
|
|
67
|
+
workingLabel: id,
|
|
68
|
+
permissionLabel: `${id} - action required`,
|
|
69
|
+
idleLabel: `${id} ready`
|
|
70
|
+
},
|
|
71
|
+
hostHookSource: id
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function menuChoiceKeys(profile, optionIndex) {
|
|
75
|
+
const keys = profile.approvalKeys;
|
|
76
|
+
if (keys !== undefined) {
|
|
77
|
+
return optionIndex === 0 ? keys.allow : optionIndex === 1 ? keys.deny : undefined;
|
|
78
|
+
}
|
|
79
|
+
return profile.clampingApprovalMenu === true ? buildMenuSelectKeys(optionIndex) : undefined;
|
|
80
|
+
}
|
|
81
|
+
function renderAgentTitle(profile, state, frame) {
|
|
82
|
+
if (state === undefined)
|
|
83
|
+
return;
|
|
84
|
+
const { title } = profile;
|
|
85
|
+
if (state === "working") {
|
|
86
|
+
if (title.synthesizeWorking === false)
|
|
87
|
+
return;
|
|
88
|
+
const spinner = SPINNER_FRAMES[(frame % SPINNER_FRAMES.length + SPINNER_FRAMES.length) % SPINNER_FRAMES.length];
|
|
89
|
+
return `${spinner} ${title.workingLabel}`;
|
|
90
|
+
}
|
|
91
|
+
if (title.synthesizeTerminal === false)
|
|
92
|
+
return;
|
|
93
|
+
return state === "done" ? title.idleLabel : title.permissionLabel;
|
|
94
|
+
}
|
|
95
|
+
function oscTitle(title) {
|
|
96
|
+
const safe = title.replace(/[\x00-\x1f\x7f]/g, "").slice(0, MAX_TITLE_LENGTH);
|
|
97
|
+
return `\x1B]0;${safe}\x07`;
|
|
98
|
+
}
|
|
99
|
+
var SPINNER_FRAMES, MENU_APPROVE = "\r", PROFILES, REGISTERED_AGENTS, MAX_TITLE_LENGTH = 200;
|
|
100
|
+
var init_agent_registry = __esm(() => {
|
|
101
|
+
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
102
|
+
PROFILES = {
|
|
103
|
+
omp: {
|
|
104
|
+
id: "omp",
|
|
105
|
+
displayName: "OMP",
|
|
106
|
+
title: {
|
|
107
|
+
workingLabel: "OMP",
|
|
108
|
+
permissionLabel: "OMP - action required",
|
|
109
|
+
idleLabel: "OMP ready",
|
|
110
|
+
synthesizeWorking: false,
|
|
111
|
+
synthesizeTerminal: false
|
|
112
|
+
},
|
|
113
|
+
hostHookSource: "omp",
|
|
114
|
+
clampingApprovalMenu: true,
|
|
115
|
+
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
116
|
+
quota: "gauge",
|
|
117
|
+
resume: { argv: ["--resume", "{ref}"], ref: "sessionFile" }
|
|
118
|
+
},
|
|
119
|
+
pi: {
|
|
120
|
+
id: "pi",
|
|
121
|
+
displayName: "Pi",
|
|
122
|
+
title: { workingLabel: "Pi", permissionLabel: "Pi - action required", idleLabel: "Pi ready" },
|
|
123
|
+
hostHookSource: "pi",
|
|
124
|
+
resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
|
|
125
|
+
},
|
|
126
|
+
codex: {
|
|
127
|
+
id: "codex",
|
|
128
|
+
displayName: "Codex",
|
|
129
|
+
title: {
|
|
130
|
+
workingLabel: "Codex",
|
|
131
|
+
permissionLabel: "Codex - action required",
|
|
132
|
+
idleLabel: "Codex ready",
|
|
133
|
+
synthesizeWorking: false
|
|
134
|
+
},
|
|
135
|
+
hostHookSource: "codex",
|
|
136
|
+
quota: "gauge",
|
|
137
|
+
approvalBypassArgs: ["--dangerously-bypass-approvals-and-sandbox"],
|
|
138
|
+
structuredApprovals: true,
|
|
139
|
+
resume: { argv: ["resume", "{ref}"], ref: "sessionId", form: "subcommand" }
|
|
140
|
+
},
|
|
141
|
+
opencode: {
|
|
142
|
+
id: "opencode",
|
|
143
|
+
displayName: "OpenCode",
|
|
144
|
+
title: {
|
|
145
|
+
workingLabel: "OpenCode",
|
|
146
|
+
permissionLabel: "OpenCode - action required",
|
|
147
|
+
idleLabel: "OpenCode ready",
|
|
148
|
+
synthesizeTerminal: false
|
|
149
|
+
},
|
|
150
|
+
hostHookSource: "opencode",
|
|
151
|
+
resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
|
|
152
|
+
},
|
|
153
|
+
cursor: {
|
|
154
|
+
id: "cursor",
|
|
155
|
+
displayName: "Cursor Agent",
|
|
156
|
+
title: {
|
|
157
|
+
workingLabel: "Cursor Agent",
|
|
158
|
+
permissionLabel: "Cursor - action required",
|
|
159
|
+
idleLabel: "Cursor ready"
|
|
160
|
+
},
|
|
161
|
+
hostHookSource: "cursor",
|
|
162
|
+
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
163
|
+
},
|
|
164
|
+
droid: {
|
|
165
|
+
id: "droid",
|
|
166
|
+
displayName: "Droid",
|
|
167
|
+
title: { workingLabel: "Droid", permissionLabel: "Droid - action required", idleLabel: "Droid ready" },
|
|
168
|
+
hostHookSource: "droid",
|
|
169
|
+
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
170
|
+
},
|
|
171
|
+
claude: {
|
|
172
|
+
id: "claude",
|
|
173
|
+
displayName: "Claude Code",
|
|
174
|
+
title: {
|
|
175
|
+
workingLabel: "Claude",
|
|
176
|
+
permissionLabel: "Claude - action required",
|
|
177
|
+
idleLabel: "Claude ready",
|
|
178
|
+
synthesizeWorking: false,
|
|
179
|
+
synthesizeTerminal: false
|
|
180
|
+
},
|
|
181
|
+
hostHookSource: "claude",
|
|
182
|
+
quota: "event",
|
|
183
|
+
approvalBypassArgs: ["--permission-mode", "bypassPermissions"],
|
|
184
|
+
approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
|
|
185
|
+
structuredApprovals: true,
|
|
186
|
+
resume: { argv: ["--resume", "{ref}"], ref: "sessionId", selfNamed: "uuid" }
|
|
187
|
+
},
|
|
188
|
+
gemini: {
|
|
189
|
+
id: "gemini",
|
|
190
|
+
displayName: "Gemini CLI",
|
|
191
|
+
title: {
|
|
192
|
+
workingLabel: "Gemini",
|
|
193
|
+
permissionLabel: "Gemini - action required",
|
|
194
|
+
idleLabel: "Gemini ready",
|
|
195
|
+
synthesizeWorking: false,
|
|
196
|
+
synthesizeTerminal: false
|
|
197
|
+
},
|
|
198
|
+
hostHookSource: "gemini",
|
|
199
|
+
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
200
|
+
hookAck: "{}",
|
|
201
|
+
approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
|
|
202
|
+
resume: { argv: ["--resume", "{ref}"], ref: "sessionId", selfNamed: "bare" }
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
REGISTERED_AGENTS = Object.keys(PROFILES);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// packages/core/src/session-id.ts
|
|
209
|
+
function uuidFromSessionId(sessionId) {
|
|
210
|
+
const hex = sessionId.replace(/-/g, "");
|
|
211
|
+
if (hex.length !== 32)
|
|
212
|
+
throw new Error(`session id is not 32 hex characters: ${sessionId}`);
|
|
213
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
214
|
+
}
|
|
215
|
+
function sessionIdFromUuid(ref) {
|
|
216
|
+
if (!ref.includes("-"))
|
|
217
|
+
return;
|
|
218
|
+
const stripped = ref.replace(/-/g, "");
|
|
219
|
+
return /^[0-9a-f]{4,32}$/i.test(stripped) ? stripped.toLowerCase() : undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
49
222
|
// packages/core/src/mcp-config.ts
|
|
50
223
|
function mcpServerEntry(launch, contextPath) {
|
|
51
224
|
return {
|
|
@@ -76,18 +249,6 @@ function codexMcpArgs(launch, contextPath) {
|
|
|
76
249
|
var MCP_SERVER_NAME = "axterm", MCP_CONTEXT_ENV = "AXTERM_MCP_CONTEXT";
|
|
77
250
|
|
|
78
251
|
// packages/core/src/agent-launch.ts
|
|
79
|
-
function uuidFromSessionId(sessionId) {
|
|
80
|
-
const hex = sessionId.replace(/-/g, "");
|
|
81
|
-
if (hex.length !== 32)
|
|
82
|
-
throw new Error(`session id is not 32 hex characters: ${sessionId}`);
|
|
83
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
84
|
-
}
|
|
85
|
-
function sessionIdFromUuid(ref) {
|
|
86
|
-
if (!ref.includes("-"))
|
|
87
|
-
return;
|
|
88
|
-
const stripped = ref.replace(/-/g, "");
|
|
89
|
-
return /^[0-9a-f]{4,32}$/i.test(stripped) ? stripped.toLowerCase() : undefined;
|
|
90
|
-
}
|
|
91
252
|
function withAppServer(argv, url) {
|
|
92
253
|
const out = [];
|
|
93
254
|
for (let i = 0;i < argv.length; i++) {
|
|
@@ -102,7 +263,13 @@ function withAppServer(argv, url) {
|
|
|
102
263
|
return [out[0], "--remote", url, ...out.slice(1)];
|
|
103
264
|
}
|
|
104
265
|
function resumesAConversation(args) {
|
|
105
|
-
return args.some((arg) =>
|
|
266
|
+
return args.some((arg) => RESUME_FLAGS.has(arg) || arg.startsWith("--resume=") || arg.startsWith("--continue="));
|
|
267
|
+
}
|
|
268
|
+
function namingFlag(agent, sessionId, extra) {
|
|
269
|
+
const named = agentProfile(agent).resume?.selfNamed;
|
|
270
|
+
if (named === undefined || resumesAConversation(extra))
|
|
271
|
+
return [];
|
|
272
|
+
return ["--session-id", named === "uuid" ? uuidFromSessionId(sessionId) : sessionId];
|
|
106
273
|
}
|
|
107
274
|
function buildAgentCommand(input) {
|
|
108
275
|
const extra = [...input.extraArgs ?? []];
|
|
@@ -112,17 +279,14 @@ function buildAgentCommand(input) {
|
|
|
112
279
|
"--settings",
|
|
113
280
|
input.claudeSettingsPath,
|
|
114
281
|
...input.mcpConfigPath === undefined ? [] : ["--mcp-config", input.mcpConfigPath],
|
|
115
|
-
"
|
|
116
|
-
uuidFromSessionId(input.sessionId),
|
|
117
|
-
...resumesAConversation(extra) ? ["--fork-session"] : [],
|
|
282
|
+
...namingFlag("claude", input.sessionId, extra),
|
|
118
283
|
...extra
|
|
119
284
|
];
|
|
120
285
|
}
|
|
121
286
|
if (input.agent === "gemini") {
|
|
122
287
|
return [
|
|
123
288
|
"gemini",
|
|
124
|
-
"
|
|
125
|
-
input.sessionId,
|
|
289
|
+
...namingFlag("gemini", input.sessionId, extra),
|
|
126
290
|
...extra
|
|
127
291
|
];
|
|
128
292
|
}
|
|
@@ -159,61 +323,16 @@ function resolveAgentArgs(input) {
|
|
|
159
323
|
});
|
|
160
324
|
return groups.filter((group, index) => group.flag === undefined || lastIndex.get(group.flag) === index).flatMap((group) => group.tokens);
|
|
161
325
|
}
|
|
162
|
-
function operatorArgsFrom(storedArgv) {
|
|
163
|
-
if (storedArgv === undefined || storedArgv.length === 0)
|
|
164
|
-
return [];
|
|
165
|
-
const kept = [];
|
|
166
|
-
for (let i = 1;i < storedArgv.length; i++) {
|
|
167
|
-
const token = storedArgv[i];
|
|
168
|
-
const width = AXTERM_OWNED_FLAGS[token];
|
|
169
|
-
if (width !== undefined) {
|
|
170
|
-
i += width - 1;
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
if (/^--(resume|continue)=/.test(token))
|
|
174
|
-
continue;
|
|
175
|
-
kept.push(token);
|
|
176
|
-
}
|
|
177
|
-
return kept;
|
|
178
|
-
}
|
|
179
|
-
function buildAgentResumeCommand(input) {
|
|
180
|
-
const operator = input.extraArgs ?? operatorArgsFrom(input.storedArgv);
|
|
181
|
-
if (input.agent === "claude") {
|
|
182
|
-
return [
|
|
183
|
-
"claude",
|
|
184
|
-
"--settings",
|
|
185
|
-
input.claudeSettingsPath,
|
|
186
|
-
...input.mcpConfigPath === undefined ? [] : ["--mcp-config", input.mcpConfigPath],
|
|
187
|
-
...operator,
|
|
188
|
-
"--resume",
|
|
189
|
-
uuidFromSessionId(input.sessionId)
|
|
190
|
-
];
|
|
191
|
-
}
|
|
192
|
-
if (input.agent === "gemini") {
|
|
193
|
-
return ["gemini", ...operator, "--resume", input.sessionId];
|
|
194
|
-
}
|
|
195
|
-
return buildAgentCommand({ ...input, extraArgs: operator });
|
|
196
|
-
}
|
|
197
326
|
function isAgentId(value) {
|
|
198
327
|
return AGENT_IDS.includes(value);
|
|
199
328
|
}
|
|
200
329
|
function supportedAgents() {
|
|
201
330
|
return AGENT_IDS;
|
|
202
331
|
}
|
|
203
|
-
var
|
|
332
|
+
var RESUME_FLAGS, AGENT_IDS;
|
|
204
333
|
var init_agent_launch = __esm(() => {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
"--settings": 2,
|
|
208
|
-
"--mcp-config": 2,
|
|
209
|
-
"--session-id": 2,
|
|
210
|
-
"--extension": 2,
|
|
211
|
-
"--fork-session": 1,
|
|
212
|
-
"--resume": 2,
|
|
213
|
-
"-r": 2,
|
|
214
|
-
"--continue": 1,
|
|
215
|
-
"-c": 1
|
|
216
|
-
};
|
|
334
|
+
init_agent_registry();
|
|
335
|
+
RESUME_FLAGS = new Set(["--resume", "-r", "--continue", "-c"]);
|
|
217
336
|
AGENT_IDS = ["omp", "claude", "codex", "gemini"];
|
|
218
337
|
});
|
|
219
338
|
|
|
@@ -917,6 +1036,7 @@ usage:
|
|
|
917
1036
|
axterm rm <id\u2026> [--force] forget a session: its row, scrollback and topic binding
|
|
918
1037
|
axterm rm --exited [--all] forget every exited session this listing would show
|
|
919
1038
|
axterm daemon status|stop
|
|
1039
|
+
axterm service install|status|uninstall start at login and restart daemon failures
|
|
920
1040
|
axterm project add <dir> [alias] | list | rm <alias> register a project for channel use
|
|
921
1041
|
axterm run [--worktree BRANCH] [--base REF] ... worktree-backed session
|
|
922
1042
|
axterm fanout --count 3 [--prefix try] "prompt"
|
|
@@ -944,6 +1064,8 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
944
1064
|
"cli.session.sent": "sent",
|
|
945
1065
|
"cli.session.sentKey": "sent {key}",
|
|
946
1066
|
"cli.session.selected": "selected option {index}",
|
|
1067
|
+
"cli.session.selectedDialog": "answered the agent's first-run dialog",
|
|
1068
|
+
"cli.choose.daemonTooOld": "this daemon is too old to answer a dialog safely \u2014 its `choose` sent keystrokes that land on the row below on claude and gemini, which is a wider grant. Restart the daemon, or send the keys yourself with `axterm key`",
|
|
947
1069
|
"cli.resume.done": "resumed session {id} ({command})",
|
|
948
1070
|
"cli.update.current": "axterm {version} is the latest version",
|
|
949
1071
|
"cli.update.available": "axterm {latest} is available (you have {current})",
|
|
@@ -987,6 +1109,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
987
1109
|
"cli.usage.worktree": "usage: axterm worktree list|rm <branch> [--force]",
|
|
988
1110
|
"cli.usage.worktreeRm": "usage: axterm worktree rm <branch> [--force]",
|
|
989
1111
|
"cli.usage.daemon": "usage: axterm daemon status|stop",
|
|
1112
|
+
"cli.usage.service": "usage: axterm service install|status|uninstall",
|
|
990
1113
|
"cli.usage.telegram": "usage: axterm telegram link|doctor|status|discover",
|
|
991
1114
|
"cli.usage.fanout": "usage: axterm fanout --count N <prompt>",
|
|
992
1115
|
"cli.config.set": "set {path}",
|
|
@@ -1003,6 +1126,13 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
1003
1126
|
"cli.worktree.refused": "refused: {reason}",
|
|
1004
1127
|
"cli.daemon.stopping": "daemon stopping",
|
|
1005
1128
|
"cli.daemon.notReady": "daemon did not become ready within 15s (see {log})",
|
|
1129
|
+
"cli.service.installed": "installed {kind} service; the daemon is running under the supervisor",
|
|
1130
|
+
"cli.service.uninstalled": "removed service registration; the current daemon keeps running",
|
|
1131
|
+
"cli.service.status": "service: {kind}; installed={installed}; active={active}",
|
|
1132
|
+
"cli.service.yes": "yes",
|
|
1133
|
+
"cli.service.no": "no",
|
|
1134
|
+
"cli.service.commandFailed": "service command failed ({command}): {error}",
|
|
1135
|
+
"cli.service.notReady": "the service started but the daemon was not ready within 15s",
|
|
1006
1136
|
"cli.telegram.missingSettings": "telegram link: no token/group/user found. Provide --token/--group/--user flags, TELEGRAM_* environment variables, or a .env file in the current directory.",
|
|
1007
1137
|
"cli.telegram.discoverHint": "Create a group with Topics enabled, add your bot to it, then run this again. Being added is enough \u2014 you do not have to send anything.",
|
|
1008
1138
|
"cli.telegram.discoverNone": "the bot cannot see any group yet.",
|
|
@@ -1013,7 +1143,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
1013
1143
|
"err.telegram.noToken": "no bot token is configured \u2014 run `axterm setup` or pass --token",
|
|
1014
1144
|
"tg.help.general.header": "axterm commands (General topic):",
|
|
1015
1145
|
"tg.help.general.projects": "/projects \u2014 registered projects",
|
|
1016
|
-
"tg.help.general.new": "/new <project> [name] \u2014 new
|
|
1146
|
+
"tg.help.general.new": "/new <project> [name] [-- agent flags] \u2014 new session (e.g. --resume <conversation>)",
|
|
1017
1147
|
"tg.help.general.wt": "/wt <project> <branch> \u2014 worktree session",
|
|
1018
1148
|
"tg.help.general.fanout": "/fanout <project> <count|branches> <prompt> \u2014 run in parallel",
|
|
1019
1149
|
"tg.help.general.worktrees": "/worktrees <project> \xB7 /rm <project> <branch> [force]",
|
|
@@ -1033,8 +1163,13 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
1033
1163
|
"tg.status.inferred": "\u2139\uFE0F Guessed from what is running under the shell \u2014 this agent reports nothing, so there are no approval buttons or replies here (axterm attach shows everything).",
|
|
1034
1164
|
"tg.session.emptyScreen": "(empty screen)",
|
|
1035
1165
|
"tg.prompt.confirm": "the session is waiting on a confirmation:",
|
|
1166
|
+
"tg.poll.conflict": "\u26A0\uFE0F {machine} cannot receive messages: another axterm is polling this bot token, and Telegram allows only one. Stop the other instance, or give this machine its own bot.",
|
|
1167
|
+
"tg.poll.failed": "\u26A0\uFE0F {machine} cannot receive messages right now: {reason}",
|
|
1036
1168
|
"tg.prompt.secretAsk": "the session is asking for a secret. Reply with it and the message will be deleted:",
|
|
1037
1169
|
"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",
|
|
1170
|
+
"tg.prompt.secretSubmitted": "the secret reached the session",
|
|
1171
|
+
"tg.prompt.secretStale": "that prompt is no longer waiting, so nothing was typed into the session",
|
|
1172
|
+
"tg.prompt.secretUndeleted": "\u26A0\uFE0F Telegram refused to delete that message \u2014 the secret is still in the chat; delete it yourself",
|
|
1038
1173
|
"tg.session.screenFile": "terminal screen",
|
|
1039
1174
|
"tg.session.none": "no sessions",
|
|
1040
1175
|
"tg.prompt.failed": "\u26A0\uFE0F Delivery failed: {error}",
|
|
@@ -1079,6 +1214,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
1079
1214
|
"tg.answer.pickAtLeastOne": "pick at least one",
|
|
1080
1215
|
"tg.answer.confirmed": "confirmed",
|
|
1081
1216
|
"tg.answer.unknownOption": "no such option",
|
|
1217
|
+
"tg.answer.noSafeKey": "axterm cannot press this row safely \u2014 use /key or /type",
|
|
1082
1218
|
"tg.answer.picked": "{label} selected",
|
|
1083
1219
|
"tg.answer.toggledOn": "{label} selected",
|
|
1084
1220
|
"tg.answer.toggledOff": "{label} cleared",
|
|
@@ -1127,6 +1263,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1127
1263
|
"doctor.codex.trustStale": "codex hooks: {stale}/{total} trust hashes stale \u2014 codex reports them Modified and silently skips them; re-grant via /hooks in the codex TUI",
|
|
1128
1264
|
"doctor.codex.trustPending": "codex hooks: {pending}/{total} not yet granted \u2014 appended at the next codex launch",
|
|
1129
1265
|
"doctor.telegram.ok": "telegram: connected",
|
|
1266
|
+
"doctor.telegram.pollFailing": "telegram cannot receive: {reason}",
|
|
1130
1267
|
"doctor.telegram.off": "telegram: not running",
|
|
1131
1268
|
"doctor.telegram.problems": "telegram: {problems}",
|
|
1132
1269
|
"quota.gauge": "{agent}: {percent}% of {window} used, resets in {resets}",
|
|
@@ -1152,10 +1289,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1152
1289
|
"err.session.dialogOpen": "session {id} is waiting on a dialog ({state}); answer it with the buttons or /esc first",
|
|
1153
1290
|
"err.agent.notInstalled": "the agent command '{command}' was not found on PATH \u2014 install it, or pass --cmd to run something else",
|
|
1154
1291
|
"err.agent.unknown": "unknown agent '{agent}' \u2014 supported: {supported}",
|
|
1292
|
+
"err.choose.noSafeKey": "axterm cannot select row {index} of a {agent} dialog: its menu wraps, so an absolute row cannot be addressed from an unknown cursor position \u2014 use `axterm key` for the keys you want",
|
|
1155
1293
|
"err.staging.dir": "could not create the session's staging directory {dir} ({error}) \u2014 fix the permissions, or set defaults.staging to runtime",
|
|
1156
1294
|
"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",
|
|
1295
|
+
"err.claude.conversationBusy": "session {session} is already in conversation {id} \u2014 two sessions writing one transcript would corrupt it; kill that one first",
|
|
1157
1296
|
"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",
|
|
1158
|
-
"
|
|
1297
|
+
"agent.approval.denied": "Denied from the axterm channel",
|
|
1159
1298
|
"err.codex.noAuth": "codex is not logged in ({authFile} not found) \u2014 run `codex login` once, then start the session again",
|
|
1160
1299
|
"err.codex.authLink": "could not link codex credentials into axterm's CODEX_HOME ({reason}) \u2014 refusing to start a logged-out agent",
|
|
1161
1300
|
"health.config.invalid": "the configuration file could not be understood",
|
|
@@ -1218,6 +1357,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1218
1357
|
axterm rm <id\u2026> [--force] \uC138\uC158\uC744 \uC78A\uB294\uB2E4: \uBAA9\uB85D\xB7\uC2A4\uD06C\uB864\uBC31\xB7\uD1A0\uD53D \uC5F0\uACB0\uAE4C\uC9C0
|
|
1219
1358
|
axterm rm --exited [--all] \uC774 \uBAA9\uB85D\uC5D0 \uBCF4\uC774\uB294 \uC885\uB8CC\uB41C \uC138\uC158\uC744 \uBAA8\uB450 \uC78A\uB294\uB2E4
|
|
1220
1359
|
axterm daemon status|stop
|
|
1360
|
+
axterm service install|status|uninstall \uB85C\uADF8\uC778 \uC2DC \uC2DC\uC791\uD558\uACE0 \uB370\uBAAC \uC2E4\uD328 \uC2DC \uC7AC\uC2DC\uC791
|
|
1221
1361
|
axterm project add <\uD3F4\uB354> [\uBCC4\uCE6D] | list | rm <\uBCC4\uCE6D> \uCC44\uB110\uC5D0\uC11C \uC4F8 \uD504\uB85C\uC81D\uD2B8 \uB4F1\uB85D
|
|
1222
1362
|
axterm run [--worktree \uBE0C\uB79C\uCE58] [--base REF] ... worktree \uAE30\uBC18 \uC138\uC158
|
|
1223
1363
|
axterm fanout --count 3 [--prefix try] "\uD504\uB86C\uD504\uD2B8"
|
|
@@ -1245,6 +1385,8 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1245
1385
|
"cli.session.sent": "\uC804\uB2EC\uB428",
|
|
1246
1386
|
"cli.session.sentKey": "{key} \uC804\uB2EC\uB428",
|
|
1247
1387
|
"cli.session.selected": "{index}\uBC88 \uD56D\uBAA9 \uC120\uD0DD\uB428",
|
|
1388
|
+
"cli.session.selectedDialog": "\uC5D0\uC774\uC804\uD2B8\uC758 \uCCAB \uC2E4\uD589 \uB300\uD654\uC0C1\uC790\uC5D0 \uC751\uB2F5\uD588\uC2B5\uB2C8\uB2E4",
|
|
1389
|
+
"cli.choose.daemonTooOld": "\uC774 \uB370\uBAAC\uC740 \uB300\uD654\uC0C1\uC790\uB97C \uC548\uC804\uD558\uAC8C \uC751\uB2F5\uD560 \uC218 \uC5C6\uB294 \uAD6C\uBC84\uC804\uC785\uB2C8\uB2E4. \uC608\uC804 `choose` \uB294 claude \uC640 gemini \uC5D0\uC11C \uD55C \uD589 \uC544\uB798\uB97C \uC120\uD0DD\uD588\uACE0 \uADF8\uAC83\uC740 \uB354 \uB113\uC740 \uC2B9\uC778\uC785\uB2C8\uB2E4. \uB370\uBAAC\uC744 \uC7AC\uC2DC\uC791\uD558\uAC70\uB098 `axterm key` \uB85C \uC9C1\uC811 \uD0A4\uB97C \uBCF4\uB0B4\uC138\uC694",
|
|
1248
1390
|
"cli.resume.done": "\uC138\uC158 {id} \uC7AC\uAC1C\uB428 ({command})",
|
|
1249
1391
|
"cli.update.current": "axterm {version} \uC774 \uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4",
|
|
1250
1392
|
"cli.update.available": "axterm {latest} \uC744(\uB97C) \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4 (\uD604\uC7AC {current})",
|
|
@@ -1288,6 +1430,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1288
1430
|
"cli.usage.worktree": "\uC0AC\uC6A9\uBC95: axterm worktree list|rm <\uBE0C\uB79C\uCE58> [--force]",
|
|
1289
1431
|
"cli.usage.worktreeRm": "\uC0AC\uC6A9\uBC95: axterm worktree rm <\uBE0C\uB79C\uCE58> [--force]",
|
|
1290
1432
|
"cli.usage.daemon": "\uC0AC\uC6A9\uBC95: axterm daemon status|stop",
|
|
1433
|
+
"cli.usage.service": "\uC0AC\uC6A9\uBC95: axterm service install|status|uninstall",
|
|
1291
1434
|
"cli.usage.telegram": "\uC0AC\uC6A9\uBC95: axterm telegram link|doctor|status|discover",
|
|
1292
1435
|
"cli.usage.fanout": "\uC0AC\uC6A9\uBC95: axterm fanout --count N <\uD504\uB86C\uD504\uD2B8>",
|
|
1293
1436
|
"cli.config.set": "{path} \uC124\uC815\uB428",
|
|
@@ -1304,10 +1447,17 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1304
1447
|
"cli.worktree.refused": "\uAC70\uBD80\uB428: {reason}",
|
|
1305
1448
|
"cli.daemon.stopping": "\uB370\uBAAC \uC885\uB8CC \uC911",
|
|
1306
1449
|
"cli.daemon.notReady": "15\uCD08 \uC548\uC5D0 \uB370\uBAAC\uC774 \uC900\uBE44\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 ({log} \uD655\uC778)",
|
|
1450
|
+
"cli.service.installed": "{kind} \uC11C\uBE44\uC2A4 \uC124\uCE58\uB428. \uB370\uBAAC\uC740 supervisor \uC544\uB798\uC5D0\uC11C \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4",
|
|
1451
|
+
"cli.service.uninstalled": "\uC11C\uBE44\uC2A4 \uB4F1\uB85D\uC744 \uC81C\uAC70\uD588\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uB370\uBAAC\uC740 \uACC4\uC18D \uC2E4\uD589\uB429\uB2C8\uB2E4",
|
|
1452
|
+
"cli.service.status": "\uC11C\uBE44\uC2A4: {kind}; \uC124\uCE58={installed}; \uD65C\uC131={active}",
|
|
1453
|
+
"cli.service.yes": "\uC608",
|
|
1454
|
+
"cli.service.no": "\uC544\uB2C8\uC694",
|
|
1455
|
+
"cli.service.commandFailed": "\uC11C\uBE44\uC2A4 \uBA85\uB839 \uC2E4\uD328 ({command}): {error}",
|
|
1456
|
+
"cli.service.notReady": "\uC11C\uBE44\uC2A4\uAC00 \uC2DC\uC791\uB410\uC9C0\uB9CC 15\uCD08 \uC548\uC5D0 \uB370\uBAAC\uC774 \uC900\uBE44\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4",
|
|
1307
1457
|
"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.",
|
|
1308
1458
|
"tg.help.general.header": "axterm \uBA85\uB839 (General \uD1A0\uD53D):",
|
|
1309
1459
|
"tg.help.general.projects": "/projects \u2014 \uB4F1\uB85D\uB41C \uD504\uB85C\uC81D\uD2B8",
|
|
1310
|
-
"tg.help.general.new": "/new <\uD504\uB85C\uC81D\uD2B8> [\uC774\uB984] \u2014 \uC0C8
|
|
1460
|
+
"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>)",
|
|
1311
1461
|
"tg.help.general.wt": "/wt <\uD504\uB85C\uC81D\uD2B8> <\uBE0C\uB79C\uCE58> \u2014 worktree \uC138\uC158",
|
|
1312
1462
|
"tg.help.general.fanout": "/fanout <\uD504\uB85C\uC81D\uD2B8> <\uAC1C\uC218|\uBE0C\uB79C\uCE58\uB4E4> <\uD504\uB86C\uD504\uD2B8> \u2014 \uBCD1\uB82C \uC2E4\uD589",
|
|
1313
1463
|
"tg.help.general.worktrees": "/worktrees <\uD504\uB85C\uC81D\uD2B8> \xB7 /rm <\uD504\uB85C\uC81D\uD2B8> <\uBE0C\uB79C\uCE58> [force]",
|
|
@@ -1334,8 +1484,13 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1334
1484
|
"tg.status.inferred": "\u2139\uFE0F \uC178\uC5D0\uC11C \uC2E4\uD589 \uC911\uC778 \uD504\uB85C\uC138\uC2A4\uB85C \uCD94\uC815\uD55C \uC0C1\uD0DC\uC785\uB2C8\uB2E4 \u2014 \uC774 \uC5D0\uC774\uC804\uD2B8\uB294 \uC0C1\uD0DC\uB97C \uBCF4\uACE0\uD558\uC9C0 \uC54A\uC544 \uC2B9\uC778 \uBC84\uD2BC\uB3C4, \uB2F5\uBCC0\uB3C4 \uC5C6\uC2B5\uB2C8\uB2E4 (\uC804\uCCB4\uB294 axterm attach\uB85C \uD655\uC778\uD558\uC138\uC694).",
|
|
1335
1485
|
"tg.session.emptyScreen": "(\uBE48 \uD654\uBA74)",
|
|
1336
1486
|
"tg.prompt.confirm": "\uC138\uC158\uC774 \uD655\uC778\uC744 \uAE30\uB2E4\uB9AC\uACE0 \uC788\uC2B5\uB2C8\uB2E4:",
|
|
1487
|
+
"tg.poll.conflict": "\u26A0\uFE0F {machine} \uC774 \uBA54\uC2DC\uC9C0\uB97C \uBC1B\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: \uB2E4\uB978 axterm \uC774 \uC774 \uBD07 \uD1A0\uD070\uC744 \uD3F4\uB9C1\uD558\uACE0 \uC788\uACE0 Telegram \uC740 \uD558\uB098\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4. \uB2E4\uB978 \uC778\uC2A4\uD134\uC2A4\uB97C \uB044\uAC70\uB098 \uC774 \uBA38\uC2E0\uC5D0 \uC804\uC6A9 \uBD07\uC744 \uC8FC\uC138\uC694.",
|
|
1488
|
+
"tg.poll.failed": "\u26A0\uFE0F {machine} \uC774 \uC9C0\uAE08 \uBA54\uC2DC\uC9C0\uB97C \uBC1B\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: {reason}",
|
|
1337
1489
|
"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:",
|
|
1338
1490
|
"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",
|
|
1491
|
+
"tg.prompt.secretSubmitted": "\uBE44\uBC00\uAC12\uC774 \uC138\uC158\uC5D0 \uC804\uB2EC\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
1492
|
+
"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",
|
|
1493
|
+
"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",
|
|
1339
1494
|
"tg.session.screenFile": "\uD130\uBBF8\uB110 \uD654\uBA74",
|
|
1340
1495
|
"tg.session.none": "\uC138\uC158 \uC5C6\uC74C",
|
|
1341
1496
|
"tg.prompt.failed": "\u26A0\uFE0F \uC804\uB2EC \uC2E4\uD328: {error}",
|
|
@@ -1380,6 +1535,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1380
1535
|
"tg.answer.pickAtLeastOne": "\uCD5C\uC18C 1\uAC1C\uB294 \uC120\uD0DD\uD558\uC138\uC694",
|
|
1381
1536
|
"tg.answer.confirmed": "\uD655\uC815\uB428",
|
|
1382
1537
|
"tg.answer.unknownOption": "\uC5C6\uB294 \uC120\uD0DD\uC9C0",
|
|
1538
|
+
"tg.answer.noSafeKey": "\uC774 \uD589\uC740 \uC548\uC804\uD558\uAC8C \uB204\uB97C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 /key \uB610\uB294 /type\uC744 \uC4F0\uC138\uC694",
|
|
1383
1539
|
"tg.answer.picked": "{label} \uC120\uD0DD\uB428",
|
|
1384
1540
|
"tg.answer.toggledOn": "{label} \uC120\uD0DD",
|
|
1385
1541
|
"tg.answer.toggledOff": "{label} \uD574\uC81C",
|
|
@@ -1428,6 +1584,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1428
1584
|
"doctor.codex.trustStale": "codex \uD6C5: {stale}/{total} \uC2E0\uB8B0 \uD574\uC2DC \uBD88\uC77C\uCE58 \u2014 codex\uAC00 Modified\uB85C \uBCF4\uACE0\uD558\uACE0 \uC870\uC6A9\uD788 \uAC74\uB108\uB701\uB2C8\uB2E4; codex TUI\uC758 /hooks\uC5D0\uC11C \uC7AC\uC2B9\uC778\uD558\uC138\uC694",
|
|
1429
1585
|
"doctor.codex.trustPending": "codex \uD6C5: {pending}/{total} \uBBF8\uC2B9\uC778 \u2014 \uB2E4\uC74C codex \uC2E4\uD589 \uC2DC \uAE30\uB85D\uB429\uB2C8\uB2E4",
|
|
1430
1586
|
"doctor.telegram.ok": "\uD154\uB808\uADF8\uB7A8: \uC5F0\uACB0\uB428",
|
|
1587
|
+
"doctor.telegram.pollFailing": "\uD154\uB808\uADF8\uB7A8 \uC218\uC2E0 \uBD88\uAC00: {reason}",
|
|
1431
1588
|
"doctor.telegram.off": "\uD154\uB808\uADF8\uB7A8: \uBBF8\uC2E4\uD589",
|
|
1432
1589
|
"doctor.telegram.problems": "\uD154\uB808\uADF8\uB7A8: {problems}",
|
|
1433
1590
|
"quota.gauge": "{agent}: {window} \uD55C\uB3C4\uC758 {percent}% \uC0AC\uC6A9, {resets} \uD6C4 \uCD08\uAE30\uD654",
|
|
@@ -1453,10 +1610,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1453
1610
|
"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",
|
|
1454
1611
|
"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",
|
|
1455
1612
|
"err.agent.unknown": "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uC774\uC804\uD2B8 '{agent}' \uC785\uB2C8\uB2E4. \uC9C0\uC6D0: {supported}",
|
|
1613
|
+
"err.choose.noSafeKey": "{agent} \uB300\uD654\uC0C1\uC790\uC758 {index}\uBC88 \uD589\uC740 \uC120\uD0DD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA54\uB274\uAC00 \uC21C\uD658\uD558\uBBC0\uB85C \uCEE4\uC11C \uC704\uCE58\uB97C \uBAA8\uB974\uB294 \uC0C1\uD0DC\uC5D0\uC11C \uC808\uB300 \uD589\uC744 \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC6D0\uD558\uB294 \uD0A4\uB294 `axterm key`\uB85C \uBCF4\uB0B4\uC138\uC694",
|
|
1456
1614
|
"err.staging.dir": "\uC138\uC158\uC758 \uC2A4\uD14C\uC774\uC9D5 \uB514\uB809\uD130\uB9AC {dir} \uC744(\uB97C) \uB9CC\uB4E4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 ({error}). \uAD8C\uD55C\uC744 \uACE0\uCE58\uAC70\uB098 defaults.staging \uC744 runtime \uC73C\uB85C \uC124\uC815\uD558\uC138\uC694",
|
|
1457
1615
|
"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",
|
|
1616
|
+
"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",
|
|
1458
1617
|
"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",
|
|
1459
|
-
"
|
|
1618
|
+
"agent.approval.denied": "axterm \uCC44\uB110\uC5D0\uC11C \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
1460
1619
|
"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",
|
|
1461
1620
|
"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",
|
|
1462
1621
|
"health.config.invalid": "\uC124\uC815 \uD30C\uC77C\uC744 \uC774\uD574\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
@@ -1645,144 +1804,16 @@ var init_setup_wizard = __esm(() => {
|
|
|
1645
1804
|
wizardConfig = new WeakMap;
|
|
1646
1805
|
});
|
|
1647
1806
|
|
|
1648
|
-
// packages/core/src/agent-registry.ts
|
|
1649
|
-
function agentProfile(id) {
|
|
1650
|
-
const known = PROFILES[id];
|
|
1651
|
-
if (known)
|
|
1652
|
-
return known;
|
|
1653
|
-
return {
|
|
1654
|
-
id,
|
|
1655
|
-
displayName: id,
|
|
1656
|
-
title: {
|
|
1657
|
-
workingLabel: id,
|
|
1658
|
-
permissionLabel: `${id} - action required`,
|
|
1659
|
-
idleLabel: `${id} ready`
|
|
1660
|
-
},
|
|
1661
|
-
hostHookSource: id
|
|
1662
|
-
};
|
|
1663
|
-
}
|
|
1664
|
-
function renderAgentTitle(profile, state, frame) {
|
|
1665
|
-
if (state === undefined)
|
|
1666
|
-
return;
|
|
1667
|
-
const { title } = profile;
|
|
1668
|
-
if (state === "working") {
|
|
1669
|
-
if (title.synthesizeWorking === false)
|
|
1670
|
-
return;
|
|
1671
|
-
const spinner = SPINNER_FRAMES[(frame % SPINNER_FRAMES.length + SPINNER_FRAMES.length) % SPINNER_FRAMES.length];
|
|
1672
|
-
return `${spinner} ${title.workingLabel}`;
|
|
1673
|
-
}
|
|
1674
|
-
if (title.synthesizeTerminal === false)
|
|
1675
|
-
return;
|
|
1676
|
-
return state === "done" ? title.idleLabel : title.permissionLabel;
|
|
1677
|
-
}
|
|
1678
|
-
function oscTitle(title) {
|
|
1679
|
-
const safe = title.replace(/[\x00-\x1f\x7f]/g, "").slice(0, MAX_TITLE_LENGTH);
|
|
1680
|
-
return `\x1B]0;${safe}\x07`;
|
|
1681
|
-
}
|
|
1682
|
-
var SPINNER_FRAMES, MENU_APPROVE = "\r", PROFILES, REGISTERED_AGENTS, MAX_TITLE_LENGTH = 200;
|
|
1683
|
-
var init_agent_registry = __esm(() => {
|
|
1684
|
-
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1685
|
-
PROFILES = {
|
|
1686
|
-
omp: {
|
|
1687
|
-
id: "omp",
|
|
1688
|
-
displayName: "OMP",
|
|
1689
|
-
title: { workingLabel: "OMP", permissionLabel: "OMP - action required", idleLabel: "OMP ready" },
|
|
1690
|
-
hostHookSource: "omp",
|
|
1691
|
-
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
1692
|
-
quota: "gauge",
|
|
1693
|
-
resume: { argv: ["--resume", "{ref}"], ref: "sessionFile" }
|
|
1694
|
-
},
|
|
1695
|
-
pi: {
|
|
1696
|
-
id: "pi",
|
|
1697
|
-
displayName: "Pi",
|
|
1698
|
-
title: { workingLabel: "Pi", permissionLabel: "Pi - action required", idleLabel: "Pi ready" },
|
|
1699
|
-
hostHookSource: "pi",
|
|
1700
|
-
resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
|
|
1701
|
-
},
|
|
1702
|
-
codex: {
|
|
1703
|
-
id: "codex",
|
|
1704
|
-
displayName: "Codex",
|
|
1705
|
-
title: {
|
|
1706
|
-
workingLabel: "Codex",
|
|
1707
|
-
permissionLabel: "Codex - action required",
|
|
1708
|
-
idleLabel: "Codex ready",
|
|
1709
|
-
synthesizeWorking: false
|
|
1710
|
-
},
|
|
1711
|
-
hostHookSource: "codex",
|
|
1712
|
-
quota: "gauge",
|
|
1713
|
-
approvalBypassArgs: ["--dangerously-bypass-approvals-and-sandbox"],
|
|
1714
|
-
structuredApprovals: true,
|
|
1715
|
-
resume: { argv: ["resume", "{ref}"], ref: "sessionId", form: "subcommand" }
|
|
1716
|
-
},
|
|
1717
|
-
opencode: {
|
|
1718
|
-
id: "opencode",
|
|
1719
|
-
displayName: "OpenCode",
|
|
1720
|
-
title: {
|
|
1721
|
-
workingLabel: "OpenCode",
|
|
1722
|
-
permissionLabel: "OpenCode - action required",
|
|
1723
|
-
idleLabel: "OpenCode ready",
|
|
1724
|
-
synthesizeTerminal: false
|
|
1725
|
-
},
|
|
1726
|
-
hostHookSource: "opencode",
|
|
1727
|
-
resume: { argv: ["--session", "{ref}"], ref: "sessionId" }
|
|
1728
|
-
},
|
|
1729
|
-
cursor: {
|
|
1730
|
-
id: "cursor",
|
|
1731
|
-
displayName: "Cursor Agent",
|
|
1732
|
-
title: {
|
|
1733
|
-
workingLabel: "Cursor Agent",
|
|
1734
|
-
permissionLabel: "Cursor - action required",
|
|
1735
|
-
idleLabel: "Cursor ready"
|
|
1736
|
-
},
|
|
1737
|
-
hostHookSource: "cursor",
|
|
1738
|
-
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1739
|
-
},
|
|
1740
|
-
droid: {
|
|
1741
|
-
id: "droid",
|
|
1742
|
-
displayName: "Droid",
|
|
1743
|
-
title: { workingLabel: "Droid", permissionLabel: "Droid - action required", idleLabel: "Droid ready" },
|
|
1744
|
-
hostHookSource: "droid",
|
|
1745
|
-
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1746
|
-
},
|
|
1747
|
-
claude: {
|
|
1748
|
-
id: "claude",
|
|
1749
|
-
displayName: "Claude Code",
|
|
1750
|
-
title: {
|
|
1751
|
-
workingLabel: "Claude",
|
|
1752
|
-
permissionLabel: "Claude - action required",
|
|
1753
|
-
idleLabel: "Claude ready",
|
|
1754
|
-
synthesizeWorking: false,
|
|
1755
|
-
synthesizeTerminal: false
|
|
1756
|
-
},
|
|
1757
|
-
hostHookSource: "claude",
|
|
1758
|
-
quota: "event",
|
|
1759
|
-
approvalBypassArgs: ["--permission-mode", "bypassPermissions"],
|
|
1760
|
-
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1761
|
-
},
|
|
1762
|
-
gemini: {
|
|
1763
|
-
id: "gemini",
|
|
1764
|
-
displayName: "Gemini CLI",
|
|
1765
|
-
title: {
|
|
1766
|
-
workingLabel: "Gemini",
|
|
1767
|
-
permissionLabel: "Gemini - action required",
|
|
1768
|
-
idleLabel: "Gemini ready",
|
|
1769
|
-
synthesizeWorking: false,
|
|
1770
|
-
synthesizeTerminal: false
|
|
1771
|
-
},
|
|
1772
|
-
hostHookSource: "gemini",
|
|
1773
|
-
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
1774
|
-
hookAck: "{}",
|
|
1775
|
-
approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
|
|
1776
|
-
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1777
|
-
}
|
|
1778
|
-
};
|
|
1779
|
-
REGISTERED_AGENTS = Object.keys(PROFILES);
|
|
1780
|
-
});
|
|
1781
|
-
|
|
1782
1807
|
// packages/core/src/agent-resume.ts
|
|
1783
1808
|
function resumeReference(profile, source) {
|
|
1784
|
-
const
|
|
1785
|
-
|
|
1809
|
+
const reported = profile.resume?.ref === "sessionId" ? source.agentSessionId : source.agentSessionFile;
|
|
1810
|
+
if (reported !== undefined && reported.length > 0)
|
|
1811
|
+
return reported;
|
|
1812
|
+
const named = profile.resume?.selfNamed;
|
|
1813
|
+
const own = source.ownSessionId;
|
|
1814
|
+
if (named === undefined || own === undefined || own.length === 0)
|
|
1815
|
+
return;
|
|
1816
|
+
return named === "uuid" ? uuidFromSessionId(own) : own;
|
|
1786
1817
|
}
|
|
1787
1818
|
function agentIdFromCommand(command) {
|
|
1788
1819
|
const head = command[0] ?? "";
|
|
@@ -1809,17 +1840,23 @@ function withoutExistingResume(command, fragment) {
|
|
|
1809
1840
|
}
|
|
1810
1841
|
return command;
|
|
1811
1842
|
}
|
|
1843
|
+
function withoutNamingFlag(command) {
|
|
1844
|
+
const at = command.indexOf("--session-id");
|
|
1845
|
+
return at < 0 ? command : [...command.slice(0, at), ...command.slice(at + 2)];
|
|
1846
|
+
}
|
|
1812
1847
|
function buildResumeCommand(command, profile, reference) {
|
|
1813
1848
|
const resume = profile.resume;
|
|
1814
1849
|
const binary = command[0];
|
|
1815
1850
|
if (!resume || binary === undefined || reference.length === 0)
|
|
1816
1851
|
return;
|
|
1817
1852
|
const template = resume.argv;
|
|
1818
|
-
const
|
|
1853
|
+
const stripped = withoutExistingResume(command, template);
|
|
1854
|
+
const base = resume.selfNamed === undefined ? stripped : withoutNamingFlag(stripped);
|
|
1819
1855
|
const fragment = template.map((token) => token.replaceAll(REF, reference));
|
|
1820
1856
|
return resume.form === "subcommand" ? [base[0], ...fragment, ...base.slice(1)] : [...base, ...fragment];
|
|
1821
1857
|
}
|
|
1822
1858
|
var REF = "{ref}";
|
|
1859
|
+
var init_agent_resume = () => {};
|
|
1823
1860
|
|
|
1824
1861
|
// packages/core/src/self-spawn.ts
|
|
1825
1862
|
function daemonLaunch(input) {
|
|
@@ -1848,9 +1885,9 @@ function ptyHostLaunch(input) {
|
|
|
1848
1885
|
function isPtyHostInvocation(args) {
|
|
1849
1886
|
return args[0] === PTY_HOST_ARGV_FLAG;
|
|
1850
1887
|
}
|
|
1851
|
-
function hookPostTarget(env, agent) {
|
|
1852
|
-
const port = Number(env["AXTERM_HOOK_PORT"]);
|
|
1853
|
-
const token = env["AXTERM_HOOK_TOKEN"];
|
|
1888
|
+
function hookPostTarget(env, agent, override) {
|
|
1889
|
+
const port = override?.port ?? Number(env["AXTERM_HOOK_PORT"]);
|
|
1890
|
+
const token = override?.token ?? env["AXTERM_HOOK_TOKEN"];
|
|
1854
1891
|
const sessionId = env["AXTERM_SESSION_ID"];
|
|
1855
1892
|
const launchToken = env["AXTERM_LAUNCH_TOKEN"];
|
|
1856
1893
|
if (!Number.isSafeInteger(port) || port <= 0)
|
|
@@ -1867,6 +1904,13 @@ function hookPostTarget(env, agent) {
|
|
|
1867
1904
|
}
|
|
1868
1905
|
};
|
|
1869
1906
|
}
|
|
1907
|
+
function parseHookEndpoint(text) {
|
|
1908
|
+
const port = Number(/^AXTERM_HOOK_PORT=(\d+)$/m.exec(text)?.[1]);
|
|
1909
|
+
const token = /^AXTERM_HOOK_TOKEN=(\S+)$/m.exec(text)?.[1];
|
|
1910
|
+
if (!Number.isSafeInteger(port) || port <= 0 || !token)
|
|
1911
|
+
return;
|
|
1912
|
+
return { port, token };
|
|
1913
|
+
}
|
|
1870
1914
|
function isDaemonInvocation(args) {
|
|
1871
1915
|
return args[0] === DAEMON_ARGV_FLAG;
|
|
1872
1916
|
}
|
|
@@ -1875,6 +1919,222 @@ var init_self_spawn = __esm(() => {
|
|
|
1875
1919
|
RUNTIME_NAMES = new Set(["bun", "bun.exe", "bun-debug", "bun-debug.exe", "node", "node.exe"]);
|
|
1876
1920
|
});
|
|
1877
1921
|
|
|
1922
|
+
// packages/core/src/service.ts
|
|
1923
|
+
import path2 from "path";
|
|
1924
|
+
function xml(value) {
|
|
1925
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1926
|
+
}
|
|
1927
|
+
function systemdArgument(value) {
|
|
1928
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("%", "%%")}"`;
|
|
1929
|
+
}
|
|
1930
|
+
function windowsArgument(value) {
|
|
1931
|
+
if (value !== "" && !/[\s"]/u.test(value))
|
|
1932
|
+
return value;
|
|
1933
|
+
let out = '"';
|
|
1934
|
+
let slashes = 0;
|
|
1935
|
+
for (const char of value) {
|
|
1936
|
+
if (char === "\\") {
|
|
1937
|
+
slashes++;
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
if (char === '"') {
|
|
1941
|
+
out += "\\".repeat(slashes * 2 + 1) + '"';
|
|
1942
|
+
slashes = 0;
|
|
1943
|
+
continue;
|
|
1944
|
+
}
|
|
1945
|
+
out += "\\".repeat(slashes) + char;
|
|
1946
|
+
slashes = 0;
|
|
1947
|
+
}
|
|
1948
|
+
return out + "\\".repeat(slashes * 2) + '"';
|
|
1949
|
+
}
|
|
1950
|
+
function windowsCommandLine(args) {
|
|
1951
|
+
return args.map(windowsArgument).join(" ");
|
|
1952
|
+
}
|
|
1953
|
+
function environmentEntries(input) {
|
|
1954
|
+
return Object.entries(input.environment ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => {
|
|
1955
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key))
|
|
1956
|
+
throw new Error(`invalid service environment key: ${key}`);
|
|
1957
|
+
if (/[\0\r\n]/u.test(value))
|
|
1958
|
+
throw new Error(`invalid service environment value for ${key}`);
|
|
1959
|
+
return [key, value];
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
function systemdEnvironment(input) {
|
|
1963
|
+
return environmentEntries(input).map(([key, value]) => `Environment="${`${key}=${value}`.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("%", "%%")}"`).join(`
|
|
1964
|
+
`);
|
|
1965
|
+
}
|
|
1966
|
+
function launchdEnvironment(input) {
|
|
1967
|
+
const entries = environmentEntries(input);
|
|
1968
|
+
if (entries.length === 0)
|
|
1969
|
+
return "";
|
|
1970
|
+
return ` <key>EnvironmentVariables</key>
|
|
1971
|
+
<dict>
|
|
1972
|
+
${entries.map(([key, value]) => ` <key>${xml(key)}</key>
|
|
1973
|
+
<string>${xml(value)}</string>`).join(`
|
|
1974
|
+
`)}
|
|
1975
|
+
</dict>
|
|
1976
|
+
`;
|
|
1977
|
+
}
|
|
1978
|
+
function systemdDefinition(input) {
|
|
1979
|
+
const argv = [input.launch.command, ...input.launch.args].map(systemdArgument).join(" ");
|
|
1980
|
+
const environment = systemdEnvironment(input);
|
|
1981
|
+
return {
|
|
1982
|
+
kind: "systemd",
|
|
1983
|
+
id: SYSTEMD_SERVICE,
|
|
1984
|
+
file: path2.posix.join(input.homeDir, ".config/systemd/user", SYSTEMD_SERVICE),
|
|
1985
|
+
contents: `[Unit]
|
|
1986
|
+
Description=axterm daemon
|
|
1987
|
+
After=network-online.target
|
|
1988
|
+
Wants=network-online.target
|
|
1989
|
+
|
|
1990
|
+
[Service]
|
|
1991
|
+
Type=simple
|
|
1992
|
+
ExecStart=:${argv}
|
|
1993
|
+
${environment ? `${environment}
|
|
1994
|
+
` : ""}Restart=on-failure
|
|
1995
|
+
RestartSec=2
|
|
1996
|
+
TimeoutStopSec=20
|
|
1997
|
+
# The PTY host is detached but remains in this cgroup. Stop only the daemon.
|
|
1998
|
+
KillMode=process
|
|
1999
|
+
|
|
2000
|
+
[Install]
|
|
2001
|
+
WantedBy=default.target
|
|
2002
|
+
`
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
function launchdDefinition(input) {
|
|
2006
|
+
const argumentsXml = [input.launch.command, ...input.launch.args].map((arg) => ` <string>${xml(arg)}</string>`).join(`
|
|
2007
|
+
`);
|
|
2008
|
+
const logFile = path2.posix.join(input.stateDir, "logs/service.log");
|
|
2009
|
+
return {
|
|
2010
|
+
kind: "launchd",
|
|
2011
|
+
id: LAUNCHD_LABEL,
|
|
2012
|
+
file: path2.posix.join(input.homeDir, "Library/LaunchAgents", `${LAUNCHD_LABEL}.plist`),
|
|
2013
|
+
contents: `<?xml version="1.0" encoding="UTF-8"?>
|
|
2014
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2015
|
+
<plist version="1.0">
|
|
2016
|
+
<dict>
|
|
2017
|
+
<key>Label</key>
|
|
2018
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
2019
|
+
<key>ProgramArguments</key>
|
|
2020
|
+
<array>
|
|
2021
|
+
${argumentsXml}
|
|
2022
|
+
</array>
|
|
2023
|
+
<key>WorkingDirectory</key>
|
|
2024
|
+
<string>${xml(input.homeDir)}</string>
|
|
2025
|
+
${launchdEnvironment(input)} <key>RunAtLoad</key>
|
|
2026
|
+
<true/>
|
|
2027
|
+
<key>KeepAlive</key>
|
|
2028
|
+
<dict>
|
|
2029
|
+
<key>SuccessfulExit</key>
|
|
2030
|
+
<false/>
|
|
2031
|
+
</dict>
|
|
2032
|
+
<key>ThrottleInterval</key>
|
|
2033
|
+
<integer>2</integer>
|
|
2034
|
+
<key>AbandonProcessGroup</key>
|
|
2035
|
+
<true/>
|
|
2036
|
+
<key>StandardOutPath</key>
|
|
2037
|
+
<string>${xml(logFile)}</string>
|
|
2038
|
+
<key>StandardErrorPath</key>
|
|
2039
|
+
<string>${xml(logFile)}</string>
|
|
2040
|
+
</dict>
|
|
2041
|
+
</plist>
|
|
2042
|
+
`
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
function powershellLiteral(value) {
|
|
2046
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
2047
|
+
}
|
|
2048
|
+
function taskDefinition(input) {
|
|
2049
|
+
if (!input.userId)
|
|
2050
|
+
throw new Error("Task Scheduler requires userId");
|
|
2051
|
+
const entries = environmentEntries(input);
|
|
2052
|
+
let command = input.launch.command;
|
|
2053
|
+
let argumentsLine = windowsCommandLine(input.launch.args);
|
|
2054
|
+
let companionFiles;
|
|
2055
|
+
if (entries.length > 0) {
|
|
2056
|
+
const wrapperFile = path2.win32.join(input.stateDir, "service", "axterm-service.ps1");
|
|
2057
|
+
const environment = entries.map(([key, value]) => `$env:${key} = ${powershellLiteral(value)}`).join(`\r
|
|
2058
|
+
`);
|
|
2059
|
+
const invocation = [input.launch.command, ...input.launch.args].map(powershellLiteral).join(" ");
|
|
2060
|
+
companionFiles = [
|
|
2061
|
+
{
|
|
2062
|
+
file: wrapperFile,
|
|
2063
|
+
contents: `$ErrorActionPreference = 'Stop'\r
|
|
2064
|
+
${environment}\r
|
|
2065
|
+
& ${invocation}\r
|
|
2066
|
+
exit $LASTEXITCODE\r
|
|
2067
|
+
`
|
|
2068
|
+
}
|
|
2069
|
+
];
|
|
2070
|
+
command = "powershell.exe";
|
|
2071
|
+
argumentsLine = windowsCommandLine([
|
|
2072
|
+
"-NoProfile",
|
|
2073
|
+
"-NonInteractive",
|
|
2074
|
+
"-ExecutionPolicy",
|
|
2075
|
+
"Bypass",
|
|
2076
|
+
"-File",
|
|
2077
|
+
wrapperFile
|
|
2078
|
+
]);
|
|
2079
|
+
}
|
|
2080
|
+
return {
|
|
2081
|
+
kind: "task-scheduler",
|
|
2082
|
+
id: WINDOWS_TASK,
|
|
2083
|
+
file: path2.win32.join(input.stateDir, "service", "axterm-task.xml"),
|
|
2084
|
+
contents: `<?xml version="1.0" encoding="UTF-16"?>
|
|
2085
|
+
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
2086
|
+
<RegistrationInfo>
|
|
2087
|
+
<Description>axterm daemon</Description>
|
|
2088
|
+
</RegistrationInfo>
|
|
2089
|
+
<Triggers>
|
|
2090
|
+
<LogonTrigger>
|
|
2091
|
+
<Enabled>true</Enabled>
|
|
2092
|
+
<UserId>${xml(input.userId)}</UserId>
|
|
2093
|
+
</LogonTrigger>
|
|
2094
|
+
</Triggers>
|
|
2095
|
+
<Principals>
|
|
2096
|
+
<Principal id="Author">
|
|
2097
|
+
<UserId>${xml(input.userId)}</UserId>
|
|
2098
|
+
<LogonType>InteractiveToken</LogonType>
|
|
2099
|
+
<RunLevel>LeastPrivilege</RunLevel>
|
|
2100
|
+
</Principal>
|
|
2101
|
+
</Principals>
|
|
2102
|
+
<Settings>
|
|
2103
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
2104
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
2105
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
2106
|
+
<AllowHardTerminate>false</AllowHardTerminate>
|
|
2107
|
+
<StartWhenAvailable>true</StartWhenAvailable>
|
|
2108
|
+
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
2109
|
+
<RestartOnFailure>
|
|
2110
|
+
<Interval>PT1M</Interval>
|
|
2111
|
+
<Count>255</Count>
|
|
2112
|
+
</RestartOnFailure>
|
|
2113
|
+
</Settings>
|
|
2114
|
+
<Actions Context="Author">
|
|
2115
|
+
<Exec>
|
|
2116
|
+
<Command>${xml(command)}</Command>
|
|
2117
|
+
<Arguments>${xml(argumentsLine)}</Arguments>
|
|
2118
|
+
<WorkingDirectory>${xml(input.homeDir)}</WorkingDirectory>
|
|
2119
|
+
</Exec>
|
|
2120
|
+
</Actions>
|
|
2121
|
+
</Task>
|
|
2122
|
+
`,
|
|
2123
|
+
...companionFiles ? { companionFiles } : {}
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
function serviceDefinition(input) {
|
|
2127
|
+
if (input.platform === "linux")
|
|
2128
|
+
return systemdDefinition(input);
|
|
2129
|
+
if (input.platform === "darwin")
|
|
2130
|
+
return launchdDefinition(input);
|
|
2131
|
+
if (input.platform === "win32")
|
|
2132
|
+
return taskDefinition(input);
|
|
2133
|
+
throw new Error(`service management is not supported on ${input.platform}`);
|
|
2134
|
+
}
|
|
2135
|
+
var SYSTEMD_SERVICE = "axterm.service", LAUNCHD_LABEL = "com.layorix.axterm.daemon", WINDOWS_TASK = "axterm-daemon";
|
|
2136
|
+
var init_service = () => {};
|
|
2137
|
+
|
|
1878
2138
|
// packages/core/src/attachment.ts
|
|
1879
2139
|
function megabytes(bytes) {
|
|
1880
2140
|
return `${Math.round(bytes / (1024 * 1024))} MB`;
|
|
@@ -1992,6 +2252,7 @@ function planUpdateRestart(input) {
|
|
|
1992
2252
|
var SEMVER;
|
|
1993
2253
|
var init_update = __esm(() => {
|
|
1994
2254
|
init_agent_registry();
|
|
2255
|
+
init_agent_resume();
|
|
1995
2256
|
SEMVER = /^\d+\.\d+\.\d+/;
|
|
1996
2257
|
});
|
|
1997
2258
|
|
|
@@ -2064,6 +2325,22 @@ function parseProjectRef(raw) {
|
|
|
2064
2325
|
return { machine: text.slice(0, split), alias: text.slice(split + 1) };
|
|
2065
2326
|
return { alias: text };
|
|
2066
2327
|
}
|
|
2328
|
+
function parseCommandAddress(token) {
|
|
2329
|
+
const at = token.indexOf("@");
|
|
2330
|
+
if (at < 0)
|
|
2331
|
+
return { command: token };
|
|
2332
|
+
const target = token.slice(at + 1);
|
|
2333
|
+
if (!/^[A-Za-z0-9_]+$/.test(target))
|
|
2334
|
+
return { command: token };
|
|
2335
|
+
return { command: token.slice(0, at), target };
|
|
2336
|
+
}
|
|
2337
|
+
function commandIsForBot(target, self) {
|
|
2338
|
+
if (target === undefined)
|
|
2339
|
+
return true;
|
|
2340
|
+
if (self === undefined)
|
|
2341
|
+
return false;
|
|
2342
|
+
return target.toLowerCase() === self.toLowerCase();
|
|
2343
|
+
}
|
|
2067
2344
|
var MAX_NAME_LENGTH = 32;
|
|
2068
2345
|
|
|
2069
2346
|
// packages/core/src/fanout.ts
|
|
@@ -2173,6 +2450,9 @@ function buildApprovalQuestion(toolName) {
|
|
|
2173
2450
|
function isModalState(state) {
|
|
2174
2451
|
return state === "blocked" || state === "waiting";
|
|
2175
2452
|
}
|
|
2453
|
+
function offersKeystrokeApproval(profile, state) {
|
|
2454
|
+
return state === "blocked" && profile.structuredApprovals !== true;
|
|
2455
|
+
}
|
|
2176
2456
|
function dialogTransition(nextState, dialogOpen) {
|
|
2177
2457
|
if (isModalState(nextState))
|
|
2178
2458
|
return { open: true, resolved: false };
|
|
@@ -2183,14 +2463,29 @@ function isPromptInjectable(state) {
|
|
|
2183
2463
|
}
|
|
2184
2464
|
var init_choice = () => {};
|
|
2185
2465
|
|
|
2186
|
-
// packages/core/src/
|
|
2187
|
-
function
|
|
2188
|
-
if (!
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
return
|
|
2466
|
+
// packages/core/src/adopt.ts
|
|
2467
|
+
function resolveSessionCwd(input) {
|
|
2468
|
+
if (!input.preferConversation)
|
|
2469
|
+
return input.requested;
|
|
2470
|
+
const own = input.conversationCwd;
|
|
2471
|
+
return own !== undefined && own.length > 0 ? own : input.requested;
|
|
2472
|
+
}
|
|
2473
|
+
function conversationHeldBy(sessions, conversationId) {
|
|
2474
|
+
if (conversationId.length === 0)
|
|
2475
|
+
return;
|
|
2476
|
+
return sessions.find((session) => {
|
|
2477
|
+
if (session.state !== "running")
|
|
2478
|
+
return false;
|
|
2479
|
+
if (session.agentSessionId === conversationId)
|
|
2480
|
+
return true;
|
|
2481
|
+
const argv = session.agentCommand;
|
|
2482
|
+
return argv.some((token, index) => {
|
|
2483
|
+
if (token === "--resume" || token === "-r" || token === "--session-id")
|
|
2484
|
+
return argv[index + 1] === conversationId;
|
|
2485
|
+
return token === `--resume=${conversationId}`;
|
|
2486
|
+
});
|
|
2487
|
+
});
|
|
2192
2488
|
}
|
|
2193
|
-
var MENU_UP = "\x1B[A", MENU_DOWN = "\x1B[B", MENU_SELECT = "\r", MENU_NORMALIZE_PRESSES = 24;
|
|
2194
2489
|
|
|
2195
2490
|
// packages/core/src/agent-status.ts
|
|
2196
2491
|
function next(prev, state, now) {
|
|
@@ -2201,17 +2496,29 @@ function next(prev, state, now) {
|
|
|
2201
2496
|
stateStartedAt: prev?.state === state ? prev.stateStartedAt : now
|
|
2202
2497
|
};
|
|
2203
2498
|
}
|
|
2499
|
+
function withConversationId(events, identity) {
|
|
2500
|
+
const id = identity.sessionId;
|
|
2501
|
+
const subagent = identity.subagentId;
|
|
2502
|
+
if (typeof id !== "string" || id === "" || typeof subagent === "string" && subagent !== "") {
|
|
2503
|
+
return [...events];
|
|
2504
|
+
}
|
|
2505
|
+
return events.map((event) => ({ session_id: id, ...event }));
|
|
2506
|
+
}
|
|
2204
2507
|
function reduceAgentState(prev, payload, now) {
|
|
2508
|
+
const result = reduceState(prev, payload, now);
|
|
2509
|
+
const identity = {
|
|
2510
|
+
...payload.session_id !== undefined ? { agentSessionId: payload.session_id } : {},
|
|
2511
|
+
...payload.session_file !== undefined ? { agentSessionFile: payload.session_file } : {},
|
|
2512
|
+
...payload.session_name !== undefined ? { sessionName: payload.session_name } : {}
|
|
2513
|
+
};
|
|
2514
|
+
if (Object.keys(identity).length === 0)
|
|
2515
|
+
return result;
|
|
2516
|
+
return { ...result, meta: { ...identity, ...result.meta } };
|
|
2517
|
+
}
|
|
2518
|
+
function reduceState(prev, payload, now) {
|
|
2205
2519
|
switch (payload.event) {
|
|
2206
2520
|
case "session_start":
|
|
2207
|
-
return {
|
|
2208
|
-
entry: prev,
|
|
2209
|
-
meta: {
|
|
2210
|
-
agentSessionId: payload.session_id,
|
|
2211
|
-
agentSessionFile: payload.session_file,
|
|
2212
|
-
sessionName: payload.session_name
|
|
2213
|
-
}
|
|
2214
|
-
};
|
|
2521
|
+
return { entry: prev };
|
|
2215
2522
|
case "before_agent_start": {
|
|
2216
2523
|
const entry = next(prev, prev?.state ?? "working", now);
|
|
2217
2524
|
entry.prompt = payload.prompt ?? "";
|
|
@@ -2378,7 +2685,18 @@ var init_pty_protocol = __esm(() => {
|
|
|
2378
2685
|
Data: 2
|
|
2379
2686
|
};
|
|
2380
2687
|
MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
2381
|
-
KNOWN_TYPES = new Set([
|
|
2688
|
+
KNOWN_TYPES = new Set([
|
|
2689
|
+
"hello",
|
|
2690
|
+
"spawn",
|
|
2691
|
+
"subscribe",
|
|
2692
|
+
"resize",
|
|
2693
|
+
"kill",
|
|
2694
|
+
"welcome",
|
|
2695
|
+
"spawned",
|
|
2696
|
+
"subscribed",
|
|
2697
|
+
"exit",
|
|
2698
|
+
"error"
|
|
2699
|
+
]);
|
|
2382
2700
|
});
|
|
2383
2701
|
|
|
2384
2702
|
// packages/core/src/workspace.ts
|
|
@@ -2511,7 +2829,7 @@ function mapClaudeHook(input) {
|
|
|
2511
2829
|
}
|
|
2512
2830
|
}
|
|
2513
2831
|
function buildClaudeSettings(input) {
|
|
2514
|
-
const handler = {
|
|
2832
|
+
const handler = (timeout) => ({
|
|
2515
2833
|
type: "http",
|
|
2516
2834
|
url: `http://127.0.0.1:${input.port}/hook/claude`,
|
|
2517
2835
|
headers: {
|
|
@@ -2519,16 +2837,36 @@ function buildClaudeSettings(input) {
|
|
|
2519
2837
|
"x-axterm-session": input.sessionId,
|
|
2520
2838
|
"x-axterm-launch": input.launchToken
|
|
2521
2839
|
},
|
|
2522
|
-
timeout
|
|
2523
|
-
};
|
|
2840
|
+
timeout
|
|
2841
|
+
});
|
|
2842
|
+
const observe = handler(HOOK_TIMEOUT_SECONDS);
|
|
2524
2843
|
const hooks = {};
|
|
2525
|
-
for (const event of TOOL_EVENTS)
|
|
2526
|
-
|
|
2844
|
+
for (const event of TOOL_EVENTS) {
|
|
2845
|
+
const single = event === "PermissionRequest" ? handler(APPROVAL_TIMEOUT_SECONDS) : observe;
|
|
2846
|
+
hooks[event] = [{ matcher: "*", hooks: [single] }];
|
|
2847
|
+
}
|
|
2527
2848
|
for (const event of PLAIN_EVENTS)
|
|
2528
|
-
hooks[event] = [{ hooks: [
|
|
2849
|
+
hooks[event] = [{ hooks: [observe] }];
|
|
2529
2850
|
return { hooks };
|
|
2530
2851
|
}
|
|
2531
|
-
|
|
2852
|
+
function hookPermissionDecision(question, answers, denyMessage) {
|
|
2853
|
+
const picked = answers?.find((a) => a.questionIndex === 0)?.selected[0];
|
|
2854
|
+
if (picked === undefined)
|
|
2855
|
+
return;
|
|
2856
|
+
if (picked === question.options[0]?.label) {
|
|
2857
|
+
return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior: "allow" } } };
|
|
2858
|
+
}
|
|
2859
|
+
if (picked === question.options[1]?.label) {
|
|
2860
|
+
return {
|
|
2861
|
+
hookSpecificOutput: {
|
|
2862
|
+
hookEventName: "PermissionRequest",
|
|
2863
|
+
decision: { behavior: "deny", ...denyMessage !== undefined ? { message: denyMessage } : {} }
|
|
2864
|
+
}
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
var MAX_RESULT_TEXT = 2000, HOOK_TIMEOUT_SECONDS = 5, APPROVAL_TIMEOUT_SECONDS = 120, APPROVAL_HOLD_MS = 110000, TOOL_EVENTS, PLAIN_EVENTS;
|
|
2532
2870
|
var init_claude_hooks = __esm(() => {
|
|
2533
2871
|
TOOL_EVENTS = ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PermissionRequest"];
|
|
2534
2872
|
PLAIN_EVENTS = [
|
|
@@ -2554,18 +2892,25 @@ function detectOnboarding(agent, screen) {
|
|
|
2554
2892
|
if (!dialog)
|
|
2555
2893
|
continue;
|
|
2556
2894
|
const options = [];
|
|
2895
|
+
let lastOption = -1;
|
|
2557
2896
|
const end = Math.min(lines.length, i + 1 + OPTION_WINDOW_LINES + 1);
|
|
2558
2897
|
for (let j = i + 1;j < end; j++) {
|
|
2559
2898
|
const row = OPTION_ROW.exec(lines[j]);
|
|
2560
|
-
if (row)
|
|
2899
|
+
if (row) {
|
|
2561
2900
|
options.push({ digit: row[1], label: row[2].trim() });
|
|
2901
|
+
lastOption = j;
|
|
2902
|
+
}
|
|
2562
2903
|
}
|
|
2563
|
-
if (options.length
|
|
2564
|
-
|
|
2904
|
+
if (options.length < 2)
|
|
2905
|
+
continue;
|
|
2906
|
+
const noise = lines.slice(lastOption + 1).filter((row) => row !== "" && !DIALOG_FOOTER.test(row));
|
|
2907
|
+
if (noise.length > QUIET_AFTER_OPTIONS)
|
|
2908
|
+
continue;
|
|
2909
|
+
return { kind: dialog.kind, question: dialog.question, options };
|
|
2565
2910
|
}
|
|
2566
2911
|
return;
|
|
2567
2912
|
}
|
|
2568
|
-
var DIALOGS, OPTION_ROW, OPTION_WINDOW_LINES = 8;
|
|
2913
|
+
var DIALOGS, OPTION_ROW, OPTION_WINDOW_LINES = 8, QUIET_AFTER_OPTIONS = 3, DIALOG_FOOTER;
|
|
2569
2914
|
var init_onboarding = __esm(() => {
|
|
2570
2915
|
DIALOGS = {
|
|
2571
2916
|
gemini: [
|
|
@@ -2573,12 +2918,105 @@ var init_onboarding = __esm(() => {
|
|
|
2573
2918
|
{ kind: "authMethod", question: "How would you like to authenticate for this project?" }
|
|
2574
2919
|
],
|
|
2575
2920
|
claude: [
|
|
2921
|
+
{ kind: "folderTrust", question: "Quick safety check: Is this a project you created or one you trust?" },
|
|
2922
|
+
{ kind: "autoMode", question: "Set up auto mode for your environment?" },
|
|
2576
2923
|
{ kind: "apiKey", question: "Do you want to use this API key?" },
|
|
2577
2924
|
{ kind: "loginMethod", question: "Select login method:" }
|
|
2578
2925
|
],
|
|
2579
2926
|
codex: [{ kind: "folderTrust", question: "Do you trust the contents of this directory?" }]
|
|
2580
2927
|
};
|
|
2581
|
-
OPTION_ROW = /^[\u25CF\u25CB\u276F>\u203A]?\s*(\d)\.\s+(.+)$/;
|
|
2928
|
+
OPTION_ROW = /^[\u25CF\u25CB\u276F>\u203A]?\s*(\d)\.\s+(.+)$/;
|
|
2929
|
+
DIALOG_FOOTER = /^[\s\u2502\u2500\u2570\u256F\u256D\u256E\u250C\u2510\u2514\u2518|]*$|enter to (confirm|continue|select)|esc to cancel|use enter to select|press enter|\u2191\/\u2193|arrow keys|tab to|^\(.*\)$/i;
|
|
2930
|
+
});
|
|
2931
|
+
|
|
2932
|
+
// packages/core/src/diff-view.ts
|
|
2933
|
+
function scanStructure(lines) {
|
|
2934
|
+
const meta = new Set;
|
|
2935
|
+
let proven = false;
|
|
2936
|
+
let inHunk = false;
|
|
2937
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2938
|
+
const line = lines[i] ?? "";
|
|
2939
|
+
if (line.startsWith("@@")) {
|
|
2940
|
+
inHunk = true;
|
|
2941
|
+
proven ||= HUNK_HEADER.test(line);
|
|
2942
|
+
continue;
|
|
2943
|
+
}
|
|
2944
|
+
if (FILE_SECTION_START.test(line)) {
|
|
2945
|
+
inHunk = false;
|
|
2946
|
+
proven ||= line.startsWith("diff --git ");
|
|
2947
|
+
continue;
|
|
2948
|
+
}
|
|
2949
|
+
if (inHunk || !line.startsWith("--- "))
|
|
2950
|
+
continue;
|
|
2951
|
+
if ((lines[i + 1] ?? "").startsWith("+++ ")) {
|
|
2952
|
+
meta.add(i);
|
|
2953
|
+
meta.add(i + 1);
|
|
2954
|
+
i += 1;
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
return { meta, proven };
|
|
2958
|
+
}
|
|
2959
|
+
function countTail(lines) {
|
|
2960
|
+
let added = 0;
|
|
2961
|
+
let removed = 0;
|
|
2962
|
+
for (const line of lines) {
|
|
2963
|
+
if (line.startsWith("+++ ") || line.startsWith("--- "))
|
|
2964
|
+
continue;
|
|
2965
|
+
if (line.startsWith("+"))
|
|
2966
|
+
added += 1;
|
|
2967
|
+
else if (line.startsWith("-"))
|
|
2968
|
+
removed += 1;
|
|
2969
|
+
}
|
|
2970
|
+
return { added, removed };
|
|
2971
|
+
}
|
|
2972
|
+
function summarizePatch(patch, maxChars = DIFF_MAX_CHARS, maxLines = DIFF_MAX_LINES) {
|
|
2973
|
+
const totalChars = patch.length;
|
|
2974
|
+
if (totalChars === 0)
|
|
2975
|
+
return;
|
|
2976
|
+
const clipped = patch.slice(0, maxChars);
|
|
2977
|
+
const all = clipped.split(`
|
|
2978
|
+
`);
|
|
2979
|
+
const cutChars = totalChars > maxChars;
|
|
2980
|
+
const cutLines = all.length > maxLines;
|
|
2981
|
+
const kept = all.slice(0, maxLines);
|
|
2982
|
+
const { meta, proven } = scanStructure(kept);
|
|
2983
|
+
if (!proven)
|
|
2984
|
+
return;
|
|
2985
|
+
let added = 0;
|
|
2986
|
+
let removed = 0;
|
|
2987
|
+
const lines = kept.map((line, i) => {
|
|
2988
|
+
if (meta.has(i) || line.startsWith("@@") || FILE_SECTION_START.test(line)) {
|
|
2989
|
+
return { kind: "meta", text: line };
|
|
2990
|
+
}
|
|
2991
|
+
if (line.startsWith("+")) {
|
|
2992
|
+
added += 1;
|
|
2993
|
+
return { kind: "add", text: line.slice(1) };
|
|
2994
|
+
}
|
|
2995
|
+
if (line.startsWith("-")) {
|
|
2996
|
+
removed += 1;
|
|
2997
|
+
return { kind: "del", text: line.slice(1) };
|
|
2998
|
+
}
|
|
2999
|
+
return { kind: "context", text: line };
|
|
3000
|
+
});
|
|
3001
|
+
if (added + removed === 0)
|
|
3002
|
+
return;
|
|
3003
|
+
const tail = countTail(all.slice(maxLines));
|
|
3004
|
+
added += tail.added;
|
|
3005
|
+
removed += tail.removed;
|
|
3006
|
+
const truncated = cutChars ? "chars" : cutLines ? "lines" : undefined;
|
|
3007
|
+
return {
|
|
3008
|
+
lines: truncated === undefined ? lines : [...lines.slice(0, maxLines - 1), TRUNCATED],
|
|
3009
|
+
added,
|
|
3010
|
+
removed,
|
|
3011
|
+
...truncated === undefined ? {} : { truncated },
|
|
3012
|
+
totalChars
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
var DIFF_MAX_CHARS = 8000, DIFF_MAX_LINES = 80, TRUNCATED, HUNK_HEADER, FILE_SECTION_START;
|
|
3016
|
+
var init_diff_view = __esm(() => {
|
|
3017
|
+
TRUNCATED = { kind: "meta", text: "\u2026 diff truncated \u2026" };
|
|
3018
|
+
HUNK_HEADER = /^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@/;
|
|
3019
|
+
FILE_SECTION_START = /^(?:diff |index |old mode |new mode |new file mode |deleted file mode |similarity index |dissimilarity index |rename |copy |Binary files )/;
|
|
2582
3020
|
});
|
|
2583
3021
|
|
|
2584
3022
|
// packages/core/src/doctor.ts
|
|
@@ -2824,7 +3262,7 @@ var SEQUENCES, KEY_NAMES;
|
|
|
2824
3262
|
var init_keys = __esm(() => {
|
|
2825
3263
|
SEQUENCES = {
|
|
2826
3264
|
approve: "\r",
|
|
2827
|
-
deny: "\x1B
|
|
3265
|
+
deny: "\x1B",
|
|
2828
3266
|
enter: "\r",
|
|
2829
3267
|
esc: "\x1B",
|
|
2830
3268
|
up: "\x1B[A",
|
|
@@ -3300,24 +3738,7 @@ trusted_hash = "${e.hash}"
|
|
|
3300
3738
|
}).join(`
|
|
3301
3739
|
`);
|
|
3302
3740
|
}
|
|
3303
|
-
|
|
3304
|
-
const picked = answers?.find((a) => a.questionIndex === 0)?.selected[0];
|
|
3305
|
-
if (picked === undefined)
|
|
3306
|
-
return;
|
|
3307
|
-
if (picked === question.options[0]?.label) {
|
|
3308
|
-
return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior: "allow" } } };
|
|
3309
|
-
}
|
|
3310
|
-
if (picked === question.options[1]?.label) {
|
|
3311
|
-
return {
|
|
3312
|
-
hookSpecificOutput: {
|
|
3313
|
-
hookEventName: "PermissionRequest",
|
|
3314
|
-
decision: { behavior: "deny", ...denyMessage !== undefined ? { message: denyMessage } : {} }
|
|
3315
|
-
}
|
|
3316
|
-
};
|
|
3317
|
-
}
|
|
3318
|
-
return;
|
|
3319
|
-
}
|
|
3320
|
-
var OBSERVATION_TIMEOUT_SECONDS = 15, CODEX_PERMISSION_TIMEOUT_SECONDS = 120, CODEX_APPROVAL_HOLD_MS = 110000, CODEX_APPROVAL_POST_TIMEOUT_MS = 115000, SESSION_END_TIMEOUT_SECONDS = 2, OBSERVATION_EVENTS, EVENT_KEY_LABELS;
|
|
3741
|
+
var OBSERVATION_TIMEOUT_SECONDS = 15, CODEX_PERMISSION_TIMEOUT_SECONDS = 120, CODEX_APPROVAL_POST_TIMEOUT_MS = 115000, SESSION_END_TIMEOUT_SECONDS = 2, OBSERVATION_EVENTS, EVENT_KEY_LABELS;
|
|
3321
3742
|
var init_codex_hooks = __esm(() => {
|
|
3322
3743
|
OBSERVATION_EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"];
|
|
3323
3744
|
EVENT_KEY_LABELS = {
|
|
@@ -3571,11 +3992,11 @@ var init_prompt_detect = __esm(() => {
|
|
|
3571
3992
|
});
|
|
3572
3993
|
|
|
3573
3994
|
// packages/core/src/session-scope.ts
|
|
3574
|
-
import
|
|
3995
|
+
import path3 from "path";
|
|
3575
3996
|
function comparable(target, resolve) {
|
|
3576
|
-
let real =
|
|
3997
|
+
let real = path3.resolve(target);
|
|
3577
3998
|
try {
|
|
3578
|
-
real =
|
|
3999
|
+
real = path3.resolve(resolve(real));
|
|
3579
4000
|
} catch {}
|
|
3580
4001
|
const trimmed = real.replace(/[\\/]+$/, "");
|
|
3581
4002
|
return process.platform === "win32" ? trimmed.toLowerCase() : trimmed;
|
|
@@ -3583,7 +4004,7 @@ function comparable(target, resolve) {
|
|
|
3583
4004
|
function pathWithin(parent, child, resolve = asGiven) {
|
|
3584
4005
|
const from = comparable(parent, resolve);
|
|
3585
4006
|
const to = comparable(child, resolve);
|
|
3586
|
-
return to === from || to.startsWith(from +
|
|
4007
|
+
return to === from || to.startsWith(from + path3.sep);
|
|
3587
4008
|
}
|
|
3588
4009
|
function sessionInScope(session, scope, resolve = asGiven) {
|
|
3589
4010
|
if (pathWithin(scope, session.cwd, resolve))
|
|
@@ -3614,6 +4035,9 @@ function sessionsByRecency(live, stored) {
|
|
|
3614
4035
|
var asGiven = (target) => target;
|
|
3615
4036
|
var init_session_scope = () => {};
|
|
3616
4037
|
|
|
4038
|
+
// packages/core/src/test-scratch.ts
|
|
4039
|
+
var init_test_scratch = () => {};
|
|
4040
|
+
|
|
3617
4041
|
// packages/core/src/index.ts
|
|
3618
4042
|
function isRpcEvent(f) {
|
|
3619
4043
|
return typeof f.ev === "string";
|
|
@@ -3633,7 +4057,9 @@ var init_src = __esm(() => {
|
|
|
3633
4057
|
init_config_paths();
|
|
3634
4058
|
init_setup_wizard();
|
|
3635
4059
|
init_agent_registry();
|
|
4060
|
+
init_agent_resume();
|
|
3636
4061
|
init_self_spawn();
|
|
4062
|
+
init_service();
|
|
3637
4063
|
init_channel_spi();
|
|
3638
4064
|
init_update();
|
|
3639
4065
|
init_errors();
|
|
@@ -3645,6 +4071,7 @@ var init_src = __esm(() => {
|
|
|
3645
4071
|
init_pty_protocol();
|
|
3646
4072
|
init_claude_hooks();
|
|
3647
4073
|
init_onboarding();
|
|
4074
|
+
init_diff_view();
|
|
3648
4075
|
init_doctor();
|
|
3649
4076
|
init_stall();
|
|
3650
4077
|
init_codex_stream();
|
|
@@ -3660,19 +4087,20 @@ var init_src = __esm(() => {
|
|
|
3660
4087
|
init_dotenv_scrub();
|
|
3661
4088
|
init_prompt_detect();
|
|
3662
4089
|
init_session_scope();
|
|
4090
|
+
init_test_scratch();
|
|
3663
4091
|
SUBMIT_DELAY_MS = process.platform === "win32" ? 1500 : 500;
|
|
3664
4092
|
PASTE_CHUNK_BYTES = 16 * 1024;
|
|
3665
4093
|
});
|
|
3666
4094
|
|
|
3667
4095
|
// packages/daemon/src/config-store.ts
|
|
3668
|
-
import
|
|
3669
|
-
import
|
|
3670
|
-
function
|
|
4096
|
+
import fs7 from "fs";
|
|
4097
|
+
import path8 from "path";
|
|
4098
|
+
function writeAtomic2(target, contents, mode) {
|
|
3671
4099
|
const tmp = `${target}.tmp`;
|
|
3672
|
-
|
|
3673
|
-
|
|
4100
|
+
fs7.writeFileSync(tmp, contents, { mode });
|
|
4101
|
+
fs7.renameSync(tmp, target);
|
|
3674
4102
|
if (process.platform !== "win32")
|
|
3675
|
-
|
|
4103
|
+
fs7.chmodSync(target, mode);
|
|
3676
4104
|
}
|
|
3677
4105
|
|
|
3678
4106
|
class ConfigStore {
|
|
@@ -3688,16 +4116,16 @@ class ConfigStore {
|
|
|
3688
4116
|
}
|
|
3689
4117
|
load() {
|
|
3690
4118
|
this.loadError = undefined;
|
|
3691
|
-
|
|
4119
|
+
fs7.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
|
|
3692
4120
|
if (process.platform !== "win32") {
|
|
3693
4121
|
try {
|
|
3694
|
-
|
|
4122
|
+
fs7.chmodSync(this.paths.configDir, DIR_MODE);
|
|
3695
4123
|
} catch {}
|
|
3696
4124
|
}
|
|
3697
4125
|
this.config = structuredClone(DEFAULT_CONFIG);
|
|
3698
|
-
if (
|
|
4126
|
+
if (fs7.existsSync(this.paths.configFile)) {
|
|
3699
4127
|
try {
|
|
3700
|
-
const parsed = parseConfig(JSON.parse(
|
|
4128
|
+
const parsed = parseConfig(JSON.parse(fs7.readFileSync(this.paths.configFile, "utf8")));
|
|
3701
4129
|
if ("errors" in parsed)
|
|
3702
4130
|
this.loadError = parsed.errors.join("; ");
|
|
3703
4131
|
else
|
|
@@ -3707,10 +4135,10 @@ class ConfigStore {
|
|
|
3707
4135
|
}
|
|
3708
4136
|
}
|
|
3709
4137
|
this.secrets = {};
|
|
3710
|
-
if (
|
|
4138
|
+
if (fs7.existsSync(this.paths.secretsFile)) {
|
|
3711
4139
|
this.tightenPermissions(this.paths.secretsFile);
|
|
3712
4140
|
try {
|
|
3713
|
-
const raw = JSON.parse(
|
|
4141
|
+
const raw = JSON.parse(fs7.readFileSync(this.paths.secretsFile, "utf8"));
|
|
3714
4142
|
if (raw !== null && typeof raw === "object") {
|
|
3715
4143
|
for (const [key, value] of Object.entries(raw)) {
|
|
3716
4144
|
if (typeof value === "string")
|
|
@@ -3726,8 +4154,8 @@ class ConfigStore {
|
|
|
3726
4154
|
if (process.platform === "win32")
|
|
3727
4155
|
return;
|
|
3728
4156
|
try {
|
|
3729
|
-
if ((
|
|
3730
|
-
|
|
4157
|
+
if ((fs7.statSync(file).mode & 63) !== 0)
|
|
4158
|
+
fs7.chmodSync(file, FILE_MODE);
|
|
3731
4159
|
} catch {}
|
|
3732
4160
|
}
|
|
3733
4161
|
get() {
|
|
@@ -3735,7 +4163,7 @@ class ConfigStore {
|
|
|
3735
4163
|
}
|
|
3736
4164
|
health() {
|
|
3737
4165
|
return configHealth({
|
|
3738
|
-
fileExists:
|
|
4166
|
+
fileExists: fs7.existsSync(this.paths.configFile),
|
|
3739
4167
|
parseError: this.loadError,
|
|
3740
4168
|
config: this.resolved()
|
|
3741
4169
|
});
|
|
@@ -3768,7 +4196,7 @@ class ConfigStore {
|
|
|
3768
4196
|
const parsed = parseConfig(candidate);
|
|
3769
4197
|
if ("errors" in parsed)
|
|
3770
4198
|
throw new Error(parsed.errors.join("; "));
|
|
3771
|
-
|
|
4199
|
+
writeAtomic2(this.paths.configFile, `${JSON.stringify(parsed.config, null, 2)}
|
|
3772
4200
|
`, FILE_MODE);
|
|
3773
4201
|
this.config = parsed.config;
|
|
3774
4202
|
this.loadError = undefined;
|
|
@@ -3780,11 +4208,11 @@ class ConfigStore {
|
|
|
3780
4208
|
delete this.secrets[dottedPath];
|
|
3781
4209
|
else
|
|
3782
4210
|
this.secrets[dottedPath] = value;
|
|
3783
|
-
if (!
|
|
3784
|
-
|
|
4211
|
+
if (!fs7.existsSync(this.paths.configFile)) {
|
|
4212
|
+
writeAtomic2(this.paths.configFile, `${JSON.stringify(this.config, null, 2)}
|
|
3785
4213
|
`, FILE_MODE);
|
|
3786
4214
|
}
|
|
3787
|
-
|
|
4215
|
+
writeAtomic2(this.paths.secretsFile, `${JSON.stringify(this.secrets, null, 2)}
|
|
3788
4216
|
`, FILE_MODE);
|
|
3789
4217
|
}
|
|
3790
4218
|
hasSecret(dottedPath) {
|
|
@@ -3810,7 +4238,7 @@ class ConfigStore {
|
|
|
3810
4238
|
fingerprint() {
|
|
3811
4239
|
const stamp = (file) => {
|
|
3812
4240
|
try {
|
|
3813
|
-
return String(
|
|
4241
|
+
return String(fs7.statSync(file).mtimeMs);
|
|
3814
4242
|
} catch {
|
|
3815
4243
|
return "-";
|
|
3816
4244
|
}
|
|
@@ -3832,8 +4260,8 @@ function applyPath(config, dottedPath, value) {
|
|
|
3832
4260
|
function defaultConfigStorePaths(configDir) {
|
|
3833
4261
|
return {
|
|
3834
4262
|
configDir,
|
|
3835
|
-
configFile:
|
|
3836
|
-
secretsFile:
|
|
4263
|
+
configFile: path8.join(configDir, "config.json"),
|
|
4264
|
+
secretsFile: path8.join(configDir, "secrets.json")
|
|
3837
4265
|
};
|
|
3838
4266
|
}
|
|
3839
4267
|
var ENV_ALIASES, DIR_MODE = 448, FILE_MODE = 384;
|
|
@@ -3852,13 +4280,13 @@ var init_config_store = __esm(() => {
|
|
|
3852
4280
|
});
|
|
3853
4281
|
|
|
3854
4282
|
// packages/daemon/src/shell.ts
|
|
3855
|
-
import
|
|
3856
|
-
import
|
|
4283
|
+
import fs8 from "fs";
|
|
4284
|
+
import path9 from "path";
|
|
3857
4285
|
function findExecutable(name, env) {
|
|
3858
4286
|
const isWindows = env.platform === "win32";
|
|
3859
|
-
const join = isWindows ?
|
|
4287
|
+
const join = isWindows ? path9.win32.join : path9.posix.join;
|
|
3860
4288
|
const separator = isWindows ? ";" : ":";
|
|
3861
|
-
const extensions = isWindows ?
|
|
4289
|
+
const extensions = isWindows ? path9.win32.extname(name).length > 0 ? [""] : env.pathExt.split(";").filter((e) => e.length > 0) : [""];
|
|
3862
4290
|
for (const rawDir of env.pathEnv.split(separator)) {
|
|
3863
4291
|
const dir = rawDir.trim().replace(/^"|"$/g, "");
|
|
3864
4292
|
if (dir.length === 0)
|
|
@@ -3876,16 +4304,16 @@ function lookupOnPath(name) {
|
|
|
3876
4304
|
platform: process.platform,
|
|
3877
4305
|
pathEnv: process.env["PATH"] ?? "",
|
|
3878
4306
|
pathExt: process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD",
|
|
3879
|
-
exists: (candidate) =>
|
|
4307
|
+
exists: (candidate) => fs8.existsSync(candidate)
|
|
3880
4308
|
});
|
|
3881
4309
|
}
|
|
3882
4310
|
function resolveShell(kind) {
|
|
3883
4311
|
if (process.platform !== "win32")
|
|
3884
4312
|
return resolvePosixShell(kind);
|
|
3885
4313
|
const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows";
|
|
3886
|
-
const windowsPowershell =
|
|
4314
|
+
const windowsPowershell = path9.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
3887
4315
|
if (kind === "cmd") {
|
|
3888
|
-
return { kind: "cmd", exe:
|
|
4316
|
+
return { kind: "cmd", exe: path9.join(systemRoot, "System32", "cmd.exe") };
|
|
3889
4317
|
}
|
|
3890
4318
|
if (kind === "powershell") {
|
|
3891
4319
|
return { kind: "powershell", exe: windowsPowershell };
|
|
@@ -3913,7 +4341,7 @@ function resolvePosixShell(kind) {
|
|
|
3913
4341
|
candidates.push({ kind: "bash", exe: login });
|
|
3914
4342
|
candidates.push({ kind: "bash", exe: "/bin/bash" }, { kind: "bash", exe: "/usr/bin/bash" });
|
|
3915
4343
|
}
|
|
3916
|
-
const found = candidates.find((c) =>
|
|
4344
|
+
const found = candidates.find((c) => fs8.existsSync(c.exe));
|
|
3917
4345
|
if (found)
|
|
3918
4346
|
return found;
|
|
3919
4347
|
return { kind: "sh", exe: "/bin/sh" };
|
|
@@ -3958,24 +4386,24 @@ function ipcEndpoint(platform, user, env) {
|
|
|
3958
4386
|
const suffix = `-${shortHash(env.runtimeDir)}`;
|
|
3959
4387
|
return `\\\\.\\pipe\\axterm-${safeUser}${suffix}`;
|
|
3960
4388
|
}
|
|
3961
|
-
return
|
|
4389
|
+
return path9.posix.join(env.runtimeDir, "axterm.sock");
|
|
3962
4390
|
}
|
|
3963
4391
|
var cachedPwsh;
|
|
3964
4392
|
var init_shell = () => {};
|
|
3965
4393
|
|
|
3966
4394
|
// packages/daemon/src/paths.ts
|
|
3967
|
-
import
|
|
3968
|
-
import
|
|
3969
|
-
import
|
|
4395
|
+
import fs9 from "fs";
|
|
4396
|
+
import os4 from "os";
|
|
4397
|
+
import path10 from "path";
|
|
3970
4398
|
function ensureDataDirs() {
|
|
3971
4399
|
for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir, SCROLLBACK_DIR, LOG_DIR, EXT_DIR]) {
|
|
3972
|
-
|
|
4400
|
+
fs9.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
|
|
3973
4401
|
}
|
|
3974
4402
|
if (process.platform === "win32")
|
|
3975
4403
|
return;
|
|
3976
4404
|
for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir]) {
|
|
3977
4405
|
try {
|
|
3978
|
-
|
|
4406
|
+
fs9.chmodSync(dir, DIR_MODE2);
|
|
3979
4407
|
} catch {}
|
|
3980
4408
|
}
|
|
3981
4409
|
}
|
|
@@ -3983,14 +4411,14 @@ function restrictFile(file) {
|
|
|
3983
4411
|
if (process.platform === "win32")
|
|
3984
4412
|
return;
|
|
3985
4413
|
try {
|
|
3986
|
-
|
|
4414
|
+
fs9.chmodSync(file, 384);
|
|
3987
4415
|
} catch {}
|
|
3988
4416
|
}
|
|
3989
4417
|
function log(message) {
|
|
3990
4418
|
const line = `${new Date().toISOString()} ${message}
|
|
3991
4419
|
`;
|
|
3992
4420
|
try {
|
|
3993
|
-
|
|
4421
|
+
fs9.appendFileSync(LOG_FILE, line);
|
|
3994
4422
|
} catch {}
|
|
3995
4423
|
}
|
|
3996
4424
|
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;
|
|
@@ -4007,15 +4435,15 @@ var init_paths = __esm(() => {
|
|
|
4007
4435
|
DB_PATH = PATHS.dbFile;
|
|
4008
4436
|
RUNTIME_FILE2 = PATHS.runtimeFile;
|
|
4009
4437
|
HOOK_ENDPOINT_FILE = PATHS.hookEndpointFile;
|
|
4010
|
-
PIPE_PATH = ipcEndpoint(process.platform,
|
|
4438
|
+
PIPE_PATH = ipcEndpoint(process.platform, os4.userInfo().username, {
|
|
4011
4439
|
runtimeDir: PATHS.runtimeDir
|
|
4012
4440
|
});
|
|
4013
|
-
LOG_FILE =
|
|
4441
|
+
LOG_FILE = path10.join(LOG_DIR, "daemon.log");
|
|
4014
4442
|
});
|
|
4015
4443
|
|
|
4016
4444
|
// packages/daemon/src/store.ts
|
|
4017
|
-
import
|
|
4018
|
-
import
|
|
4445
|
+
import fs10 from "fs";
|
|
4446
|
+
import path11 from "path";
|
|
4019
4447
|
import { Database } from "bun:sqlite";
|
|
4020
4448
|
function rowToInfo(row) {
|
|
4021
4449
|
return {
|
|
@@ -4046,7 +4474,7 @@ function rowToInfo(row) {
|
|
|
4046
4474
|
class Store {
|
|
4047
4475
|
db;
|
|
4048
4476
|
constructor(dbPath = DB_PATH) {
|
|
4049
|
-
|
|
4477
|
+
fs10.mkdirSync(path11.dirname(dbPath), { recursive: true });
|
|
4050
4478
|
this.db = new Database(dbPath, { strict: true });
|
|
4051
4479
|
this.db.run("PRAGMA journal_mode = WAL");
|
|
4052
4480
|
this.db.run(`
|
|
@@ -4125,8 +4553,10 @@ class Store {
|
|
|
4125
4553
|
this.db.prepare(`UPDATE sessions SET state = 'running', exit_code = NULL, exited_at = NULL,
|
|
4126
4554
|
agent_command = ?, pty_pid = ? WHERE id = ?`).run(JSON.stringify(agentCommand), ptyPid ?? null, id);
|
|
4127
4555
|
}
|
|
4128
|
-
markStaleRunningAsExited() {
|
|
4129
|
-
|
|
4556
|
+
markStaleRunningAsExited(keep = []) {
|
|
4557
|
+
const holes = keep.map(() => "?").join(",");
|
|
4558
|
+
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})`;
|
|
4559
|
+
return this.db.prepare(sql).run(Date.now(), ...keep).changes;
|
|
4130
4560
|
}
|
|
4131
4561
|
listSessions() {
|
|
4132
4562
|
const rows = this.db.prepare(`SELECT * FROM sessions ORDER BY created_at DESC`).all();
|
|
@@ -4212,11 +4642,11 @@ var init_store = __esm(() => {
|
|
|
4212
4642
|
});
|
|
4213
4643
|
|
|
4214
4644
|
// packages/daemon/src/config-migrate.ts
|
|
4215
|
-
import
|
|
4216
|
-
import
|
|
4645
|
+
import fs11 from "fs";
|
|
4646
|
+
import path12 from "path";
|
|
4217
4647
|
import { Database as Database2 } from "bun:sqlite";
|
|
4218
4648
|
function checkpointWal(dbFile) {
|
|
4219
|
-
if (!
|
|
4649
|
+
if (!fs11.existsSync(dbFile))
|
|
4220
4650
|
return;
|
|
4221
4651
|
try {
|
|
4222
4652
|
const db = new Database2(dbFile);
|
|
@@ -4226,32 +4656,32 @@ function checkpointWal(dbFile) {
|
|
|
4226
4656
|
}
|
|
4227
4657
|
function relocate(source, target) {
|
|
4228
4658
|
try {
|
|
4229
|
-
|
|
4659
|
+
fs11.renameSync(source, target);
|
|
4230
4660
|
} catch {
|
|
4231
|
-
|
|
4661
|
+
fs11.cpSync(source, target, { recursive: true });
|
|
4232
4662
|
}
|
|
4233
4663
|
}
|
|
4234
4664
|
function moveInto(from, to, notes) {
|
|
4235
|
-
checkpointWal(
|
|
4236
|
-
|
|
4665
|
+
checkpointWal(path12.join(from, "axterm.db"));
|
|
4666
|
+
fs11.mkdirSync(to, { recursive: true });
|
|
4237
4667
|
for (const entry of STATE_ENTRIES) {
|
|
4238
|
-
const source =
|
|
4239
|
-
const target =
|
|
4240
|
-
if (!
|
|
4668
|
+
const source = path12.join(from, entry);
|
|
4669
|
+
const target = path12.join(to, entry);
|
|
4670
|
+
if (!fs11.existsSync(source))
|
|
4241
4671
|
continue;
|
|
4242
|
-
if (!
|
|
4672
|
+
if (!fs11.existsSync(target)) {
|
|
4243
4673
|
relocate(source, target);
|
|
4244
4674
|
notes.push(`moved ${entry}`);
|
|
4245
4675
|
continue;
|
|
4246
4676
|
}
|
|
4247
|
-
if (!
|
|
4677
|
+
if (!fs11.statSync(source).isDirectory() || !fs11.statSync(target).isDirectory())
|
|
4248
4678
|
continue;
|
|
4249
4679
|
let moved = 0;
|
|
4250
|
-
for (const child of
|
|
4251
|
-
const childTarget =
|
|
4252
|
-
if (
|
|
4680
|
+
for (const child of fs11.readdirSync(source)) {
|
|
4681
|
+
const childTarget = path12.join(target, child);
|
|
4682
|
+
if (fs11.existsSync(childTarget))
|
|
4253
4683
|
continue;
|
|
4254
|
-
relocate(
|
|
4684
|
+
relocate(path12.join(source, child), childTarget);
|
|
4255
4685
|
moved += 1;
|
|
4256
4686
|
}
|
|
4257
4687
|
if (moved > 0)
|
|
@@ -4261,29 +4691,29 @@ function moveInto(from, to, notes) {
|
|
|
4261
4691
|
function migrateLegacyConfig(input) {
|
|
4262
4692
|
const notes = [];
|
|
4263
4693
|
const storePaths = defaultConfigStorePaths(input.configDir);
|
|
4264
|
-
if (
|
|
4694
|
+
if (fs11.existsSync(storePaths.configFile))
|
|
4265
4695
|
return { migrated: false, notes };
|
|
4266
|
-
const configSource = input.legacyRoots.find((dir) =>
|
|
4696
|
+
const configSource = input.legacyRoots.find((dir) => fs11.existsSync(path12.join(dir, "config.json")));
|
|
4267
4697
|
if (configSource !== undefined) {
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
const secrets =
|
|
4271
|
-
if (
|
|
4272
|
-
|
|
4698
|
+
fs11.mkdirSync(input.configDir, { recursive: true });
|
|
4699
|
+
fs11.copyFileSync(path12.join(configSource, "config.json"), storePaths.configFile);
|
|
4700
|
+
const secrets = path12.join(configSource, "secrets.json");
|
|
4701
|
+
if (fs11.existsSync(secrets)) {
|
|
4702
|
+
fs11.copyFileSync(secrets, storePaths.secretsFile);
|
|
4273
4703
|
try {
|
|
4274
|
-
|
|
4704
|
+
fs11.chmodSync(storePaths.secretsFile, 384);
|
|
4275
4705
|
} catch {}
|
|
4276
4706
|
}
|
|
4277
|
-
|
|
4707
|
+
fs11.writeFileSync(path12.join(configSource, "MIGRATED.txt"), `axterm now reads its configuration from:
|
|
4278
4708
|
${input.configDir}
|
|
4279
4709
|
` + `These files are no longer used. secrets.json still holds your bot token \u2014 delete this directory once you are satisfied.
|
|
4280
4710
|
`);
|
|
4281
4711
|
notes.push(`adopted configuration from ${configSource}`);
|
|
4282
4712
|
}
|
|
4283
|
-
const legacy = input.legacyRoots.find((dir) =>
|
|
4713
|
+
const legacy = input.legacyRoots.find((dir) => fs11.existsSync(path12.join(dir, "axterm.db")));
|
|
4284
4714
|
if (legacy !== undefined) {
|
|
4285
4715
|
moveInto(legacy, input.stateDir, notes);
|
|
4286
|
-
|
|
4716
|
+
fs11.writeFileSync(path12.join(legacy, "MIGRATED.txt"), `axterm now stores state in:
|
|
4287
4717
|
${input.stateDir}
|
|
4288
4718
|
and configuration in:
|
|
4289
4719
|
${input.configDir}
|
|
@@ -4292,8 +4722,8 @@ and configuration in:
|
|
|
4292
4722
|
}
|
|
4293
4723
|
if (configSource !== undefined)
|
|
4294
4724
|
return { migrated: true, ...legacy ? { from: legacy } : {}, notes };
|
|
4295
|
-
const dbFile =
|
|
4296
|
-
if (!
|
|
4725
|
+
const dbFile = path12.join(input.stateDir, "axterm.db");
|
|
4726
|
+
if (!fs11.existsSync(dbFile))
|
|
4297
4727
|
return { migrated: notes.length > 0, ...legacy ? { from: legacy } : {}, notes };
|
|
4298
4728
|
const store = new Store(dbFile);
|
|
4299
4729
|
const machine2 = store.getConfig("axterm_machine");
|
|
@@ -5131,13 +5561,13 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
5131
5561
|
var init_axterm_agent_status = () => {};
|
|
5132
5562
|
|
|
5133
5563
|
// packages/daemon/src/ext-install.ts
|
|
5134
|
-
import
|
|
5135
|
-
import
|
|
5564
|
+
import fs12 from "fs";
|
|
5565
|
+
import path13 from "path";
|
|
5136
5566
|
function installExtensionAsset(targetDir) {
|
|
5137
|
-
|
|
5138
|
-
const target =
|
|
5567
|
+
fs12.mkdirSync(targetDir, { recursive: true });
|
|
5568
|
+
const target = path13.join(targetDir, "axterm-agent-status.mjs");
|
|
5139
5569
|
const js = new Bun.Transpiler({ loader: "ts" }).transformSync(extensionSource);
|
|
5140
|
-
|
|
5570
|
+
fs12.writeFileSync(target, MANAGED_HEADER + js);
|
|
5141
5571
|
return target;
|
|
5142
5572
|
}
|
|
5143
5573
|
var extensionSource, MANAGED_HEADER = `// @axterm-managed-omp-extension
|
|
@@ -5150,8 +5580,8 @@ var init_ext_install = __esm(() => {
|
|
|
5150
5580
|
|
|
5151
5581
|
// packages/daemon/src/workspace-local.ts
|
|
5152
5582
|
import { spawn as spawn2 } from "child_process";
|
|
5153
|
-
import
|
|
5154
|
-
import
|
|
5583
|
+
import fs13 from "fs/promises";
|
|
5584
|
+
import path14 from "path";
|
|
5155
5585
|
function isAbsent(error) {
|
|
5156
5586
|
const code = error?.code;
|
|
5157
5587
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
@@ -5164,10 +5594,10 @@ class LocalWorkspace {
|
|
|
5164
5594
|
this.stagingDir = stagingDir;
|
|
5165
5595
|
}
|
|
5166
5596
|
async list(dir) {
|
|
5167
|
-
const dirents = await
|
|
5597
|
+
const dirents = await fs13.readdir(dir, { withFileTypes: true });
|
|
5168
5598
|
const stats = await Promise.all(dirents.map(async (dirent) => {
|
|
5169
5599
|
try {
|
|
5170
|
-
return await
|
|
5600
|
+
return await fs13.lstat(path14.join(dir, dirent.name));
|
|
5171
5601
|
} catch (error) {
|
|
5172
5602
|
if (isAbsent(error))
|
|
5173
5603
|
return;
|
|
@@ -5191,7 +5621,7 @@ class LocalWorkspace {
|
|
|
5191
5621
|
}
|
|
5192
5622
|
async stat(target) {
|
|
5193
5623
|
try {
|
|
5194
|
-
const stat = await
|
|
5624
|
+
const stat = await fs13.stat(target);
|
|
5195
5625
|
const kind = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "other";
|
|
5196
5626
|
return { kind, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
5197
5627
|
} catch (error) {
|
|
@@ -5202,8 +5632,8 @@ class LocalWorkspace {
|
|
|
5202
5632
|
}
|
|
5203
5633
|
async read(file, limit) {
|
|
5204
5634
|
if (limit === undefined)
|
|
5205
|
-
return await
|
|
5206
|
-
const handle = await
|
|
5635
|
+
return await fs13.readFile(file);
|
|
5636
|
+
const handle = await fs13.open(file, "r");
|
|
5207
5637
|
try {
|
|
5208
5638
|
const buffer = Buffer.allocUnsafe(Math.min(limit, (await handle.stat()).size));
|
|
5209
5639
|
let filled = 0;
|
|
@@ -5222,10 +5652,10 @@ class LocalWorkspace {
|
|
|
5222
5652
|
return Promise.resolve(file);
|
|
5223
5653
|
}
|
|
5224
5654
|
async mkdirp(dir, mode) {
|
|
5225
|
-
await
|
|
5655
|
+
await fs13.mkdir(dir, mode === undefined ? { recursive: true } : { recursive: true, mode });
|
|
5226
5656
|
}
|
|
5227
5657
|
async write(file, bytes, mode) {
|
|
5228
|
-
await
|
|
5658
|
+
await fs13.writeFile(file, bytes, mode === undefined ? undefined : { mode });
|
|
5229
5659
|
}
|
|
5230
5660
|
exec(argv, cwd) {
|
|
5231
5661
|
const [command, ...args] = argv;
|
|
@@ -5254,9 +5684,9 @@ var init_workspace_local = () => {};
|
|
|
5254
5684
|
|
|
5255
5685
|
// packages/daemon/src/hook-host.ts
|
|
5256
5686
|
import { randomBytes } from "crypto";
|
|
5257
|
-
import
|
|
5687
|
+
import fs14 from "fs";
|
|
5258
5688
|
import http from "http";
|
|
5259
|
-
import
|
|
5689
|
+
import path15 from "path";
|
|
5260
5690
|
function header(req, name) {
|
|
5261
5691
|
const value = req.headers[name];
|
|
5262
5692
|
return Array.isArray(value) ? value[0] : value;
|
|
@@ -5270,17 +5700,18 @@ class HookHost {
|
|
|
5270
5700
|
}
|
|
5271
5701
|
sessions = new Map;
|
|
5272
5702
|
pendingAsks = new Map;
|
|
5703
|
+
refusedHooks = new Set;
|
|
5273
5704
|
endpointFile;
|
|
5274
5705
|
server;
|
|
5275
5706
|
askOpenHandler;
|
|
5276
5707
|
resolveWorkspace;
|
|
5277
5708
|
local;
|
|
5278
|
-
|
|
5279
|
-
|
|
5709
|
+
approvalHoldMs;
|
|
5710
|
+
approvalSeq = 0;
|
|
5280
5711
|
constructor(options) {
|
|
5281
5712
|
this.endpointFile = options.endpointFile;
|
|
5282
|
-
this.
|
|
5283
|
-
this.resolveWorkspace = options.workspaceFor ?? (() => this.local ??= new LocalWorkspace(
|
|
5713
|
+
this.approvalHoldMs = options.approvalHoldMs ?? APPROVAL_HOLD_MS;
|
|
5714
|
+
this.resolveWorkspace = options.workspaceFor ?? (() => this.local ??= new LocalWorkspace(path15.join(RUNTIME_DIR, "materialised")));
|
|
5284
5715
|
}
|
|
5285
5716
|
start() {
|
|
5286
5717
|
const server = http.createServer((req, res) => this.handle(req, res));
|
|
@@ -5296,16 +5727,19 @@ class HookHost {
|
|
|
5296
5727
|
return promise;
|
|
5297
5728
|
}
|
|
5298
5729
|
writeEndpointFile(port) {
|
|
5299
|
-
|
|
5730
|
+
fs14.mkdirSync(path15.dirname(this.endpointFile), { recursive: true });
|
|
5300
5731
|
const tmp = `${this.endpointFile}.tmp`;
|
|
5301
|
-
|
|
5732
|
+
fs14.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
|
|
5302
5733
|
AXTERM_HOOK_TOKEN=${this.token}
|
|
5303
5734
|
`);
|
|
5304
|
-
|
|
5735
|
+
fs14.renameSync(tmp, this.endpointFile);
|
|
5305
5736
|
}
|
|
5306
5737
|
registerSession(sessionId, launchToken, onEvent) {
|
|
5307
5738
|
this.sessions.set(sessionId, { launchToken, onEvent });
|
|
5308
5739
|
}
|
|
5740
|
+
launchMatches(session, presented) {
|
|
5741
|
+
return session.launchToken === undefined || session.launchToken === presented;
|
|
5742
|
+
}
|
|
5309
5743
|
unregisterSession(sessionId) {
|
|
5310
5744
|
this.sessions.delete(sessionId);
|
|
5311
5745
|
for (const [askId, pending] of this.pendingAsks) {
|
|
@@ -5367,11 +5801,11 @@ AXTERM_HOOK_TOKEN=${this.token}
|
|
|
5367
5801
|
req.on("end", () => {
|
|
5368
5802
|
const raw = Buffer.concat(chunks).toString("utf8");
|
|
5369
5803
|
if (isClaude) {
|
|
5370
|
-
this.
|
|
5804
|
+
this.handleDecidingAgentHook(req, raw, res, "claude");
|
|
5371
5805
|
return;
|
|
5372
5806
|
}
|
|
5373
5807
|
if (isCodex) {
|
|
5374
|
-
this.
|
|
5808
|
+
this.handleDecidingAgentHook(req, raw, res, "codex");
|
|
5375
5809
|
return;
|
|
5376
5810
|
}
|
|
5377
5811
|
if (isGemini) {
|
|
@@ -5394,7 +5828,7 @@ AXTERM_HOOK_TOKEN=${this.token}
|
|
|
5394
5828
|
res.writeHead(404).end();
|
|
5395
5829
|
return;
|
|
5396
5830
|
}
|
|
5397
|
-
if (
|
|
5831
|
+
if (!this.launchMatches(session, body.launchToken)) {
|
|
5398
5832
|
res.writeHead(403).end();
|
|
5399
5833
|
return;
|
|
5400
5834
|
}
|
|
@@ -5429,9 +5863,11 @@ AXTERM_HOOK_TOKEN=${this.token}
|
|
|
5429
5863
|
return;
|
|
5430
5864
|
}
|
|
5431
5865
|
res.writeHead(200).end();
|
|
5432
|
-
|
|
5866
|
+
const named = input;
|
|
5867
|
+
const events = withConversationId(map(input), { sessionId: named.session_id, subagentId: named.agent_id });
|
|
5868
|
+
this.dispatchHookPayloads(auth.session, events, agent);
|
|
5433
5869
|
}
|
|
5434
|
-
|
|
5870
|
+
handleDecidingAgentHook(req, raw, res, agent) {
|
|
5435
5871
|
const auth = this.agentHookSession(req);
|
|
5436
5872
|
if (!auth) {
|
|
5437
5873
|
res.writeHead(200).end();
|
|
@@ -5444,20 +5880,27 @@ AXTERM_HOOK_TOKEN=${this.token}
|
|
|
5444
5880
|
res.writeHead(200).end();
|
|
5445
5881
|
return;
|
|
5446
5882
|
}
|
|
5883
|
+
const dispatch = () => {
|
|
5884
|
+
const events = withConversationId(mapClaudeHook(input), {
|
|
5885
|
+
sessionId: input.session_id,
|
|
5886
|
+
subagentId: input.agent_id
|
|
5887
|
+
});
|
|
5888
|
+
this.dispatchHookPayloads(auth.session, events, agent);
|
|
5889
|
+
};
|
|
5447
5890
|
if (input.hook_event_name !== "PermissionRequest") {
|
|
5448
5891
|
res.writeHead(200).end();
|
|
5449
|
-
|
|
5892
|
+
dispatch();
|
|
5450
5893
|
return;
|
|
5451
5894
|
}
|
|
5452
5895
|
const question = buildApprovalQuestion(input.tool_name);
|
|
5453
|
-
|
|
5454
|
-
const askId =
|
|
5896
|
+
dispatch();
|
|
5897
|
+
const askId = `${agent}-${auth.sessionId}-${this.approvalSeq++}`;
|
|
5455
5898
|
let fence;
|
|
5456
5899
|
const settle = (body) => {
|
|
5457
5900
|
clearTimeout(fence);
|
|
5458
5901
|
if (res.writableEnded)
|
|
5459
5902
|
return;
|
|
5460
|
-
const decision = body.status === "answered" ?
|
|
5903
|
+
const decision = body.status === "answered" ? hookPermissionDecision(question, body.answers, t("agent.approval.denied")) : undefined;
|
|
5461
5904
|
if (decision === undefined) {
|
|
5462
5905
|
res.writeHead(200).end();
|
|
5463
5906
|
return;
|
|
@@ -5478,15 +5921,22 @@ AXTERM_HOOK_TOKEN=${this.token}
|
|
|
5478
5921
|
return;
|
|
5479
5922
|
this.pendingAsks.delete(askId);
|
|
5480
5923
|
settle({ status: "cancelled" });
|
|
5481
|
-
}, this.
|
|
5924
|
+
}, this.approvalHoldMs);
|
|
5482
5925
|
fence.unref?.();
|
|
5483
5926
|
}
|
|
5484
5927
|
agentHookSession(req) {
|
|
5485
5928
|
const sessionId = header(req, "x-axterm-session");
|
|
5486
5929
|
const launchToken = header(req, "x-axterm-launch");
|
|
5487
5930
|
const session = sessionId === undefined ? undefined : this.sessions.get(sessionId);
|
|
5488
|
-
if (sessionId === undefined || !session || session
|
|
5931
|
+
if (sessionId === undefined || !session || !this.launchMatches(session, launchToken)) {
|
|
5932
|
+
const why = sessionId === undefined ? "no session header" : !session ? "unknown session" : "launch token mismatch";
|
|
5933
|
+
const key = `${req.url}\x00${sessionId ?? ""}\x00${why}`;
|
|
5934
|
+
if (!this.refusedHooks.has(key)) {
|
|
5935
|
+
this.refusedHooks.add(key);
|
|
5936
|
+
log(`refused hook post to ${req.url}: ${why} (session ${sessionId ?? "-"}) \u2014 reported once per session`);
|
|
5937
|
+
}
|
|
5489
5938
|
return;
|
|
5939
|
+
}
|
|
5490
5940
|
return { sessionId, session };
|
|
5491
5941
|
}
|
|
5492
5942
|
dispatchHookPayloads(session, payloads, agent) {
|
|
@@ -5575,8 +6025,112 @@ var init_hook_host = __esm(() => {
|
|
|
5575
6025
|
MAX_BODY_BYTES = 256 * 1024;
|
|
5576
6026
|
});
|
|
5577
6027
|
|
|
6028
|
+
// packages/daemon/src/process-guards.ts
|
|
6029
|
+
class RejectionLogLimiter {
|
|
6030
|
+
detailLimit;
|
|
6031
|
+
windowMs;
|
|
6032
|
+
windowStartedAt;
|
|
6033
|
+
details = 0;
|
|
6034
|
+
noticeEmitted = false;
|
|
6035
|
+
constructor(detailLimit = DEFAULT_REJECTION_DETAILS, windowMs = DEFAULT_REJECTION_WINDOW_MS) {
|
|
6036
|
+
this.detailLimit = detailLimit;
|
|
6037
|
+
this.windowMs = windowMs;
|
|
6038
|
+
if (!Number.isInteger(detailLimit) || detailLimit < 1)
|
|
6039
|
+
throw new RangeError("detailLimit must be a positive integer");
|
|
6040
|
+
if (!Number.isFinite(windowMs) || windowMs <= 0)
|
|
6041
|
+
throw new RangeError("windowMs must be positive");
|
|
6042
|
+
}
|
|
6043
|
+
decide(now) {
|
|
6044
|
+
if (this.windowStartedAt === undefined || now - this.windowStartedAt >= this.windowMs || now < this.windowStartedAt) {
|
|
6045
|
+
this.windowStartedAt = now;
|
|
6046
|
+
this.details = 0;
|
|
6047
|
+
this.noticeEmitted = false;
|
|
6048
|
+
}
|
|
6049
|
+
if (this.details < this.detailLimit) {
|
|
6050
|
+
this.details++;
|
|
6051
|
+
return "detail";
|
|
6052
|
+
}
|
|
6053
|
+
if (!this.noticeEmitted) {
|
|
6054
|
+
this.noticeEmitted = true;
|
|
6055
|
+
return "notice";
|
|
6056
|
+
}
|
|
6057
|
+
return "drop";
|
|
6058
|
+
}
|
|
6059
|
+
}
|
|
6060
|
+
function thrownText(reason) {
|
|
6061
|
+
let text;
|
|
6062
|
+
if (reason instanceof Error) {
|
|
6063
|
+
text = reason.stack ?? reason.message;
|
|
6064
|
+
} else if (typeof reason === "string") {
|
|
6065
|
+
text = reason;
|
|
6066
|
+
} else {
|
|
6067
|
+
try {
|
|
6068
|
+
text = JSON.stringify(reason) ?? String(reason);
|
|
6069
|
+
} catch {
|
|
6070
|
+
text = String(reason);
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
6073
|
+
if (text.length <= MAX_BREADCRUMB_CHARS)
|
|
6074
|
+
return text;
|
|
6075
|
+
return `${text.slice(0, MAX_BREADCRUMB_CHARS)}\u2026 [truncated]`;
|
|
6076
|
+
}
|
|
6077
|
+
|
|
6078
|
+
class ProcessCrashGuards {
|
|
6079
|
+
options;
|
|
6080
|
+
limiter;
|
|
6081
|
+
now;
|
|
6082
|
+
fatal = false;
|
|
6083
|
+
uninstallCurrent;
|
|
6084
|
+
constructor(options) {
|
|
6085
|
+
this.options = options;
|
|
6086
|
+
this.now = options.now ?? Date.now;
|
|
6087
|
+
this.limiter = new RejectionLogLimiter(options.rejectionDetailsPerWindow, options.rejectionWindowMs);
|
|
6088
|
+
}
|
|
6089
|
+
install(events) {
|
|
6090
|
+
if (this.uninstallCurrent !== undefined)
|
|
6091
|
+
throw new Error("process crash guards are already installed");
|
|
6092
|
+
const uncaught = (reason) => this.fatalExit("uncaughtException", reason);
|
|
6093
|
+
const rejected = (reason) => this.unhandledRejection(reason);
|
|
6094
|
+
events.on("uncaughtException", uncaught);
|
|
6095
|
+
events.on("unhandledRejection", rejected);
|
|
6096
|
+
let installed = true;
|
|
6097
|
+
const uninstall = () => {
|
|
6098
|
+
if (!installed)
|
|
6099
|
+
return;
|
|
6100
|
+
installed = false;
|
|
6101
|
+
events.off("uncaughtException", uncaught);
|
|
6102
|
+
events.off("unhandledRejection", rejected);
|
|
6103
|
+
if (this.uninstallCurrent === uninstall)
|
|
6104
|
+
this.uninstallCurrent = undefined;
|
|
6105
|
+
};
|
|
6106
|
+
this.uninstallCurrent = uninstall;
|
|
6107
|
+
return uninstall;
|
|
6108
|
+
}
|
|
6109
|
+
startupFailed(reason) {
|
|
6110
|
+
this.fatalExit("startupFailure", reason);
|
|
6111
|
+
}
|
|
6112
|
+
unhandledRejection(reason) {
|
|
6113
|
+
if (this.fatal)
|
|
6114
|
+
return;
|
|
6115
|
+
const decision = this.limiter.decide(this.now());
|
|
6116
|
+
if (decision === "detail") {
|
|
6117
|
+
this.options.log(`unhandledRejection: ${thrownText(reason)}`);
|
|
6118
|
+
} else if (decision === "notice") {
|
|
6119
|
+
this.options.log("unhandledRejection: suppressing further unhandled rejections in this rate-limit window");
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
fatalExit(origin, reason) {
|
|
6123
|
+
if (this.fatal)
|
|
6124
|
+
return;
|
|
6125
|
+
this.fatal = true;
|
|
6126
|
+
this.options.log(`${origin}: ${thrownText(reason)}`);
|
|
6127
|
+
this.options.exit(1);
|
|
6128
|
+
}
|
|
6129
|
+
}
|
|
6130
|
+
var DEFAULT_REJECTION_DETAILS = 20, DEFAULT_REJECTION_WINDOW_MS = 60000, MAX_BREADCRUMB_CHARS = 16384;
|
|
6131
|
+
|
|
5578
6132
|
// packages/daemon/src/rpc-server.ts
|
|
5579
|
-
import
|
|
6133
|
+
import fs15 from "fs";
|
|
5580
6134
|
import net2 from "net";
|
|
5581
6135
|
function writeFrame(socket, frame) {
|
|
5582
6136
|
if (!socket.destroyed)
|
|
@@ -5686,6 +6240,8 @@ function startRpcServer(options) {
|
|
|
5686
6240
|
session.write(Buffer.from(p["dataB64"], "base64").toString("utf8"));
|
|
5687
6241
|
return {};
|
|
5688
6242
|
}
|
|
6243
|
+
case "session.choose":
|
|
6244
|
+
return { answered: manager.answerScreenChoice(p["id"], p["index"]) };
|
|
5689
6245
|
case "attach": {
|
|
5690
6246
|
const session = manager.resolveLive(p["id"]);
|
|
5691
6247
|
cleanup();
|
|
@@ -5812,7 +6368,7 @@ function startRpcServer(options) {
|
|
|
5812
6368
|
});
|
|
5813
6369
|
if (process.platform !== "win32") {
|
|
5814
6370
|
try {
|
|
5815
|
-
|
|
6371
|
+
fs15.unlinkSync(pipePath);
|
|
5816
6372
|
} catch {}
|
|
5817
6373
|
}
|
|
5818
6374
|
server.listen(pipePath, () => {
|
|
@@ -5827,17 +6383,34 @@ var init_rpc_server = __esm(() => {
|
|
|
5827
6383
|
});
|
|
5828
6384
|
|
|
5829
6385
|
// packages/daemon/src/codex-home.ts
|
|
5830
|
-
import
|
|
5831
|
-
import
|
|
6386
|
+
import fs16 from "fs";
|
|
6387
|
+
import path16 from "path";
|
|
5832
6388
|
function codexTrustKeyPath(home) {
|
|
5833
6389
|
const resolved = (() => {
|
|
5834
6390
|
try {
|
|
5835
|
-
return
|
|
6391
|
+
return fs16.realpathSync.native(home);
|
|
5836
6392
|
} catch {
|
|
5837
|
-
return
|
|
6393
|
+
return path16.resolve(home);
|
|
5838
6394
|
}
|
|
5839
6395
|
})();
|
|
5840
|
-
return
|
|
6396
|
+
return path16.join(resolved, "hooks.json");
|
|
6397
|
+
}
|
|
6398
|
+
function referenceCredential(source, target, ops) {
|
|
6399
|
+
const symlink = ops?.symlink ?? ((s, t2) => fs16.symlinkSync(s, t2, "file"));
|
|
6400
|
+
const link = ops?.link ?? fs16.linkSync;
|
|
6401
|
+
try {
|
|
6402
|
+
symlink(source, target);
|
|
6403
|
+
return "symlink";
|
|
6404
|
+
} catch {
|
|
6405
|
+
try {
|
|
6406
|
+
link(source, target);
|
|
6407
|
+
return "hardlink";
|
|
6408
|
+
} catch {
|
|
6409
|
+
fs16.copyFileSync(source, target);
|
|
6410
|
+
fs16.chmodSync(target, 384);
|
|
6411
|
+
return "copy";
|
|
6412
|
+
}
|
|
6413
|
+
}
|
|
5841
6414
|
}
|
|
5842
6415
|
var init_codex_home = () => {};
|
|
5843
6416
|
|
|
@@ -5853,15 +6426,36 @@ function stripHostAttribution(env) {
|
|
|
5853
6426
|
}
|
|
5854
6427
|
var ATTRIBUTION_PREFIXES, ATTRIBUTION_KEEP;
|
|
5855
6428
|
var init_host_env = __esm(() => {
|
|
5856
|
-
ATTRIBUTION_PREFIXES = ["ORCA_"];
|
|
6429
|
+
ATTRIBUTION_PREFIXES = ["ORCA_", "AXTERM_"];
|
|
5857
6430
|
ATTRIBUTION_KEEP = new Set([
|
|
5858
6431
|
"ORCA_CODEX_HOME",
|
|
5859
6432
|
"ORCA_OPENCODE_CONFIG_DIR",
|
|
5860
6433
|
"ORCA_USER_DATA_PATH",
|
|
5861
|
-
"ORCA_APP_VERSION"
|
|
6434
|
+
"ORCA_APP_VERSION",
|
|
6435
|
+
"AXTERM_CONFIG_DIR",
|
|
6436
|
+
"AXTERM_STATE_DIR",
|
|
6437
|
+
"AXTERM_RUNTIME_DIR",
|
|
6438
|
+
"AXTERM_LANG",
|
|
6439
|
+
"AXTERM_PTY_HOST",
|
|
6440
|
+
"AXTERM_PTY_HOST_MAIN"
|
|
5862
6441
|
]);
|
|
5863
6442
|
});
|
|
5864
6443
|
|
|
6444
|
+
// packages/daemon/src/session-env.ts
|
|
6445
|
+
function sessionEnv(base, identity) {
|
|
6446
|
+
return {
|
|
6447
|
+
...stripHostAttribution(base),
|
|
6448
|
+
AXTERM_SESSION_ID: identity.sessionId,
|
|
6449
|
+
AXTERM_LAUNCH_TOKEN: identity.launchToken,
|
|
6450
|
+
AXTERM_HOOK_PORT: String(identity.hookPort),
|
|
6451
|
+
AXTERM_HOOK_TOKEN: identity.hookToken,
|
|
6452
|
+
AXTERM_HOOK_ENDPOINT: identity.endpointFile
|
|
6453
|
+
};
|
|
6454
|
+
}
|
|
6455
|
+
var init_session_env = __esm(() => {
|
|
6456
|
+
init_host_env();
|
|
6457
|
+
});
|
|
6458
|
+
|
|
5865
6459
|
// packages/daemon/src/git-porcelain.ts
|
|
5866
6460
|
function parseWorktreeList(porcelain) {
|
|
5867
6461
|
const entries = [];
|
|
@@ -5900,8 +6494,8 @@ function slugifyBranch(branch) {
|
|
|
5900
6494
|
var MAX_SLUG_LENGTH = 60;
|
|
5901
6495
|
|
|
5902
6496
|
// packages/daemon/src/git.ts
|
|
5903
|
-
import
|
|
5904
|
-
import
|
|
6497
|
+
import fs17 from "fs";
|
|
6498
|
+
import path17 from "path";
|
|
5905
6499
|
async function git(ws, cwd, args) {
|
|
5906
6500
|
const run = await ws.exec(["git", ...args], cwd);
|
|
5907
6501
|
return {
|
|
@@ -5918,15 +6512,15 @@ async function gitOrThrow(ws, cwd, args) {
|
|
|
5918
6512
|
return result.stdout;
|
|
5919
6513
|
}
|
|
5920
6514
|
function canonicalPath(target) {
|
|
5921
|
-
let resolved =
|
|
6515
|
+
let resolved = path17.resolve(target);
|
|
5922
6516
|
try {
|
|
5923
|
-
resolved =
|
|
6517
|
+
resolved = fs17.realpathSync.native(resolved);
|
|
5924
6518
|
} catch {}
|
|
5925
6519
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
5926
6520
|
}
|
|
5927
6521
|
async function resolveRepoRoot(ws, cwd) {
|
|
5928
6522
|
const result = await git(ws, cwd, ["rev-parse", "--show-toplevel"]);
|
|
5929
|
-
return result.ok ?
|
|
6523
|
+
return result.ok ? path17.resolve(result.stdout) : undefined;
|
|
5930
6524
|
}
|
|
5931
6525
|
async function currentBranch(ws, cwd) {
|
|
5932
6526
|
const result = await git(ws, cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
@@ -5935,8 +6529,8 @@ async function currentBranch(ws, cwd) {
|
|
|
5935
6529
|
async function listWorktrees(ws, repoRoot) {
|
|
5936
6530
|
return parseWorktreeList(await gitOrThrow(ws, repoRoot, ["worktree", "list", "--porcelain"]));
|
|
5937
6531
|
}
|
|
5938
|
-
function worktreePathFor(repoRoot, branch, root =
|
|
5939
|
-
return
|
|
6532
|
+
function worktreePathFor(repoRoot, branch, root = path17.join(STATE_DIR, "worktrees")) {
|
|
6533
|
+
return path17.join(root, slugifyBranch(path17.basename(repoRoot)), slugifyBranch(branch));
|
|
5940
6534
|
}
|
|
5941
6535
|
async function createWorktree(ws, options) {
|
|
5942
6536
|
const { repoRoot, branch, base } = options;
|
|
@@ -5944,7 +6538,7 @@ async function createWorktree(ws, options) {
|
|
|
5944
6538
|
const existing = (await listWorktrees(ws, repoRoot)).find((w) => canonicalPath(w.path) === canonicalPath(target));
|
|
5945
6539
|
if (existing)
|
|
5946
6540
|
return { path: target, branch, createdBranch: false };
|
|
5947
|
-
|
|
6541
|
+
fs17.mkdirSync(path17.dirname(target), { recursive: true });
|
|
5948
6542
|
const exists = (await git(ws, repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`])).ok;
|
|
5949
6543
|
const args = exists ? ["worktree", "add", target, branch] : ["worktree", "add", "-b", branch, target, ...base !== undefined ? [base] : []];
|
|
5950
6544
|
await gitOrThrow(ws, repoRoot, args);
|
|
@@ -6005,7 +6599,7 @@ async function diffStat(ws, worktreePath, base) {
|
|
|
6005
6599
|
const untracked = await untrackedFiles(ws, worktreePath);
|
|
6006
6600
|
let newLines = 0;
|
|
6007
6601
|
for (const file of untracked)
|
|
6008
|
-
newLines += await countLines(ws,
|
|
6602
|
+
newLines += await countLines(ws, path17.join(worktreePath, file));
|
|
6009
6603
|
return {
|
|
6010
6604
|
filesChanged: a.filesChanged + b.filesChanged + untracked.length,
|
|
6011
6605
|
insertions: a.insertions + b.insertions + newLines,
|
|
@@ -6028,21 +6622,84 @@ async function changedFiles(ws, worktreePath, base, limit = 20) {
|
|
|
6028
6622
|
names.add(file);
|
|
6029
6623
|
return [...names].slice(0, limit);
|
|
6030
6624
|
}
|
|
6031
|
-
|
|
6625
|
+
async function diffPatch(ws, worktreePath, base, maxChars = 64 * 1024) {
|
|
6626
|
+
const parts = [];
|
|
6627
|
+
let size = 0;
|
|
6628
|
+
const push = (text) => {
|
|
6629
|
+
if (size >= maxChars)
|
|
6630
|
+
return false;
|
|
6631
|
+
const room = maxChars - size;
|
|
6632
|
+
const piece = text.length > room ? text.slice(0, room) : text;
|
|
6633
|
+
parts.push(piece);
|
|
6634
|
+
size += piece.length;
|
|
6635
|
+
return piece.length === text.length;
|
|
6636
|
+
};
|
|
6637
|
+
for (const args of [
|
|
6638
|
+
["diff", `${base}...HEAD`, "--", "."],
|
|
6639
|
+
["diff", "HEAD"]
|
|
6640
|
+
]) {
|
|
6641
|
+
const out = await git(ws, worktreePath, args);
|
|
6642
|
+
if (out.ok && out.stdout.trim().length > 0 && !push(`${out.stdout.replace(/\s+$/, "")}
|
|
6643
|
+
`)) {
|
|
6644
|
+
return parts.join("");
|
|
6645
|
+
}
|
|
6646
|
+
}
|
|
6647
|
+
for (const file of await untrackedFiles(ws, worktreePath)) {
|
|
6648
|
+
if (size >= maxChars)
|
|
6649
|
+
break;
|
|
6650
|
+
let text;
|
|
6651
|
+
try {
|
|
6652
|
+
const stat = await ws.stat(path17.join(worktreePath, file));
|
|
6653
|
+
if (stat?.kind !== "file" || stat.size > UNTRACKED_LINE_SCAN_LIMIT)
|
|
6654
|
+
continue;
|
|
6655
|
+
text = new TextDecoder().decode(await ws.read(path17.join(worktreePath, file)));
|
|
6656
|
+
} catch {
|
|
6657
|
+
continue;
|
|
6658
|
+
}
|
|
6659
|
+
if (text.slice(0, 8000).includes("\x00")) {
|
|
6660
|
+
if (!push(`diff --git a/${file} b/${file}
|
|
6661
|
+
Binary files /dev/null and b/${file} differ
|
|
6662
|
+
`))
|
|
6663
|
+
break;
|
|
6664
|
+
continue;
|
|
6665
|
+
}
|
|
6666
|
+
const lines = text.split(`
|
|
6667
|
+
`);
|
|
6668
|
+
if (lines.at(-1) === "")
|
|
6669
|
+
lines.pop();
|
|
6670
|
+
const shown = lines.slice(0, UNTRACKED_PATCH_LINES);
|
|
6671
|
+
const body = shown.map((line) => `+${line}`).join(`
|
|
6672
|
+
`);
|
|
6673
|
+
const elided = lines.length > shown.length ? `
|
|
6674
|
+
+\u2026 ${lines.length - shown.length} more lines
|
|
6675
|
+
` : `
|
|
6676
|
+
`;
|
|
6677
|
+
const header2 = `diff --git a/${file} b/${file}
|
|
6678
|
+
new file mode 100644
|
|
6679
|
+
--- /dev/null
|
|
6680
|
+
+++ b/${file}
|
|
6681
|
+
@@ -0,0 +1,${lines.length} @@
|
|
6682
|
+
`;
|
|
6683
|
+
if (!push(`${header2}${body}${elided}`))
|
|
6684
|
+
break;
|
|
6685
|
+
}
|
|
6686
|
+
return parts.join("");
|
|
6687
|
+
}
|
|
6688
|
+
var UNTRACKED_LINE_SCAN_LIMIT, UNTRACKED_PATCH_LINES = 40;
|
|
6032
6689
|
var init_git = __esm(() => {
|
|
6033
6690
|
init_paths();
|
|
6034
6691
|
UNTRACKED_LINE_SCAN_LIMIT = 1024 * 1024;
|
|
6035
6692
|
});
|
|
6036
6693
|
|
|
6037
6694
|
// packages/daemon/src/claude-store.ts
|
|
6038
|
-
import
|
|
6039
|
-
import
|
|
6040
|
-
import
|
|
6695
|
+
import fs18 from "fs";
|
|
6696
|
+
import os5 from "os";
|
|
6697
|
+
import path18 from "path";
|
|
6041
6698
|
function claudeConfigDir(env = process.env) {
|
|
6042
6699
|
const override = env["CLAUDE_CONFIG_DIR"];
|
|
6043
6700
|
if (override !== undefined && override.length > 0)
|
|
6044
6701
|
return override;
|
|
6045
|
-
return
|
|
6702
|
+
return path18.join(os5.homedir(), ".claude");
|
|
6046
6703
|
}
|
|
6047
6704
|
function resumedConversationId(argv) {
|
|
6048
6705
|
for (let i = 0;i < argv.length; i++) {
|
|
@@ -6058,29 +6715,65 @@ function resumedConversationId(argv) {
|
|
|
6058
6715
|
return;
|
|
6059
6716
|
}
|
|
6060
6717
|
function claudeConversationExists(configDir, conversationId) {
|
|
6061
|
-
const projects =
|
|
6718
|
+
const projects = path18.join(configDir, "projects");
|
|
6062
6719
|
let entries;
|
|
6063
6720
|
try {
|
|
6064
|
-
entries =
|
|
6721
|
+
entries = fs18.readdirSync(projects);
|
|
6065
6722
|
} catch {
|
|
6066
6723
|
return;
|
|
6067
6724
|
}
|
|
6068
6725
|
const file = `${conversationId}.jsonl`;
|
|
6069
6726
|
for (const entry of entries) {
|
|
6070
6727
|
try {
|
|
6071
|
-
if (
|
|
6728
|
+
if (fs18.existsSync(path18.join(projects, entry, file)))
|
|
6072
6729
|
return true;
|
|
6073
6730
|
} catch {}
|
|
6074
6731
|
}
|
|
6075
6732
|
return false;
|
|
6076
6733
|
}
|
|
6734
|
+
function claudeConversationCwd(configDir, conversationId) {
|
|
6735
|
+
const projects = path18.join(configDir, "projects");
|
|
6736
|
+
let entries;
|
|
6737
|
+
try {
|
|
6738
|
+
entries = fs18.readdirSync(projects);
|
|
6739
|
+
} catch {
|
|
6740
|
+
return;
|
|
6741
|
+
}
|
|
6742
|
+
for (const entry of entries) {
|
|
6743
|
+
const file = path18.join(projects, entry, `${conversationId}.jsonl`);
|
|
6744
|
+
let head;
|
|
6745
|
+
try {
|
|
6746
|
+
const handle = fs18.openSync(file, "r");
|
|
6747
|
+
try {
|
|
6748
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
6749
|
+
const read = fs18.readSync(handle, buffer, 0, buffer.byteLength, 0);
|
|
6750
|
+
head = buffer.subarray(0, read).toString("utf8");
|
|
6751
|
+
} finally {
|
|
6752
|
+
fs18.closeSync(handle);
|
|
6753
|
+
}
|
|
6754
|
+
} catch {
|
|
6755
|
+
continue;
|
|
6756
|
+
}
|
|
6757
|
+
for (const line of head.split(/\r?\n/)) {
|
|
6758
|
+
if (line.length === 0)
|
|
6759
|
+
continue;
|
|
6760
|
+
try {
|
|
6761
|
+
const record = JSON.parse(line);
|
|
6762
|
+
const cwd = record.cwd;
|
|
6763
|
+
if (typeof cwd === "string" && cwd.length > 0)
|
|
6764
|
+
return cwd;
|
|
6765
|
+
} catch {}
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
return;
|
|
6769
|
+
}
|
|
6077
6770
|
var CONVERSATION_ID;
|
|
6078
6771
|
var init_claude_store = __esm(() => {
|
|
6079
6772
|
CONVERSATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6080
6773
|
});
|
|
6081
6774
|
|
|
6082
6775
|
// packages/daemon/src/injector.ts
|
|
6083
|
-
import { setTimeout as
|
|
6776
|
+
import { setTimeout as delay3 } from "timers/promises";
|
|
6084
6777
|
|
|
6085
6778
|
class Injector {
|
|
6086
6779
|
queues = new Map;
|
|
@@ -6095,14 +6788,14 @@ class Injector {
|
|
|
6095
6788
|
try {
|
|
6096
6789
|
const deadline = Date.now() + 5000;
|
|
6097
6790
|
while (!session.pasteReady && !session.isExited && Date.now() < deadline) {
|
|
6098
|
-
await
|
|
6791
|
+
await delay3(100);
|
|
6099
6792
|
}
|
|
6100
6793
|
session.write(CTRL_U);
|
|
6101
|
-
await
|
|
6794
|
+
await delay3(CLEAR_SETTLE_MS);
|
|
6102
6795
|
for (const chunk of iteratePasteChunks(text)) {
|
|
6103
6796
|
session.write(chunk);
|
|
6104
6797
|
}
|
|
6105
|
-
await
|
|
6798
|
+
await delay3(SUBMIT_DELAY_MS);
|
|
6106
6799
|
session.write(SUBMIT);
|
|
6107
6800
|
} catch (err) {
|
|
6108
6801
|
session.write(BRACKETED_PASTE_END);
|
|
@@ -6114,7 +6807,7 @@ class Injector {
|
|
|
6114
6807
|
interrupt(session) {
|
|
6115
6808
|
return this.enqueue(session.info.id, async () => {
|
|
6116
6809
|
session.write(ESC);
|
|
6117
|
-
await
|
|
6810
|
+
await delay3(500);
|
|
6118
6811
|
});
|
|
6119
6812
|
}
|
|
6120
6813
|
}
|
|
@@ -6194,6 +6887,7 @@ function connectPtyHost(options) {
|
|
|
6194
6887
|
const decoder = new FrameDecoder;
|
|
6195
6888
|
const ptys = new Map;
|
|
6196
6889
|
const pending = new Map;
|
|
6890
|
+
const subscriptions = new Map;
|
|
6197
6891
|
const dataListeners = [];
|
|
6198
6892
|
const rawDecoders = new Map;
|
|
6199
6893
|
let welcomed = false;
|
|
@@ -6207,6 +6901,9 @@ function connectPtyHost(options) {
|
|
|
6207
6901
|
for (const waiter of pending.values())
|
|
6208
6902
|
waiter.reject(error);
|
|
6209
6903
|
pending.clear();
|
|
6904
|
+
for (const waiter of subscriptions.values())
|
|
6905
|
+
waiter.reject(error);
|
|
6906
|
+
subscriptions.clear();
|
|
6210
6907
|
socket.destroy();
|
|
6211
6908
|
};
|
|
6212
6909
|
const timer = setTimeout(() => fail(new Error(`pty host did not greet within ${options.timeoutMs ?? 5000}ms`)), options.timeoutMs ?? 5000);
|
|
@@ -6273,6 +6970,15 @@ function connectPtyHost(options) {
|
|
|
6273
6970
|
term: spawnOptions.term
|
|
6274
6971
|
}));
|
|
6275
6972
|
}),
|
|
6973
|
+
adopt: (session) => new Promise((done, failed) => {
|
|
6974
|
+
const pty = ptys.get(session.id) ?? new RemotePty(session.id, session.pid, send);
|
|
6975
|
+
ptys.set(session.id, pty);
|
|
6976
|
+
subscriptions.set(session.id, {
|
|
6977
|
+
resolve: () => done(pty),
|
|
6978
|
+
reject: failed
|
|
6979
|
+
});
|
|
6980
|
+
send(controlFrame({ t: "subscribe", id: session.id, replay: true }));
|
|
6981
|
+
}),
|
|
6276
6982
|
onData: (listener) => dataListeners.push(listener),
|
|
6277
6983
|
close: () => socket.destroy()
|
|
6278
6984
|
});
|
|
@@ -6288,10 +6994,17 @@ function connectPtyHost(options) {
|
|
|
6288
6994
|
pending.delete(message.id);
|
|
6289
6995
|
continue;
|
|
6290
6996
|
}
|
|
6997
|
+
if (message.t === "subscribed") {
|
|
6998
|
+
subscriptions.get(message.id)?.resolve();
|
|
6999
|
+
subscriptions.delete(message.id);
|
|
7000
|
+
continue;
|
|
7001
|
+
}
|
|
6291
7002
|
if (message.t === "exit") {
|
|
6292
7003
|
const pty = ptys.get(message.id);
|
|
6293
7004
|
ptys.delete(message.id);
|
|
6294
7005
|
rawDecoders.delete(message.id);
|
|
7006
|
+
subscriptions.get(message.id)?.reject(new Error(`session ${message.id} exited`));
|
|
7007
|
+
subscriptions.delete(message.id);
|
|
6295
7008
|
pty?.finish(message.code);
|
|
6296
7009
|
}
|
|
6297
7010
|
}
|
|
@@ -6304,15 +7017,18 @@ var init_pty_host_client = __esm(() => {
|
|
|
6304
7017
|
|
|
6305
7018
|
// packages/daemon/src/pty-host-launch.ts
|
|
6306
7019
|
import { spawn as spawn3 } from "child_process";
|
|
6307
|
-
import
|
|
6308
|
-
import
|
|
6309
|
-
import
|
|
7020
|
+
import fs19 from "fs";
|
|
7021
|
+
import os6 from "os";
|
|
7022
|
+
import path19 from "path";
|
|
6310
7023
|
function hostFile(runtimeDir) {
|
|
6311
|
-
return
|
|
7024
|
+
return path19.join(runtimeDir, "pty-host.json");
|
|
7025
|
+
}
|
|
7026
|
+
function readPtyHostIn(runtimeDir) {
|
|
7027
|
+
return readPtyHostFile(hostFile(runtimeDir));
|
|
6312
7028
|
}
|
|
6313
7029
|
function readPtyHostFile(file) {
|
|
6314
7030
|
try {
|
|
6315
|
-
const parsed = JSON.parse(
|
|
7031
|
+
const parsed = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
6316
7032
|
if (typeof parsed !== "object" || parsed === null)
|
|
6317
7033
|
return;
|
|
6318
7034
|
const { pid, endpoint, token, version: version2 } = parsed;
|
|
@@ -6332,8 +7048,8 @@ function isRunning(pid) {
|
|
|
6332
7048
|
}
|
|
6333
7049
|
}
|
|
6334
7050
|
function hostEndpoint(runtimeDir) {
|
|
6335
|
-
const base = ipcEndpoint(process.platform,
|
|
6336
|
-
return process.platform === "win32" ? `${base}-pty` :
|
|
7051
|
+
const base = ipcEndpoint(process.platform, os6.userInfo().username, { runtimeDir });
|
|
7052
|
+
return process.platform === "win32" ? `${base}-pty` : path19.join(runtimeDir, "axterm-pty.sock");
|
|
6337
7053
|
}
|
|
6338
7054
|
async function ensurePtyHost(location) {
|
|
6339
7055
|
const file = hostFile(location.runtimeDir);
|
|
@@ -6344,7 +7060,7 @@ async function ensurePtyHost(location) {
|
|
|
6344
7060
|
log(`pty host: handoff file names a dead pid ${existing.pid}, starting a new host`);
|
|
6345
7061
|
const endpoint = hostEndpoint(location.runtimeDir);
|
|
6346
7062
|
if (process.platform !== "win32")
|
|
6347
|
-
|
|
7063
|
+
fs19.rmSync(endpoint, { force: true });
|
|
6348
7064
|
const token = shortHash(`${location.runtimeDir}:${Date.now()}:${Math.random()}`) + shortHash(String(process.pid));
|
|
6349
7065
|
const launch = ptyHostLaunch({ execPath: process.execPath, mainScript: location.mainScript ?? Bun.main });
|
|
6350
7066
|
const child = spawn3(launch.command, launch.args, {
|
|
@@ -6373,10 +7089,41 @@ async function ensurePtyHost(location) {
|
|
|
6373
7089
|
}
|
|
6374
7090
|
}
|
|
6375
7091
|
function writePtyHostFile(file, info) {
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
7092
|
+
const dir = path19.dirname(file);
|
|
7093
|
+
const tmp = `${file}.tmp`;
|
|
7094
|
+
fs19.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
7095
|
+
let handle;
|
|
7096
|
+
try {
|
|
7097
|
+
handle = fs19.openSync(tmp, "w", 384);
|
|
7098
|
+
fs19.writeFileSync(handle, `${JSON.stringify(info, null, 2)}
|
|
7099
|
+
`, "utf8");
|
|
7100
|
+
fs19.fsyncSync(handle);
|
|
7101
|
+
fs19.closeSync(handle);
|
|
7102
|
+
handle = undefined;
|
|
7103
|
+
restrictFile(tmp);
|
|
7104
|
+
fs19.renameSync(tmp, file);
|
|
7105
|
+
restrictFile(file);
|
|
7106
|
+
if (process.platform !== "win32") {
|
|
7107
|
+
try {
|
|
7108
|
+
const directory = fs19.openSync(dir, "r");
|
|
7109
|
+
try {
|
|
7110
|
+
fs19.fsyncSync(directory);
|
|
7111
|
+
} finally {
|
|
7112
|
+
fs19.closeSync(directory);
|
|
7113
|
+
}
|
|
7114
|
+
} catch {}
|
|
7115
|
+
}
|
|
7116
|
+
} catch (err) {
|
|
7117
|
+
if (handle !== undefined) {
|
|
7118
|
+
try {
|
|
7119
|
+
fs19.closeSync(handle);
|
|
7120
|
+
} catch {}
|
|
7121
|
+
}
|
|
7122
|
+
try {
|
|
7123
|
+
fs19.rmSync(tmp, { force: true });
|
|
7124
|
+
} catch {}
|
|
7125
|
+
throw err;
|
|
7126
|
+
}
|
|
6380
7127
|
}
|
|
6381
7128
|
var init_pty_host_launch = __esm(() => {
|
|
6382
7129
|
init_src();
|
|
@@ -6385,8 +7132,8 @@ var init_pty_host_launch = __esm(() => {
|
|
|
6385
7132
|
});
|
|
6386
7133
|
|
|
6387
7134
|
// packages/daemon/src/pty.ts
|
|
6388
|
-
import { spawnSync as
|
|
6389
|
-
import
|
|
7135
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
7136
|
+
import path20 from "path";
|
|
6390
7137
|
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
6391
7138
|
|
|
6392
7139
|
class Fanout {
|
|
@@ -6407,13 +7154,13 @@ class Fanout {
|
|
|
6407
7154
|
}
|
|
6408
7155
|
function killTree(pid, exeName) {
|
|
6409
7156
|
if (process.platform === "win32") {
|
|
6410
|
-
const check =
|
|
7157
|
+
const check = spawnSync3("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
|
|
6411
7158
|
encoding: "utf8",
|
|
6412
7159
|
windowsHide: true
|
|
6413
7160
|
});
|
|
6414
7161
|
const image = check.stdout?.split(",")[0]?.replace(/"/g, "").toLowerCase() ?? "";
|
|
6415
7162
|
if (image === exeName) {
|
|
6416
|
-
|
|
7163
|
+
spawnSync3("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
|
|
6417
7164
|
}
|
|
6418
7165
|
return;
|
|
6419
7166
|
}
|
|
@@ -6486,7 +7233,7 @@ function spawnPty(options) {
|
|
|
6486
7233
|
}
|
|
6487
7234
|
}
|
|
6488
7235
|
});
|
|
6489
|
-
handle = new BunPtyHandle(proc,
|
|
7236
|
+
handle = new BunPtyHandle(proc, path20.basename(options.exe).toLowerCase());
|
|
6490
7237
|
proc.exited.then((code) => handle?.finish(code ?? null));
|
|
6491
7238
|
return handle;
|
|
6492
7239
|
}
|
|
@@ -6494,7 +7241,7 @@ var init_pty = () => {};
|
|
|
6494
7241
|
|
|
6495
7242
|
// packages/daemon/src/occupancy.ts
|
|
6496
7243
|
import { spawn as spawn4 } from "child_process";
|
|
6497
|
-
import
|
|
7244
|
+
import fs20 from "fs/promises";
|
|
6498
7245
|
function shellOccupied(shellPid) {
|
|
6499
7246
|
return process.platform === "win32" ? occupiedFromSnapshot(shellPid) : occupiedFromPidProbe(shellPid);
|
|
6500
7247
|
}
|
|
@@ -6539,11 +7286,11 @@ async function occupiedFromSnapshot(shellPid) {
|
|
|
6539
7286
|
}
|
|
6540
7287
|
async function childrenViaProc(pid) {
|
|
6541
7288
|
try {
|
|
6542
|
-
const children = await
|
|
7289
|
+
const children = await fs20.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
|
|
6543
7290
|
return children.trim().length > 0;
|
|
6544
7291
|
} catch {
|
|
6545
7292
|
try {
|
|
6546
|
-
await
|
|
7293
|
+
await fs20.access(`/proc/${pid}`);
|
|
6547
7294
|
return;
|
|
6548
7295
|
} catch {
|
|
6549
7296
|
return false;
|
|
@@ -6607,20 +7354,20 @@ var init_occupancy = __esm(() => {
|
|
|
6607
7354
|
});
|
|
6608
7355
|
|
|
6609
7356
|
// packages/daemon/src/agent-store.ts
|
|
6610
|
-
import
|
|
6611
|
-
import
|
|
6612
|
-
import
|
|
7357
|
+
import fs21 from "fs";
|
|
7358
|
+
import os7 from "os";
|
|
7359
|
+
import path21 from "path";
|
|
6613
7360
|
import { Database as Database3 } from "bun:sqlite";
|
|
6614
7361
|
function readTailLines(file, maxBytes) {
|
|
6615
7362
|
let fd;
|
|
6616
7363
|
try {
|
|
6617
|
-
fd =
|
|
6618
|
-
const size =
|
|
7364
|
+
fd = fs21.openSync(file, "r");
|
|
7365
|
+
const size = fs21.fstatSync(fd).size;
|
|
6619
7366
|
if (size === 0)
|
|
6620
7367
|
return [];
|
|
6621
7368
|
const length = Math.min(size, maxBytes);
|
|
6622
7369
|
const buffer = Buffer.allocUnsafe(length);
|
|
6623
|
-
|
|
7370
|
+
fs21.readSync(fd, buffer, 0, length, size - length);
|
|
6624
7371
|
const text = buffer.toString("utf8");
|
|
6625
7372
|
const lines = text.split(`
|
|
6626
7373
|
`);
|
|
@@ -6633,46 +7380,46 @@ function readTailLines(file, maxBytes) {
|
|
|
6633
7380
|
return [];
|
|
6634
7381
|
} finally {
|
|
6635
7382
|
if (fd !== undefined)
|
|
6636
|
-
|
|
7383
|
+
fs21.closeSync(fd);
|
|
6637
7384
|
}
|
|
6638
7385
|
}
|
|
6639
7386
|
function claudeConfigDir2() {
|
|
6640
|
-
return process.env["CLAUDE_CONFIG_DIR"] ??
|
|
7387
|
+
return process.env["CLAUDE_CONFIG_DIR"] ?? path21.join(os7.homedir(), ".claude");
|
|
6641
7388
|
}
|
|
6642
7389
|
function codexHome() {
|
|
6643
|
-
return
|
|
7390
|
+
return path21.join(STATE_DIR, "codex-home");
|
|
6644
7391
|
}
|
|
6645
7392
|
function ompAgentDb() {
|
|
6646
|
-
return
|
|
7393
|
+
return path21.join(os7.homedir(), ".omp", "agent", "agent.db");
|
|
6647
7394
|
}
|
|
6648
7395
|
function findCodexRollout(sessionId) {
|
|
6649
|
-
const root =
|
|
7396
|
+
const root = path21.join(codexHome(), "sessions");
|
|
6650
7397
|
for (const shard2 of codexRolloutShards(sessionId)) {
|
|
6651
|
-
const dir =
|
|
7398
|
+
const dir = path21.join(root, ...shard2.split("/"));
|
|
6652
7399
|
let entries;
|
|
6653
7400
|
try {
|
|
6654
|
-
entries =
|
|
7401
|
+
entries = fs21.readdirSync(dir);
|
|
6655
7402
|
} catch {
|
|
6656
7403
|
continue;
|
|
6657
7404
|
}
|
|
6658
7405
|
const match2 = entries.find((name) => name.endsWith(`-${sessionId}.jsonl`));
|
|
6659
7406
|
if (match2 !== undefined)
|
|
6660
|
-
return
|
|
7407
|
+
return path21.join(dir, match2);
|
|
6661
7408
|
}
|
|
6662
7409
|
return;
|
|
6663
7410
|
}
|
|
6664
7411
|
function newestCodexRollout() {
|
|
6665
|
-
const root =
|
|
7412
|
+
const root = path21.join(codexHome(), "sessions");
|
|
6666
7413
|
let best;
|
|
6667
7414
|
const walk = (dir, depth) => {
|
|
6668
7415
|
let entries;
|
|
6669
7416
|
try {
|
|
6670
|
-
entries =
|
|
7417
|
+
entries = fs21.readdirSync(dir, { withFileTypes: true });
|
|
6671
7418
|
} catch {
|
|
6672
7419
|
return;
|
|
6673
7420
|
}
|
|
6674
7421
|
for (const entry of entries) {
|
|
6675
|
-
const full =
|
|
7422
|
+
const full = path21.join(dir, entry.name);
|
|
6676
7423
|
if (entry.isDirectory()) {
|
|
6677
7424
|
if (depth < 3)
|
|
6678
7425
|
walk(full, depth + 1);
|
|
@@ -6681,7 +7428,7 @@ function newestCodexRollout() {
|
|
|
6681
7428
|
if (!entry.name.endsWith(".jsonl"))
|
|
6682
7429
|
continue;
|
|
6683
7430
|
try {
|
|
6684
|
-
const { mtimeMs } =
|
|
7431
|
+
const { mtimeMs } = fs21.statSync(full);
|
|
6685
7432
|
if (!best || mtimeMs > best.mtimeMs)
|
|
6686
7433
|
best = { file: full, mtimeMs };
|
|
6687
7434
|
} catch {}
|
|
@@ -6692,7 +7439,7 @@ function newestCodexRollout() {
|
|
|
6692
7439
|
}
|
|
6693
7440
|
function ompQuotaFacts() {
|
|
6694
7441
|
const file = ompAgentDb();
|
|
6695
|
-
if (!
|
|
7442
|
+
if (!fs21.existsSync(file))
|
|
6696
7443
|
return { agent: "omp", kind: "gauge" };
|
|
6697
7444
|
let db;
|
|
6698
7445
|
try {
|
|
@@ -6724,7 +7471,7 @@ function claudeQuotaFacts(sessions) {
|
|
|
6724
7471
|
for (const session of sessions) {
|
|
6725
7472
|
if (session.agentSessionId === undefined)
|
|
6726
7473
|
continue;
|
|
6727
|
-
const file =
|
|
7474
|
+
const file = path21.join(dir, claudeTranscriptRelPath(session.cwd, session.agentSessionId));
|
|
6728
7475
|
const event = parseClaudeLimitEvent(readTailLines(file, QUOTA_TAIL_BYTES));
|
|
6729
7476
|
if (event && (!newest || event.at > newest.at))
|
|
6730
7477
|
newest = event;
|
|
@@ -6756,11 +7503,11 @@ function collectQuotaFacts(agents, sessions, onPath) {
|
|
|
6756
7503
|
function storeQuietForMs(agent, cwd, agentSessionId, nowMs) {
|
|
6757
7504
|
if (agentSessionId === undefined)
|
|
6758
7505
|
return;
|
|
6759
|
-
const file = agent === "claude" ?
|
|
7506
|
+
const file = agent === "claude" ? path21.join(claudeConfigDir2(), claudeTranscriptRelPath(cwd, agentSessionId)) : agent === "codex" ? findCodexRollout(agentSessionId) : undefined;
|
|
6760
7507
|
if (file === undefined)
|
|
6761
7508
|
return;
|
|
6762
7509
|
try {
|
|
6763
|
-
return Math.max(0, nowMs -
|
|
7510
|
+
return Math.max(0, nowMs - fs21.statSync(file).mtimeMs);
|
|
6764
7511
|
} catch {
|
|
6765
7512
|
return;
|
|
6766
7513
|
}
|
|
@@ -6770,15 +7517,15 @@ function readFileRange(file, from, to) {
|
|
|
6770
7517
|
return "";
|
|
6771
7518
|
let fd;
|
|
6772
7519
|
try {
|
|
6773
|
-
fd =
|
|
7520
|
+
fd = fs21.openSync(file, "r");
|
|
6774
7521
|
const buffer = Buffer.allocUnsafe(to - from);
|
|
6775
|
-
const read =
|
|
7522
|
+
const read = fs21.readSync(fd, buffer, 0, to - from, from);
|
|
6776
7523
|
return buffer.subarray(0, read).toString("utf8");
|
|
6777
7524
|
} catch {
|
|
6778
7525
|
return "";
|
|
6779
7526
|
} finally {
|
|
6780
7527
|
if (fd !== undefined)
|
|
6781
|
-
|
|
7528
|
+
fs21.closeSync(fd);
|
|
6782
7529
|
}
|
|
6783
7530
|
}
|
|
6784
7531
|
var QUOTA_TAIL_BYTES;
|
|
@@ -11229,15 +11976,55 @@ var require_addon_serialize = __commonJS((exports, module) => {
|
|
|
11229
11976
|
})());
|
|
11230
11977
|
});
|
|
11231
11978
|
|
|
11979
|
+
// packages/daemon/src/terminal-mirror.ts
|
|
11980
|
+
class TerminalMirror {
|
|
11981
|
+
scrollbackLines;
|
|
11982
|
+
term;
|
|
11983
|
+
serializeAddon;
|
|
11984
|
+
constructor(cols, rows, scrollbackLines) {
|
|
11985
|
+
this.scrollbackLines = scrollbackLines;
|
|
11986
|
+
this.term = new import_headless.Terminal({ cols, rows, scrollback: scrollbackLines, allowProposedApi: true });
|
|
11987
|
+
this.serializeAddon = new import_addon_serialize.SerializeAddon;
|
|
11988
|
+
this.term.loadAddon(this.serializeAddon);
|
|
11989
|
+
}
|
|
11990
|
+
write(data) {
|
|
11991
|
+
this.term.write(data);
|
|
11992
|
+
}
|
|
11993
|
+
resize(cols, rows) {
|
|
11994
|
+
this.term.resize(cols, rows);
|
|
11995
|
+
}
|
|
11996
|
+
serialize(maxChars = Number.POSITIVE_INFINITY) {
|
|
11997
|
+
const ansi = this.serializeAddon.serialize({ scrollback: this.scrollbackLines });
|
|
11998
|
+
return ansi.length > maxChars ? ansi.slice(-maxChars) : ansi;
|
|
11999
|
+
}
|
|
12000
|
+
text(maxLines) {
|
|
12001
|
+
const buffer = this.term.buffer.active;
|
|
12002
|
+
let last = buffer.length - 1;
|
|
12003
|
+
while (last >= 0 && (buffer.getLine(last)?.translateToString(true).trim() ?? "") === "")
|
|
12004
|
+
last--;
|
|
12005
|
+
const start = Math.max(0, last - maxLines + 1);
|
|
12006
|
+
const lines = [];
|
|
12007
|
+
for (let y = start;y <= last; y++) {
|
|
12008
|
+
lines.push(buffer.getLine(y)?.translateToString(true).trimEnd() ?? "");
|
|
12009
|
+
}
|
|
12010
|
+
return lines.join(`
|
|
12011
|
+
`);
|
|
12012
|
+
}
|
|
12013
|
+
}
|
|
12014
|
+
var import_headless, import_addon_serialize;
|
|
12015
|
+
var init_terminal_mirror = __esm(() => {
|
|
12016
|
+
import_headless = __toESM(require_xterm_headless(), 1);
|
|
12017
|
+
import_addon_serialize = __toESM(require_addon_serialize(), 1);
|
|
12018
|
+
});
|
|
12019
|
+
|
|
11232
12020
|
// packages/daemon/src/pty-session.ts
|
|
11233
|
-
import
|
|
11234
|
-
import
|
|
12021
|
+
import fs22 from "fs";
|
|
12022
|
+
import path22 from "path";
|
|
11235
12023
|
|
|
11236
12024
|
class PtySession {
|
|
11237
12025
|
info;
|
|
11238
12026
|
pty;
|
|
11239
|
-
|
|
11240
|
-
serializeAddon;
|
|
12027
|
+
mirror;
|
|
11241
12028
|
dataListeners = new Set;
|
|
11242
12029
|
exitListeners = new Set;
|
|
11243
12030
|
saveTimer;
|
|
@@ -11254,26 +12041,23 @@ class PtySession {
|
|
|
11254
12041
|
cols: spawn5.cols,
|
|
11255
12042
|
rows: spawn5.rows,
|
|
11256
12043
|
env: spawn5.env,
|
|
11257
|
-
term: "xterm-256color"
|
|
12044
|
+
term: "xterm-256color",
|
|
12045
|
+
id: info.id
|
|
11258
12046
|
});
|
|
11259
12047
|
return new PtySession(info, spawn5, pty);
|
|
11260
12048
|
}
|
|
12049
|
+
static adopt(info, pty, cols, rows) {
|
|
12050
|
+
return new PtySession(info, { exe: "", argv: [], cwd: info.cwd, cols, rows, env: {} }, pty);
|
|
12051
|
+
}
|
|
11261
12052
|
constructor(info, spawn5, pty) {
|
|
11262
12053
|
this.info = info;
|
|
11263
|
-
this.
|
|
11264
|
-
cols: spawn5.cols,
|
|
11265
|
-
rows: spawn5.rows,
|
|
11266
|
-
scrollback: SCROLLBACK_LINES,
|
|
11267
|
-
allowProposedApi: true
|
|
11268
|
-
});
|
|
11269
|
-
this.serializeAddon = new import_addon_serialize.SerializeAddon;
|
|
11270
|
-
this.term.loadAddon(this.serializeAddon);
|
|
12054
|
+
this.mirror = new TerminalMirror(spawn5.cols, spawn5.rows, SCROLLBACK_LINES);
|
|
11271
12055
|
this.pty = pty;
|
|
11272
12056
|
info.ptyPid = this.pty.pid;
|
|
11273
12057
|
this.pty.onData((data) => {
|
|
11274
12058
|
this.seq += 1;
|
|
11275
12059
|
this.lastOutputAt = Date.now();
|
|
11276
|
-
this.
|
|
12060
|
+
this.mirror.write(data);
|
|
11277
12061
|
if (!this.pasteReady) {
|
|
11278
12062
|
const window2 = this.readyScanTail + data;
|
|
11279
12063
|
if (window2.includes("\x1B[?2004h"))
|
|
@@ -11318,27 +12102,16 @@ class PtySession {
|
|
|
11318
12102
|
this.info.rows = rows;
|
|
11319
12103
|
try {
|
|
11320
12104
|
this.pty.resize(cols, rows);
|
|
11321
|
-
this.
|
|
12105
|
+
this.mirror.resize(cols, rows);
|
|
11322
12106
|
} catch (err) {
|
|
11323
12107
|
log(`resize failed for ${this.info.shortId}: ${String(err)}`);
|
|
11324
12108
|
}
|
|
11325
12109
|
}
|
|
11326
12110
|
screenText(maxLines = 60) {
|
|
11327
|
-
|
|
11328
|
-
let last = buffer.length - 1;
|
|
11329
|
-
while (last >= 0 && (buffer.getLine(last)?.translateToString(true).trim() ?? "") === "")
|
|
11330
|
-
last--;
|
|
11331
|
-
const start = Math.max(0, last - maxLines + 1);
|
|
11332
|
-
const lines = [];
|
|
11333
|
-
for (let y = start;y <= last; y++) {
|
|
11334
|
-
lines.push(buffer.getLine(y)?.translateToString(true).trimEnd() ?? "");
|
|
11335
|
-
}
|
|
11336
|
-
return lines.join(`
|
|
11337
|
-
`);
|
|
12111
|
+
return this.mirror.text(maxLines);
|
|
11338
12112
|
}
|
|
11339
12113
|
serializeBuffer() {
|
|
11340
|
-
|
|
11341
|
-
return ansi.length > SCROLLBACK_MAX_CHARS ? ansi.slice(-SCROLLBACK_MAX_CHARS) : ansi;
|
|
12114
|
+
return this.mirror.serialize(SCROLLBACK_MAX_CHARS);
|
|
11342
12115
|
}
|
|
11343
12116
|
scheduleSave() {
|
|
11344
12117
|
if (this.exited || this.saveTimer)
|
|
@@ -11351,10 +12124,10 @@ class PtySession {
|
|
|
11351
12124
|
}
|
|
11352
12125
|
saveScrollback() {
|
|
11353
12126
|
try {
|
|
11354
|
-
const file =
|
|
12127
|
+
const file = path22.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
|
|
11355
12128
|
const tmp = `${file}.tmp`;
|
|
11356
|
-
|
|
11357
|
-
|
|
12129
|
+
fs22.writeFileSync(tmp, this.serializeBuffer(), "utf8");
|
|
12130
|
+
fs22.renameSync(tmp, file);
|
|
11358
12131
|
} catch (err) {
|
|
11359
12132
|
log(`scrollback save failed for ${this.info.shortId}: ${String(err)}`);
|
|
11360
12133
|
}
|
|
@@ -11365,20 +12138,20 @@ class PtySession {
|
|
|
11365
12138
|
this.pty.kill();
|
|
11366
12139
|
}
|
|
11367
12140
|
}
|
|
11368
|
-
var
|
|
12141
|
+
var SCROLLBACK_LINES = 5000, SAVE_DEBOUNCE_MS = 5000, SCROLLBACK_MAX_CHARS;
|
|
11369
12142
|
var init_pty_session = __esm(() => {
|
|
11370
12143
|
init_paths();
|
|
11371
12144
|
init_pty();
|
|
11372
|
-
|
|
11373
|
-
import_addon_serialize = __toESM(require_addon_serialize(), 1);
|
|
12145
|
+
init_terminal_mirror();
|
|
11374
12146
|
SCROLLBACK_MAX_CHARS = 2 * 1024 * 1024;
|
|
11375
12147
|
});
|
|
11376
12148
|
|
|
11377
12149
|
// packages/daemon/src/session-manager.ts
|
|
11378
12150
|
import { randomBytes as randomBytes2, randomUUID } from "crypto";
|
|
11379
|
-
import
|
|
11380
|
-
import
|
|
11381
|
-
import
|
|
12151
|
+
import { spawn as spawn5 } from "child_process";
|
|
12152
|
+
import fs23 from "fs";
|
|
12153
|
+
import os8 from "os";
|
|
12154
|
+
import path23 from "path";
|
|
11382
12155
|
function sessionScratchRoot(info) {
|
|
11383
12156
|
return info.worktreePath ?? info.cwd;
|
|
11384
12157
|
}
|
|
@@ -11420,11 +12193,23 @@ async function sendableNow(runtime, info, probe, now) {
|
|
|
11420
12193
|
occupied
|
|
11421
12194
|
}, now);
|
|
11422
12195
|
}
|
|
12196
|
+
function pidIsRunning(pid) {
|
|
12197
|
+
try {
|
|
12198
|
+
process.kill(pid, 0);
|
|
12199
|
+
return true;
|
|
12200
|
+
} catch (err) {
|
|
12201
|
+
return err.code === "EPERM";
|
|
12202
|
+
}
|
|
12203
|
+
}
|
|
12204
|
+
function ptyHostEnabled(env) {
|
|
12205
|
+
return env["AXTERM_PTY_HOST"] !== "0";
|
|
12206
|
+
}
|
|
11423
12207
|
|
|
11424
12208
|
class SessionManager {
|
|
11425
12209
|
store;
|
|
11426
12210
|
hooks;
|
|
11427
12211
|
configuredDefaults;
|
|
12212
|
+
ptyHostPolicy;
|
|
11428
12213
|
live = new Map;
|
|
11429
12214
|
agentRuntime = new Map;
|
|
11430
12215
|
injector = new Injector;
|
|
@@ -11432,18 +12217,66 @@ class SessionManager {
|
|
|
11432
12217
|
eventListeners = new Set;
|
|
11433
12218
|
rawHookListeners = new Set;
|
|
11434
12219
|
workspace = new LocalWorkspace(MATERIALISED_DIR);
|
|
11435
|
-
constructor(store, hooks, configuredDefaults = () => ({})
|
|
12220
|
+
constructor(store, hooks, configuredDefaults = () => ({}), ptyHostPolicy = {
|
|
12221
|
+
enabled: ptyHostEnabled(process.env),
|
|
12222
|
+
...process.env["AXTERM_PTY_HOST_MAIN"] ? { mainScript: process.env["AXTERM_PTY_HOST_MAIN"] } : {}
|
|
12223
|
+
}) {
|
|
11436
12224
|
this.store = store;
|
|
11437
12225
|
this.hooks = hooks;
|
|
11438
12226
|
this.configuredDefaults = configuredDefaults;
|
|
11439
|
-
|
|
11440
|
-
if (stale > 0)
|
|
11441
|
-
log(`boot: marked ${stale} stale session(s) as exited`);
|
|
12227
|
+
this.ptyHostPolicy = ptyHostPolicy;
|
|
11442
12228
|
this.sessionWatch = setInterval(() => this.tickSessions(), SESSION_TICK_MS);
|
|
11443
12229
|
this.sessionWatch.unref();
|
|
11444
12230
|
}
|
|
12231
|
+
async recover() {
|
|
12232
|
+
const running = this.store.listSessions().filter((info) => info.state !== "exited");
|
|
12233
|
+
const held = await this.hostSessions();
|
|
12234
|
+
const adoptedIds = [];
|
|
12235
|
+
for (const info of running) {
|
|
12236
|
+
const description = held.get(info.id);
|
|
12237
|
+
if (description === undefined)
|
|
12238
|
+
continue;
|
|
12239
|
+
try {
|
|
12240
|
+
const handle = await this.ptyHost.adopt(description);
|
|
12241
|
+
info.state = "running";
|
|
12242
|
+
delete info.exitCode;
|
|
12243
|
+
delete info.exitedAt;
|
|
12244
|
+
const session = PtySession.adopt(info, handle, description.cols, description.rows);
|
|
12245
|
+
this.wire(session, undefined);
|
|
12246
|
+
this.hostOwned.add(info.id);
|
|
12247
|
+
this.store.markResumed(info.id, info.agentCommand, info.ptyPid);
|
|
12248
|
+
if (agentIdFromCommand(info.agentCommand) === "codex")
|
|
12249
|
+
this.adoptCodexAppServer(info.id);
|
|
12250
|
+
if (agentIdFromCommand(info.agentCommand) === "gemini")
|
|
12251
|
+
this.skipGeminiTelemetryHistory(info.id);
|
|
12252
|
+
adoptedIds.push(info.id);
|
|
12253
|
+
log(`boot: adopted session ${info.shortId} (pty ${description.pid}) from the terminal host`);
|
|
12254
|
+
} catch (err) {
|
|
12255
|
+
log(`boot: could not adopt ${info.shortId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12256
|
+
}
|
|
12257
|
+
}
|
|
12258
|
+
const sweptExited = this.store.markStaleRunningAsExited(adoptedIds);
|
|
12259
|
+
if (sweptExited > 0)
|
|
12260
|
+
log(`boot: marked ${sweptExited} stale session(s) as exited`);
|
|
12261
|
+
return { adopted: adoptedIds.length, sweptExited };
|
|
12262
|
+
}
|
|
12263
|
+
async hostSessions() {
|
|
12264
|
+
if (!this.ptyHostPolicy.enabled)
|
|
12265
|
+
return new Map;
|
|
12266
|
+
const existing = readPtyHostIn(RUNTIME_DIR);
|
|
12267
|
+
if (existing === undefined)
|
|
12268
|
+
return new Map;
|
|
12269
|
+
try {
|
|
12270
|
+
this.ptyHost ??= await connectPtyHost({ endpoint: existing.endpoint, token: existing.token });
|
|
12271
|
+
return new Map(this.ptyHost.sessions.map((s) => [s.id, s]));
|
|
12272
|
+
} catch (err) {
|
|
12273
|
+
log(`boot: the terminal host named in pty-host.json did not answer: ${err instanceof Error ? err.message : String(err)}`);
|
|
12274
|
+
return new Map;
|
|
12275
|
+
}
|
|
12276
|
+
}
|
|
11445
12277
|
sessionWatch;
|
|
11446
12278
|
ptyHost;
|
|
12279
|
+
hostOwned = new Set;
|
|
11447
12280
|
ptyHostUnavailable = false;
|
|
11448
12281
|
codexServers = new Map;
|
|
11449
12282
|
workspaceFor(_sessionId) {
|
|
@@ -11517,13 +12350,14 @@ class SessionManager {
|
|
|
11517
12350
|
return {
|
|
11518
12351
|
stat: await diffStat(ws, target, compare),
|
|
11519
12352
|
files: await changedFiles(ws, target, compare),
|
|
12353
|
+
patch: await diffPatch(ws, target, compare),
|
|
11520
12354
|
base: compare,
|
|
11521
12355
|
...info.branch !== undefined ? { branch: info.branch } : {}
|
|
11522
12356
|
};
|
|
11523
12357
|
}
|
|
11524
12358
|
async create(params) {
|
|
11525
|
-
const requestedCwd = params.cwd ?
|
|
11526
|
-
if (!
|
|
12359
|
+
const requestedCwd = params.cwd ? path23.resolve(params.cwd) : os8.homedir();
|
|
12360
|
+
if (!fs23.existsSync(requestedCwd)) {
|
|
11527
12361
|
throw new AxtermError("err.cwd.missing", { cwd: requestedCwd });
|
|
11528
12362
|
}
|
|
11529
12363
|
let worktree;
|
|
@@ -11567,11 +12401,27 @@ class SessionManager {
|
|
|
11567
12401
|
const missing = unresolvableAgent(params.commandOverride, agentCommand, lookupOnPath);
|
|
11568
12402
|
if (missing !== undefined)
|
|
11569
12403
|
throw new AxtermError("err.agent.notInstalled", { command: missing });
|
|
12404
|
+
const adopted = agent === "claude" ? resumedConversationId(agentCommand) : undefined;
|
|
12405
|
+
let sessionCwd = cwd;
|
|
12406
|
+
if (adopted !== undefined) {
|
|
12407
|
+
const holder = conversationHeldBy(this.allSessions(), adopted);
|
|
12408
|
+
if (holder !== undefined) {
|
|
12409
|
+
throw new AxtermError("err.claude.conversationBusy", { id: adopted, session: holder.shortId });
|
|
12410
|
+
}
|
|
12411
|
+
if (worktree === undefined) {
|
|
12412
|
+
const recorded = claudeConversationCwd(claudeConfigDir(), adopted);
|
|
12413
|
+
sessionCwd = resolveSessionCwd({
|
|
12414
|
+
requested: cwd,
|
|
12415
|
+
preferConversation: params.preferConversationCwd === true,
|
|
12416
|
+
...recorded !== undefined && fs23.existsSync(recorded) ? { conversationCwd: recorded } : {}
|
|
12417
|
+
});
|
|
12418
|
+
}
|
|
12419
|
+
}
|
|
11570
12420
|
const info = {
|
|
11571
12421
|
id,
|
|
11572
12422
|
shortId: id.slice(0, 6),
|
|
11573
|
-
name: params.name ?? worktree?.branch ??
|
|
11574
|
-
cwd,
|
|
12423
|
+
name: params.name ?? worktree?.branch ?? path23.basename(sessionCwd),
|
|
12424
|
+
cwd: sessionCwd,
|
|
11575
12425
|
shell: shell.kind,
|
|
11576
12426
|
agentCommand,
|
|
11577
12427
|
state: "running",
|
|
@@ -11580,8 +12430,7 @@ class SessionManager {
|
|
|
11580
12430
|
createdAt: Date.now(),
|
|
11581
12431
|
...worktree !== undefined ? { worktreePath: worktree.path, branch: worktree.branch, repoRoot: worktree.repoRoot } : {}
|
|
11582
12432
|
};
|
|
11583
|
-
|
|
11584
|
-
await this.launch(info, shell, agentCommand, appServerUrl);
|
|
12433
|
+
await this.launch(info, shell, agentCommand);
|
|
11585
12434
|
this.store.insertSession(info);
|
|
11586
12435
|
log(`session ${info.shortId} created shell=${shell.kind} cmd=${agentCommand.join(" ") || "(shell only)"}`);
|
|
11587
12436
|
this.emit({ t: "session.created", info });
|
|
@@ -11595,7 +12444,7 @@ class SessionManager {
|
|
|
11595
12444
|
launchToken
|
|
11596
12445
|
});
|
|
11597
12446
|
const file = this.claudeSettingsPath(id);
|
|
11598
|
-
|
|
12447
|
+
fs23.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
|
|
11599
12448
|
}
|
|
11600
12449
|
writeGeminiSettings(sessionId) {
|
|
11601
12450
|
try {
|
|
@@ -11608,69 +12457,104 @@ class SessionManager {
|
|
|
11608
12457
|
});
|
|
11609
12458
|
const file = this.geminiSettingsPath();
|
|
11610
12459
|
const tmp = `${file}.tmp`;
|
|
11611
|
-
|
|
11612
|
-
|
|
12460
|
+
fs23.writeFileSync(tmp, JSON.stringify(settings, null, 2));
|
|
12461
|
+
fs23.renameSync(tmp, file);
|
|
11613
12462
|
} catch (err) {
|
|
11614
12463
|
log(`gemini settings write failed; session starts without hooks: ${String(err)}`);
|
|
11615
12464
|
}
|
|
11616
12465
|
}
|
|
11617
|
-
async startCodexAppServer(id, codexHome2, cwd) {
|
|
12466
|
+
async startCodexAppServer(id, codexHome2, cwd, identity) {
|
|
11618
12467
|
try {
|
|
12468
|
+
const inherited = this.adoptCodexAppServer(id);
|
|
12469
|
+
if (inherited !== undefined)
|
|
12470
|
+
return inherited;
|
|
11619
12471
|
const port = await freeLoopbackPort();
|
|
11620
12472
|
const url = `ws://127.0.0.1:${port}`;
|
|
11621
|
-
const
|
|
12473
|
+
const child = spawn5("codex", ["app-server", "--listen", url], {
|
|
11622
12474
|
cwd,
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
12475
|
+
detached: true,
|
|
12476
|
+
windowsHide: true,
|
|
12477
|
+
stdio: "ignore",
|
|
12478
|
+
env: { ...sessionEnv(process.env, identity), CODEX_HOME: codexHome2 }
|
|
11627
12479
|
});
|
|
12480
|
+
child.unref();
|
|
12481
|
+
const pid = child.pid;
|
|
12482
|
+
if (pid === undefined)
|
|
12483
|
+
throw new Error("the app-server was not given a pid");
|
|
11628
12484
|
if (!await waitForPort(port)) {
|
|
11629
|
-
|
|
12485
|
+
this.killCodexAppServer(pid);
|
|
11630
12486
|
log(`codex app-server did not open ${port}; session ${id.slice(0, 6)} keeps its hooks`);
|
|
11631
12487
|
return;
|
|
11632
12488
|
}
|
|
11633
|
-
|
|
11634
|
-
|
|
11635
|
-
|
|
11636
|
-
stop: () => {
|
|
11637
|
-
client.close();
|
|
11638
|
-
try {
|
|
11639
|
-
killTree(proc.pid, "codex.exe");
|
|
11640
|
-
} catch {}
|
|
11641
|
-
try {
|
|
11642
|
-
proc.kill();
|
|
11643
|
-
} catch {}
|
|
11644
|
-
}
|
|
11645
|
-
});
|
|
11646
|
-
log(`codex app-server on ${url} for session ${id.slice(0, 6)} in ${cwd}`);
|
|
12489
|
+
this.watchCodexAppServer(id, url, pid);
|
|
12490
|
+
fs23.writeFileSync(this.codexAppServerFile(id), JSON.stringify({ pid, url }), { mode: 384 });
|
|
12491
|
+
log(`codex app-server on ${url} (pid ${pid}) for session ${id.slice(0, 6)} in ${cwd}`);
|
|
11647
12492
|
return url;
|
|
11648
12493
|
} catch (err) {
|
|
11649
12494
|
log(`codex app-server unavailable (${String(err)}); session ${id.slice(0, 6)} keeps its hooks`);
|
|
11650
12495
|
return;
|
|
11651
12496
|
}
|
|
11652
12497
|
}
|
|
12498
|
+
codexAppServerFile(id) {
|
|
12499
|
+
return path23.join(RUNTIME_DIR, `codex-app-${id}.json`);
|
|
12500
|
+
}
|
|
12501
|
+
adoptCodexAppServer(id) {
|
|
12502
|
+
const file = this.codexAppServerFile(id);
|
|
12503
|
+
let record;
|
|
12504
|
+
try {
|
|
12505
|
+
record = JSON.parse(fs23.readFileSync(file, "utf8"));
|
|
12506
|
+
} catch {
|
|
12507
|
+
return;
|
|
12508
|
+
}
|
|
12509
|
+
const pid = record.pid;
|
|
12510
|
+
const url = record.url;
|
|
12511
|
+
if (typeof pid !== "number" || typeof url !== "string")
|
|
12512
|
+
return;
|
|
12513
|
+
if (!pidIsRunning(pid)) {
|
|
12514
|
+
fs23.rmSync(file, { force: true });
|
|
12515
|
+
return;
|
|
12516
|
+
}
|
|
12517
|
+
this.watchCodexAppServer(id, url, pid);
|
|
12518
|
+
log(`codex app-server: reconnected to ${url} (pid ${pid}) for session ${id.slice(0, 6)}`);
|
|
12519
|
+
return url;
|
|
12520
|
+
}
|
|
12521
|
+
watchCodexAppServer(id, url, pid) {
|
|
12522
|
+
const client = new CodexAppServerClient(url, (payload) => this.ingestHookEvent(id, payload));
|
|
12523
|
+
client.connect();
|
|
12524
|
+
this.codexServers.set(id, {
|
|
12525
|
+
stop: () => {
|
|
12526
|
+
client.close();
|
|
12527
|
+
this.killCodexAppServer(pid);
|
|
12528
|
+
fs23.rmSync(this.codexAppServerFile(id), { force: true });
|
|
12529
|
+
}
|
|
12530
|
+
});
|
|
12531
|
+
}
|
|
12532
|
+
killCodexAppServer(pid) {
|
|
12533
|
+
try {
|
|
12534
|
+
killTree(pid, "codex.exe");
|
|
12535
|
+
} catch {}
|
|
12536
|
+
}
|
|
11653
12537
|
stopCodexAppServer(id) {
|
|
11654
12538
|
this.codexServers.get(id)?.stop();
|
|
11655
12539
|
this.codexServers.delete(id);
|
|
11656
12540
|
}
|
|
11657
12541
|
ensureCodexHome() {
|
|
11658
|
-
const home =
|
|
11659
|
-
|
|
12542
|
+
const home = path23.join(STATE_DIR, "codex-home");
|
|
12543
|
+
fs23.mkdirSync(home, { recursive: true, mode: 448 });
|
|
11660
12544
|
this.linkCodexAuth(home);
|
|
11661
12545
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
|
|
11662
12546
|
const hooksFile = buildCodexHooks({ command: bridge.command, args: bridge.args });
|
|
11663
12547
|
const hooksPath = codexTrustKeyPath(home);
|
|
11664
12548
|
const tmp = `${hooksPath}.tmp`;
|
|
11665
|
-
|
|
11666
|
-
|
|
12549
|
+
fs23.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
|
|
12550
|
+
fs23.renameSync(tmp, hooksPath);
|
|
11667
12551
|
const entries = codexHookTrust(hooksPath, hooksFile);
|
|
11668
|
-
const configPath =
|
|
11669
|
-
const existing =
|
|
12552
|
+
const configPath = path23.join(home, "config.toml");
|
|
12553
|
+
const existing = fs23.existsSync(configPath) ? fs23.readFileSync(configPath, "utf8") : "";
|
|
11670
12554
|
const missing = entries.filter((e) => !existing.includes(`"${e.key.replaceAll("\\", "\\\\")}"`));
|
|
11671
12555
|
if (missing.length > 0) {
|
|
11672
12556
|
const block = codexTrustStateToml(missing);
|
|
11673
|
-
|
|
12557
|
+
fs23.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
|
|
11674
12558
|
`) ? `
|
|
11675
12559
|
${block}` : block);
|
|
11676
12560
|
}
|
|
@@ -11683,50 +12567,50 @@ ${block}` : block);
|
|
|
11683
12567
|
return home;
|
|
11684
12568
|
}
|
|
11685
12569
|
linkCodexAuth(home) {
|
|
11686
|
-
const target =
|
|
11687
|
-
if (
|
|
12570
|
+
const target = path23.join(home, "auth.json");
|
|
12571
|
+
if (fs23.existsSync(target))
|
|
11688
12572
|
return;
|
|
11689
|
-
const operatorHome = process.env["CODEX_HOME"] ??
|
|
11690
|
-
const source =
|
|
11691
|
-
if (!
|
|
12573
|
+
const operatorHome = process.env["CODEX_HOME"] ?? path23.join(os8.homedir(), ".codex");
|
|
12574
|
+
const source = path23.join(operatorHome, "auth.json");
|
|
12575
|
+
if (!fs23.existsSync(source))
|
|
11692
12576
|
throw new AxtermError("err.codex.noAuth", { authFile: source });
|
|
12577
|
+
let how;
|
|
11693
12578
|
try {
|
|
11694
|
-
|
|
11695
|
-
} catch {
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
}
|
|
12579
|
+
how = referenceCredential(source, target);
|
|
12580
|
+
} catch (err) {
|
|
12581
|
+
throw new AxtermError("err.codex.authLink", { reason: err instanceof Error ? err.message : String(err) });
|
|
12582
|
+
}
|
|
12583
|
+
if (how === "copy") {
|
|
12584
|
+
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`);
|
|
11701
12585
|
}
|
|
11702
12586
|
}
|
|
11703
12587
|
doctorReport() {
|
|
11704
12588
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
|
|
11705
|
-
const home =
|
|
11706
|
-
const operatorHome = process.env["CODEX_HOME"] ??
|
|
11707
|
-
const authSource =
|
|
12589
|
+
const home = path23.join(STATE_DIR, "codex-home");
|
|
12590
|
+
const operatorHome = process.env["CODEX_HOME"] ?? path23.join(os8.homedir(), ".codex");
|
|
12591
|
+
const authSource = path23.join(operatorHome, "auth.json");
|
|
11708
12592
|
const geminiSettings = this.geminiSettingsPath();
|
|
11709
|
-
const configPath =
|
|
12593
|
+
const configPath = path23.join(home, "config.toml");
|
|
11710
12594
|
const read = (file) => {
|
|
11711
12595
|
try {
|
|
11712
|
-
return
|
|
12596
|
+
return fs23.existsSync(file) ? fs23.readFileSync(file, "utf8") : undefined;
|
|
11713
12597
|
} catch {
|
|
11714
12598
|
return;
|
|
11715
12599
|
}
|
|
11716
12600
|
};
|
|
11717
12601
|
return [
|
|
11718
|
-
...evaluateBridgeDoctor({ command: bridge.command, exists:
|
|
12602
|
+
...evaluateBridgeDoctor({ command: bridge.command, exists: fs23.existsSync(bridge.command) }),
|
|
11719
12603
|
...evaluateOmpDoctor({
|
|
11720
12604
|
binary: lookupOnPath("omp"),
|
|
11721
12605
|
extensionPath: this.hooks.extensionPath,
|
|
11722
|
-
extensionExists:
|
|
12606
|
+
extensionExists: fs23.existsSync(this.hooks.extensionPath)
|
|
11723
12607
|
}),
|
|
11724
12608
|
...evaluateClaudeDoctor({ binary: lookupOnPath("claude") }),
|
|
11725
12609
|
...evaluateCodexDoctor({
|
|
11726
12610
|
binary: lookupOnPath("codex"),
|
|
11727
12611
|
authSourcePath: authSource,
|
|
11728
|
-
authSourceExists:
|
|
11729
|
-
homeExists:
|
|
12612
|
+
authSourceExists: fs23.existsSync(authSource),
|
|
12613
|
+
homeExists: fs23.existsSync(home),
|
|
11730
12614
|
expected: codexHookTrust(codexTrustKeyPath(home), buildCodexHooks({ command: bridge.command, args: bridge.args })),
|
|
11731
12615
|
configToml: read(configPath)
|
|
11732
12616
|
}),
|
|
@@ -11825,6 +12709,22 @@ ${block}` : block);
|
|
|
11825
12709
|
session.write(digit);
|
|
11826
12710
|
return true;
|
|
11827
12711
|
}
|
|
12712
|
+
answerScreenChoice(idOrPrefix, optionIndex) {
|
|
12713
|
+
const session = this.resolveLive(idOrPrefix);
|
|
12714
|
+
const agent = agentIdFromCommand(session.info.agentCommand);
|
|
12715
|
+
const dialog = detectOnboarding(agent, session.screenText(30));
|
|
12716
|
+
const digit = dialog?.options[optionIndex]?.digit;
|
|
12717
|
+
if (digit === undefined) {
|
|
12718
|
+
const keys2 = menuChoiceKeys(agentProfile(agent), optionIndex);
|
|
12719
|
+
if (keys2 === undefined)
|
|
12720
|
+
throw new AxtermError("err.choose.noSafeKey", { agent, index: optionIndex });
|
|
12721
|
+
session.write(keys2);
|
|
12722
|
+
return "menu";
|
|
12723
|
+
}
|
|
12724
|
+
this.pendingPrompt.delete(session.info.id);
|
|
12725
|
+
session.write(digit);
|
|
12726
|
+
return "onboarding";
|
|
12727
|
+
}
|
|
11828
12728
|
sinceLastCapture(id) {
|
|
11829
12729
|
const at = this.lastCaptureAt.get(id);
|
|
11830
12730
|
return at === undefined ? undefined : Date.now() - at;
|
|
@@ -11851,13 +12751,13 @@ ${block}` : block);
|
|
|
11851
12751
|
}
|
|
11852
12752
|
writeScreenFile(idOrPrefix, lines) {
|
|
11853
12753
|
const session = this.resolveLive(idOrPrefix);
|
|
11854
|
-
const file =
|
|
11855
|
-
|
|
12754
|
+
const file = path23.join(path23.dirname(this.hooks.endpointFile), `screen-${session.info.id}.txt`);
|
|
12755
|
+
fs23.writeFileSync(file, session.screenText(lines), { mode: 384 });
|
|
11856
12756
|
return file;
|
|
11857
12757
|
}
|
|
11858
12758
|
removeScreenFile(id) {
|
|
11859
12759
|
try {
|
|
11860
|
-
|
|
12760
|
+
fs23.rmSync(path23.join(path23.dirname(this.hooks.endpointFile), `screen-${id}.txt`), { force: true });
|
|
11861
12761
|
} catch {}
|
|
11862
12762
|
}
|
|
11863
12763
|
async stageInboundFile(idOrPrefix, suggestedName, bytes) {
|
|
@@ -11866,20 +12766,20 @@ ${block}` : block);
|
|
|
11866
12766
|
return this.stageInRuntimeDir(info, suggestedName, bytes);
|
|
11867
12767
|
const ws = this.workspaceFor(info.id);
|
|
11868
12768
|
const dir = sessionScratchRoot(info);
|
|
11869
|
-
const file =
|
|
12769
|
+
const file = path23.resolve(planMaterialisedPath(inboundStagingDir(dir), info.id, suggestedName));
|
|
11870
12770
|
await this.prepareScratchDir(ws, dir, file);
|
|
11871
12771
|
await ws.write(file, bytes, 384);
|
|
11872
|
-
log(`session ${info.shortId} staged ${
|
|
12772
|
+
log(`session ${info.shortId} staged ${path23.basename(file)} (${bytes.byteLength} bytes)`);
|
|
11873
12773
|
return file;
|
|
11874
12774
|
}
|
|
11875
12775
|
stagingMode() {
|
|
11876
12776
|
return this.configuredDefaults().staging ?? "project";
|
|
11877
12777
|
}
|
|
11878
12778
|
async stageInRuntimeDir(info, suggestedName, bytes) {
|
|
11879
|
-
const file =
|
|
11880
|
-
await
|
|
11881
|
-
await
|
|
11882
|
-
log(`session ${info.shortId} staged ${
|
|
12779
|
+
const file = path23.resolve(planMaterialisedPath(MATERIALISED_DIR, info.id, suggestedName));
|
|
12780
|
+
await fs23.promises.mkdir(path23.dirname(file), { recursive: true, mode: 448 });
|
|
12781
|
+
await fs23.promises.writeFile(file, bytes, { mode: 384 });
|
|
12782
|
+
log(`session ${info.shortId} staged ${path23.basename(file)} (${bytes.byteLength} bytes, runtime dir)`);
|
|
11883
12783
|
return file;
|
|
11884
12784
|
}
|
|
11885
12785
|
async prepareScratchDir(ws, sessionDir, file) {
|
|
@@ -11896,47 +12796,47 @@ ${block}` : block);
|
|
|
11896
12796
|
throw new AxtermError("err.staging.notIgnored", { dir: scratch, file: SCRATCH_IGNORE_NAME });
|
|
11897
12797
|
}
|
|
11898
12798
|
try {
|
|
11899
|
-
await ws.mkdirp(
|
|
12799
|
+
await ws.mkdirp(path23.dirname(file), 448);
|
|
11900
12800
|
} catch (err) {
|
|
11901
12801
|
throw new AxtermError("err.staging.dir", { dir: scratch, error: err instanceof Error ? err.message : String(err) });
|
|
11902
12802
|
}
|
|
11903
12803
|
}
|
|
11904
12804
|
removeStagedFiles(id, info) {
|
|
11905
12805
|
try {
|
|
11906
|
-
|
|
12806
|
+
fs23.rmSync(path23.join(MATERIALISED_DIR, id), { recursive: true, force: true });
|
|
11907
12807
|
} catch {}
|
|
11908
12808
|
if (info === undefined)
|
|
11909
12809
|
return;
|
|
11910
|
-
const scratch =
|
|
12810
|
+
const scratch = path23.resolve(scratchDir(sessionScratchRoot(info)));
|
|
11911
12811
|
try {
|
|
11912
|
-
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
12812
|
+
fs23.rmSync(path23.resolve(inboundStagingDir(sessionScratchRoot(info)), id), { recursive: true, force: true });
|
|
12813
|
+
fs23.rmdirSync(path23.join(scratch, "inbound"));
|
|
12814
|
+
fs23.rmSync(path23.join(scratch, SCRATCH_IGNORE_NAME), { force: true });
|
|
12815
|
+
fs23.rmdirSync(scratch);
|
|
11916
12816
|
} catch {}
|
|
11917
12817
|
}
|
|
11918
12818
|
removeClaudeSettings(id) {
|
|
11919
12819
|
try {
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
11923
|
-
|
|
12820
|
+
fs23.rmSync(this.claudeSettingsPath(id), { force: true });
|
|
12821
|
+
fs23.rmSync(this.mcpConfigPath(id), { force: true });
|
|
12822
|
+
fs23.rmSync(this.mcpContextPath(id), { force: true });
|
|
12823
|
+
fs23.rmSync(this.geminiTelemetryPath(id), { force: true });
|
|
11924
12824
|
this.stopCodexAppServer(id);
|
|
11925
12825
|
} catch {}
|
|
11926
12826
|
}
|
|
11927
12827
|
claudeSettingsPath(id) {
|
|
11928
|
-
return
|
|
12828
|
+
return path23.join(path23.dirname(this.hooks.endpointFile), `claude-${id}.json`);
|
|
11929
12829
|
}
|
|
11930
12830
|
mcpConfigPath(id) {
|
|
11931
|
-
return
|
|
12831
|
+
return path23.join(path23.dirname(this.hooks.endpointFile), `mcp-${id}.json`);
|
|
11932
12832
|
}
|
|
11933
12833
|
mcpContextPath(id) {
|
|
11934
|
-
return
|
|
12834
|
+
return path23.join(path23.dirname(this.hooks.endpointFile), `mcp-ctx-${id}.json`);
|
|
11935
12835
|
}
|
|
11936
12836
|
writeMcpContext(id, launchToken) {
|
|
11937
12837
|
const file = this.mcpContextPath(id);
|
|
11938
12838
|
try {
|
|
11939
|
-
|
|
12839
|
+
fs23.writeFileSync(file, JSON.stringify({ port: this.hooks.port, token: this.hooks.host.token, sessionId: id, launchToken }), { mode: 384 });
|
|
11940
12840
|
return file;
|
|
11941
12841
|
} catch (err) {
|
|
11942
12842
|
log(`mcp context write failed; session starts without axterm's tools: ${String(err)}`);
|
|
@@ -11949,7 +12849,7 @@ ${block}` : block);
|
|
|
11949
12849
|
writeMcpConfig(id, contextPath) {
|
|
11950
12850
|
const file = this.mcpConfigPath(id);
|
|
11951
12851
|
try {
|
|
11952
|
-
|
|
12852
|
+
fs23.writeFileSync(file, claudeMcpConfig(this.mcpServerLaunch(), contextPath), { mode: 384 });
|
|
11953
12853
|
return file;
|
|
11954
12854
|
} catch (err) {
|
|
11955
12855
|
log(`mcp config write failed; session starts without axterm's tools: ${String(err)}`);
|
|
@@ -11957,27 +12857,29 @@ ${block}` : block);
|
|
|
11957
12857
|
}
|
|
11958
12858
|
}
|
|
11959
12859
|
geminiTelemetryPath(id) {
|
|
11960
|
-
return
|
|
12860
|
+
return path23.join(path23.dirname(this.hooks.endpointFile), `gemini-telemetry-${id}.log`);
|
|
11961
12861
|
}
|
|
11962
12862
|
geminiSettingsPath() {
|
|
11963
|
-
return
|
|
12863
|
+
return path23.join(path23.dirname(this.hooks.endpointFile), "gemini-defaults.json");
|
|
11964
12864
|
}
|
|
11965
12865
|
async ptySpawner() {
|
|
11966
|
-
if (
|
|
11967
|
-
return spawnPty;
|
|
12866
|
+
if (!this.ptyHostPolicy.enabled || this.ptyHostUnavailable)
|
|
12867
|
+
return { spawn: spawnPty, hostOwned: false };
|
|
11968
12868
|
try {
|
|
11969
12869
|
if (this.ptyHost === undefined) {
|
|
11970
|
-
const
|
|
11971
|
-
|
|
12870
|
+
const info = await ensurePtyHost({
|
|
12871
|
+
runtimeDir: RUNTIME_DIR,
|
|
12872
|
+
...this.ptyHostPolicy.mainScript ? { mainScript: this.ptyHostPolicy.mainScript } : {}
|
|
12873
|
+
});
|
|
11972
12874
|
this.ptyHost = await connectPtyHost({ endpoint: info.endpoint, token: info.token });
|
|
11973
12875
|
log(`pty host: connected (pid ${info.pid})`);
|
|
11974
12876
|
}
|
|
11975
12877
|
const host = this.ptyHost;
|
|
11976
|
-
return (options) => host.spawn(options);
|
|
12878
|
+
return { spawn: (options) => host.spawn(options), hostOwned: true };
|
|
11977
12879
|
} catch (err) {
|
|
11978
12880
|
this.ptyHostUnavailable = true;
|
|
11979
12881
|
log(`pty host unavailable, owning ptys in this process: ${err instanceof Error ? err.message : String(err)}`);
|
|
11980
|
-
return spawnPty;
|
|
12882
|
+
return { spawn: spawnPty, hostOwned: false };
|
|
11981
12883
|
}
|
|
11982
12884
|
}
|
|
11983
12885
|
requireClaudeConversation(agentCommand) {
|
|
@@ -11988,7 +12890,7 @@ ${block}` : block);
|
|
|
11988
12890
|
throw new AxtermError("err.claude.noConversation", { id: conversation });
|
|
11989
12891
|
}
|
|
11990
12892
|
}
|
|
11991
|
-
async launch(info, shell, agentCommand
|
|
12893
|
+
async launch(info, shell, agentCommand) {
|
|
11992
12894
|
const id = info.id;
|
|
11993
12895
|
const launchToken = randomBytes2(16).toString("hex");
|
|
11994
12896
|
const agentId = agentIdFromCommand(info.agentCommand);
|
|
@@ -11996,11 +12898,22 @@ ${block}` : block);
|
|
|
11996
12898
|
this.writeClaudeSettings(id, launchToken);
|
|
11997
12899
|
if (agentId !== "omp")
|
|
11998
12900
|
this.writeMcpContext(id, launchToken);
|
|
12901
|
+
if (agentId === "claude")
|
|
12902
|
+
this.writeMcpConfig(id, this.mcpContextPath(id));
|
|
11999
12903
|
if (agentId === "gemini")
|
|
12000
12904
|
this.writeGeminiSettings(id);
|
|
12001
12905
|
const codexHome2 = agentId === "codex" ? this.ensureCodexHome() : undefined;
|
|
12002
12906
|
if (agentId === "claude")
|
|
12003
12907
|
this.requireClaudeConversation(agentCommand);
|
|
12908
|
+
const identity = {
|
|
12909
|
+
sessionId: id,
|
|
12910
|
+
launchToken,
|
|
12911
|
+
hookPort: this.hooks.port,
|
|
12912
|
+
hookToken: this.hooks.host.token,
|
|
12913
|
+
endpointFile: this.hooks.endpointFile
|
|
12914
|
+
};
|
|
12915
|
+
const appServerUrl = codexHome2 === undefined ? undefined : await this.startCodexAppServer(id, codexHome2, info.cwd, identity);
|
|
12916
|
+
const spawner = await this.ptySpawner();
|
|
12004
12917
|
const session = await PtySession.open(info, {
|
|
12005
12918
|
exe: shell.exe,
|
|
12006
12919
|
argv: buildShellArgv(shell, withAppServer(agentCommand, appServerUrl)),
|
|
@@ -12008,12 +12921,7 @@ ${block}` : block);
|
|
|
12008
12921
|
cols: info.cols,
|
|
12009
12922
|
rows: info.rows,
|
|
12010
12923
|
env: {
|
|
12011
|
-
...
|
|
12012
|
-
AXTERM_SESSION_ID: id,
|
|
12013
|
-
AXTERM_LAUNCH_TOKEN: launchToken,
|
|
12014
|
-
AXTERM_HOOK_PORT: String(this.hooks.port),
|
|
12015
|
-
AXTERM_HOOK_TOKEN: this.hooks.host.token,
|
|
12016
|
-
AXTERM_HOOK_ENDPOINT: this.hooks.endpointFile,
|
|
12924
|
+
...sessionEnv(process.env, identity),
|
|
12017
12925
|
...agentId === "gemini" ? {
|
|
12018
12926
|
GEMINI_CLI_SYSTEM_DEFAULTS_PATH: this.geminiSettingsPath(),
|
|
12019
12927
|
GEMINI_TELEMETRY_ENABLED: "true",
|
|
@@ -12024,7 +12932,15 @@ ${block}` : block);
|
|
|
12024
12932
|
} : {},
|
|
12025
12933
|
...codexHome2 !== undefined ? { CODEX_HOME: codexHome2 } : {}
|
|
12026
12934
|
}
|
|
12027
|
-
},
|
|
12935
|
+
}, spawner.spawn);
|
|
12936
|
+
if (spawner.hostOwned)
|
|
12937
|
+
this.hostOwned.add(id);
|
|
12938
|
+
this.wire(session, launchToken);
|
|
12939
|
+
return session;
|
|
12940
|
+
}
|
|
12941
|
+
wire(session, launchToken) {
|
|
12942
|
+
const id = session.info.id;
|
|
12943
|
+
const info = session.info;
|
|
12028
12944
|
this.agentRuntime.set(id, { agentExited: false, choiceSeq: 0, dialogOpen: false });
|
|
12029
12945
|
this.hooks.host.registerSession(id, launchToken, (payload) => this.ingestHookEvent(id, payload));
|
|
12030
12946
|
session.onData(() => {
|
|
@@ -12051,11 +12967,11 @@ ${block}` : block);
|
|
|
12051
12967
|
this.pendingPrompt.delete(id);
|
|
12052
12968
|
this.live.delete(id);
|
|
12053
12969
|
this.agentRuntime.delete(id);
|
|
12970
|
+
this.hostOwned.delete(id);
|
|
12054
12971
|
log(`session ${info.shortId} exited code=${code}`);
|
|
12055
12972
|
this.emit({ t: "session.exit", sessionId: id, code });
|
|
12056
12973
|
});
|
|
12057
12974
|
this.live.set(id, session);
|
|
12058
|
-
return session;
|
|
12059
12975
|
}
|
|
12060
12976
|
emitActivity(id, payload) {
|
|
12061
12977
|
const dropped = payload.dropped;
|
|
@@ -12119,7 +13035,7 @@ ${block}` : block);
|
|
|
12119
13035
|
this.store.updateAgentState(id, result.entry.state, "hook", now);
|
|
12120
13036
|
log(`session ${info.shortId} agent ${runtime.entry?.state ?? "-"} -> ${result.entry.state}`);
|
|
12121
13037
|
this.emit({ t: "agent.status", sessionId: id, state: result.entry.state, toolName: result.entry.toolName });
|
|
12122
|
-
if (
|
|
13038
|
+
if (offersKeystrokeApproval(agentProfile(agentIdFromCommand(info.agentCommand)), result.entry.state)) {
|
|
12123
13039
|
runtime.choiceSeq += 1;
|
|
12124
13040
|
this.emit({
|
|
12125
13041
|
t: "choice.request",
|
|
@@ -12226,38 +13142,14 @@ ${block}` : block);
|
|
|
12226
13142
|
allSessions() {
|
|
12227
13143
|
return sessionsByRecency([...this.live.values()].map((s) => s.info), this.store.listSessions());
|
|
12228
13144
|
}
|
|
12229
|
-
async resumeNamed(stored, agent) {
|
|
12230
|
-
const mcpConfigPath = agent === "claude" ? this.writeMcpConfig(stored.id, this.mcpContextPath(stored.id)) : undefined;
|
|
12231
|
-
const agentCommand = buildAgentResumeCommand({
|
|
12232
|
-
agent,
|
|
12233
|
-
sessionId: stored.id,
|
|
12234
|
-
extensionPath: this.hooks.extensionPath,
|
|
12235
|
-
claudeSettingsPath: this.claudeSettingsPath(stored.id),
|
|
12236
|
-
...mcpConfigPath === undefined ? {} : { mcpConfigPath },
|
|
12237
|
-
storedArgv: stored.agentCommand
|
|
12238
|
-
});
|
|
12239
|
-
const info = { ...stored, agentCommand, state: "running" };
|
|
12240
|
-
delete info.exitCode;
|
|
12241
|
-
delete info.exitedAt;
|
|
12242
|
-
delete info.agentState;
|
|
12243
|
-
delete info.agentStateSource;
|
|
12244
|
-
await this.launch(info, resolveShell(info.shell), agentCommand);
|
|
12245
|
-
this.store.markResumed(info.id, agentCommand, info.ptyPid);
|
|
12246
|
-
log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
|
|
12247
|
-
this.emit({ t: "session.created", info });
|
|
12248
|
-
return info;
|
|
12249
|
-
}
|
|
12250
13145
|
async resume(idOrPrefix) {
|
|
12251
13146
|
const stored = this.store.findByPrefix(idOrPrefix);
|
|
12252
13147
|
if (!stored)
|
|
12253
13148
|
throw new AxtermError("err.session.unknown", { id: idOrPrefix });
|
|
12254
13149
|
if (this.live.has(stored.id))
|
|
12255
13150
|
throw new AxtermError("err.session.running", { id: stored.shortId });
|
|
12256
|
-
const storedAgent = agentIdFromCommand(stored.agentCommand);
|
|
12257
|
-
if (storedAgent === "claude" || storedAgent === "gemini")
|
|
12258
|
-
return this.resumeNamed(stored, storedAgent);
|
|
12259
13151
|
const profile = agentProfile(agentIdFromCommand(stored.agentCommand));
|
|
12260
|
-
const reference = resumeReference(profile, stored);
|
|
13152
|
+
const reference = resumeReference(profile, { ...stored, ownSessionId: stored.id });
|
|
12261
13153
|
if (reference === undefined) {
|
|
12262
13154
|
throw new AxtermError("err.session.noConversation", {
|
|
12263
13155
|
id: stored.shortId,
|
|
@@ -12268,7 +13160,7 @@ ${block}` : block);
|
|
|
12268
13160
|
if (agentCommand === undefined) {
|
|
12269
13161
|
throw new AxtermError("err.session.noResumeSupport", { agent: profile.displayName });
|
|
12270
13162
|
}
|
|
12271
|
-
if (!
|
|
13163
|
+
if (!fs23.existsSync(stored.cwd))
|
|
12272
13164
|
throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
|
|
12273
13165
|
const info = { ...stored, agentCommand, state: "running" };
|
|
12274
13166
|
delete info.exitCode;
|
|
@@ -12276,8 +13168,7 @@ ${block}` : block);
|
|
|
12276
13168
|
delete info.agentState;
|
|
12277
13169
|
delete info.agentStateSource;
|
|
12278
13170
|
const shell = resolveShell(info.shell);
|
|
12279
|
-
|
|
12280
|
-
await this.launch(info, shell, agentCommand, resumedAppServer);
|
|
13171
|
+
await this.launch(info, shell, agentCommand);
|
|
12281
13172
|
this.store.markResumed(info.id, agentCommand, info.ptyPid);
|
|
12282
13173
|
log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
|
|
12283
13174
|
this.emit({ t: "session.created", info });
|
|
@@ -12343,7 +13234,7 @@ ${block}` : block);
|
|
|
12343
13234
|
removeScrollback(id) {
|
|
12344
13235
|
for (const file of [`${id}.bin`, `${id}.bin.tmp`]) {
|
|
12345
13236
|
try {
|
|
12346
|
-
|
|
13237
|
+
fs23.rmSync(path23.join(SCROLLBACK_DIR, file), { force: true });
|
|
12347
13238
|
} catch {}
|
|
12348
13239
|
}
|
|
12349
13240
|
}
|
|
@@ -12351,6 +13242,14 @@ ${block}` : block);
|
|
|
12351
13242
|
this.drainGeminiTelemetry();
|
|
12352
13243
|
this.watchForStalls();
|
|
12353
13244
|
}
|
|
13245
|
+
skipGeminiTelemetryHistory(id) {
|
|
13246
|
+
const runtime = this.agentRuntime.get(id);
|
|
13247
|
+
if (runtime === undefined)
|
|
13248
|
+
return;
|
|
13249
|
+
try {
|
|
13250
|
+
runtime.telemetryOffset = fs23.statSync(this.geminiTelemetryPath(id)).size;
|
|
13251
|
+
} catch {}
|
|
13252
|
+
}
|
|
12354
13253
|
drainGeminiTelemetry() {
|
|
12355
13254
|
for (const [id, session] of this.live) {
|
|
12356
13255
|
if (agentIdFromCommand(session.info.agentCommand) !== "gemini")
|
|
@@ -12361,7 +13260,7 @@ ${block}` : block);
|
|
|
12361
13260
|
const file = this.geminiTelemetryPath(id);
|
|
12362
13261
|
let size = 0;
|
|
12363
13262
|
try {
|
|
12364
|
-
size =
|
|
13263
|
+
size = fs23.statSync(file).size;
|
|
12365
13264
|
} catch {
|
|
12366
13265
|
continue;
|
|
12367
13266
|
}
|
|
@@ -12423,10 +13322,17 @@ ${block}` : block);
|
|
|
12423
13322
|
clearInterval(this.sessionWatch);
|
|
12424
13323
|
for (const id of [...this.codexServers.keys()])
|
|
12425
13324
|
this.stopCodexAppServer(id);
|
|
12426
|
-
|
|
13325
|
+
let handedOn = 0;
|
|
13326
|
+
for (const [id, session] of this.live) {
|
|
12427
13327
|
session.saveScrollback();
|
|
13328
|
+
if (this.hostOwned.has(id)) {
|
|
13329
|
+
handedOn += 1;
|
|
13330
|
+
continue;
|
|
13331
|
+
}
|
|
12428
13332
|
session.kill();
|
|
12429
13333
|
}
|
|
13334
|
+
if (handedOn > 0)
|
|
13335
|
+
log(`left ${handedOn} session(s) running in the terminal host for the next daemon`);
|
|
12430
13336
|
this.live.clear();
|
|
12431
13337
|
this.agentRuntime.clear();
|
|
12432
13338
|
}
|
|
@@ -12435,7 +13341,7 @@ var DEFAULT_COLS = 120, DEFAULT_ROWS = 30, MATERIALISED_DIR, ACTIVITY_ARG_CHARS
|
|
|
12435
13341
|
var init_session_manager = __esm(() => {
|
|
12436
13342
|
init_src();
|
|
12437
13343
|
init_codex_home();
|
|
12438
|
-
|
|
13344
|
+
init_session_env();
|
|
12439
13345
|
init_git();
|
|
12440
13346
|
init_claude_store();
|
|
12441
13347
|
init_injector();
|
|
@@ -12450,7 +13356,7 @@ var init_session_manager = __esm(() => {
|
|
|
12450
13356
|
init_pty_session();
|
|
12451
13357
|
init_shell();
|
|
12452
13358
|
init_workspace_local();
|
|
12453
|
-
MATERIALISED_DIR =
|
|
13359
|
+
MATERIALISED_DIR = path23.join(RUNTIME_DIR, "materialised");
|
|
12454
13360
|
PROMPT_OPTIONS = {
|
|
12455
13361
|
hostKey: [{ label: "yes" }, { label: "no" }],
|
|
12456
13362
|
password: [],
|
|
@@ -12481,6 +13387,9 @@ class FetchTelegramApi {
|
|
|
12481
13387
|
throw new Error(`telegram file download failed: ${res.status}`);
|
|
12482
13388
|
return new Uint8Array(await res.arrayBuffer());
|
|
12483
13389
|
}
|
|
13390
|
+
async deleteMessage(chatId, messageId) {
|
|
13391
|
+
await this.call("deleteMessage", { chat_id: chatId, message_id: messageId });
|
|
13392
|
+
}
|
|
12484
13393
|
async call(method, params = {}) {
|
|
12485
13394
|
const attempt = async () => {
|
|
12486
13395
|
const res = await fetch(`https://api.telegram.org/bot${this.botToken}/${method}`, {
|
|
@@ -12599,11 +13508,11 @@ var init_acknowledge = __esm(() => {
|
|
|
12599
13508
|
});
|
|
12600
13509
|
|
|
12601
13510
|
// packages/channel-telegram/src/attach.ts
|
|
12602
|
-
import
|
|
12603
|
-
import
|
|
13511
|
+
import fs24 from "fs";
|
|
13512
|
+
import path24 from "path";
|
|
12604
13513
|
function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELEGRAM_LIMITS) {
|
|
12605
|
-
const name =
|
|
12606
|
-
const bytes =
|
|
13514
|
+
const name = path24.basename(filePath);
|
|
13515
|
+
const bytes = fs24.statSync(filePath).size;
|
|
12607
13516
|
const plan = planAttachment({ name, bytes }, limits);
|
|
12608
13517
|
if (plan.kind === "rejected")
|
|
12609
13518
|
throw new Error(plan.reason);
|
|
@@ -12613,7 +13522,7 @@ function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELE
|
|
|
12613
13522
|
form.append("message_thread_id", String(topicId));
|
|
12614
13523
|
if (caption !== undefined && caption.length > 0)
|
|
12615
13524
|
form.append("caption", caption);
|
|
12616
|
-
form.append(field, new Blob([
|
|
13525
|
+
form.append(field, new Blob([fs24.readFileSync(filePath)]), name);
|
|
12617
13526
|
return { method: plan.kind === "photo" ? "sendPhoto" : "sendDocument", field, form };
|
|
12618
13527
|
}
|
|
12619
13528
|
function isPhotoRejection(error) {
|
|
@@ -12621,13 +13530,13 @@ function isPhotoRejection(error) {
|
|
|
12621
13530
|
return /IMAGE_PROCESS_FAILED|PHOTO_INVALID_DIMENSIONS|PHOTO_EXT_INVALID|PHOTO_SAVE_FILE_INVALID/i.test(message);
|
|
12622
13531
|
}
|
|
12623
13532
|
function asDocumentUpload(filePath, chatId, topicId, caption) {
|
|
12624
|
-
const name =
|
|
13533
|
+
const name = path24.basename(filePath);
|
|
12625
13534
|
const form = new FormData;
|
|
12626
13535
|
form.append("chat_id", String(chatId));
|
|
12627
13536
|
form.append("message_thread_id", String(topicId));
|
|
12628
13537
|
if (caption !== undefined && caption.length > 0)
|
|
12629
13538
|
form.append("caption", caption);
|
|
12630
|
-
form.append("document", new Blob([
|
|
13539
|
+
form.append("document", new Blob([fs24.readFileSync(filePath)]), name);
|
|
12631
13540
|
return { method: "sendDocument", field: "document", form };
|
|
12632
13541
|
}
|
|
12633
13542
|
var TELEGRAM_LIMITS;
|
|
@@ -12697,6 +13606,31 @@ function planScreenDelivery(text, maxMessages) {
|
|
|
12697
13606
|
const chunks = preChunks(text);
|
|
12698
13607
|
return chunks.length <= maxMessages ? { kind: "messages", chunks } : { kind: "file", text };
|
|
12699
13608
|
}
|
|
13609
|
+
function diffQuote(summary, limit = TELEGRAM_TEXT_LIMIT, reserved = 0) {
|
|
13610
|
+
const OPEN = "<blockquote expandable><pre>";
|
|
13611
|
+
const CLOSE = "</pre></blockquote>";
|
|
13612
|
+
const budget = limit - reserved - OPEN.length - CLOSE.length;
|
|
13613
|
+
const cut = (total) => escapeHtml(`\u2026 truncated, ${total} characters in the full patch`);
|
|
13614
|
+
const rendered = [];
|
|
13615
|
+
let used = 0;
|
|
13616
|
+
let stopped = false;
|
|
13617
|
+
for (const line of summary.lines) {
|
|
13618
|
+
const marker = line.kind === "add" ? "+" : line.kind === "del" ? "-" : "";
|
|
13619
|
+
const escaped = escapeHtml(`${marker}${line.text}`);
|
|
13620
|
+
if (used + escaped.length + 1 + cut(summary.totalChars).length > budget) {
|
|
13621
|
+
stopped = true;
|
|
13622
|
+
break;
|
|
13623
|
+
}
|
|
13624
|
+
rendered.push(escaped);
|
|
13625
|
+
used += escaped.length + 1;
|
|
13626
|
+
}
|
|
13627
|
+
if (rendered.length === 0)
|
|
13628
|
+
return "";
|
|
13629
|
+
if (stopped || summary.truncated !== undefined)
|
|
13630
|
+
rendered.push(cut(summary.totalChars));
|
|
13631
|
+
return `${OPEN}${rendered.join(`
|
|
13632
|
+
`)}${CLOSE}`;
|
|
13633
|
+
}
|
|
12700
13634
|
function parseScreenLines(args) {
|
|
12701
13635
|
const raw = args[0];
|
|
12702
13636
|
if (raw === undefined || !/^-?\d+$/.test(raw))
|
|
@@ -13171,7 +14105,7 @@ var init_topic_title = __esm(() => {
|
|
|
13171
14105
|
});
|
|
13172
14106
|
|
|
13173
14107
|
// packages/channel-telegram/src/adapter.ts
|
|
13174
|
-
import { setTimeout as
|
|
14108
|
+
import { setTimeout as delay4 } from "timers/promises";
|
|
13175
14109
|
function readMedia(msg) {
|
|
13176
14110
|
const photo = msg.photo?.at(-1);
|
|
13177
14111
|
if (photo) {
|
|
@@ -13207,6 +14141,7 @@ class TelegramChannel {
|
|
|
13207
14141
|
titleFlushes = new Map;
|
|
13208
14142
|
flows = new Map;
|
|
13209
14143
|
outbound = Promise.resolve();
|
|
14144
|
+
username;
|
|
13210
14145
|
polling = false;
|
|
13211
14146
|
stopped = false;
|
|
13212
14147
|
pollAbort;
|
|
@@ -13391,7 +14326,16 @@ class TelegramChannel {
|
|
|
13391
14326
|
`${d.stat.filesChanged} files +${d.stat.insertions} -${d.stat.deletions}`,
|
|
13392
14327
|
...d.files.map((f) => `\u2022 ${f}`)
|
|
13393
14328
|
];
|
|
13394
|
-
|
|
14329
|
+
const messages = htmlLines(lines);
|
|
14330
|
+
const summary = d.patch === undefined ? undefined : summarizePatch(d.patch);
|
|
14331
|
+
const last = messages.at(-1);
|
|
14332
|
+
if (summary !== undefined && last !== undefined) {
|
|
14333
|
+
const quote = diffQuote(summary, TELEGRAM_TEXT_LIMIT, last.length + 1);
|
|
14334
|
+
if (quote !== "")
|
|
14335
|
+
messages[messages.length - 1] = `${last}
|
|
14336
|
+
${quote}`;
|
|
14337
|
+
}
|
|
14338
|
+
for (const message of messages) {
|
|
13395
14339
|
await this.sendToSession(sessionId, message);
|
|
13396
14340
|
}
|
|
13397
14341
|
}
|
|
@@ -13523,6 +14467,26 @@ ${code(prompt.line)}`,
|
|
|
13523
14467
|
});
|
|
13524
14468
|
}
|
|
13525
14469
|
awaitingSecret = new Map;
|
|
14470
|
+
async captureSecret(sessionId, msg, text) {
|
|
14471
|
+
const seq = this.awaitingSecret.get(sessionId);
|
|
14472
|
+
if (seq === undefined)
|
|
14473
|
+
return false;
|
|
14474
|
+
this.awaitingSecret.delete(sessionId);
|
|
14475
|
+
const accepted = this.control.answerShellPrompt(sessionId, seq, text);
|
|
14476
|
+
let deleted = false;
|
|
14477
|
+
if (this.api.deleteMessage && msg.chat && msg.message_id !== undefined) {
|
|
14478
|
+
try {
|
|
14479
|
+
await this.api.deleteMessage(msg.chat.id, msg.message_id);
|
|
14480
|
+
deleted = true;
|
|
14481
|
+
} catch (err) {
|
|
14482
|
+
this.log(`secret reply message ${msg.message_id} not deleted: ${String(err)}`);
|
|
14483
|
+
}
|
|
14484
|
+
}
|
|
14485
|
+
await this.sendToSession(sessionId, tHtml(accepted ? "tg.prompt.secretSubmitted" : "tg.prompt.secretStale"));
|
|
14486
|
+
if (!deleted)
|
|
14487
|
+
await this.sendToSession(sessionId, tHtml("tg.prompt.secretUndeleted"));
|
|
14488
|
+
return true;
|
|
14489
|
+
}
|
|
13526
14490
|
async onChoiceRequest(sessionId, seq, questions, askId, onboarding2 = false) {
|
|
13527
14491
|
const binding = this.control.findBindingBySession(sessionId);
|
|
13528
14492
|
if (!binding || questions.length === 0)
|
|
@@ -13686,6 +14650,8 @@ ${code(prompt.line)}`,
|
|
|
13686
14650
|
}
|
|
13687
14651
|
if (!binding)
|
|
13688
14652
|
return;
|
|
14653
|
+
if (await this.captureSecret(binding.sessionId, msg, text))
|
|
14654
|
+
return;
|
|
13689
14655
|
if (await this.captureFreeText(binding.sessionId, text))
|
|
13690
14656
|
return;
|
|
13691
14657
|
const chatId = msg.chat.id;
|
|
@@ -13792,8 +14758,8 @@ ${code(prompt.line)}`,
|
|
|
13792
14758
|
throw new Error("this Telegram client cannot download files");
|
|
13793
14759
|
const files = [];
|
|
13794
14760
|
for (const item of batch.items) {
|
|
13795
|
-
const
|
|
13796
|
-
files.push({ path:
|
|
14761
|
+
const path25 = await this.control.stageInboundFile(batch.sessionId, item.name, await download2(item.fileId));
|
|
14762
|
+
files.push({ path: path25, kind: item.kind });
|
|
13797
14763
|
}
|
|
13798
14764
|
await this.control.prompt(batch.sessionId, buildAttachmentPrompt(files, batch.caption));
|
|
13799
14765
|
this.log(`staged ${files.length} inbound file(s) for ${batch.sessionId}`);
|
|
@@ -13808,7 +14774,7 @@ ${code(prompt.line)}`,
|
|
|
13808
14774
|
async settleInboundForTest() {
|
|
13809
14775
|
while (this.albums.size > 0 || this.inboundQueues.size > 0) {
|
|
13810
14776
|
if (this.albums.size > 0)
|
|
13811
|
-
await
|
|
14777
|
+
await delay4(this.albumDebounceMs + 5);
|
|
13812
14778
|
await Promise.all([...this.inboundQueues.values()]);
|
|
13813
14779
|
}
|
|
13814
14780
|
}
|
|
@@ -13817,10 +14783,22 @@ ${code(prompt.line)}`,
|
|
|
13817
14783
|
return;
|
|
13818
14784
|
await this.api.call("setMessageReaction", { chat_id: chatId, message_id: messageId, reaction: reactionPayload(state) }).catch(() => {});
|
|
13819
14785
|
}
|
|
14786
|
+
async selfUsername() {
|
|
14787
|
+
if (this.username !== undefined)
|
|
14788
|
+
return this.username;
|
|
14789
|
+
try {
|
|
14790
|
+
const me = await this.api.call("getMe");
|
|
14791
|
+
if (typeof me.username === "string" && me.username.length > 0)
|
|
14792
|
+
this.username = me.username;
|
|
14793
|
+
} catch {}
|
|
14794
|
+
return this.username;
|
|
14795
|
+
}
|
|
13820
14796
|
async handleCommand(text, binding, msg) {
|
|
13821
14797
|
const [rawCmd = "", ...args] = text.split(/\s+/);
|
|
13822
14798
|
const rawTail = text.slice(rawCmd.length);
|
|
13823
|
-
const cmd = rawCmd
|
|
14799
|
+
const { command: cmd, target } = parseCommandAddress(rawCmd);
|
|
14800
|
+
if (target !== undefined && !commandIsForBot(target, await this.selfUsername()))
|
|
14801
|
+
return;
|
|
13824
14802
|
if (binding) {
|
|
13825
14803
|
switch (cmd) {
|
|
13826
14804
|
case "/esc":
|
|
@@ -13916,8 +14894,15 @@ ${code(prompt.line)}`,
|
|
|
13916
14894
|
const project = await resolveProject(args[0]);
|
|
13917
14895
|
if (!project)
|
|
13918
14896
|
return;
|
|
13919
|
-
const
|
|
13920
|
-
|
|
14897
|
+
const tail = args.slice(1);
|
|
14898
|
+
const flagAt = tail.findIndex((token) => token.startsWith("-"));
|
|
14899
|
+
const name = (flagAt < 0 ? tail : tail.slice(0, flagAt)).join(" ") || undefined;
|
|
14900
|
+
const agentArgs = flagAt < 0 ? [] : tail.slice(flagAt);
|
|
14901
|
+
await this.control.createSession({
|
|
14902
|
+
cwd: project.path,
|
|
14903
|
+
...name !== undefined ? { name } : {},
|
|
14904
|
+
...agentArgs.length > 0 ? { agentArgs } : {}
|
|
14905
|
+
});
|
|
13921
14906
|
return;
|
|
13922
14907
|
}
|
|
13923
14908
|
case "/wt": {
|
|
@@ -13969,13 +14954,13 @@ ${code(prompt.line)}`,
|
|
|
13969
14954
|
const project = await resolveProject(args[0]);
|
|
13970
14955
|
if (!project)
|
|
13971
14956
|
return;
|
|
13972
|
-
const
|
|
13973
|
-
if (
|
|
14957
|
+
const target2 = args[1];
|
|
14958
|
+
if (target2 === undefined) {
|
|
13974
14959
|
await reply(t("tg.usage.rm"));
|
|
13975
14960
|
return;
|
|
13976
14961
|
}
|
|
13977
|
-
const result = await this.control.removeWorktree(project.alias,
|
|
13978
|
-
await reply(result.removed ? t("tg.worktree.removed", { target }) : t("tg.worktree.refused", { reason: String(result.reason) }));
|
|
14962
|
+
const result = await this.control.removeWorktree(project.alias, target2, args[2] === "force");
|
|
14963
|
+
await reply(result.removed ? t("tg.worktree.removed", { target: target2 }) : t("tg.worktree.refused", { reason: String(result.reason) }));
|
|
13979
14964
|
return;
|
|
13980
14965
|
}
|
|
13981
14966
|
case "/sessions": {
|
|
@@ -14073,8 +15058,12 @@ ${code(prompt.line)}`,
|
|
|
14073
15058
|
return;
|
|
14074
15059
|
}
|
|
14075
15060
|
if (flow.askId === undefined) {
|
|
14076
|
-
const keys2 = agentProfile(agentIdFromCommand(session.agentCommand)).
|
|
14077
|
-
|
|
15061
|
+
const keys2 = menuChoiceKeys(agentProfile(agentIdFromCommand(session.agentCommand)), decoded.optionIndex);
|
|
15062
|
+
if (keys2 === undefined) {
|
|
15063
|
+
await answer(t("tg.answer.noSafeKey"));
|
|
15064
|
+
return;
|
|
15065
|
+
}
|
|
15066
|
+
this.control.write(session.id, keys2);
|
|
14078
15067
|
}
|
|
14079
15068
|
flow.answers.push({ questionIndex: flow.cursor, selected: [label] });
|
|
14080
15069
|
flow.cursor += 1;
|
|
@@ -14127,6 +15116,11 @@ ${code(prompt.line)}`,
|
|
|
14127
15116
|
clearTimeout(album.timer);
|
|
14128
15117
|
this.albums.clear();
|
|
14129
15118
|
}
|
|
15119
|
+
pollHealth() {
|
|
15120
|
+
return this.pollError;
|
|
15121
|
+
}
|
|
15122
|
+
pollError;
|
|
15123
|
+
pollWarned = false;
|
|
14130
15124
|
async pollLoop() {
|
|
14131
15125
|
while (this.polling) {
|
|
14132
15126
|
try {
|
|
@@ -14137,6 +15131,10 @@ ${code(prompt.line)}`,
|
|
|
14137
15131
|
});
|
|
14138
15132
|
if (this.stopped)
|
|
14139
15133
|
return;
|
|
15134
|
+
if (this.pollError !== undefined)
|
|
15135
|
+
this.log("polling recovered");
|
|
15136
|
+
this.pollError = undefined;
|
|
15137
|
+
this.pollWarned = false;
|
|
14140
15138
|
for (const update2 of updates) {
|
|
14141
15139
|
this.updateOffset = Math.max(this.updateOffset, update2.update_id + 1);
|
|
14142
15140
|
await this.handleUpdate(update2);
|
|
@@ -14146,11 +15144,29 @@ ${code(prompt.line)}`,
|
|
|
14146
15144
|
} catch (err) {
|
|
14147
15145
|
if (!this.polling)
|
|
14148
15146
|
return;
|
|
14149
|
-
|
|
14150
|
-
|
|
15147
|
+
const reason = String(err);
|
|
15148
|
+
this.pollError = reason;
|
|
15149
|
+
this.log(`poll failed: ${reason}`);
|
|
15150
|
+
if (!this.pollWarned) {
|
|
15151
|
+
this.pollWarned = true;
|
|
15152
|
+
this.warnPollFailure(reason);
|
|
15153
|
+
}
|
|
15154
|
+
await delay4(3000);
|
|
14151
15155
|
}
|
|
14152
15156
|
}
|
|
14153
15157
|
}
|
|
15158
|
+
async warnPollFailure(reason) {
|
|
15159
|
+
const conflict = /\b409\b|terminated by other getUpdates/i.test(reason);
|
|
15160
|
+
const text = conflict ? tHtml("tg.poll.conflict", { machine: this.config.machine.name }) : tHtml("tg.poll.failed", { machine: this.config.machine.name, reason: oneLineReason(reason) });
|
|
15161
|
+
try {
|
|
15162
|
+
await this.api.call("sendMessage", { chat_id: this.config.groupId, text, parse_mode: "HTML" });
|
|
15163
|
+
} catch (err) {
|
|
15164
|
+
this.log(`could not report the poll failure: ${String(err)}`);
|
|
15165
|
+
}
|
|
15166
|
+
}
|
|
15167
|
+
}
|
|
15168
|
+
function oneLineReason(reason) {
|
|
15169
|
+
return reason.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
14154
15170
|
}
|
|
14155
15171
|
var SCREEN_MESSAGE_BUDGET = 2, AGENT_ERROR_CHARS = 1200, RAW_TYPE_LIMIT = 500, RAW_SCREEN_LINES = 12, HELP_KEYS, helpText = () => HELP_KEYS.map((key) => escapeHtml(t(key))).join(`
|
|
14156
15172
|
`), ALBUM_DEBOUNCE_MS = 2000, UNKNOWN_SIZE = 1, REFUSAL_KEYS, megabytes2 = (bytes) => `${Math.round(bytes / (1024 * 1024))} MB`;
|
|
@@ -14187,13 +15203,13 @@ var init_src2 = __esm(() => {
|
|
|
14187
15203
|
});
|
|
14188
15204
|
|
|
14189
15205
|
// packages/daemon/src/machine-identity.ts
|
|
14190
|
-
import
|
|
15206
|
+
import os9 from "os";
|
|
14191
15207
|
function resolveMachineIdentity(override) {
|
|
14192
|
-
const hostname =
|
|
15208
|
+
const hostname = os9.hostname();
|
|
14193
15209
|
return {
|
|
14194
15210
|
name: slugifyMachineName(override && override.trim().length > 0 ? override : hostname),
|
|
14195
15211
|
hostname,
|
|
14196
|
-
addresses: collectAddresses(
|
|
15212
|
+
addresses: collectAddresses(os9.networkInterfaces())
|
|
14197
15213
|
};
|
|
14198
15214
|
}
|
|
14199
15215
|
var init_machine_identity = __esm(() => {
|
|
@@ -14205,13 +15221,15 @@ class TelegramService {
|
|
|
14205
15221
|
config;
|
|
14206
15222
|
store;
|
|
14207
15223
|
manager;
|
|
15224
|
+
apiFactory;
|
|
14208
15225
|
channel;
|
|
14209
15226
|
unsubscribe;
|
|
14210
15227
|
runningConfig;
|
|
14211
|
-
constructor(config, store, manager) {
|
|
15228
|
+
constructor(config, store, manager, apiFactory = (token) => new FetchTelegramApi(token)) {
|
|
14212
15229
|
this.config = config;
|
|
14213
15230
|
this.store = store;
|
|
14214
15231
|
this.manager = manager;
|
|
15232
|
+
this.apiFactory = apiFactory;
|
|
14215
15233
|
}
|
|
14216
15234
|
readSettings() {
|
|
14217
15235
|
const telegram = this.config.resolved().channels.telegram;
|
|
@@ -14260,7 +15278,7 @@ class TelegramService {
|
|
|
14260
15278
|
const token = botToken ?? this.readSettings().botToken;
|
|
14261
15279
|
if (!token)
|
|
14262
15280
|
throw new AxtermError("err.telegram.noToken");
|
|
14263
|
-
const api =
|
|
15281
|
+
const api = this.apiFactory(token);
|
|
14264
15282
|
const updates = await api.call("getUpdates", { timeout: 0 });
|
|
14265
15283
|
return discoverTelegramTargets(updates);
|
|
14266
15284
|
}
|
|
@@ -14275,7 +15293,7 @@ class TelegramService {
|
|
|
14275
15293
|
log("telegram: not configured, channel not started");
|
|
14276
15294
|
return false;
|
|
14277
15295
|
}
|
|
14278
|
-
const api =
|
|
15296
|
+
const api = this.apiFactory(s.botToken);
|
|
14279
15297
|
const config = {
|
|
14280
15298
|
groupId: s.groupId,
|
|
14281
15299
|
allowedUserIds: [s.allowUserId],
|
|
@@ -14383,6 +15401,9 @@ class TelegramService {
|
|
|
14383
15401
|
problems.push(t("doctor.noUser"));
|
|
14384
15402
|
if (problems.length > 0)
|
|
14385
15403
|
return { ok: false, problems };
|
|
15404
|
+
const pollError = this.channel?.pollHealth();
|
|
15405
|
+
if (pollError !== undefined)
|
|
15406
|
+
problems.push(t("doctor.telegram.pollFailing", { reason: pollError.replace(/\s+/g, " ").slice(0, 160) }));
|
|
14386
15407
|
const api = new FetchTelegramApi(s.botToken);
|
|
14387
15408
|
const report = { ok: true, problems };
|
|
14388
15409
|
try {
|
|
@@ -14423,7 +15444,7 @@ var init_telegram_service = __esm(() => {
|
|
|
14423
15444
|
// packages/daemon/src/index.ts
|
|
14424
15445
|
var exports_src = {};
|
|
14425
15446
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
14426
|
-
import
|
|
15447
|
+
import fs25 from "fs";
|
|
14427
15448
|
import net5 from "net";
|
|
14428
15449
|
function isPidAlive(pid) {
|
|
14429
15450
|
try {
|
|
@@ -14435,9 +15456,9 @@ function isPidAlive(pid) {
|
|
|
14435
15456
|
}
|
|
14436
15457
|
async function main() {
|
|
14437
15458
|
ensureDataDirs();
|
|
14438
|
-
if (
|
|
15459
|
+
if (fs25.existsSync(RUNTIME_FILE2)) {
|
|
14439
15460
|
try {
|
|
14440
|
-
const existing = JSON.parse(
|
|
15461
|
+
const existing = JSON.parse(fs25.readFileSync(RUNTIME_FILE2, "utf8"));
|
|
14441
15462
|
if (existing.pid !== process.pid && isPidAlive(existing.pid)) {
|
|
14442
15463
|
log(`daemon already running (pid ${existing.pid}); exiting`);
|
|
14443
15464
|
process.exit(0);
|
|
@@ -14460,7 +15481,7 @@ async function main() {
|
|
|
14460
15481
|
version: productVersion(),
|
|
14461
15482
|
startedAt: Date.now()
|
|
14462
15483
|
};
|
|
14463
|
-
|
|
15484
|
+
fs25.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
|
|
14464
15485
|
restrictFile(RUNTIME_FILE2);
|
|
14465
15486
|
const config = new ConfigStore(defaultConfigStorePaths(CONFIG_DIR));
|
|
14466
15487
|
if (config.loadError !== undefined)
|
|
@@ -14512,12 +15533,15 @@ async function main() {
|
|
|
14512
15533
|
hookHost.stop();
|
|
14513
15534
|
store.close();
|
|
14514
15535
|
try {
|
|
14515
|
-
const current = JSON.parse(
|
|
15536
|
+
const current = JSON.parse(fs25.readFileSync(RUNTIME_FILE2, "utf8"));
|
|
14516
15537
|
if (current.pid === process.pid)
|
|
14517
|
-
|
|
15538
|
+
fs25.rmSync(RUNTIME_FILE2);
|
|
14518
15539
|
} catch {}
|
|
14519
15540
|
process.exit(0);
|
|
14520
15541
|
};
|
|
15542
|
+
const recovered = await manager.recover();
|
|
15543
|
+
if (recovered.adopted > 0)
|
|
15544
|
+
log(`boot: ${recovered.adopted} session(s) adopted from the terminal host`);
|
|
14521
15545
|
startRpcServer({
|
|
14522
15546
|
pipePath: PIPE_PATH,
|
|
14523
15547
|
token,
|
|
@@ -14530,12 +15554,9 @@ async function main() {
|
|
|
14530
15554
|
});
|
|
14531
15555
|
process.on("SIGINT", () => stop("SIGINT"));
|
|
14532
15556
|
process.on("SIGTERM", () => stop("SIGTERM"));
|
|
14533
|
-
process.on("uncaughtException", (err) => {
|
|
14534
|
-
log(`uncaughtException: ${err.stack ?? String(err)}`);
|
|
14535
|
-
stop("uncaughtException");
|
|
14536
|
-
});
|
|
14537
15557
|
log(`daemon started pid=${process.pid} config=${CONFIG_DIR} state=${STATE_DIR}`);
|
|
14538
15558
|
}
|
|
15559
|
+
var crashGuards;
|
|
14539
15560
|
var init_src3 = __esm(() => {
|
|
14540
15561
|
init_src();
|
|
14541
15562
|
init_config_migrate();
|
|
@@ -14548,7 +15569,12 @@ var init_src3 = __esm(() => {
|
|
|
14548
15569
|
init_telegram_service();
|
|
14549
15570
|
init_store();
|
|
14550
15571
|
net5.setDefaultAutoSelectFamilyAttemptTimeout(3000);
|
|
14551
|
-
|
|
15572
|
+
crashGuards = new ProcessCrashGuards({
|
|
15573
|
+
log,
|
|
15574
|
+
exit: (code2) => process.exit(code2)
|
|
15575
|
+
});
|
|
15576
|
+
crashGuards.install(process);
|
|
15577
|
+
main().catch((reason) => crashGuards.startupFailed(reason));
|
|
14552
15578
|
});
|
|
14553
15579
|
|
|
14554
15580
|
// packages/daemon/src/pty-host.ts
|
|
@@ -14603,7 +15629,7 @@ function startPtyHost(options) {
|
|
|
14603
15629
|
send(socket, {
|
|
14604
15630
|
t: "welcome",
|
|
14605
15631
|
v: PTY_PROTOCOL_VERSION,
|
|
14606
|
-
sessions: [...sessions.entries()].filter(([, s]) => !s.exited).map(([id, s]) => ({ id, pid: s.handle.pid }))
|
|
15632
|
+
sessions: [...sessions.entries()].filter(([, s]) => !s.exited).map(([id, s]) => ({ id, pid: s.handle.pid, cols: s.cols, rows: s.rows }))
|
|
14607
15633
|
});
|
|
14608
15634
|
continue;
|
|
14609
15635
|
}
|
|
@@ -14620,9 +15646,18 @@ function startPtyHost(options) {
|
|
|
14620
15646
|
env: message.env,
|
|
14621
15647
|
term: message.term
|
|
14622
15648
|
});
|
|
14623
|
-
const session = {
|
|
15649
|
+
const session = {
|
|
15650
|
+
handle,
|
|
15651
|
+
cols: message.cols,
|
|
15652
|
+
rows: message.rows,
|
|
15653
|
+
exited: false,
|
|
15654
|
+
mirror: new TerminalMirror(message.cols, message.rows, REPLAY_SCROLLBACK_LINES)
|
|
15655
|
+
};
|
|
14624
15656
|
sessions.set(message.id, session);
|
|
14625
|
-
handle.onData((text) =>
|
|
15657
|
+
handle.onData((text) => {
|
|
15658
|
+
session.mirror.write(text);
|
|
15659
|
+
broadcast(encodeFrame(FrameKind.Data, message.id, new TextEncoder().encode(text)));
|
|
15660
|
+
});
|
|
14626
15661
|
handle.onExit((code2) => {
|
|
14627
15662
|
session.exited = true;
|
|
14628
15663
|
for (const client of clients)
|
|
@@ -14637,6 +15672,21 @@ function startPtyHost(options) {
|
|
|
14637
15672
|
}
|
|
14638
15673
|
continue;
|
|
14639
15674
|
}
|
|
15675
|
+
if (message.t === "subscribe") {
|
|
15676
|
+
const session = sessions.get(message.id);
|
|
15677
|
+
if (session === undefined) {
|
|
15678
|
+
send(socket, { t: "error", id: message.id, message: "no such session" });
|
|
15679
|
+
continue;
|
|
15680
|
+
}
|
|
15681
|
+
if (message.replay) {
|
|
15682
|
+
const ansi = session.mirror.serialize(REPLAY_MAX_CHARS);
|
|
15683
|
+
if (ansi.length > 0 && !socket.destroyed) {
|
|
15684
|
+
socket.write(encodeFrame(FrameKind.Data, message.id, new TextEncoder().encode(ansi)));
|
|
15685
|
+
}
|
|
15686
|
+
}
|
|
15687
|
+
send(socket, { t: "subscribed", id: message.id });
|
|
15688
|
+
continue;
|
|
15689
|
+
}
|
|
14640
15690
|
if (message.t === "resize") {
|
|
14641
15691
|
const session = sessions.get(message.id);
|
|
14642
15692
|
if (session === undefined || session.exited)
|
|
@@ -14644,6 +15694,7 @@ function startPtyHost(options) {
|
|
|
14644
15694
|
session.cols = message.cols;
|
|
14645
15695
|
session.rows = message.rows;
|
|
14646
15696
|
session.handle.resize(message.cols, message.rows);
|
|
15697
|
+
session.mirror.resize(message.cols, message.rows);
|
|
14647
15698
|
continue;
|
|
14648
15699
|
}
|
|
14649
15700
|
if (message.t === "kill") {
|
|
@@ -14674,10 +15725,13 @@ function startPtyHost(options) {
|
|
|
14674
15725
|
});
|
|
14675
15726
|
});
|
|
14676
15727
|
}
|
|
15728
|
+
var REPLAY_MAX_CHARS, REPLAY_SCROLLBACK_LINES = 5000;
|
|
14677
15729
|
var init_pty_host = __esm(() => {
|
|
14678
15730
|
init_src();
|
|
14679
15731
|
init_paths();
|
|
14680
15732
|
init_pty();
|
|
15733
|
+
init_terminal_mirror();
|
|
15734
|
+
REPLAY_MAX_CHARS = 2 * 1024 * 1024;
|
|
14681
15735
|
});
|
|
14682
15736
|
|
|
14683
15737
|
// packages/daemon/src/pty-host-main.ts
|
|
@@ -14730,8 +15784,8 @@ var init_pty_host_main = __esm(() => {
|
|
|
14730
15784
|
|
|
14731
15785
|
// packages/cli/src/index.ts
|
|
14732
15786
|
init_src();
|
|
14733
|
-
import
|
|
14734
|
-
import
|
|
15787
|
+
import fs26 from "fs";
|
|
15788
|
+
import path25 from "path";
|
|
14735
15789
|
|
|
14736
15790
|
// packages/cli/src/attach.ts
|
|
14737
15791
|
import fs from "fs";
|
|
@@ -15150,11 +16204,11 @@ ${bold(t(state.promptKey))} ${dim("[yes]")} `);
|
|
|
15150
16204
|
const finished = advance({ ...state, kind: "review", step: "review" }, "");
|
|
15151
16205
|
if (!("done" in finished))
|
|
15152
16206
|
return { saved: false };
|
|
15153
|
-
for (const [
|
|
15154
|
-
await client.call("config.set", { path:
|
|
16207
|
+
for (const [path4, value] of finished.done.settings) {
|
|
16208
|
+
await client.call("config.set", { path: path4, value });
|
|
15155
16209
|
}
|
|
15156
|
-
for (const [
|
|
15157
|
-
await client.call("config.set", { path:
|
|
16210
|
+
for (const [path4, value] of finished.done.secrets) {
|
|
16211
|
+
await client.call("config.set", { path: path4, value });
|
|
15158
16212
|
}
|
|
15159
16213
|
stdout.write(`
|
|
15160
16214
|
${green("\u2713")} ${t("wizard.saved")}
|
|
@@ -15192,10 +16246,202 @@ function describeHealth(health) {
|
|
|
15192
16246
|
// packages/cli/src/client.ts
|
|
15193
16247
|
init_src();
|
|
15194
16248
|
import { spawn } from "child_process";
|
|
15195
|
-
import
|
|
16249
|
+
import fs3 from "fs";
|
|
15196
16250
|
import net from "net";
|
|
15197
|
-
import
|
|
16251
|
+
import path5 from "path";
|
|
15198
16252
|
import { setTimeout as delay } from "timers/promises";
|
|
16253
|
+
|
|
16254
|
+
// packages/cli/src/service-manager.ts
|
|
16255
|
+
init_src();
|
|
16256
|
+
import { spawnSync } from "child_process";
|
|
16257
|
+
import fs2 from "fs";
|
|
16258
|
+
import os3 from "os";
|
|
16259
|
+
import path4 from "path";
|
|
16260
|
+
|
|
16261
|
+
class ServiceCommandError extends Error {
|
|
16262
|
+
invocation;
|
|
16263
|
+
result;
|
|
16264
|
+
constructor(invocation, result) {
|
|
16265
|
+
super(result.stderr.trim() || result.stdout.trim() || `${invocation.command} exited ${result.status}`);
|
|
16266
|
+
this.invocation = invocation;
|
|
16267
|
+
this.result = result;
|
|
16268
|
+
this.name = "ServiceCommandError";
|
|
16269
|
+
}
|
|
16270
|
+
}
|
|
16271
|
+
var defaultRun = ({ command, args }) => {
|
|
16272
|
+
const result = spawnSync(command, args, { encoding: "utf8", windowsHide: true });
|
|
16273
|
+
return {
|
|
16274
|
+
status: result.status ?? 1,
|
|
16275
|
+
stdout: result.stdout ?? "",
|
|
16276
|
+
stderr: result.stderr ?? result.error?.message ?? ""
|
|
16277
|
+
};
|
|
16278
|
+
};
|
|
16279
|
+
function writeAtomic(file, contents) {
|
|
16280
|
+
fs2.mkdirSync(path4.dirname(file), { recursive: true, mode: 448 });
|
|
16281
|
+
const tmp = `${file}.tmp`;
|
|
16282
|
+
try {
|
|
16283
|
+
fs2.writeFileSync(tmp, contents, { mode: 384 });
|
|
16284
|
+
fs2.renameSync(tmp, file);
|
|
16285
|
+
if (process.platform !== "win32")
|
|
16286
|
+
fs2.chmodSync(file, 384);
|
|
16287
|
+
} catch (err) {
|
|
16288
|
+
try {
|
|
16289
|
+
fs2.rmSync(tmp, { force: true });
|
|
16290
|
+
} catch {}
|
|
16291
|
+
throw err;
|
|
16292
|
+
}
|
|
16293
|
+
}
|
|
16294
|
+
function utf16Xml(contents) {
|
|
16295
|
+
return Buffer.concat([Buffer.from([255, 254]), Buffer.from(contents, "utf16le")]);
|
|
16296
|
+
}
|
|
16297
|
+
function utf8Script(contents) {
|
|
16298
|
+
return Buffer.concat([Buffer.from([239, 187, 191]), Buffer.from(contents, "utf8")]);
|
|
16299
|
+
}
|
|
16300
|
+
|
|
16301
|
+
class ServiceManager {
|
|
16302
|
+
options;
|
|
16303
|
+
definition;
|
|
16304
|
+
run;
|
|
16305
|
+
uid;
|
|
16306
|
+
constructor(options) {
|
|
16307
|
+
this.options = options;
|
|
16308
|
+
this.run = options.run ?? defaultRun;
|
|
16309
|
+
this.uid = options.uid ?? process.getuid?.();
|
|
16310
|
+
this.definition = serviceDefinition({
|
|
16311
|
+
platform: options.platform,
|
|
16312
|
+
launch: options.launch,
|
|
16313
|
+
homeDir: options.homeDir,
|
|
16314
|
+
stateDir: options.stateDir,
|
|
16315
|
+
...options.userId ? { userId: options.userId } : {},
|
|
16316
|
+
...options.environment ? { environment: options.environment } : {}
|
|
16317
|
+
});
|
|
16318
|
+
}
|
|
16319
|
+
install() {
|
|
16320
|
+
fs2.mkdirSync(path4.join(this.options.stateDir, "logs"), { recursive: true, mode: 448 });
|
|
16321
|
+
for (const companion of this.definition.companionFiles ?? []) {
|
|
16322
|
+
writeAtomic(companion.file, this.definition.kind === "task-scheduler" ? utf8Script(companion.contents) : companion.contents);
|
|
16323
|
+
}
|
|
16324
|
+
writeAtomic(this.definition.file, this.definition.kind === "task-scheduler" ? utf16Xml(this.definition.contents) : this.definition.contents);
|
|
16325
|
+
if (this.definition.kind === "systemd") {
|
|
16326
|
+
this.must({ command: "systemctl", args: ["--user", "daemon-reload"] });
|
|
16327
|
+
this.must({ command: "systemctl", args: ["--user", "enable", SYSTEMD_SERVICE] });
|
|
16328
|
+
} else if (this.definition.kind === "launchd") {
|
|
16329
|
+
this.must({ command: "launchctl", args: ["enable", this.launchdTarget()] });
|
|
16330
|
+
} else {
|
|
16331
|
+
this.must({ command: "schtasks.exe", args: ["/Create", "/TN", WINDOWS_TASK, "/XML", this.definition.file, "/F"] });
|
|
16332
|
+
}
|
|
16333
|
+
}
|
|
16334
|
+
start() {
|
|
16335
|
+
if (this.definition.kind === "systemd") {
|
|
16336
|
+
this.must({ command: "systemctl", args: ["--user", "start", SYSTEMD_SERVICE] });
|
|
16337
|
+
} else if (this.definition.kind === "launchd") {
|
|
16338
|
+
const target = this.launchdTarget();
|
|
16339
|
+
const loaded = this.run({ command: "launchctl", args: ["print", target] }).status === 0;
|
|
16340
|
+
this.must(loaded ? { command: "launchctl", args: ["kickstart", target] } : { command: "launchctl", args: ["bootstrap", `gui/${this.requireUid()}`, this.definition.file] });
|
|
16341
|
+
} else {
|
|
16342
|
+
this.must({ command: "schtasks.exe", args: ["/Run", "/TN", WINDOWS_TASK] });
|
|
16343
|
+
}
|
|
16344
|
+
}
|
|
16345
|
+
status() {
|
|
16346
|
+
if (this.definition.kind === "systemd") {
|
|
16347
|
+
return {
|
|
16348
|
+
kind: this.definition.kind,
|
|
16349
|
+
installed: fs2.existsSync(this.definition.file),
|
|
16350
|
+
active: this.run({ command: "systemctl", args: ["--user", "is-active", "--quiet", SYSTEMD_SERVICE] }).status === 0
|
|
16351
|
+
};
|
|
16352
|
+
}
|
|
16353
|
+
if (this.definition.kind === "launchd") {
|
|
16354
|
+
return {
|
|
16355
|
+
kind: this.definition.kind,
|
|
16356
|
+
installed: fs2.existsSync(this.definition.file),
|
|
16357
|
+
active: this.run({ command: "launchctl", args: ["print", this.launchdTarget()] }).status === 0
|
|
16358
|
+
};
|
|
16359
|
+
}
|
|
16360
|
+
const installed = this.run({ command: "schtasks.exe", args: ["/Query", "/TN", WINDOWS_TASK] }).status === 0;
|
|
16361
|
+
const active = installed && this.run({
|
|
16362
|
+
command: "powershell.exe",
|
|
16363
|
+
args: [
|
|
16364
|
+
"-NoProfile",
|
|
16365
|
+
"-NonInteractive",
|
|
16366
|
+
"-Command",
|
|
16367
|
+
`if ((Get-ScheduledTask -TaskName '${WINDOWS_TASK}').State -eq 'Running') { exit 0 } else { exit 1 }`
|
|
16368
|
+
]
|
|
16369
|
+
}).status === 0;
|
|
16370
|
+
return { kind: this.definition.kind, installed, active };
|
|
16371
|
+
}
|
|
16372
|
+
uninstall() {
|
|
16373
|
+
if (this.definition.kind === "systemd") {
|
|
16374
|
+
this.run({ command: "systemctl", args: ["--user", "disable", SYSTEMD_SERVICE] });
|
|
16375
|
+
fs2.rmSync(this.definition.file, { force: true });
|
|
16376
|
+
this.must({ command: "systemctl", args: ["--user", "daemon-reload"] });
|
|
16377
|
+
} else if (this.definition.kind === "launchd") {
|
|
16378
|
+
this.run({ command: "launchctl", args: ["disable", this.launchdTarget()] });
|
|
16379
|
+
fs2.rmSync(this.definition.file, { force: true });
|
|
16380
|
+
} else {
|
|
16381
|
+
this.run({ command: "schtasks.exe", args: ["/Delete", "/TN", WINDOWS_TASK, "/F"] });
|
|
16382
|
+
fs2.rmSync(this.definition.file, { force: true });
|
|
16383
|
+
}
|
|
16384
|
+
for (const companion of this.definition.companionFiles ?? [])
|
|
16385
|
+
fs2.rmSync(companion.file, { force: true });
|
|
16386
|
+
}
|
|
16387
|
+
must(command) {
|
|
16388
|
+
const result = this.run(command);
|
|
16389
|
+
if (result.status !== 0)
|
|
16390
|
+
throw new ServiceCommandError(command, result);
|
|
16391
|
+
}
|
|
16392
|
+
requireUid() {
|
|
16393
|
+
if (this.uid === undefined)
|
|
16394
|
+
throw new Error("launchd requires the current uid");
|
|
16395
|
+
return this.uid;
|
|
16396
|
+
}
|
|
16397
|
+
launchdTarget() {
|
|
16398
|
+
return `gui/${this.requireUid()}/${LAUNCHD_LABEL}`;
|
|
16399
|
+
}
|
|
16400
|
+
}
|
|
16401
|
+
function windowsUserId(run = defaultRun) {
|
|
16402
|
+
const command = {
|
|
16403
|
+
command: "powershell.exe",
|
|
16404
|
+
args: [
|
|
16405
|
+
"-NoProfile",
|
|
16406
|
+
"-NonInteractive",
|
|
16407
|
+
"-Command",
|
|
16408
|
+
"[System.Security.Principal.WindowsIdentity]::GetCurrent().Name"
|
|
16409
|
+
]
|
|
16410
|
+
};
|
|
16411
|
+
const result = run(command);
|
|
16412
|
+
const value = result.stdout.trim();
|
|
16413
|
+
if (result.status !== 0 || value === "")
|
|
16414
|
+
throw new ServiceCommandError(command, result);
|
|
16415
|
+
return value;
|
|
16416
|
+
}
|
|
16417
|
+
function startInstalledService(manager) {
|
|
16418
|
+
if (!manager.status().installed)
|
|
16419
|
+
return false;
|
|
16420
|
+
manager.start();
|
|
16421
|
+
return true;
|
|
16422
|
+
}
|
|
16423
|
+
function currentServiceManager(run = defaultRun) {
|
|
16424
|
+
const paths = resolvePaths(currentPathEnv());
|
|
16425
|
+
const environment = {};
|
|
16426
|
+
if (process.env["AXTERM_CONFIG_DIR"] === paths.configDir)
|
|
16427
|
+
environment["AXTERM_CONFIG_DIR"] = paths.configDir;
|
|
16428
|
+
if (process.env["AXTERM_STATE_DIR"] === paths.stateDir)
|
|
16429
|
+
environment["AXTERM_STATE_DIR"] = paths.stateDir;
|
|
16430
|
+
if (process.env["AXTERM_RUNTIME_DIR"] === paths.runtimeDir)
|
|
16431
|
+
environment["AXTERM_RUNTIME_DIR"] = paths.runtimeDir;
|
|
16432
|
+
return new ServiceManager({
|
|
16433
|
+
platform: process.platform,
|
|
16434
|
+
homeDir: os3.homedir(),
|
|
16435
|
+
stateDir: paths.stateDir,
|
|
16436
|
+
launch: daemonLaunch({ execPath: process.execPath, mainScript: Bun.main }),
|
|
16437
|
+
...process.getuid ? { uid: process.getuid() } : {},
|
|
16438
|
+
...process.platform === "win32" ? { userId: windowsUserId(run) } : {},
|
|
16439
|
+
...Object.keys(environment).length > 0 ? { environment } : {},
|
|
16440
|
+
run
|
|
16441
|
+
});
|
|
16442
|
+
}
|
|
16443
|
+
|
|
16444
|
+
// packages/cli/src/client.ts
|
|
15199
16445
|
var RUNTIME_FILE = resolvePaths(currentPathEnv()).runtimeFile;
|
|
15200
16446
|
|
|
15201
16447
|
class RpcClient {
|
|
@@ -15281,7 +16527,7 @@ class RpcClient {
|
|
|
15281
16527
|
}
|
|
15282
16528
|
function readRuntime() {
|
|
15283
16529
|
try {
|
|
15284
|
-
const runtime = JSON.parse(
|
|
16530
|
+
const runtime = JSON.parse(fs3.readFileSync(RUNTIME_FILE, "utf8"));
|
|
15285
16531
|
process.kill(runtime.pid, 0);
|
|
15286
16532
|
return runtime;
|
|
15287
16533
|
} catch {
|
|
@@ -15300,11 +16546,14 @@ async function tryConnect() {
|
|
|
15300
16546
|
return;
|
|
15301
16547
|
}
|
|
15302
16548
|
}
|
|
16549
|
+
function connectDaemonIfRunning() {
|
|
16550
|
+
return tryConnect();
|
|
16551
|
+
}
|
|
15303
16552
|
function daemonEnv(cwd, env) {
|
|
15304
16553
|
const files = [];
|
|
15305
16554
|
for (const name of dotenvCandidates(env["NODE_ENV"])) {
|
|
15306
16555
|
try {
|
|
15307
|
-
files.push(parseDotenv(
|
|
16556
|
+
files.push(parseDotenv(fs3.readFileSync(path5.join(cwd, name), "utf8")));
|
|
15308
16557
|
} catch {}
|
|
15309
16558
|
}
|
|
15310
16559
|
return scrubDotenvKeys(env, files);
|
|
@@ -15313,14 +16562,26 @@ async function ensureDaemon() {
|
|
|
15313
16562
|
const existing = await tryConnect();
|
|
15314
16563
|
if (existing)
|
|
15315
16564
|
return existing;
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
|
|
15322
|
-
|
|
15323
|
-
|
|
16565
|
+
let supervised = false;
|
|
16566
|
+
try {
|
|
16567
|
+
supervised = startInstalledService(currentServiceManager());
|
|
16568
|
+
} catch (err) {
|
|
16569
|
+
if (err instanceof ServiceCommandError) {
|
|
16570
|
+
const command = [err.invocation.command, ...err.invocation.args].join(" ");
|
|
16571
|
+
throw new Error(t("cli.service.commandFailed", { command, error: err.message }));
|
|
16572
|
+
}
|
|
16573
|
+
throw err;
|
|
16574
|
+
}
|
|
16575
|
+
if (!supervised) {
|
|
16576
|
+
const launch = daemonLaunch({ execPath: process.execPath, mainScript: Bun.main });
|
|
16577
|
+
const child = spawn(launch.command, launch.args, {
|
|
16578
|
+
detached: true,
|
|
16579
|
+
stdio: "ignore",
|
|
16580
|
+
windowsHide: true,
|
|
16581
|
+
env: daemonEnv(process.cwd(), process.env)
|
|
16582
|
+
});
|
|
16583
|
+
child.unref();
|
|
16584
|
+
}
|
|
15324
16585
|
const deadline = Date.now() + 15000;
|
|
15325
16586
|
while (Date.now() < deadline) {
|
|
15326
16587
|
await delay(300);
|
|
@@ -15328,11 +16589,12 @@ async function ensureDaemon() {
|
|
|
15328
16589
|
if (client)
|
|
15329
16590
|
return client;
|
|
15330
16591
|
}
|
|
15331
|
-
throw new Error(t("cli.daemon.notReady", { log:
|
|
16592
|
+
throw new Error(t("cli.daemon.notReady", { log: path5.join(resolvePaths(currentPathEnv()).logDir, "daemon.log") }));
|
|
15332
16593
|
}
|
|
15333
16594
|
|
|
15334
16595
|
// packages/cli/src/hook-bridge.ts
|
|
15335
16596
|
init_src();
|
|
16597
|
+
import fs4 from "fs";
|
|
15336
16598
|
var POST_TIMEOUT_MS = 1500;
|
|
15337
16599
|
function isCodexApproval(agent, body) {
|
|
15338
16600
|
if (agent !== "codex")
|
|
@@ -15357,24 +16619,44 @@ async function relayHookEvent(agent) {
|
|
|
15357
16619
|
if (body === "")
|
|
15358
16620
|
return;
|
|
15359
16621
|
const approval = isCodexApproval(agent, body);
|
|
15360
|
-
const
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
}
|
|
15366
|
-
if (
|
|
16622
|
+
const timeout = approval ? CODEX_APPROVAL_POST_TIMEOUT_MS : POST_TIMEOUT_MS;
|
|
16623
|
+
const post = (to) => fetch(to.url, { method: "POST", headers: to.headers, body, signal: AbortSignal.timeout(timeout) });
|
|
16624
|
+
let res;
|
|
16625
|
+
try {
|
|
16626
|
+
res = await post(target);
|
|
16627
|
+
} catch {}
|
|
16628
|
+
if (res === undefined || res.status === 401 || res.status === 404) {
|
|
16629
|
+
if (approval && res === undefined)
|
|
16630
|
+
return;
|
|
16631
|
+
const fresh = currentEndpoint();
|
|
16632
|
+
const retarget = fresh === undefined ? undefined : hookPostTarget(process.env, agent, fresh);
|
|
16633
|
+
if (retarget !== undefined && retarget.url !== target.url)
|
|
16634
|
+
res = await post(retarget);
|
|
16635
|
+
else if (res === undefined)
|
|
16636
|
+
return;
|
|
16637
|
+
}
|
|
16638
|
+
if (approval && res !== undefined && res.ok) {
|
|
15367
16639
|
const decision = await res.text();
|
|
15368
16640
|
if (decision !== "")
|
|
15369
16641
|
process.stdout.write(decision);
|
|
15370
16642
|
}
|
|
15371
16643
|
} catch {}
|
|
15372
16644
|
}
|
|
16645
|
+
function currentEndpoint() {
|
|
16646
|
+
const file = process.env["AXTERM_HOOK_ENDPOINT"];
|
|
16647
|
+
if (file === undefined)
|
|
16648
|
+
return;
|
|
16649
|
+
try {
|
|
16650
|
+
return parseHookEndpoint(fs4.readFileSync(file, "utf8"));
|
|
16651
|
+
} catch {
|
|
16652
|
+
return;
|
|
16653
|
+
}
|
|
16654
|
+
}
|
|
15373
16655
|
|
|
15374
16656
|
// packages/cli/src/mcp-server.ts
|
|
15375
16657
|
init_src();
|
|
15376
|
-
import
|
|
15377
|
-
import
|
|
16658
|
+
import fs5 from "fs";
|
|
16659
|
+
import path6 from "path";
|
|
15378
16660
|
var ASK_TIMEOUT_MS = 10 * 60000;
|
|
15379
16661
|
var POST_TIMEOUT_MS2 = 5000;
|
|
15380
16662
|
var TOOLS = [
|
|
@@ -15490,7 +16772,7 @@ class McpServer {
|
|
|
15490
16772
|
const contextFile = this.io.env[MCP_CONTEXT_ENV];
|
|
15491
16773
|
if (contextFile !== undefined) {
|
|
15492
16774
|
try {
|
|
15493
|
-
const context = JSON.parse(
|
|
16775
|
+
const context = JSON.parse(fs5.readFileSync(contextFile, "utf8"));
|
|
15494
16776
|
if (context.port && context.token && context.sessionId) {
|
|
15495
16777
|
return {
|
|
15496
16778
|
url: `http://127.0.0.1:${context.port}`,
|
|
@@ -15535,7 +16817,7 @@ class McpServer {
|
|
|
15535
16817
|
const target = this.target();
|
|
15536
16818
|
if (!target)
|
|
15537
16819
|
return { text: "axterm is not reachable from this session", isError: true };
|
|
15538
|
-
const resolved =
|
|
16820
|
+
const resolved = path6.isAbsolute(file) ? file : path6.resolve(this.io.cwd, file);
|
|
15539
16821
|
const caption = typeof args["caption"] === "string" ? args["caption"] : undefined;
|
|
15540
16822
|
const res = await this.post("/hook/attach", { sessionId: target.sessionId, launchToken: target.launchToken, path: resolved, caption }, POST_TIMEOUT_MS2);
|
|
15541
16823
|
if (!res)
|
|
@@ -15645,15 +16927,15 @@ function runMcpServer() {
|
|
|
15645
16927
|
|
|
15646
16928
|
// packages/cli/src/update.ts
|
|
15647
16929
|
init_src();
|
|
15648
|
-
import { spawnSync } from "child_process";
|
|
16930
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
15649
16931
|
import { createHash as createHash2 } from "crypto";
|
|
15650
|
-
import
|
|
15651
|
-
import
|
|
16932
|
+
import fs6 from "fs";
|
|
16933
|
+
import path7 from "path";
|
|
15652
16934
|
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
15653
16935
|
// packages/cli/package.json
|
|
15654
16936
|
var package_default = {
|
|
15655
16937
|
name: "@layorixinc/axterm",
|
|
15656
|
-
version: "0.
|
|
16938
|
+
version: "0.6.1",
|
|
15657
16939
|
type: "module",
|
|
15658
16940
|
bin: {
|
|
15659
16941
|
axterm: "dist/cli.js"
|
|
@@ -15727,13 +17009,13 @@ function assetName(platform, arch, isMusl) {
|
|
|
15727
17009
|
function usesMusl() {
|
|
15728
17010
|
if (process.platform !== "linux")
|
|
15729
17011
|
return false;
|
|
15730
|
-
if (
|
|
17012
|
+
if (fs6.existsSync("/etc/alpine-release"))
|
|
15731
17013
|
return true;
|
|
15732
|
-
const ldd =
|
|
17014
|
+
const ldd = spawnSync2("ldd", ["--version"], { encoding: "utf8" });
|
|
15733
17015
|
return `${ldd.stdout ?? ""}${ldd.stderr ?? ""}`.toLowerCase().includes("musl");
|
|
15734
17016
|
}
|
|
15735
17017
|
function firstLine(command, args) {
|
|
15736
|
-
const result =
|
|
17018
|
+
const result = spawnSync2(command, args, { encoding: "utf8", windowsHide: true });
|
|
15737
17019
|
if (result.status !== 0)
|
|
15738
17020
|
return;
|
|
15739
17021
|
return result.stdout?.trim().split(/\r?\n/)[0]?.trim() || undefined;
|
|
@@ -15741,7 +17023,7 @@ function firstLine(command, args) {
|
|
|
15741
17023
|
function detectOrigin(execPath) {
|
|
15742
17024
|
const resolved = (() => {
|
|
15743
17025
|
try {
|
|
15744
|
-
return
|
|
17026
|
+
return fs6.realpathSync(execPath);
|
|
15745
17027
|
} catch {
|
|
15746
17028
|
return execPath;
|
|
15747
17029
|
}
|
|
@@ -15766,7 +17048,7 @@ function npmGlobalBin() {
|
|
|
15766
17048
|
const prefix = firstLine("npm", ["prefix", "-g"]);
|
|
15767
17049
|
if (prefix === undefined)
|
|
15768
17050
|
return;
|
|
15769
|
-
return process.platform === "win32" ? prefix :
|
|
17051
|
+
return process.platform === "win32" ? prefix : path7.join(prefix, "bin");
|
|
15770
17052
|
}
|
|
15771
17053
|
async function download(url, into) {
|
|
15772
17054
|
const response = await fetch(url, { signal: AbortSignal.timeout(600000) });
|
|
@@ -15783,7 +17065,7 @@ function checksumFor(sums, asset) {
|
|
|
15783
17065
|
return;
|
|
15784
17066
|
}
|
|
15785
17067
|
function sha256(file) {
|
|
15786
|
-
return createHash2("sha256").update(
|
|
17068
|
+
return createHash2("sha256").update(fs6.readFileSync(file)).digest("hex");
|
|
15787
17069
|
}
|
|
15788
17070
|
function ask2(question) {
|
|
15789
17071
|
if (stdin2.isTTY !== true)
|
|
@@ -15830,8 +17112,8 @@ async function runUpdate(client, current, options) {
|
|
|
15830
17112
|
}
|
|
15831
17113
|
const asset = assetName(process.platform, process.arch, usesMusl());
|
|
15832
17114
|
const base = `${RELEASES}/v${latest.version}`;
|
|
15833
|
-
const staging =
|
|
15834
|
-
const downloaded =
|
|
17115
|
+
const staging = fs6.mkdtempSync(`${process.execPath}.update-`);
|
|
17116
|
+
const downloaded = path7.join(staging, asset);
|
|
15835
17117
|
try {
|
|
15836
17118
|
console.log(t("cli.update.downloading", { asset }));
|
|
15837
17119
|
await download(`${base}/${asset}`, downloaded);
|
|
@@ -15844,8 +17126,8 @@ async function runUpdate(client, current, options) {
|
|
|
15844
17126
|
return;
|
|
15845
17127
|
}
|
|
15846
17128
|
if (process.platform !== "win32")
|
|
15847
|
-
|
|
15848
|
-
const reported =
|
|
17129
|
+
fs6.chmodSync(downloaded, 493);
|
|
17130
|
+
const reported = spawnSync2(downloaded, ["--version"], { encoding: "utf8" }).stdout?.trim();
|
|
15849
17131
|
if (reported !== latest.version) {
|
|
15850
17132
|
console.error(t("cli.update.wrongVersion", { got: reported ?? "nothing", want: latest.version }));
|
|
15851
17133
|
process.exitCode = 1;
|
|
@@ -15877,14 +17159,14 @@ async function runUpdate(client, current, options) {
|
|
|
15877
17159
|
if (plan.stale > 0)
|
|
15878
17160
|
console.log(t("cli.update.staleDaemon", { count: plan.stale }));
|
|
15879
17161
|
for (const id of plan.resume) {
|
|
15880
|
-
const result =
|
|
17162
|
+
const result = spawnSync2(process.execPath, ["resume", id, "--no-attach", "--no-setup"], { encoding: "utf8" });
|
|
15881
17163
|
if (result.status === 0)
|
|
15882
17164
|
console.log(t("cli.update.restored", { id }));
|
|
15883
17165
|
else
|
|
15884
17166
|
console.log(t("cli.update.restoreFailed", { id, reason: (result.stderr ?? "").trim() || "unknown" }));
|
|
15885
17167
|
}
|
|
15886
17168
|
} finally {
|
|
15887
|
-
|
|
17169
|
+
fs6.rmSync(staging, { recursive: true, force: true });
|
|
15888
17170
|
}
|
|
15889
17171
|
}
|
|
15890
17172
|
function applySwap(target, downloaded) {
|
|
@@ -15893,20 +17175,88 @@ function applySwap(target, downloaded) {
|
|
|
15893
17175
|
try {
|
|
15894
17176
|
for (const step of steps) {
|
|
15895
17177
|
if (step.op === "chmod")
|
|
15896
|
-
|
|
17178
|
+
fs6.chmodSync(step.from, 493);
|
|
15897
17179
|
else if (step.op === "backup") {
|
|
15898
|
-
|
|
17180
|
+
fs6.renameSync(step.from, step.to);
|
|
15899
17181
|
backup = step.to;
|
|
15900
17182
|
} else
|
|
15901
|
-
|
|
17183
|
+
fs6.renameSync(step.from, step.to);
|
|
15902
17184
|
}
|
|
15903
17185
|
} catch (err) {
|
|
15904
|
-
if (backup !== undefined && !
|
|
15905
|
-
|
|
17186
|
+
if (backup !== undefined && !fs6.existsSync(target))
|
|
17187
|
+
fs6.renameSync(backup, target);
|
|
15906
17188
|
throw err;
|
|
15907
17189
|
}
|
|
15908
17190
|
}
|
|
15909
17191
|
|
|
17192
|
+
// packages/cli/src/service-command.ts
|
|
17193
|
+
init_src();
|
|
17194
|
+
import { setTimeout as delay2 } from "timers/promises";
|
|
17195
|
+
var SERVICE_WAIT_MS = 15000;
|
|
17196
|
+
var SERVICE_POLL_MS = 200;
|
|
17197
|
+
async function waitForDaemon(deps, running) {
|
|
17198
|
+
const sleep = deps.sleep ?? ((ms) => delay2(ms));
|
|
17199
|
+
const now = deps.now ?? Date.now;
|
|
17200
|
+
const deadline = now() + SERVICE_WAIT_MS;
|
|
17201
|
+
for (;; ) {
|
|
17202
|
+
const connected = await deps.connect();
|
|
17203
|
+
if (running && connected !== undefined)
|
|
17204
|
+
return connected;
|
|
17205
|
+
connected?.close();
|
|
17206
|
+
if (!running && connected === undefined)
|
|
17207
|
+
return;
|
|
17208
|
+
if (now() >= deadline)
|
|
17209
|
+
throw new Error(t("cli.service.notReady"));
|
|
17210
|
+
await sleep(SERVICE_POLL_MS);
|
|
17211
|
+
}
|
|
17212
|
+
}
|
|
17213
|
+
function localizedStatus(status) {
|
|
17214
|
+
return t("cli.service.status", {
|
|
17215
|
+
kind: status.kind,
|
|
17216
|
+
installed: t(status.installed ? "cli.service.yes" : "cli.service.no"),
|
|
17217
|
+
active: t(status.active ? "cli.service.yes" : "cli.service.no")
|
|
17218
|
+
});
|
|
17219
|
+
}
|
|
17220
|
+
function localizeCommandError(err) {
|
|
17221
|
+
if (err instanceof ServiceCommandError) {
|
|
17222
|
+
const command = [err.invocation.command, ...err.invocation.args].join(" ");
|
|
17223
|
+
throw new Error(t("cli.service.commandFailed", { command, error: err.message }));
|
|
17224
|
+
}
|
|
17225
|
+
throw err;
|
|
17226
|
+
}
|
|
17227
|
+
async function runServiceCommand(subcommand, deps) {
|
|
17228
|
+
const write = deps.write ?? console.log;
|
|
17229
|
+
try {
|
|
17230
|
+
if (subcommand === "status") {
|
|
17231
|
+
write(localizedStatus(deps.manager.status()));
|
|
17232
|
+
return;
|
|
17233
|
+
}
|
|
17234
|
+
if (subcommand === "uninstall") {
|
|
17235
|
+
deps.manager.uninstall();
|
|
17236
|
+
write(t("cli.service.uninstalled"));
|
|
17237
|
+
return;
|
|
17238
|
+
}
|
|
17239
|
+
if (subcommand !== "install")
|
|
17240
|
+
throw new Error(t("cli.usage.service"));
|
|
17241
|
+
deps.manager.install();
|
|
17242
|
+
const existing = await deps.connect();
|
|
17243
|
+
if (existing !== undefined) {
|
|
17244
|
+
try {
|
|
17245
|
+
await existing.call("daemon.stop");
|
|
17246
|
+
} finally {
|
|
17247
|
+
existing.close();
|
|
17248
|
+
}
|
|
17249
|
+
await waitForDaemon(deps, false);
|
|
17250
|
+
}
|
|
17251
|
+
deps.manager.start();
|
|
17252
|
+
const ready = await waitForDaemon(deps, true);
|
|
17253
|
+
ready?.close();
|
|
17254
|
+
write(t("cli.service.installed", { kind: deps.manager.definition.kind }));
|
|
17255
|
+
} catch (err) {
|
|
17256
|
+
localizeCommandError(err);
|
|
17257
|
+
}
|
|
17258
|
+
}
|
|
17259
|
+
|
|
15910
17260
|
// packages/cli/src/index.ts
|
|
15911
17261
|
var VERSION = package_default.version;
|
|
15912
17262
|
var VALUE_FLAGS = new Set([
|
|
@@ -15975,7 +17325,7 @@ function printSessionTable(sessions) {
|
|
|
15975
17325
|
console.log(row.map((cell, col) => cell.padEnd(widths[col])).join(" "));
|
|
15976
17326
|
}
|
|
15977
17327
|
}
|
|
15978
|
-
var realPath = (target) =>
|
|
17328
|
+
var realPath = (target) => fs26.realpathSync.native(target);
|
|
15979
17329
|
function scopedHere(sessions) {
|
|
15980
17330
|
return sessionsInScope(sessions, process.cwd(), realPath);
|
|
15981
17331
|
}
|
|
@@ -15988,7 +17338,7 @@ var help = () => t("cli.help");
|
|
|
15988
17338
|
function readDotEnv(file = ".env") {
|
|
15989
17339
|
const out = {};
|
|
15990
17340
|
try {
|
|
15991
|
-
for (const line of
|
|
17341
|
+
for (const line of fs26.readFileSync(file, "utf8").split(/\r?\n/)) {
|
|
15992
17342
|
const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
15993
17343
|
if (m)
|
|
15994
17344
|
out[m[1]] = m[2].trim();
|
|
@@ -15999,7 +17349,7 @@ function readDotEnv(file = ".env") {
|
|
|
15999
17349
|
function configuredLocale() {
|
|
16000
17350
|
try {
|
|
16001
17351
|
const file = resolvePaths(currentPathEnv()).configFile;
|
|
16002
|
-
const raw = JSON.parse(
|
|
17352
|
+
const raw = JSON.parse(fs26.readFileSync(file, "utf8"));
|
|
16003
17353
|
return typeof raw.locale === "string" ? raw.locale : undefined;
|
|
16004
17354
|
} catch {
|
|
16005
17355
|
return;
|
|
@@ -16036,6 +17386,13 @@ async function main2() {
|
|
|
16036
17386
|
console.log(help());
|
|
16037
17387
|
return;
|
|
16038
17388
|
}
|
|
17389
|
+
if (command === "service") {
|
|
17390
|
+
await runServiceCommand(positional[0] ?? "status", {
|
|
17391
|
+
manager: currentServiceManager(),
|
|
17392
|
+
connect: connectDaemonIfRunning
|
|
17393
|
+
});
|
|
17394
|
+
return;
|
|
17395
|
+
}
|
|
16039
17396
|
const client = await ensureDaemon();
|
|
16040
17397
|
const health = await client.call("config.health").catch(() => {
|
|
16041
17398
|
return;
|
|
@@ -16055,6 +17412,7 @@ async function main2() {
|
|
|
16055
17412
|
const commandOverride = typeof flags.get("cmd") === "string" ? flags.get("cmd").split(/\s+/).filter(Boolean) : undefined;
|
|
16056
17413
|
const info = await client.call("session.create", {
|
|
16057
17414
|
cwd: flags.get("cwd") ?? process.cwd(),
|
|
17415
|
+
preferConversationCwd: flags.get("cwd") === undefined,
|
|
16058
17416
|
name: flags.get("name"),
|
|
16059
17417
|
shell: flags.get("shell"),
|
|
16060
17418
|
agent: flags.get("agent"),
|
|
@@ -16148,9 +17506,15 @@ async function main2() {
|
|
|
16148
17506
|
const [id, indexArg] = positional;
|
|
16149
17507
|
if (!id || indexArg === undefined)
|
|
16150
17508
|
throw new Error(t("cli.usage.choose"));
|
|
16151
|
-
const
|
|
16152
|
-
await client.call("
|
|
17509
|
+
const index = Number(indexArg);
|
|
17510
|
+
const answered = await client.call("session.choose", { id, index }).catch((err) => {
|
|
17511
|
+
if (!/unknown method/i.test(String(err)))
|
|
17512
|
+
throw err;
|
|
17513
|
+
throw new Error(t("cli.choose.daemonTooOld"));
|
|
17514
|
+
});
|
|
16153
17515
|
console.log(t("cli.session.selected", { index: indexArg }));
|
|
17516
|
+
if (answered.answered === "onboarding")
|
|
17517
|
+
console.log(t("cli.session.selectedDialog"));
|
|
16154
17518
|
break;
|
|
16155
17519
|
}
|
|
16156
17520
|
case "setup": {
|
|
@@ -16226,8 +17590,8 @@ async function main2() {
|
|
|
16226
17590
|
case "project": {
|
|
16227
17591
|
const sub = positional[0] ?? "list";
|
|
16228
17592
|
if (sub === "add") {
|
|
16229
|
-
const dir = positional[1] ?
|
|
16230
|
-
const alias = positional[2] ??
|
|
17593
|
+
const dir = positional[1] ? path25.resolve(positional[1]) : process.cwd();
|
|
17594
|
+
const alias = positional[2] ?? path25.basename(dir);
|
|
16231
17595
|
const projects = await client.call("project.add", {
|
|
16232
17596
|
alias,
|
|
16233
17597
|
path: dir
|