@layorixinc/axterm 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2229 -587
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -33,6 +33,136 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
33
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
34
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
35
35
|
|
|
36
|
+
// packages/core/src/mcp-config.ts
|
|
37
|
+
function mcpServerEntry(launch, contextPath) {
|
|
38
|
+
return {
|
|
39
|
+
command: launch.command,
|
|
40
|
+
args: [...launch.args],
|
|
41
|
+
...contextPath === undefined ? {} : { env: { [MCP_CONTEXT_ENV]: contextPath } }
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function claudeMcpConfig(launch, contextPath) {
|
|
45
|
+
return JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: mcpServerEntry(launch, contextPath) } }, null, 2);
|
|
46
|
+
}
|
|
47
|
+
function tomlLiteral(value) {
|
|
48
|
+
if (value.includes("'")) {
|
|
49
|
+
throw new Error(`cannot pass a path containing a single quote to codex: ${value}`);
|
|
50
|
+
}
|
|
51
|
+
return `'${value}'`;
|
|
52
|
+
}
|
|
53
|
+
function codexMcpArgs(launch, contextPath) {
|
|
54
|
+
const args = launch.args.map(tomlLiteral).join(",");
|
|
55
|
+
return [
|
|
56
|
+
"-c",
|
|
57
|
+
`mcp_servers.${MCP_SERVER_NAME}.command=${tomlLiteral(launch.command)}`,
|
|
58
|
+
"-c",
|
|
59
|
+
`mcp_servers.${MCP_SERVER_NAME}.args=[${args}]`,
|
|
60
|
+
...contextPath === undefined ? [] : ["-c", `mcp_servers.${MCP_SERVER_NAME}.env={${MCP_CONTEXT_ENV}=${tomlLiteral(contextPath)}}`]
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
var MCP_SERVER_NAME = "axterm", MCP_CONTEXT_ENV = "AXTERM_MCP_CONTEXT";
|
|
64
|
+
|
|
65
|
+
// packages/core/src/agent-launch.ts
|
|
66
|
+
function uuidFromSessionId(sessionId) {
|
|
67
|
+
const hex = sessionId.replace(/-/g, "");
|
|
68
|
+
if (hex.length !== 32)
|
|
69
|
+
throw new Error(`session id is not 32 hex characters: ${sessionId}`);
|
|
70
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
71
|
+
}
|
|
72
|
+
function withAppServer(argv, url) {
|
|
73
|
+
const out = [];
|
|
74
|
+
for (let i = 0;i < argv.length; i++) {
|
|
75
|
+
if (argv[i] === "--remote") {
|
|
76
|
+
i++;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
out.push(argv[i]);
|
|
80
|
+
}
|
|
81
|
+
if (url === undefined || out.length === 0)
|
|
82
|
+
return out;
|
|
83
|
+
return [out[0], "--remote", url, ...out.slice(1)];
|
|
84
|
+
}
|
|
85
|
+
function buildAgentCommand(input) {
|
|
86
|
+
const extra = [...input.extraArgs ?? []];
|
|
87
|
+
if (input.agent === "claude") {
|
|
88
|
+
return [
|
|
89
|
+
"claude",
|
|
90
|
+
"--settings",
|
|
91
|
+
input.claudeSettingsPath,
|
|
92
|
+
...input.mcpConfigPath === undefined ? [] : ["--mcp-config", input.mcpConfigPath],
|
|
93
|
+
"--session-id",
|
|
94
|
+
uuidFromSessionId(input.sessionId),
|
|
95
|
+
...extra
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
if (input.agent === "gemini") {
|
|
99
|
+
return [
|
|
100
|
+
"gemini",
|
|
101
|
+
"--session-id",
|
|
102
|
+
input.sessionId,
|
|
103
|
+
...extra
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
if (input.agent === "codex") {
|
|
107
|
+
return [
|
|
108
|
+
"codex",
|
|
109
|
+
...input.appServerUrl === undefined ? [] : ["--remote", input.appServerUrl],
|
|
110
|
+
...input.mcpLaunch === undefined ? [] : codexMcpArgs(input.mcpLaunch, input.mcpContextPath),
|
|
111
|
+
...extra
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
return ["omp", "--extension", input.extensionPath, ...extra];
|
|
115
|
+
}
|
|
116
|
+
function resolveAgentArgs(input) {
|
|
117
|
+
const approve = input.autoApprove ?? input.autoApproveConfigured;
|
|
118
|
+
const layered = [
|
|
119
|
+
...approve === true ? input.bypass ?? [] : [],
|
|
120
|
+
...input.configured ?? [],
|
|
121
|
+
...input.requested ?? []
|
|
122
|
+
];
|
|
123
|
+
const groups = [];
|
|
124
|
+
for (const token of layered) {
|
|
125
|
+
if (token.startsWith("-"))
|
|
126
|
+
groups.push({ flag: token, tokens: [token] });
|
|
127
|
+
else if (groups.length > 0)
|
|
128
|
+
groups[groups.length - 1].tokens.push(token);
|
|
129
|
+
else
|
|
130
|
+
groups.push({ flag: undefined, tokens: [token] });
|
|
131
|
+
}
|
|
132
|
+
const lastIndex = new Map;
|
|
133
|
+
groups.forEach((group, index) => {
|
|
134
|
+
if (group.flag !== undefined)
|
|
135
|
+
lastIndex.set(group.flag, index);
|
|
136
|
+
});
|
|
137
|
+
return groups.filter((group, index) => group.flag === undefined || lastIndex.get(group.flag) === index).flatMap((group) => group.tokens);
|
|
138
|
+
}
|
|
139
|
+
function buildAgentResumeCommand(input) {
|
|
140
|
+
if (input.agent === "claude") {
|
|
141
|
+
return [
|
|
142
|
+
"claude",
|
|
143
|
+
"--settings",
|
|
144
|
+
input.claudeSettingsPath,
|
|
145
|
+
"--resume",
|
|
146
|
+
uuidFromSessionId(input.sessionId),
|
|
147
|
+
...input.extraArgs ?? []
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
if (input.agent === "gemini") {
|
|
151
|
+
return ["gemini", "--resume", input.sessionId, ...input.extraArgs ?? []];
|
|
152
|
+
}
|
|
153
|
+
return buildAgentCommand(input);
|
|
154
|
+
}
|
|
155
|
+
function isAgentId(value) {
|
|
156
|
+
return AGENT_IDS.includes(value);
|
|
157
|
+
}
|
|
158
|
+
function supportedAgents() {
|
|
159
|
+
return AGENT_IDS;
|
|
160
|
+
}
|
|
161
|
+
var AGENT_IDS;
|
|
162
|
+
var init_agent_launch = __esm(() => {
|
|
163
|
+
AGENT_IDS = ["omp", "claude", "codex", "gemini"];
|
|
164
|
+
});
|
|
165
|
+
|
|
36
166
|
// packages/core/src/activity.ts
|
|
37
167
|
function shouldStream(kind, settings) {
|
|
38
168
|
if (!settings.enabled)
|
|
@@ -41,6 +171,8 @@ function shouldStream(kind, settings) {
|
|
|
41
171
|
return settings.thinking;
|
|
42
172
|
if (kind === "tool")
|
|
43
173
|
return settings.tools;
|
|
174
|
+
if (kind === "reply")
|
|
175
|
+
return settings.replies;
|
|
44
176
|
return settings.toolResults;
|
|
45
177
|
}
|
|
46
178
|
function clipText(text, maxChars) {
|
|
@@ -147,7 +279,8 @@ var init_activity = __esm(() => {
|
|
|
147
279
|
MARKERS = {
|
|
148
280
|
thinking: "\uD83E\uDDE0",
|
|
149
281
|
tool: "\uD83D\uDD27",
|
|
150
|
-
toolResult: "\u21B3"
|
|
282
|
+
toolResult: "\u21B3",
|
|
283
|
+
reply: "\uD83D\uDCAC"
|
|
151
284
|
};
|
|
152
285
|
});
|
|
153
286
|
|
|
@@ -279,6 +412,7 @@ function resolveStreamConfig(file) {
|
|
|
279
412
|
thinking: file?.thinking ?? DEFAULT_STREAM_CONFIG.thinking,
|
|
280
413
|
tools: file?.tools ?? DEFAULT_STREAM_CONFIG.tools,
|
|
281
414
|
toolResults: file?.toolResults ?? DEFAULT_STREAM_CONFIG.toolResults,
|
|
415
|
+
replies: file?.replies ?? DEFAULT_STREAM_CONFIG.replies,
|
|
282
416
|
intervalMs: file?.intervalMs ?? DEFAULT_STREAM_CONFIG.intervalMs,
|
|
283
417
|
maxChars: file?.maxChars ?? DEFAULT_STREAM_CONFIG.maxChars
|
|
284
418
|
};
|
|
@@ -365,12 +499,30 @@ function parseConfig(raw) {
|
|
|
365
499
|
}
|
|
366
500
|
const defaults = raw["defaults"];
|
|
367
501
|
if (isRecord(defaults)) {
|
|
368
|
-
checkKeys(defaults, ["agent", "shell"], "defaults.", errors);
|
|
502
|
+
checkKeys(defaults, ["agent", "shell", "autoApprove", "agentArgs"], "defaults.", errors);
|
|
369
503
|
const agent = defaults["agent"];
|
|
370
504
|
const shell = defaults["shell"];
|
|
505
|
+
const rawArgs = defaults["agentArgs"];
|
|
506
|
+
const agentArgs = {};
|
|
507
|
+
if (isRecord(rawArgs)) {
|
|
508
|
+
for (const [id, value] of Object.entries(rawArgs)) {
|
|
509
|
+
if (Array.isArray(value) && value.every((part) => typeof part === "string"))
|
|
510
|
+
agentArgs[id] = value;
|
|
511
|
+
else
|
|
512
|
+
errors.push(`defaults.agentArgs.${id} must be a list of strings`);
|
|
513
|
+
}
|
|
514
|
+
} else if (rawArgs !== undefined) {
|
|
515
|
+
errors.push("defaults.agentArgs must be an object keyed by agent");
|
|
516
|
+
}
|
|
517
|
+
const autoApprove = defaults["autoApprove"];
|
|
518
|
+
if (autoApprove !== undefined && typeof autoApprove !== "boolean") {
|
|
519
|
+
errors.push("defaults.autoApprove must be true or false");
|
|
520
|
+
}
|
|
371
521
|
config.defaults = {
|
|
372
522
|
...typeof agent === "string" ? { agent } : {},
|
|
373
|
-
...typeof shell === "string" ? { shell } : {}
|
|
523
|
+
...typeof shell === "string" ? { shell } : {},
|
|
524
|
+
...typeof autoApprove === "boolean" ? { autoApprove } : {},
|
|
525
|
+
...Object.keys(agentArgs).length > 0 ? { agentArgs } : {}
|
|
374
526
|
};
|
|
375
527
|
}
|
|
376
528
|
const projects = raw["projects"];
|
|
@@ -404,9 +556,9 @@ function parseConfig(raw) {
|
|
|
404
556
|
}
|
|
405
557
|
const stream = telegram["stream"];
|
|
406
558
|
if (isRecord(stream)) {
|
|
407
|
-
checkKeys(stream, ["enabled", "thinking", "tools", "toolResults", "intervalMs", "maxChars"], "channels.telegram.stream.", errors);
|
|
559
|
+
checkKeys(stream, ["enabled", "thinking", "tools", "toolResults", "replies", "intervalMs", "maxChars"], "channels.telegram.stream.", errors);
|
|
408
560
|
const streamEntry = {};
|
|
409
|
-
for (const key of ["enabled", "thinking", "tools", "toolResults", "intervalMs", "maxChars"]) {
|
|
561
|
+
for (const key of ["enabled", "thinking", "tools", "toolResults", "replies", "intervalMs", "maxChars"]) {
|
|
410
562
|
const value = stream[key];
|
|
411
563
|
if (value === undefined)
|
|
412
564
|
continue;
|
|
@@ -445,21 +597,28 @@ function getConfigValue(config, dottedPath) {
|
|
|
445
597
|
return cursor;
|
|
446
598
|
}
|
|
447
599
|
function coerce(input, type) {
|
|
600
|
+
if (Array.isArray(input))
|
|
601
|
+
return type === "string[]" && input.length > 0 ? [...input] : undefined;
|
|
602
|
+
const text = input;
|
|
448
603
|
if (type === "string")
|
|
449
|
-
return
|
|
604
|
+
return text;
|
|
450
605
|
if (type === "number") {
|
|
451
|
-
const value = Number(
|
|
606
|
+
const value = Number(text);
|
|
452
607
|
return Number.isFinite(value) ? value : undefined;
|
|
453
608
|
}
|
|
454
609
|
if (type === "boolean") {
|
|
455
|
-
if (/^(true|yes|on|1)$/i.test(
|
|
610
|
+
if (/^(true|yes|on|1)$/i.test(text))
|
|
456
611
|
return true;
|
|
457
|
-
if (/^(false|no|off|0)$/i.test(
|
|
612
|
+
if (/^(false|no|off|0)$/i.test(text))
|
|
458
613
|
return false;
|
|
459
614
|
return;
|
|
460
615
|
}
|
|
461
|
-
|
|
462
|
-
|
|
616
|
+
if (type === "string[]") {
|
|
617
|
+
const parts = text.trim().split(/\s+/).filter((part) => part !== "");
|
|
618
|
+
return parts.length > 0 ? parts : undefined;
|
|
619
|
+
}
|
|
620
|
+
const numbers = text.split(",").map((part) => Number(part.trim()));
|
|
621
|
+
return numbers.every((part) => Number.isFinite(part)) ? numbers : undefined;
|
|
463
622
|
}
|
|
464
623
|
function setConfigValue(config, dottedPath, rawValue) {
|
|
465
624
|
const type = FIELDS[dottedPath];
|
|
@@ -468,8 +627,10 @@ function setConfigValue(config, dottedPath, rawValue) {
|
|
|
468
627
|
return { error: `unknown setting "${dottedPath}"${suggestion ? ` \u2014 did you mean "${suggestion}"?` : ""}` };
|
|
469
628
|
}
|
|
470
629
|
const value = coerce(rawValue, type);
|
|
471
|
-
if (value === undefined)
|
|
472
|
-
|
|
630
|
+
if (value === undefined) {
|
|
631
|
+
const shown = Array.isArray(rawValue) ? rawValue.join(" ") : rawValue;
|
|
632
|
+
return { error: `"${shown}" is not a valid ${type} for ${dottedPath}` };
|
|
633
|
+
}
|
|
473
634
|
const next = structuredClone(config);
|
|
474
635
|
const segments = dottedPath.split(".");
|
|
475
636
|
let cursor = next;
|
|
@@ -515,6 +676,7 @@ function resolveEnvRefs(config, env) {
|
|
|
515
676
|
}
|
|
516
677
|
var CONFIG_VERSION = 1, DEFAULT_CONFIG, DEFAULT_STREAM_CONFIG, DEFAULT_INBOUND_CONFIG, FIELDS, SECRET_SUFFIXES, ENV_REF;
|
|
517
678
|
var init_config_schema = __esm(() => {
|
|
679
|
+
init_agent_launch();
|
|
518
680
|
init_channel_spi();
|
|
519
681
|
DEFAULT_CONFIG = {
|
|
520
682
|
version: CONFIG_VERSION,
|
|
@@ -526,6 +688,7 @@ var init_config_schema = __esm(() => {
|
|
|
526
688
|
thinking: false,
|
|
527
689
|
tools: true,
|
|
528
690
|
toolResults: false,
|
|
691
|
+
replies: false,
|
|
529
692
|
intervalMs: 1e4,
|
|
530
693
|
maxChars: 300
|
|
531
694
|
};
|
|
@@ -534,10 +697,12 @@ var init_config_schema = __esm(() => {
|
|
|
534
697
|
maxBytes: TELEGRAM_CAPABILITIES.attachments.maxBytes
|
|
535
698
|
};
|
|
536
699
|
FIELDS = {
|
|
700
|
+
...Object.fromEntries(supportedAgents().map((agent) => [`defaults.agentArgs.${agent}`, "string[]"])),
|
|
537
701
|
locale: "string",
|
|
538
702
|
"machine.name": "string",
|
|
539
703
|
"defaults.agent": "string",
|
|
540
704
|
"defaults.shell": "string",
|
|
705
|
+
"defaults.autoApprove": "boolean",
|
|
541
706
|
"channels.telegram.enabled": "boolean",
|
|
542
707
|
"channels.telegram.groupId": "number",
|
|
543
708
|
"channels.telegram.allowUserIds": "number[]",
|
|
@@ -548,6 +713,7 @@ var init_config_schema = __esm(() => {
|
|
|
548
713
|
"channels.telegram.stream.thinking": "boolean",
|
|
549
714
|
"channels.telegram.stream.tools": "boolean",
|
|
550
715
|
"channels.telegram.stream.toolResults": "boolean",
|
|
716
|
+
"channels.telegram.stream.replies": "boolean",
|
|
551
717
|
"channels.telegram.stream.intervalMs": "number",
|
|
552
718
|
"channels.telegram.stream.maxChars": "number",
|
|
553
719
|
"channels.telegram.inbound.enabled": "boolean",
|
|
@@ -664,8 +830,12 @@ var init_i18n = __esm(() => {
|
|
|
664
830
|
usage:
|
|
665
831
|
axterm run [--cwd DIR] [--name NAME] [--shell pwsh|powershell|cmd|bash|zsh]
|
|
666
832
|
[--no-attach] [--cmd "raw command"] [-- <extra agent args>]
|
|
833
|
+
[--auto-approve | --no-auto-approve] let the agent act without asking,
|
|
834
|
+
overriding defaults.autoApprove for this session only
|
|
667
835
|
axterm list [--all] sessions started in this folder; --all for every one
|
|
668
|
-
axterm attach <id
|
|
836
|
+
axterm attach <id>|--last [--all] [--host-status orca]
|
|
837
|
+
--last: the newest live session here, for when the id is gone
|
|
838
|
+
detach: Ctrl+X Ctrl+X (or F12 / Ctrl+])
|
|
669
839
|
the host terminal title always reflects agent status;
|
|
670
840
|
--host-status orca also reports full status to the pane
|
|
671
841
|
axterm esc <id> send ESC (interrupt)
|
|
@@ -688,6 +858,7 @@ usage:
|
|
|
688
858
|
discover lists the group and user ids your bot can see
|
|
689
859
|
axterm setup interactive configuration (runs automatically on first use)
|
|
690
860
|
axterm doctor diagnose the silent failures: agent binaries, hook configs, codex trust, the bridge
|
|
861
|
+
axterm quota how much of each agent's rate limit is spent, from the agent's own records
|
|
691
862
|
axterm config list|get <path>|set <path> <v>|unset <path>|path|import-env [file]
|
|
692
863
|
|
|
693
864
|
locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
@@ -698,7 +869,9 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
698
869
|
"cli.sessions.removed": "removed {count} session(s)",
|
|
699
870
|
"cli.sessions.noneExited": "no exited sessions to remove",
|
|
700
871
|
"cli.session.removeFailed": "could not remove {id}: {error}",
|
|
701
|
-
"cli.session.created": "created session {id} ({shell}, pid {pid})",
|
|
872
|
+
"cli.session.created": "created session {id} ({shell}, pid {pid}) \u2014 reattach with `axterm attach {id}`, or `axterm attach --last`",
|
|
873
|
+
"cli.attach.picked": "attaching to {id} ({name}), the most recent live session here",
|
|
874
|
+
"cli.attach.noRecent": "no live session here to reattach \u2014 `axterm list --all` shows every one",
|
|
702
875
|
"cli.session.killed": "killed",
|
|
703
876
|
"cli.session.sent": "sent",
|
|
704
877
|
"cli.session.sentKey": "sent {key}",
|
|
@@ -726,7 +899,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
726
899
|
"cli.update.notBinary": "this axterm runs from source, so there is nothing to replace \u2014 use git pull",
|
|
727
900
|
"cli.flag.needsValue": "--{flag} requires a value",
|
|
728
901
|
"cli.key.unknown": "unknown key: {name}",
|
|
729
|
-
"cli.usage.attach": "usage: axterm attach <id>",
|
|
902
|
+
"cli.usage.attach": "usage: axterm attach <id> (or `axterm attach --last`)",
|
|
730
903
|
"cli.usage.send": "usage: axterm send [--raw] <id> <text...>",
|
|
731
904
|
"cli.usage.screen": "usage: axterm screen <id>",
|
|
732
905
|
"cli.usage.esc": "usage: axterm esc <id>",
|
|
@@ -739,7 +912,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
739
912
|
"cli.usage.diff": "usage: axterm diff <id> [--base REF]",
|
|
740
913
|
"cli.usage.config": "usage: axterm config list|get|set|unset|path|import-env",
|
|
741
914
|
"cli.usage.configGet": "usage: axterm config get <path> [--reveal]",
|
|
742
|
-
"cli.usage.configSet": "usage: axterm config set <path> <value>",
|
|
915
|
+
"cli.usage.configSet": "usage: axterm config set <path> <value> (a value starting with - goes after `--`)",
|
|
743
916
|
"cli.usage.configUnset": "usage: axterm config unset <path>",
|
|
744
917
|
"cli.usage.project": "usage: axterm project add|list|rm",
|
|
745
918
|
"cli.usage.projectRm": "usage: axterm project rm <alias>",
|
|
@@ -778,7 +951,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
778
951
|
"tg.help.general.worktrees": "/worktrees <project> \xB7 /rm <project> <branch> [force]",
|
|
779
952
|
"tg.help.general.sessions": "/sessions \u2014 session list",
|
|
780
953
|
"tg.help.topic.header": "Inside a session topic, a plain message is delivered as a prompt.",
|
|
781
|
-
"tg.help.topic.commands": "/esc interrupt \xB7 /screen snapshot \xB7 /diff change summary \xB7 /resume continue the conversation \xB7 /kill stop",
|
|
954
|
+
"tg.help.topic.commands": "/esc interrupt \xB7 /screen snapshot \xB7 /diff change summary \xB7 /quota rate limits \xB7 /key & /type raw input \xB7 /resume continue the conversation \xB7 /kill stop",
|
|
782
955
|
"tg.session.started": `\u25B6 Session {id} started
|
|
783
956
|
{command}
|
|
784
957
|
\uD83D\uDCC1 {cwd}`,
|
|
@@ -802,6 +975,15 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
802
975
|
"tg.shell.only": "(shell)",
|
|
803
976
|
"tg.agent.failed": "\uD83D\uDED1 The agent could not finish this turn: {error}",
|
|
804
977
|
"tg.agent.retry": "\u23F3 The agent is rate limited and will retry in {wait}: {error}",
|
|
978
|
+
"tg.raw.keysSent": "\u2328\uFE0F sent: {keys}",
|
|
979
|
+
"tg.raw.unknownKey": 'unknown key "{name}" \u2014 nothing was sent. Accepted: {keys}',
|
|
980
|
+
"tg.raw.typed": `\u2328\uFE0F typed (not submitted \u2014 /key enter to send):
|
|
981
|
+
<pre>{screen}</pre>`,
|
|
982
|
+
"tg.raw.needText": "usage: /type <text> \u2014 written verbatim, without Enter. Then /key enter.",
|
|
983
|
+
"tg.raw.tooLong": "too long for raw input ({limit} characters max) \u2014 send it as an ordinary message instead",
|
|
984
|
+
"tg.agent.attempt": "\u26A0\uFE0F An attempt failed and the agent is retrying: {error}",
|
|
985
|
+
"tg.agent.stalled": `\u23F8\uFE0F No progress for {wait}. The agent has not said why \u2014 this is the last thing it printed:
|
|
986
|
+
<pre>{screen}</pre>`,
|
|
805
987
|
"tg.inbound.tooLarge": "\u26A0\uFE0F That file is larger than the {limit} limit \u2014 nothing was downloaded.",
|
|
806
988
|
"tg.inbound.empty": "\u26A0\uFE0F That file is empty \u2014 nothing was downloaded.",
|
|
807
989
|
"tg.inbound.noSession": "\u26A0\uFE0F Send the file inside a session topic \u2014 no session is bound to this one.",
|
|
@@ -877,6 +1059,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
877
1059
|
"doctor.telegram.ok": "telegram: connected",
|
|
878
1060
|
"doctor.telegram.off": "telegram: not running",
|
|
879
1061
|
"doctor.telegram.problems": "telegram: {problems}",
|
|
1062
|
+
"quota.gauge": "{agent}: {percent}% of {window} used, resets in {resets}",
|
|
1063
|
+
"quota.gaugeStale": "{agent}: {percent}% of {window} used, resets in {resets} (recorded {age} ago)",
|
|
1064
|
+
"quota.gaugeElapsed": "{agent}: {percent}% of {window} used, and that window has already reset",
|
|
1065
|
+
"quota.reached": "{agent}: the limit was reached \u2014 {percent}% of {window}, resets in {resets}",
|
|
1066
|
+
"quota.event": "{agent}: hit its limit {age} ago \u2014 {text}",
|
|
1067
|
+
"quota.eventNone": "{agent}: no limit reported (it records no percentage)",
|
|
1068
|
+
"quota.noData": "{agent}: nothing recorded yet \u2014 run a turn first",
|
|
1069
|
+
"quota.unsupported": "{agent}: reports no quota at all",
|
|
1070
|
+
"cli.quota.none": "no agent on this machine reports a quota",
|
|
880
1071
|
"fanout.needCountOrBranches": "a branch count or a branch list is required",
|
|
881
1072
|
"fanout.needPrompt": "a prompt is required",
|
|
882
1073
|
"fanout.outOfRange": "must be between 1 and {max}",
|
|
@@ -900,9 +1091,10 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
900
1091
|
"health.telegram.noUsers": "Telegram is enabled but no user is allowed to control it",
|
|
901
1092
|
"wizard.prompt.locale": "Language",
|
|
902
1093
|
"wizard.prompt.machine": "Name for this machine (used to address it from a channel)",
|
|
903
|
-
"wizard.prompt.agent": "Default agent
|
|
1094
|
+
"wizard.prompt.agent": "Default agent (extra flags go in defaults.agentArgs.<agent>)",
|
|
904
1095
|
"wizard.prompt.telegram": "Set up Telegram control now?",
|
|
905
1096
|
"wizard.prompt.telegramToken": "Bot token from @BotFather",
|
|
1097
|
+
"wizard.prompt.agentApproval": "Let this agent act without asking for approval? It skips every permission prompt \u2014 convenient from a phone, and it means the agent may edit or run anything in the session's folder. Reversible with `axterm config set defaults.autoApprove false`, or for one session with `axterm run --no-auto-approve`.",
|
|
906
1098
|
"wizard.prompt.telegramGroup": "Group id (negative, starts with -100; `axterm telegram discover` lists them)",
|
|
907
1099
|
"wizard.prompt.telegramUsers": "Telegram user ids allowed to control axterm (comma separated; `axterm telegram discover` lists them)",
|
|
908
1100
|
"wizard.prompt.review": "Save this configuration?",
|
|
@@ -934,8 +1126,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
934
1126
|
\uC0AC\uC6A9\uBC95:
|
|
935
1127
|
axterm run [--cwd \uD3F4\uB354] [--name \uC774\uB984] [--shell pwsh|powershell|cmd|bash|zsh]
|
|
936
1128
|
[--no-attach] [--cmd "\uC6D0\uC2DC \uBA85\uB839"] [-- <\uC5D0\uC774\uC804\uD2B8 \uCD94\uAC00 \uC778\uC790>]
|
|
1129
|
+
[--auto-approve | --no-auto-approve] \uC774 \uC138\uC158\uB9CC \uC2B9\uC778 \uC5C6\uC774 \uB3D9\uC791\uD558\uAC8C,
|
|
1130
|
+
defaults.autoApprove \uB97C \uB36E\uC5B4\uC501\uB2C8\uB2E4
|
|
937
1131
|
axterm list [--all] \uC774 \uD3F4\uB354\uC5D0\uC11C \uB9CC\uB4E0 \uC138\uC158. --all \uC740 \uC804\uCCB4
|
|
938
|
-
axterm attach <id
|
|
1132
|
+
axterm attach <id>|--last [--all] [--host-status orca]
|
|
1133
|
+
--last: \uC774 \uD3F4\uB354\uC758 \uAC00\uC7A5 \uCD5C\uADFC \uC0B4\uC544\uC788\uB294 \uC138\uC158 (id\uB97C \uC783\uC5C8\uC744 \uB54C)
|
|
1134
|
+
\uBD84\uB9AC: Ctrl+X Ctrl+X (\uB610\uB294 F12 / Ctrl+])
|
|
939
1135
|
\uD638\uC2A4\uD2B8 \uD130\uBBF8\uB110 \uC81C\uBAA9\uC740 \uD56D\uC0C1 \uC5D0\uC774\uC804\uD2B8 \uC0C1\uD0DC\uB97C \uBC18\uC601\uD55C\uB2E4;
|
|
940
1136
|
--host-status orca \uB294 \uD32C\uC5D0\uB3C4 \uC804\uCCB4 \uC0C1\uD0DC\uB97C \uBCF4\uACE0\uD55C\uB2E4
|
|
941
1137
|
axterm esc <id> ESC \uC804\uC1A1 (\uC911\uB2E8)
|
|
@@ -958,6 +1154,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
958
1154
|
discover \u2014 \uBD07\uC774 \uBCFC \uC218 \uC788\uB294 \uADF8\uB8F9\xB7\uC0AC\uC6A9\uC790 id \uBAA9\uB85D
|
|
959
1155
|
axterm setup \uB300\uD654\uD615 \uC124\uC815 (\uCD5C\uCD08 \uC2E4\uD589 \uC2DC \uC790\uB3D9)
|
|
960
1156
|
axterm doctor \uCE68\uBB35\uD558\uB294 \uC2E4\uD328\uB97C \uC9C4\uB2E8: \uC5D0\uC774\uC804\uD2B8 \uBC14\uC774\uB108\uB9AC, \uD6C5 \uC124\uC815, codex \uC2E0\uB8B0, \uBE0C\uB9AC\uC9C0
|
|
1157
|
+
axterm quota \uAC01 \uC5D0\uC774\uC804\uD2B8\uC758 \uC0AC\uC6A9\uB7C9 \uD55C\uB3C4 \uC18C\uC9C4 \uC815\uB3C4 (\uC5D0\uC774\uC804\uD2B8 \uC790\uC2E0\uC758 \uAE30\uB85D\uC5D0\uC11C)
|
|
961
1158
|
axterm config list|get <\uACBD\uB85C>|set <\uACBD\uB85C> <\uAC12>|unset <\uACBD\uB85C>|path|import-env [\uD30C\uC77C]
|
|
962
1159
|
|
|
963
1160
|
\uC5B8\uC5B4: AXTERM_LANG=en|ko \uB610\uB294 \`axterm config set locale ko\`
|
|
@@ -968,7 +1165,9 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
968
1165
|
"cli.sessions.removed": "\uC138\uC158 {count}\uAC1C \uC0AD\uC81C\uB428",
|
|
969
1166
|
"cli.sessions.noneExited": "\uC0AD\uC81C\uD560 \uC885\uB8CC\uB41C \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
970
1167
|
"cli.session.removeFailed": "{id} \uC0AD\uC81C \uC2E4\uD328: {error}",
|
|
971
|
-
"cli.session.created": "\uC138\uC158 {id} \uC0DD\uC131\uB428 ({shell}, pid {pid})",
|
|
1168
|
+
"cli.session.created": "\uC138\uC158 {id} \uC0DD\uC131\uB428 ({shell}, pid {pid}) \u2014 \uC7AC\uC811\uC18D: `axterm attach {id}` \uB610\uB294 `axterm attach --last`",
|
|
1169
|
+
"cli.attach.picked": "{id} ({name}) \uC5D0 \uC811\uC18D\uD569\uB2C8\uB2E4 \u2014 \uC774 \uD3F4\uB354\uC5D0\uC11C \uAC00\uC7A5 \uCD5C\uADFC\uC5D0 \uB9CC\uB4E0 \uC0B4\uC544\uC788\uB294 \uC138\uC158\uC785\uB2C8\uB2E4",
|
|
1170
|
+
"cli.attach.noRecent": "\uC774 \uD3F4\uB354\uC5D0 \uC7AC\uC811\uC18D\uD560 \uC0B4\uC544\uC788\uB294 \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 `axterm list --all` \uB85C \uC804\uCCB4\uB97C \uBCF4\uC138\uC694",
|
|
972
1171
|
"cli.session.killed": "\uC885\uB8CC\uB428",
|
|
973
1172
|
"cli.session.sent": "\uC804\uB2EC\uB428",
|
|
974
1173
|
"cli.session.sentKey": "{key} \uC804\uB2EC\uB428",
|
|
@@ -996,7 +1195,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
996
1195
|
"cli.update.notBinary": "\uC774 axterm \uC740 \uC18C\uC2A4\uC5D0\uC11C \uC2E4\uD589 \uC911\uC774\uB77C \uAD50\uCCB4\uD560 \uC2E4\uD589 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. git pull \uC744 \uC0AC\uC6A9\uD558\uC138\uC694",
|
|
997
1196
|
"cli.flag.needsValue": "--{flag} \uC5D0\uB294 \uAC12\uC774 \uD544\uC694\uD569\uB2C8\uB2E4",
|
|
998
1197
|
"cli.key.unknown": "\uC54C \uC218 \uC5C6\uB294 \uD0A4: {name}",
|
|
999
|
-
"cli.usage.attach": "\uC0AC\uC6A9\uBC95: axterm attach <id>",
|
|
1198
|
+
"cli.usage.attach": "\uC0AC\uC6A9\uBC95: axterm attach <id> (\uB610\uB294 `axterm attach --last`)",
|
|
1000
1199
|
"cli.usage.send": "\uC0AC\uC6A9\uBC95: axterm send [--raw] <id> <\uD14D\uC2A4\uD2B8...>",
|
|
1001
1200
|
"cli.usage.screen": "\uC0AC\uC6A9\uBC95: axterm screen <id>",
|
|
1002
1201
|
"cli.usage.esc": "\uC0AC\uC6A9\uBC95: axterm esc <id>",
|
|
@@ -1009,7 +1208,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1009
1208
|
"cli.usage.diff": "\uC0AC\uC6A9\uBC95: axterm diff <id> [--base REF]",
|
|
1010
1209
|
"cli.usage.config": "\uC0AC\uC6A9\uBC95: axterm config list|get|set|unset|path|import-env",
|
|
1011
1210
|
"cli.usage.configGet": "\uC0AC\uC6A9\uBC95: axterm config get <\uACBD\uB85C> [--reveal]",
|
|
1012
|
-
"cli.usage.configSet": "\uC0AC\uC6A9\uBC95: axterm config set <\uACBD\uB85C> <\uAC12>",
|
|
1211
|
+
"cli.usage.configSet": "\uC0AC\uC6A9\uBC95: axterm config set <\uACBD\uB85C> <\uAC12> (- \uB85C \uC2DC\uC791\uD558\uB294 \uAC12\uC740 `--` \uB4A4\uC5D0)",
|
|
1013
1212
|
"cli.usage.configUnset": "\uC0AC\uC6A9\uBC95: axterm config unset <\uACBD\uB85C>",
|
|
1014
1213
|
"cli.usage.project": "\uC0AC\uC6A9\uBC95: axterm project add|list|rm",
|
|
1015
1214
|
"cli.usage.projectRm": "\uC0AC\uC6A9\uBC95: axterm project rm <\uBCC4\uCE6D>",
|
|
@@ -1048,7 +1247,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1048
1247
|
"err.telegram.noToken": "\uBD07 \uD1A0\uD070\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 \u2014 `axterm setup` \uC744 \uC2E4\uD589\uD558\uAC70\uB098 --token \uC744 \uB118\uAE30\uC138\uC694",
|
|
1049
1248
|
"tg.help.general.sessions": "/sessions \u2014 \uC138\uC158 \uBAA9\uB85D",
|
|
1050
1249
|
"tg.help.topic.header": "\uC138\uC158 \uD1A0\uD53D \uC548\uC5D0\uC11C\uB294 \uC77C\uBC18 \uBA54\uC2DC\uC9C0\uAC00 \uD504\uB86C\uD504\uD2B8\uB85C \uC804\uB2EC\uB429\uB2C8\uB2E4.",
|
|
1051
|
-
"tg.help.topic.commands": "/esc \uC911\uB2E8 \xB7 /screen \uD654\uBA74 \xB7 /diff \uBCC0\uACBD\uC694\uC57D \xB7 /resume \uB300\uD654 \uC774\uC5B4\uC11C \uC7AC\uC2DC\uC791 \xB7 /kill \uC885\uB8CC",
|
|
1250
|
+
"tg.help.topic.commands": "/esc \uC911\uB2E8 \xB7 /screen \uD654\uBA74 \xB7 /diff \uBCC0\uACBD\uC694\uC57D \xB7 /quota \uC0AC\uC6A9\uB7C9 \xB7 /key\xB7/type \uC6D0\uC2DC \uC785\uB825 \xB7 /resume \uB300\uD654 \uC774\uC5B4\uC11C \uC7AC\uC2DC\uC791 \xB7 /kill \uC885\uB8CC",
|
|
1052
1251
|
"tg.session.started": `\u25B6 \uC138\uC158 {id} \uC2DC\uC791
|
|
1053
1252
|
{command}
|
|
1054
1253
|
\uD83D\uDCC1 {cwd}`,
|
|
@@ -1072,6 +1271,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1072
1271
|
"tg.shell.only": "(\uC178)",
|
|
1073
1272
|
"tg.agent.failed": "\uD83D\uDED1 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC774\uBC88 \uD134\uC744 \uB05D\uB0B4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: {error}",
|
|
1074
1273
|
"tg.agent.retry": "\u23F3 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC694\uCCAD \uD55C\uB3C4\uC5D0 \uAC78\uB824 {wait} \uD6C4 \uC7AC\uC2DC\uB3C4\uD569\uB2C8\uB2E4: {error}",
|
|
1274
|
+
"tg.raw.keysSent": "\u2328\uFE0F \uC804\uC1A1: {keys}",
|
|
1275
|
+
"tg.raw.unknownKey": '\uC54C \uC218 \uC5C6\uB294 \uD0A4 "{name}" \u2014 \uC544\uBB34\uAC83\uB3C4 \uBCF4\uB0B4\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uAC00\uB2A5\uD55C \uD0A4: {keys}',
|
|
1276
|
+
"tg.raw.typed": `\u2328\uFE0F \uC785\uB825\uD588\uC2B5\uB2C8\uB2E4 (\uC544\uC9C1 \uC81C\uCD9C \uC548 \uD568 \u2014 \uBCF4\uB0B4\uB824\uBA74 /key enter):
|
|
1277
|
+
<pre>{screen}</pre>`,
|
|
1278
|
+
"tg.raw.needText": "\uC0AC\uC6A9\uBC95: /type <\uD14D\uC2A4\uD2B8> \u2014 Enter \uC5C6\uC774 \uADF8\uB300\uB85C \uC785\uB825\uB429\uB2C8\uB2E4. \uC774\uD6C4 /key enter.",
|
|
1279
|
+
"tg.raw.tooLong": "\uC6D0\uC2DC \uC785\uB825\uC73C\uB85C\uB294 \uB108\uBB34 \uAE41\uB2C8\uB2E4 (\uCD5C\uB300 {limit}\uC790) \u2014 \uC77C\uBC18 \uBA54\uC2DC\uC9C0\uB85C \uBCF4\uB0B4\uC138\uC694",
|
|
1280
|
+
"tg.agent.attempt": "\u26A0\uFE0F \uC2DC\uB3C4\uAC00 \uD55C \uBC88 \uC2E4\uD328\uD574 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC7AC\uC2DC\uB3C4\uD569\uB2C8\uB2E4: {error}",
|
|
1281
|
+
"tg.agent.stalled": `\u23F8\uFE0F {wait} \uB3D9\uC548 \uC9C4\uC804\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC5D0\uC774\uC804\uD2B8\uAC00 \uC774\uC720\uB97C \uB9D0\uD558\uC9C0 \uC54A\uC558\uC73C\uBBC0\uB85C, \uB9C8\uC9C0\uB9C9\uC73C\uB85C \uCD9C\uB825\uD55C \uB0B4\uC6A9\uC785\uB2C8\uB2E4:
|
|
1282
|
+
<pre>{screen}</pre>`,
|
|
1075
1283
|
"tg.inbound.tooLarge": "\u26A0\uFE0F {limit} \uC81C\uD55C\uC744 \uB118\uB294 \uD30C\uC77C\uC785\uB2C8\uB2E4 \u2014 \uB0B4\uB824\uBC1B\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
|
|
1076
1284
|
"tg.inbound.empty": "\u26A0\uFE0F \uBE48 \uD30C\uC77C\uC774\uB77C \uC138\uC158\uC5D0 \uC804\uB2EC\uD560 \uB0B4\uC6A9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB0B4\uB824\uBC1B\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
|
|
1077
1285
|
"tg.inbound.noSession": "\u26A0\uFE0F \uD30C\uC77C\uC740 \uC138\uC158 \uD1A0\uD53D \uC548\uC5D0\uC11C \uBCF4\uB0B4\uC8FC\uC138\uC694 \u2014 \uC774 \uD1A0\uD53D\uC5D0 \uC5F0\uACB0\uB41C \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
@@ -1147,6 +1355,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1147
1355
|
"doctor.telegram.ok": "\uD154\uB808\uADF8\uB7A8: \uC5F0\uACB0\uB428",
|
|
1148
1356
|
"doctor.telegram.off": "\uD154\uB808\uADF8\uB7A8: \uBBF8\uC2E4\uD589",
|
|
1149
1357
|
"doctor.telegram.problems": "\uD154\uB808\uADF8\uB7A8: {problems}",
|
|
1358
|
+
"quota.gauge": "{agent}: {window} \uD55C\uB3C4\uC758 {percent}% \uC0AC\uC6A9, {resets} \uD6C4 \uCD08\uAE30\uD654",
|
|
1359
|
+
"quota.gaugeStale": "{agent}: {window} \uD55C\uB3C4\uC758 {percent}% \uC0AC\uC6A9, {resets} \uD6C4 \uCD08\uAE30\uD654 ({age} \uC804 \uAE30\uB85D)",
|
|
1360
|
+
"quota.gaugeElapsed": "{agent}: {window} \uD55C\uB3C4\uC758 {percent}% \uC0AC\uC6A9, \uD574\uB2F9 \uCC3D\uC740 \uC774\uBBF8 \uCD08\uAE30\uD654\uB428",
|
|
1361
|
+
"quota.reached": "{agent}: \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD568 \u2014 {window} \uD55C\uB3C4\uC758 {percent}%, {resets} \uD6C4 \uCD08\uAE30\uD654",
|
|
1362
|
+
"quota.event": "{agent}: {age} \uC804 \uD55C\uB3C4\uC5D0 \uAC78\uB838\uC2B5\uB2C8\uB2E4 \u2014 {text}",
|
|
1363
|
+
"quota.eventNone": "{agent}: \uD55C\uB3C4 \uAE30\uB85D \uC5C6\uC74C (\uC0AC\uC6A9\uB960\uC744 \uB0A8\uAE30\uC9C0 \uC54A\uB294 \uC5D0\uC774\uC804\uD2B8)",
|
|
1364
|
+
"quota.noData": "{agent}: \uC544\uC9C1 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uBA3C\uC800 \uD55C \uD134 \uC2E4\uD589\uD558\uC138\uC694",
|
|
1365
|
+
"quota.unsupported": "{agent}: \uC0AC\uC6A9\uB7C9 \uD55C\uB3C4\uB97C \uC804\uD600 \uBCF4\uACE0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
1366
|
+
"cli.quota.none": "\uC774 \uCEF4\uD4E8\uD130\uC758 \uC5B4\uB5A4 \uC5D0\uC774\uC804\uD2B8\uB3C4 \uC0AC\uC6A9\uB7C9 \uD55C\uB3C4\uB97C \uBCF4\uACE0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
1150
1367
|
"fanout.needCountOrBranches": "\uBE0C\uB79C\uCE58 \uC218\uB098 \uBE0C\uB79C\uCE58 \uBAA9\uB85D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4",
|
|
1151
1368
|
"fanout.needPrompt": "\uD504\uB86C\uD504\uD2B8\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4",
|
|
1152
1369
|
"fanout.outOfRange": "1..{max} \uBC94\uC704\uC5EC\uC57C \uD569\uB2C8\uB2E4",
|
|
@@ -1170,8 +1387,9 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1170
1387
|
"health.telegram.noUsers": "Telegram\uC774 \uCF1C\uC838 \uC788\uC9C0\uB9CC \uC870\uC791\uC744 \uD5C8\uC6A9\uD55C \uC0AC\uC6A9\uC790\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
1171
1388
|
"wizard.prompt.locale": "\uC5B8\uC5B4",
|
|
1172
1389
|
"wizard.prompt.machine": "\uC774 \uBA38\uC2E0\uC758 \uC774\uB984 (\uCC44\uB110\uC5D0\uC11C \uC9C0\uC815\uD560 \uB54C \uC500)",
|
|
1173
|
-
"wizard.prompt.agent": "\uAE30\uBCF8 \uC5D0\uC774\uC804\uD2B8 \
|
|
1390
|
+
"wizard.prompt.agent": "\uAE30\uBCF8 \uC5D0\uC774\uC804\uD2B8 (\uCD94\uAC00 \uD50C\uB798\uADF8\uB294 defaults.agentArgs.<\uC5D0\uC774\uC804\uD2B8> \uC5D0)",
|
|
1174
1391
|
"wizard.prompt.telegram": "\uC9C0\uAE08 Telegram \uC81C\uC5B4\uB97C \uC124\uC815\uD560\uAE4C\uC694?",
|
|
1392
|
+
"wizard.prompt.agentApproval": "\uC774 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC2B9\uC778 \uC5C6\uC774 \uC791\uC5C5\uD558\uB3C4\uB85D \uD560\uAE4C\uC694? \uBAA8\uB4E0 \uAD8C\uD55C \uD655\uC778\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4 \u2014 \uD3F0\uC5D0\uC11C \uD3B8\uD558\uC9C0\uB9CC, \uC138\uC158 \uD3F4\uB354 \uC548\uC5D0\uC11C \uBB34\uC5C7\uC774\uB4E0 \uC218\uC815\xB7\uC2E4\uD589\uD560 \uC218 \uC788\uAC8C \uB429\uB2C8\uB2E4. `axterm config set defaults.autoApprove false` \uB85C \uB418\uB3CC\uB9AC\uAC70\uB098, \uD55C \uC138\uC158\uB9CC\uC774\uB77C\uBA74 `axterm run --no-auto-approve` \uB97C \uC4F0\uC138\uC694.",
|
|
1175
1393
|
"wizard.prompt.telegramToken": "@BotFather \uC5D0\uC11C \uBC1B\uC740 \uBD07 \uD1A0\uD070",
|
|
1176
1394
|
"wizard.prompt.telegramGroup": "\uADF8\uB8F9 id (\uC74C\uC218, \uBCF4\uD1B5 -100 \uC73C\uB85C \uC2DC\uC791. `axterm telegram discover` \uB85C \uD655\uC778 \uAC00\uB2A5)",
|
|
1177
1395
|
"wizard.prompt.telegramUsers": "axterm \uC870\uC791\uC744 \uD5C8\uC6A9\uD560 Telegram user id (\uC27C\uD45C\uB85C \uAD6C\uBD84. `axterm telegram discover` \uB85C \uD655\uC778 \uAC00\uB2A5)",
|
|
@@ -1211,6 +1429,8 @@ function defaultFor(step, config, hostname) {
|
|
|
1211
1429
|
return config.machine?.name ?? hostname;
|
|
1212
1430
|
case "agent":
|
|
1213
1431
|
return config.defaults?.agent ?? "omp";
|
|
1432
|
+
case "agentApproval":
|
|
1433
|
+
return "no";
|
|
1214
1434
|
case "telegram":
|
|
1215
1435
|
return telegram?.enabled === true ? "yes" : "no";
|
|
1216
1436
|
case "telegramGroup":
|
|
@@ -1244,6 +1464,7 @@ function collect(answers) {
|
|
|
1244
1464
|
["machine.name", answers["machine"] ?? ""],
|
|
1245
1465
|
["defaults.agent", answers["agent"] ?? "omp"]
|
|
1246
1466
|
];
|
|
1467
|
+
settings.push(["defaults.autoApprove", YES.has(answers["agentApproval"] ?? "") ? "true" : "false"]);
|
|
1247
1468
|
const secrets = [];
|
|
1248
1469
|
const wantsTelegram = YES.has(answers["telegram"] ?? "");
|
|
1249
1470
|
settings.push(["channels.telegram.enabled", wantsTelegram ? "true" : "false"]);
|
|
@@ -1281,6 +1502,7 @@ var YES, NO, numericList = (value) => {
|
|
|
1281
1502
|
return parts.length > 0 && parts.every((p) => /^-?\d+$/.test(p)) ? undefined : "wizard.error.numberList";
|
|
1282
1503
|
}, STEPS, wizardConfig;
|
|
1283
1504
|
var init_setup_wizard = __esm(() => {
|
|
1505
|
+
init_agent_launch();
|
|
1284
1506
|
init_i18n();
|
|
1285
1507
|
YES = new Set(["y", "yes", "true", "1"]);
|
|
1286
1508
|
NO = new Set(["n", "no", "false", "0"]);
|
|
@@ -1299,9 +1521,17 @@ var init_setup_wizard = __esm(() => {
|
|
|
1299
1521
|
next: () => "agent"
|
|
1300
1522
|
},
|
|
1301
1523
|
agent: {
|
|
1302
|
-
kind: "
|
|
1524
|
+
kind: "select",
|
|
1303
1525
|
promptKey: "wizard.prompt.agent",
|
|
1304
|
-
|
|
1526
|
+
choices: [...supportedAgents()],
|
|
1527
|
+
validate: (v) => supportedAgents().includes(v.trim()) ? undefined : "wizard.error.agent",
|
|
1528
|
+
next: () => "agentApproval"
|
|
1529
|
+
},
|
|
1530
|
+
agentApproval: {
|
|
1531
|
+
kind: "select",
|
|
1532
|
+
promptKey: "wizard.prompt.agentApproval",
|
|
1533
|
+
choices: ["yes", "no"],
|
|
1534
|
+
validate: (v) => YES.has(v) || NO.has(v) ? undefined : "wizard.error.yesNo",
|
|
1305
1535
|
next: () => "telegram"
|
|
1306
1536
|
},
|
|
1307
1537
|
telegram: {
|
|
@@ -1371,7 +1601,7 @@ function oscTitle(title) {
|
|
|
1371
1601
|
const safe = title.replace(/[\x00-\x1f\x7f]/g, "").slice(0, MAX_TITLE_LENGTH);
|
|
1372
1602
|
return `\x1B]0;${safe}\x07`;
|
|
1373
1603
|
}
|
|
1374
|
-
var SPINNER_FRAMES, MENU_APPROVE = "\r", PROFILES, MAX_TITLE_LENGTH = 200;
|
|
1604
|
+
var SPINNER_FRAMES, MENU_APPROVE = "\r", PROFILES, REGISTERED_AGENTS, MAX_TITLE_LENGTH = 200;
|
|
1375
1605
|
var init_agent_registry = __esm(() => {
|
|
1376
1606
|
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1377
1607
|
PROFILES = {
|
|
@@ -1380,6 +1610,8 @@ var init_agent_registry = __esm(() => {
|
|
|
1380
1610
|
displayName: "OMP",
|
|
1381
1611
|
title: { workingLabel: "OMP", permissionLabel: "OMP - action required", idleLabel: "OMP ready" },
|
|
1382
1612
|
hostHookSource: "omp",
|
|
1613
|
+
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
1614
|
+
quota: "gauge",
|
|
1383
1615
|
resume: { argv: ["--resume", "{ref}"], ref: "sessionFile" }
|
|
1384
1616
|
},
|
|
1385
1617
|
pi: {
|
|
@@ -1399,6 +1631,8 @@ var init_agent_registry = __esm(() => {
|
|
|
1399
1631
|
synthesizeWorking: false
|
|
1400
1632
|
},
|
|
1401
1633
|
hostHookSource: "codex",
|
|
1634
|
+
quota: "gauge",
|
|
1635
|
+
approvalBypassArgs: ["--dangerously-bypass-approvals-and-sandbox"],
|
|
1402
1636
|
structuredApprovals: true,
|
|
1403
1637
|
resume: { argv: ["resume", "{ref}"], ref: "sessionId", form: "subcommand" }
|
|
1404
1638
|
},
|
|
@@ -1443,6 +1677,8 @@ var init_agent_registry = __esm(() => {
|
|
|
1443
1677
|
synthesizeTerminal: false
|
|
1444
1678
|
},
|
|
1445
1679
|
hostHookSource: "claude",
|
|
1680
|
+
quota: "event",
|
|
1681
|
+
approvalBypassArgs: ["--permission-mode", "bypassPermissions"],
|
|
1446
1682
|
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1447
1683
|
},
|
|
1448
1684
|
gemini: {
|
|
@@ -1456,11 +1692,13 @@ var init_agent_registry = __esm(() => {
|
|
|
1456
1692
|
synthesizeTerminal: false
|
|
1457
1693
|
},
|
|
1458
1694
|
hostHookSource: "gemini",
|
|
1695
|
+
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
1459
1696
|
hookAck: "{}",
|
|
1460
1697
|
approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
|
|
1461
1698
|
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1462
1699
|
}
|
|
1463
1700
|
};
|
|
1701
|
+
REGISTERED_AGENTS = Object.keys(PROFILES);
|
|
1464
1702
|
});
|
|
1465
1703
|
|
|
1466
1704
|
// packages/core/src/agent-resume.ts
|
|
@@ -1518,6 +1756,13 @@ function hookLaunch(input) {
|
|
|
1518
1756
|
function isHookInvocation(args) {
|
|
1519
1757
|
return args[0] === HOOK_ARGV_FLAG && typeof args[1] === "string" && args[1] !== "";
|
|
1520
1758
|
}
|
|
1759
|
+
function mcpLaunch(input) {
|
|
1760
|
+
const base = daemonLaunch(input);
|
|
1761
|
+
return { command: base.command, args: [...base.args.slice(0, -1), MCP_ARGV_FLAG] };
|
|
1762
|
+
}
|
|
1763
|
+
function isMcpInvocation(args) {
|
|
1764
|
+
return args[0] === MCP_ARGV_FLAG;
|
|
1765
|
+
}
|
|
1521
1766
|
function hookPostTarget(env, agent) {
|
|
1522
1767
|
const port = Number(env["AXTERM_HOOK_PORT"]);
|
|
1523
1768
|
const token = env["AXTERM_HOOK_TOKEN"];
|
|
@@ -1540,7 +1785,7 @@ function hookPostTarget(env, agent) {
|
|
|
1540
1785
|
function isDaemonInvocation(args) {
|
|
1541
1786
|
return args[0] === DAEMON_ARGV_FLAG;
|
|
1542
1787
|
}
|
|
1543
|
-
var DAEMON_ARGV_FLAG = "__daemon", HOOK_ARGV_FLAG = "__hook", RUNTIME_NAMES;
|
|
1788
|
+
var DAEMON_ARGV_FLAG = "__daemon", HOOK_ARGV_FLAG = "__hook", MCP_ARGV_FLAG = "__mcp", RUNTIME_NAMES;
|
|
1544
1789
|
var init_self_spawn = __esm(() => {
|
|
1545
1790
|
RUNTIME_NAMES = new Set(["bun", "bun.exe", "bun-debug", "bun-debug.exe", "node", "node.exe"]);
|
|
1546
1791
|
});
|
|
@@ -1619,6 +1864,15 @@ function updateNeeded(current, latest) {
|
|
|
1619
1864
|
return false;
|
|
1620
1865
|
return Bun.semver.order(latest, current) > 0;
|
|
1621
1866
|
}
|
|
1867
|
+
function classifyRuntimeLane(main) {
|
|
1868
|
+
const normalised = main.replace(/\\/g, "/");
|
|
1869
|
+
if (normalised.split("/").includes("node_modules")) {
|
|
1870
|
+
return normalised.includes("/.bun/") ? "bun" : "npm";
|
|
1871
|
+
}
|
|
1872
|
+
if (normalised.endsWith(".ts") || normalised.endsWith(".js"))
|
|
1873
|
+
return "checkout";
|
|
1874
|
+
return "binary";
|
|
1875
|
+
}
|
|
1622
1876
|
function planBinarySwap(input) {
|
|
1623
1877
|
const steps = [];
|
|
1624
1878
|
if (input.platform !== "win32")
|
|
@@ -1628,16 +1882,16 @@ function planBinarySwap(input) {
|
|
|
1628
1882
|
return steps;
|
|
1629
1883
|
}
|
|
1630
1884
|
function planSessionRestore(sessions) {
|
|
1631
|
-
const
|
|
1885
|
+
const live = sessions.filter((s) => s.state !== "exited");
|
|
1632
1886
|
const resumable = (s) => agentProfile(agentIdFromCommand(s.agentCommand)).resume !== undefined;
|
|
1633
1887
|
return {
|
|
1634
|
-
resume:
|
|
1635
|
-
cannotResume:
|
|
1636
|
-
needsConfirmation:
|
|
1888
|
+
resume: live.filter(resumable).map((s) => s.id),
|
|
1889
|
+
cannotResume: live.filter((s) => !resumable(s)),
|
|
1890
|
+
needsConfirmation: live.length > 0
|
|
1637
1891
|
};
|
|
1638
1892
|
}
|
|
1639
1893
|
function planUpdateRestart(input) {
|
|
1640
|
-
const live = input.sessions.filter((s) => s.state
|
|
1894
|
+
const live = input.sessions.filter((s) => s.state !== "exited").length;
|
|
1641
1895
|
if (!input.restart && live > 0) {
|
|
1642
1896
|
return { stopDaemon: false, resume: [], lost: [], stale: live, needsConfirmation: false };
|
|
1643
1897
|
}
|
|
@@ -1785,6 +2039,44 @@ class EchoGuard {
|
|
|
1785
2039
|
this.pending.delete(sessionId);
|
|
1786
2040
|
}
|
|
1787
2041
|
}
|
|
2042
|
+
|
|
2043
|
+
// packages/core/src/ask.ts
|
|
2044
|
+
function answerText(answer) {
|
|
2045
|
+
if (!answer)
|
|
2046
|
+
return "(no answer)";
|
|
2047
|
+
const parts = [...answer.selected];
|
|
2048
|
+
if (answer.customInput !== undefined && answer.customInput.length > 0)
|
|
2049
|
+
parts.push(answer.customInput);
|
|
2050
|
+
return parts.join(", ") || "(no answer)";
|
|
2051
|
+
}
|
|
2052
|
+
function detailsFor(question, answer) {
|
|
2053
|
+
return {
|
|
2054
|
+
...question.id !== undefined ? { id: question.id } : {},
|
|
2055
|
+
question: question.question,
|
|
2056
|
+
options: question.options.map((o) => o.label),
|
|
2057
|
+
multi: question.multi,
|
|
2058
|
+
selectedOptions: answer?.selected ?? [],
|
|
2059
|
+
...answer?.customInput !== undefined ? { customInput: answer.customInput } : {}
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
function formatAskResult(questions, answers) {
|
|
2063
|
+
const byIndex = new Map(answers.map((a) => [a.questionIndex, a]));
|
|
2064
|
+
if (questions.length === 1) {
|
|
2065
|
+
const question = questions[0];
|
|
2066
|
+
return {
|
|
2067
|
+
text: answerText(byIndex.get(0)),
|
|
2068
|
+
details: detailsFor(question, byIndex.get(0))
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
const lines = questions.map((q, i) => `${q.id ?? String(i)}: ${answerText(byIndex.get(i))}`);
|
|
2072
|
+
return {
|
|
2073
|
+
text: `User answers:
|
|
2074
|
+
${lines.join(`
|
|
2075
|
+
`)}`,
|
|
2076
|
+
details: { results: questions.map((q, i) => detailsFor(q, byIndex.get(i))) }
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
|
|
1788
2080
|
// packages/core/src/choice.ts
|
|
1789
2081
|
function buildApprovalQuestion(toolName) {
|
|
1790
2082
|
return {
|
|
@@ -2015,8 +2307,7 @@ function mapClaudeHook(input) {
|
|
|
2015
2307
|
...input.tool_input !== undefined ? { tool_input: input.tool_input } : {}
|
|
2016
2308
|
}
|
|
2017
2309
|
];
|
|
2018
|
-
case "Stop":
|
|
2019
|
-
case "StopFailure": {
|
|
2310
|
+
case "Stop": {
|
|
2020
2311
|
const out = [];
|
|
2021
2312
|
const text = input.last_assistant_message;
|
|
2022
2313
|
if (typeof text === "string" && text.trim() !== "") {
|
|
@@ -2025,6 +2316,19 @@ function mapClaudeHook(input) {
|
|
|
2025
2316
|
out.push({ event: "agent_end" });
|
|
2026
2317
|
return out;
|
|
2027
2318
|
}
|
|
2319
|
+
case "StopFailure": {
|
|
2320
|
+
const parts = [input.error, input.error_details, input.last_assistant_message].filter((part) => typeof part === "string" && part.trim() !== "").map((part) => part.trim());
|
|
2321
|
+
return [
|
|
2322
|
+
{ event: "agent_error", error_kind: "failed", text: parts.join(" \u2014 ") },
|
|
2323
|
+
{ event: "agent_end" }
|
|
2324
|
+
];
|
|
2325
|
+
}
|
|
2326
|
+
case "MessageDisplay": {
|
|
2327
|
+
const delta = input.delta;
|
|
2328
|
+
if (typeof delta !== "string" || delta === "")
|
|
2329
|
+
return [];
|
|
2330
|
+
return [{ event: "activity", activity: "reply", text: delta }];
|
|
2331
|
+
}
|
|
2028
2332
|
case "SessionEnd":
|
|
2029
2333
|
return [
|
|
2030
2334
|
{ event: "session_shutdown", ...input.session_id !== undefined ? { session_id: input.session_id } : {} }
|
|
@@ -2057,16 +2361,26 @@ function buildClaudeSettings(input) {
|
|
|
2057
2361
|
var MAX_RESULT_TEXT = 2000, HOOK_TIMEOUT_SECONDS = 5, TOOL_EVENTS, PLAIN_EVENTS;
|
|
2058
2362
|
var init_claude_hooks = __esm(() => {
|
|
2059
2363
|
TOOL_EVENTS = ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PermissionRequest"];
|
|
2060
|
-
PLAIN_EVENTS = [
|
|
2364
|
+
PLAIN_EVENTS = [
|
|
2365
|
+
"SessionStart",
|
|
2366
|
+
"UserPromptSubmit",
|
|
2367
|
+
"Stop",
|
|
2368
|
+
"StopFailure",
|
|
2369
|
+
"MessageDisplay",
|
|
2370
|
+
"SessionEnd"
|
|
2371
|
+
];
|
|
2061
2372
|
});
|
|
2062
2373
|
|
|
2063
|
-
// packages/core/src/
|
|
2064
|
-
function
|
|
2374
|
+
// packages/core/src/onboarding.ts
|
|
2375
|
+
function detectOnboarding(agent, screen) {
|
|
2376
|
+
const dialogs = DIALOGS[agent];
|
|
2377
|
+
if (dialogs === undefined)
|
|
2378
|
+
return;
|
|
2065
2379
|
const lines = screen.split(`
|
|
2066
2380
|
`).map((line) => line.replace(/^\s*\u2502/, "").replace(/\u2502\s*$/, "").trim());
|
|
2067
2381
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
2068
2382
|
const line = lines[i];
|
|
2069
|
-
const dialog =
|
|
2383
|
+
const dialog = dialogs.find((d) => line.startsWith(d.question));
|
|
2070
2384
|
if (!dialog)
|
|
2071
2385
|
continue;
|
|
2072
2386
|
const options = [];
|
|
@@ -2082,12 +2396,19 @@ function detectGeminiOnboarding(screen) {
|
|
|
2082
2396
|
return;
|
|
2083
2397
|
}
|
|
2084
2398
|
var DIALOGS, OPTION_ROW, OPTION_WINDOW_LINES = 8;
|
|
2085
|
-
var
|
|
2086
|
-
DIALOGS =
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2399
|
+
var init_onboarding = __esm(() => {
|
|
2400
|
+
DIALOGS = {
|
|
2401
|
+
gemini: [
|
|
2402
|
+
{ kind: "folderTrust", question: "Do you trust the files in this folder?" },
|
|
2403
|
+
{ kind: "authMethod", question: "How would you like to authenticate for this project?" }
|
|
2404
|
+
],
|
|
2405
|
+
claude: [
|
|
2406
|
+
{ kind: "apiKey", question: "Do you want to use this API key?" },
|
|
2407
|
+
{ kind: "loginMethod", question: "Select login method:" }
|
|
2408
|
+
],
|
|
2409
|
+
codex: [{ kind: "folderTrust", question: "Do you trust the contents of this directory?" }]
|
|
2410
|
+
};
|
|
2411
|
+
OPTION_ROW = /^[\u25CF\u25CB\u276F>\u203A]?\s*(\d)\.\s+(.+)$/;
|
|
2091
2412
|
});
|
|
2092
2413
|
|
|
2093
2414
|
// packages/core/src/doctor.ts
|
|
@@ -2222,6 +2543,427 @@ var init_doctor = __esm(() => {
|
|
|
2222
2543
|
GEMINI_FORBIDDEN_EVENTS = ["AfterModel", "BeforeModel"];
|
|
2223
2544
|
});
|
|
2224
2545
|
|
|
2546
|
+
// packages/core/src/stall.ts
|
|
2547
|
+
function shouldReportStall(input) {
|
|
2548
|
+
if (input.reported)
|
|
2549
|
+
return false;
|
|
2550
|
+
if (input.agentState !== "working")
|
|
2551
|
+
return false;
|
|
2552
|
+
if (input.quietForMs <= input.thresholdMs)
|
|
2553
|
+
return false;
|
|
2554
|
+
if (input.storeQuietForMs !== undefined && input.storeQuietForMs <= input.thresholdMs)
|
|
2555
|
+
return false;
|
|
2556
|
+
return true;
|
|
2557
|
+
}
|
|
2558
|
+
function quoteScreenTail(screen, maxLines) {
|
|
2559
|
+
const kept = [];
|
|
2560
|
+
const lines = screen.split(`
|
|
2561
|
+
`);
|
|
2562
|
+
for (let i = lines.length - 1;i >= 0 && kept.length < maxLines; i--) {
|
|
2563
|
+
const line = lines[i];
|
|
2564
|
+
if (CHROME_ONLY.test(line))
|
|
2565
|
+
continue;
|
|
2566
|
+
const text = line.replace(EDGE_CHROME, "");
|
|
2567
|
+
if (text === "")
|
|
2568
|
+
continue;
|
|
2569
|
+
kept.push(text);
|
|
2570
|
+
}
|
|
2571
|
+
return kept.reverse();
|
|
2572
|
+
}
|
|
2573
|
+
var CHROME_ONLY, EDGE_CHROME;
|
|
2574
|
+
var init_stall = __esm(() => {
|
|
2575
|
+
CHROME_ONLY = /^[\s\u2502\u2503|\u256D\u256E\u2570\u256F\u251C\u2524\u252C\u2534\u253C\u2500\u2501\u2550\u254C\u254D\u2504\u2505.\u00B7*]*$/u;
|
|
2576
|
+
EDGE_CHROME = /^[\s\u2502\u2503|]+|[\s\u2502\u2503|]+$/gu;
|
|
2577
|
+
});
|
|
2578
|
+
|
|
2579
|
+
// packages/core/src/codex-stream.ts
|
|
2580
|
+
class CodexStreamMapper {
|
|
2581
|
+
finalAnswerItems = new Set;
|
|
2582
|
+
handle(method, params) {
|
|
2583
|
+
switch (method) {
|
|
2584
|
+
case "item/started": {
|
|
2585
|
+
const item = params["item"];
|
|
2586
|
+
if (!isRecord(item) || item["type"] !== "agentMessage")
|
|
2587
|
+
return [];
|
|
2588
|
+
if (item["phase"] === "final_answer" && typeof item["id"] === "string") {
|
|
2589
|
+
this.finalAnswerItems.add(item["id"]);
|
|
2590
|
+
}
|
|
2591
|
+
return [];
|
|
2592
|
+
}
|
|
2593
|
+
case "item/reasoning/summaryTextDelta": {
|
|
2594
|
+
const delta = params["delta"];
|
|
2595
|
+
if (typeof delta !== "string" || delta === "")
|
|
2596
|
+
return [];
|
|
2597
|
+
return [{ event: "activity", activity: "thinking", text: delta }];
|
|
2598
|
+
}
|
|
2599
|
+
case "item/agentMessage/delta": {
|
|
2600
|
+
const delta = params["delta"];
|
|
2601
|
+
const itemId = params["itemId"];
|
|
2602
|
+
if (typeof delta !== "string" || delta === "" || typeof itemId !== "string")
|
|
2603
|
+
return [];
|
|
2604
|
+
if (!this.finalAnswerItems.has(itemId))
|
|
2605
|
+
return [];
|
|
2606
|
+
return [{ event: "activity", activity: "reply", text: delta }];
|
|
2607
|
+
}
|
|
2608
|
+
case "error": {
|
|
2609
|
+
const willRetry = params["willRetry"] === true;
|
|
2610
|
+
const raw = params["error"];
|
|
2611
|
+
const text = typeof raw === "string" ? raw : isRecord(raw) && typeof raw["message"] === "string" ? raw["message"] : JSON.stringify(raw ?? {});
|
|
2612
|
+
return [{ event: "agent_error", error_kind: willRetry ? "retry" : "failed", text }];
|
|
2613
|
+
}
|
|
2614
|
+
case "thread/status/changed": {
|
|
2615
|
+
const status = params["status"];
|
|
2616
|
+
if (isRecord(status) && status["type"] === "idle")
|
|
2617
|
+
this.finalAnswerItems.clear();
|
|
2618
|
+
return [];
|
|
2619
|
+
}
|
|
2620
|
+
default:
|
|
2621
|
+
return [];
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
var init_codex_stream = () => {};
|
|
2626
|
+
|
|
2627
|
+
// packages/core/src/keys.ts
|
|
2628
|
+
function keySequence(name) {
|
|
2629
|
+
return SEQUENCES[name];
|
|
2630
|
+
}
|
|
2631
|
+
function parseKeyNames(input) {
|
|
2632
|
+
const names = input.trim().split(/\s+/).filter((name) => name !== "");
|
|
2633
|
+
if (names.length === 0)
|
|
2634
|
+
return { unknown: "" };
|
|
2635
|
+
const keys = [];
|
|
2636
|
+
for (const name of names) {
|
|
2637
|
+
const sequence = keySequence(name);
|
|
2638
|
+
if (sequence === undefined)
|
|
2639
|
+
return { unknown: name };
|
|
2640
|
+
keys.push(sequence);
|
|
2641
|
+
}
|
|
2642
|
+
return { keys };
|
|
2643
|
+
}
|
|
2644
|
+
var SEQUENCES, KEY_NAMES;
|
|
2645
|
+
var init_keys = __esm(() => {
|
|
2646
|
+
SEQUENCES = {
|
|
2647
|
+
approve: "\r",
|
|
2648
|
+
deny: "\x1B[B\r",
|
|
2649
|
+
enter: "\r",
|
|
2650
|
+
esc: "\x1B",
|
|
2651
|
+
up: "\x1B[A",
|
|
2652
|
+
down: "\x1B[B",
|
|
2653
|
+
left: "\x1B[D",
|
|
2654
|
+
right: "\x1B[C",
|
|
2655
|
+
tab: "\t",
|
|
2656
|
+
space: " ",
|
|
2657
|
+
"ctrl+c": "\x03",
|
|
2658
|
+
"ctrl+d": "\x04",
|
|
2659
|
+
backspace: "\x7F",
|
|
2660
|
+
"shift+tab": "\x1B[Z"
|
|
2661
|
+
};
|
|
2662
|
+
KEY_NAMES = Object.keys(SEQUENCES).sort();
|
|
2663
|
+
});
|
|
2664
|
+
|
|
2665
|
+
// packages/core/src/duration.ts
|
|
2666
|
+
function humanDuration(ms) {
|
|
2667
|
+
if (!Number.isFinite(ms) || ms <= 0)
|
|
2668
|
+
return "0s";
|
|
2669
|
+
const seconds = Math.max(1, Math.round(ms / 1000));
|
|
2670
|
+
if (seconds < 60)
|
|
2671
|
+
return `${seconds}s`;
|
|
2672
|
+
const minutes = Math.round(seconds / 60);
|
|
2673
|
+
if (minutes < 60)
|
|
2674
|
+
return `${minutes}m`;
|
|
2675
|
+
const hours = Math.floor(minutes / 60);
|
|
2676
|
+
if (hours < 24) {
|
|
2677
|
+
const rest = minutes % 60;
|
|
2678
|
+
return rest === 0 ? `${hours}h` : `${hours}h ${rest}m`;
|
|
2679
|
+
}
|
|
2680
|
+
const days = Math.floor(hours / 24);
|
|
2681
|
+
const restHours = hours % 24;
|
|
2682
|
+
return restHours === 0 ? `${days}d` : `${days}d ${restHours}h`;
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
// packages/core/src/quota.ts
|
|
2686
|
+
function quotaReport(facts, nowMs) {
|
|
2687
|
+
const out = [];
|
|
2688
|
+
for (const fact of facts) {
|
|
2689
|
+
if (fact.kind === "none") {
|
|
2690
|
+
out.push({ id: `quota.${fact.agent}`, status: "info", key: "quota.unsupported", params: { agent: fact.agent } });
|
|
2691
|
+
continue;
|
|
2692
|
+
}
|
|
2693
|
+
if (fact.kind === "event") {
|
|
2694
|
+
if (!fact.event) {
|
|
2695
|
+
out.push({ id: `quota.${fact.agent}`, status: "ok", key: "quota.eventNone", params: { agent: fact.agent } });
|
|
2696
|
+
continue;
|
|
2697
|
+
}
|
|
2698
|
+
out.push({
|
|
2699
|
+
id: `quota.${fact.agent}`,
|
|
2700
|
+
status: "warn",
|
|
2701
|
+
key: "quota.event",
|
|
2702
|
+
params: {
|
|
2703
|
+
agent: fact.agent,
|
|
2704
|
+
age: humanDuration(Math.max(0, nowMs - fact.event.at)),
|
|
2705
|
+
text: fact.event.text
|
|
2706
|
+
}
|
|
2707
|
+
});
|
|
2708
|
+
continue;
|
|
2709
|
+
}
|
|
2710
|
+
const reading = fact.reading;
|
|
2711
|
+
if (!reading || reading.windows.length === 0) {
|
|
2712
|
+
out.push({ id: `quota.${fact.agent}`, status: "info", key: "quota.noData", params: { agent: fact.agent } });
|
|
2713
|
+
continue;
|
|
2714
|
+
}
|
|
2715
|
+
const age = nowMs - reading.recordedAt;
|
|
2716
|
+
reading.windows.forEach((window2, index) => {
|
|
2717
|
+
const params = {
|
|
2718
|
+
agent: fact.agent,
|
|
2719
|
+
percent: window2.usedPercent,
|
|
2720
|
+
window: window2.label ?? (window2.windowMinutes === undefined ? "?" : humanDuration(window2.windowMinutes * 60000)),
|
|
2721
|
+
resets: humanDuration(Math.max(0, window2.resetsAt - nowMs)),
|
|
2722
|
+
plan: reading.planType ?? "",
|
|
2723
|
+
age: humanDuration(Math.max(0, age))
|
|
2724
|
+
};
|
|
2725
|
+
const id = reading.windows.length === 1 ? `quota.${fact.agent}` : `quota.${fact.agent}.${index}`;
|
|
2726
|
+
if (reading.limitReached) {
|
|
2727
|
+
out.push({ id, status: "warn", key: "quota.reached", params });
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2730
|
+
if (window2.resetsAt <= nowMs) {
|
|
2731
|
+
out.push({ id, status: "info", key: "quota.gaugeElapsed", params });
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
out.push({ id, status: "ok", key: age > STALE_AFTER_MS ? "quota.gaugeStale" : "quota.gauge", params });
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
return out;
|
|
2738
|
+
}
|
|
2739
|
+
var STALE_AFTER_MS = 3600000;
|
|
2740
|
+
var init_quota = () => {};
|
|
2741
|
+
|
|
2742
|
+
// packages/core/src/gemini-telemetry.ts
|
|
2743
|
+
function splitTelemetryRecords(buffer) {
|
|
2744
|
+
if (buffer === "")
|
|
2745
|
+
return { complete: [], remainder: "" };
|
|
2746
|
+
const chunks = buffer.split(/\n(?=\{)/);
|
|
2747
|
+
const last = chunks.pop() ?? "";
|
|
2748
|
+
const complete = chunks.filter((chunk) => chunk.trim() !== "");
|
|
2749
|
+
if (/\n\s*$/.test(last) || tryParse(last) !== undefined) {
|
|
2750
|
+
if (last.trim() !== "")
|
|
2751
|
+
complete.push(last);
|
|
2752
|
+
return { complete, remainder: "" };
|
|
2753
|
+
}
|
|
2754
|
+
return { complete, remainder: last };
|
|
2755
|
+
}
|
|
2756
|
+
function tryParse(text) {
|
|
2757
|
+
if (text.trim() === "")
|
|
2758
|
+
return;
|
|
2759
|
+
try {
|
|
2760
|
+
const value = JSON.parse(text);
|
|
2761
|
+
return isRecord(value) ? value : undefined;
|
|
2762
|
+
} catch {
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
function attributesOf(record) {
|
|
2767
|
+
const attributes = record["attributes"];
|
|
2768
|
+
if (Array.isArray(attributes)) {
|
|
2769
|
+
const out = {};
|
|
2770
|
+
for (const entry of attributes) {
|
|
2771
|
+
if (Array.isArray(entry) && typeof entry[0] === "string")
|
|
2772
|
+
out[entry[0]] = entry[1];
|
|
2773
|
+
}
|
|
2774
|
+
return out;
|
|
2775
|
+
}
|
|
2776
|
+
return isRecord(attributes) ? attributes : {};
|
|
2777
|
+
}
|
|
2778
|
+
function parseGeminiTelemetry(records) {
|
|
2779
|
+
const failures = [];
|
|
2780
|
+
for (const raw of records) {
|
|
2781
|
+
const record = tryParse(raw);
|
|
2782
|
+
if (record === undefined)
|
|
2783
|
+
continue;
|
|
2784
|
+
const attributes = attributesOf(record);
|
|
2785
|
+
if (attributes["event.name"] !== "gemini_cli.api_error")
|
|
2786
|
+
continue;
|
|
2787
|
+
const type = typeof attributes["error.type"] === "string" ? attributes["error.type"] : "";
|
|
2788
|
+
const message = typeof attributes["error.message"] === "string" ? attributes["error.message"] : "";
|
|
2789
|
+
const timestamp = attributes["event.timestamp"];
|
|
2790
|
+
const at = typeof timestamp === "string" ? Date.parse(timestamp) : Number.NaN;
|
|
2791
|
+
const status = attributes["status_code"];
|
|
2792
|
+
failures.push({
|
|
2793
|
+
...Number.isFinite(at) ? { at } : {},
|
|
2794
|
+
...typeof status === "number" ? { status } : {},
|
|
2795
|
+
text: [type, message].filter((part) => part !== "").join(": ")
|
|
2796
|
+
});
|
|
2797
|
+
}
|
|
2798
|
+
return failures;
|
|
2799
|
+
}
|
|
2800
|
+
var init_gemini_telemetry = () => {};
|
|
2801
|
+
|
|
2802
|
+
// packages/core/src/mcp-ask.ts
|
|
2803
|
+
function fieldName(index) {
|
|
2804
|
+
return `q${index}`;
|
|
2805
|
+
}
|
|
2806
|
+
function elicitationSchema(questions) {
|
|
2807
|
+
const properties = {};
|
|
2808
|
+
const required = [];
|
|
2809
|
+
questions.forEach((question, index) => {
|
|
2810
|
+
const labels = question.options.map((option) => option.label).filter((label) => label !== "");
|
|
2811
|
+
properties[fieldName(index)] = {
|
|
2812
|
+
type: "string",
|
|
2813
|
+
title: question.question,
|
|
2814
|
+
...labels.length > 0 ? { enum: labels } : {}
|
|
2815
|
+
};
|
|
2816
|
+
required.push(fieldName(index));
|
|
2817
|
+
});
|
|
2818
|
+
return { type: "object", properties, required };
|
|
2819
|
+
}
|
|
2820
|
+
function answersFromElicitation(questions, result) {
|
|
2821
|
+
if (!result || result.action !== "accept" || !isRecord(result.content))
|
|
2822
|
+
return;
|
|
2823
|
+
const answers = [];
|
|
2824
|
+
questions.forEach((question, index) => {
|
|
2825
|
+
const value = result.content?.[fieldName(index)];
|
|
2826
|
+
if (typeof value !== "string" || value === "")
|
|
2827
|
+
return;
|
|
2828
|
+
const matched = question.options.some((option) => option.label === value);
|
|
2829
|
+
answers.push(matched ? { questionIndex: index, selected: [value] } : { questionIndex: index, selected: [], customInput: value });
|
|
2830
|
+
});
|
|
2831
|
+
return answers.length > 0 ? answers : undefined;
|
|
2832
|
+
}
|
|
2833
|
+
var init_mcp_ask = () => {};
|
|
2834
|
+
|
|
2835
|
+
// packages/core/src/store-locate.ts
|
|
2836
|
+
function claudeHash(text) {
|
|
2837
|
+
let hash = 0;
|
|
2838
|
+
for (let i = 0;i < text.length; i++)
|
|
2839
|
+
hash = (hash << 5) - hash + text.charCodeAt(i) | 0;
|
|
2840
|
+
return hash;
|
|
2841
|
+
}
|
|
2842
|
+
function claudeProjectKey(cwd) {
|
|
2843
|
+
const key = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
2844
|
+
if (key.length <= CLAUDE_KEY_LIMIT)
|
|
2845
|
+
return key;
|
|
2846
|
+
return `${key.slice(0, CLAUDE_KEY_LIMIT)}-${Math.abs(claudeHash(cwd)).toString(36)}`;
|
|
2847
|
+
}
|
|
2848
|
+
function claudeTranscriptRelPath(cwd, sessionId) {
|
|
2849
|
+
return `projects/${claudeProjectKey(cwd)}/${sessionId}.jsonl`;
|
|
2850
|
+
}
|
|
2851
|
+
function uuidV7TimeMs(id) {
|
|
2852
|
+
const hex = id.replace(/-/g, "");
|
|
2853
|
+
if (!/^[0-9a-f]{32}$/i.test(hex))
|
|
2854
|
+
return;
|
|
2855
|
+
const ms = Number.parseInt(hex.slice(0, 12), 16);
|
|
2856
|
+
return Number.isFinite(ms) && ms > 0 ? ms : undefined;
|
|
2857
|
+
}
|
|
2858
|
+
function shard(date) {
|
|
2859
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
2860
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
2861
|
+
return `${date.getFullYear()}/${month}/${day}`;
|
|
2862
|
+
}
|
|
2863
|
+
function codexRolloutShards(sessionId) {
|
|
2864
|
+
const ms = uuidV7TimeMs(sessionId);
|
|
2865
|
+
if (ms === undefined)
|
|
2866
|
+
return [];
|
|
2867
|
+
return [shard(new Date(ms)), shard(new Date(ms - 86400000)), shard(new Date(ms + 86400000))];
|
|
2868
|
+
}
|
|
2869
|
+
var CLAUDE_KEY_LIMIT = 200;
|
|
2870
|
+
|
|
2871
|
+
// packages/core/src/store-quota.ts
|
|
2872
|
+
function parseLine(line) {
|
|
2873
|
+
if (line.trim() === "")
|
|
2874
|
+
return;
|
|
2875
|
+
try {
|
|
2876
|
+
const value = JSON.parse(line);
|
|
2877
|
+
return isRecord(value) ? value : undefined;
|
|
2878
|
+
} catch {
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
function numberOf(value) {
|
|
2883
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
2884
|
+
}
|
|
2885
|
+
function codexWindow(value) {
|
|
2886
|
+
if (!isRecord(value))
|
|
2887
|
+
return;
|
|
2888
|
+
const usedPercent = numberOf(value["used_percent"]);
|
|
2889
|
+
const resetsAtSeconds = numberOf(value["resets_at"]);
|
|
2890
|
+
if (usedPercent === undefined || resetsAtSeconds === undefined)
|
|
2891
|
+
return;
|
|
2892
|
+
const windowMinutes = numberOf(value["window_minutes"]);
|
|
2893
|
+
return {
|
|
2894
|
+
usedPercent,
|
|
2895
|
+
resetsAt: resetsAtSeconds * 1000,
|
|
2896
|
+
...windowMinutes === undefined ? {} : { windowMinutes }
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
function parseCodexQuota(lines) {
|
|
2900
|
+
for (let i = lines.length - 1;i >= 0; i--) {
|
|
2901
|
+
const record = parseLine(lines[i]);
|
|
2902
|
+
const payload = record?.["payload"];
|
|
2903
|
+
if (!isRecord(payload) || payload["type"] !== "token_count")
|
|
2904
|
+
continue;
|
|
2905
|
+
const limits = payload["rate_limits"];
|
|
2906
|
+
if (!isRecord(limits))
|
|
2907
|
+
continue;
|
|
2908
|
+
const windows = [];
|
|
2909
|
+
for (const key of ["primary", "secondary"]) {
|
|
2910
|
+
const window2 = codexWindow(limits[key]);
|
|
2911
|
+
if (window2)
|
|
2912
|
+
windows.push(window2);
|
|
2913
|
+
}
|
|
2914
|
+
if (windows.length === 0)
|
|
2915
|
+
continue;
|
|
2916
|
+
const recordedAt = Date.parse(String(record?.["timestamp"] ?? ""));
|
|
2917
|
+
const planType = limits["plan_type"];
|
|
2918
|
+
return {
|
|
2919
|
+
recordedAt: Number.isFinite(recordedAt) ? recordedAt : 0,
|
|
2920
|
+
windows,
|
|
2921
|
+
limitReached: limits["rate_limit_reached_type"] != null || limits["spend_control_reached"] === true,
|
|
2922
|
+
...typeof planType === "string" ? { planType } : {}
|
|
2923
|
+
};
|
|
2924
|
+
}
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
function parseOmpQuota(rows) {
|
|
2928
|
+
const windows = [];
|
|
2929
|
+
let recordedAt = 0;
|
|
2930
|
+
let limitReached = false;
|
|
2931
|
+
for (const row of rows) {
|
|
2932
|
+
if (row.used_fraction === null || row.resets_at === null)
|
|
2933
|
+
continue;
|
|
2934
|
+
windows.push({
|
|
2935
|
+
usedPercent: Math.round(row.used_fraction * 100),
|
|
2936
|
+
label: row.label,
|
|
2937
|
+
resetsAt: row.resets_at
|
|
2938
|
+
});
|
|
2939
|
+
recordedAt = Math.max(recordedAt, row.recorded_at);
|
|
2940
|
+
if (row.status !== null && row.status !== "ok")
|
|
2941
|
+
limitReached = true;
|
|
2942
|
+
}
|
|
2943
|
+
return windows.length === 0 ? undefined : { recordedAt, windows, limitReached };
|
|
2944
|
+
}
|
|
2945
|
+
function parseClaudeLimitEvent(lines) {
|
|
2946
|
+
for (let i = lines.length - 1;i >= 0; i--) {
|
|
2947
|
+
const record = parseLine(lines[i]);
|
|
2948
|
+
if (!record || record["error"] !== "rate_limit")
|
|
2949
|
+
continue;
|
|
2950
|
+
const at = Date.parse(String(record["timestamp"] ?? ""));
|
|
2951
|
+
const message = record["message"];
|
|
2952
|
+
let text = "";
|
|
2953
|
+
if (isRecord(message) && Array.isArray(message["content"])) {
|
|
2954
|
+
for (const block of message["content"]) {
|
|
2955
|
+
if (isRecord(block) && typeof block["text"] === "string")
|
|
2956
|
+
text += block["text"];
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
if (text === "")
|
|
2960
|
+
continue;
|
|
2961
|
+
return { at: Number.isFinite(at) ? at : 0, text };
|
|
2962
|
+
}
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
var init_store_quota = () => {};
|
|
2966
|
+
|
|
2225
2967
|
// packages/core/src/version.ts
|
|
2226
2968
|
function setProductVersion(version) {
|
|
2227
2969
|
product = version;
|
|
@@ -2441,6 +3183,19 @@ function errorText(value) {
|
|
|
2441
3183
|
return message.slice(0, MAX_RESULT_TEXT2);
|
|
2442
3184
|
return llmContentText(value);
|
|
2443
3185
|
}
|
|
3186
|
+
function permissionToolName(details) {
|
|
3187
|
+
if (!isRecord(details))
|
|
3188
|
+
return;
|
|
3189
|
+
const pick = (key) => {
|
|
3190
|
+
const value = details[key];
|
|
3191
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
3192
|
+
};
|
|
3193
|
+
if (details["type"] === "mcp")
|
|
3194
|
+
return pick("toolName") ?? pick("toolDisplayName") ?? pick("title");
|
|
3195
|
+
if (details["type"] === "exec")
|
|
3196
|
+
return pick("rootCommand") ?? pick("command") ?? pick("title");
|
|
3197
|
+
return pick("title");
|
|
3198
|
+
}
|
|
2444
3199
|
function mapGeminiHook(input) {
|
|
2445
3200
|
switch (input.hook_event_name) {
|
|
2446
3201
|
case "SessionStart":
|
|
@@ -2473,15 +3228,17 @@ function mapGeminiHook(input) {
|
|
|
2473
3228
|
}
|
|
2474
3229
|
return out;
|
|
2475
3230
|
}
|
|
2476
|
-
case "Notification":
|
|
3231
|
+
case "Notification": {
|
|
2477
3232
|
if (input.notification_type !== "ToolPermission")
|
|
2478
3233
|
return [];
|
|
3234
|
+
const toolName = permissionToolName(input.details);
|
|
2479
3235
|
return [
|
|
2480
3236
|
{
|
|
2481
3237
|
event: "tool_approval_requested",
|
|
2482
|
-
...
|
|
3238
|
+
...toolName !== undefined ? { tool_name: toolName } : {}
|
|
2483
3239
|
}
|
|
2484
3240
|
];
|
|
3241
|
+
}
|
|
2485
3242
|
case "AfterAgent": {
|
|
2486
3243
|
const out = [];
|
|
2487
3244
|
const text = input.prompt_response;
|
|
@@ -2513,7 +3270,10 @@ function buildGeminiSettings(input) {
|
|
|
2513
3270
|
const hooks = {};
|
|
2514
3271
|
for (const event of GEMINI_EVENTS)
|
|
2515
3272
|
hooks[event] = [{ hooks: [handler] }];
|
|
2516
|
-
return {
|
|
3273
|
+
return {
|
|
3274
|
+
hooks,
|
|
3275
|
+
...input.mcp === undefined ? {} : { mcpServers: { [MCP_SERVER_NAME]: mcpServerEntry(input.mcp, input.mcpContextPath) } }
|
|
3276
|
+
};
|
|
2517
3277
|
}
|
|
2518
3278
|
var MAX_RESULT_TEXT2 = 2000, HOOK_TIMEOUT_MS = 2000, GEMINI_EVENTS;
|
|
2519
3279
|
var init_gemini_hooks = __esm(() => {
|
|
@@ -2528,65 +3288,6 @@ var init_gemini_hooks = __esm(() => {
|
|
|
2528
3288
|
];
|
|
2529
3289
|
});
|
|
2530
3290
|
|
|
2531
|
-
// packages/core/src/agent-launch.ts
|
|
2532
|
-
function uuidFromSessionId(sessionId) {
|
|
2533
|
-
const hex = sessionId.replace(/-/g, "");
|
|
2534
|
-
if (hex.length !== 32)
|
|
2535
|
-
throw new Error(`session id is not 32 hex characters: ${sessionId}`);
|
|
2536
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
2537
|
-
}
|
|
2538
|
-
function buildAgentCommand(input) {
|
|
2539
|
-
const extra = [...input.extraArgs ?? []];
|
|
2540
|
-
if (input.agent === "claude") {
|
|
2541
|
-
return [
|
|
2542
|
-
"claude",
|
|
2543
|
-
"--settings",
|
|
2544
|
-
input.claudeSettingsPath,
|
|
2545
|
-
"--session-id",
|
|
2546
|
-
uuidFromSessionId(input.sessionId),
|
|
2547
|
-
...extra
|
|
2548
|
-
];
|
|
2549
|
-
}
|
|
2550
|
-
if (input.agent === "gemini") {
|
|
2551
|
-
return [
|
|
2552
|
-
"gemini",
|
|
2553
|
-
"--session-id",
|
|
2554
|
-
input.sessionId,
|
|
2555
|
-
...extra
|
|
2556
|
-
];
|
|
2557
|
-
}
|
|
2558
|
-
if (input.agent === "codex") {
|
|
2559
|
-
return ["codex", ...extra];
|
|
2560
|
-
}
|
|
2561
|
-
return ["omp", "--extension", input.extensionPath, ...extra];
|
|
2562
|
-
}
|
|
2563
|
-
function buildAgentResumeCommand(input) {
|
|
2564
|
-
if (input.agent === "claude") {
|
|
2565
|
-
return [
|
|
2566
|
-
"claude",
|
|
2567
|
-
"--settings",
|
|
2568
|
-
input.claudeSettingsPath,
|
|
2569
|
-
"--resume",
|
|
2570
|
-
uuidFromSessionId(input.sessionId),
|
|
2571
|
-
...input.extraArgs ?? []
|
|
2572
|
-
];
|
|
2573
|
-
}
|
|
2574
|
-
if (input.agent === "gemini") {
|
|
2575
|
-
return ["gemini", "--resume", input.sessionId, ...input.extraArgs ?? []];
|
|
2576
|
-
}
|
|
2577
|
-
return buildAgentCommand(input);
|
|
2578
|
-
}
|
|
2579
|
-
function isAgentId(value) {
|
|
2580
|
-
return AGENT_IDS.includes(value);
|
|
2581
|
-
}
|
|
2582
|
-
function supportedAgents() {
|
|
2583
|
-
return AGENT_IDS;
|
|
2584
|
-
}
|
|
2585
|
-
var AGENT_IDS;
|
|
2586
|
-
var init_agent_launch = __esm(() => {
|
|
2587
|
-
AGENT_IDS = ["omp", "claude", "codex", "gemini"];
|
|
2588
|
-
});
|
|
2589
|
-
|
|
2590
3291
|
// packages/core/src/dotenv-scrub.ts
|
|
2591
3292
|
function dotenvCandidates(nodeEnv) {
|
|
2592
3293
|
const mode = nodeEnv === undefined || nodeEnv === "" ? "development" : nodeEnv;
|
|
@@ -2638,21 +3339,6 @@ var init_dotenv_scrub = __esm(() => {
|
|
|
2638
3339
|
LINE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
|
|
2639
3340
|
});
|
|
2640
3341
|
|
|
2641
|
-
// packages/core/src/duration.ts
|
|
2642
|
-
function humanDuration(ms) {
|
|
2643
|
-
if (!Number.isFinite(ms) || ms <= 0)
|
|
2644
|
-
return "0s";
|
|
2645
|
-
const seconds = Math.max(1, Math.round(ms / 1000));
|
|
2646
|
-
if (seconds < 60)
|
|
2647
|
-
return `${seconds}s`;
|
|
2648
|
-
const minutes = Math.round(seconds / 60);
|
|
2649
|
-
if (minutes < 60)
|
|
2650
|
-
return `${minutes}m`;
|
|
2651
|
-
const hours = Math.floor(minutes / 60);
|
|
2652
|
-
const rest = minutes % 60;
|
|
2653
|
-
return rest === 0 ? `${hours}h` : `${hours}h ${rest}m`;
|
|
2654
|
-
}
|
|
2655
|
-
|
|
2656
3342
|
// packages/core/src/idle-screen.ts
|
|
2657
3343
|
function screenHash(screen) {
|
|
2658
3344
|
const normalised = screen.split(`
|
|
@@ -2725,8 +3411,26 @@ function sessionInScope(session, scope, resolve = asGiven) {
|
|
|
2725
3411
|
return true;
|
|
2726
3412
|
return session.repoRoot !== undefined && pathWithin(scope, session.repoRoot, resolve);
|
|
2727
3413
|
}
|
|
2728
|
-
function sessionsInScope(sessions, scope, resolve = asGiven) {
|
|
2729
|
-
return sessions.filter((session) => sessionInScope(session, scope, resolve));
|
|
3414
|
+
function sessionsInScope(sessions, scope, resolve = asGiven) {
|
|
3415
|
+
return sessions.filter((session) => sessionInScope(session, scope, resolve));
|
|
3416
|
+
}
|
|
3417
|
+
function mostRecentSession(sessions) {
|
|
3418
|
+
let newest;
|
|
3419
|
+
for (const session of sessions) {
|
|
3420
|
+
if (session.state === "exited")
|
|
3421
|
+
continue;
|
|
3422
|
+
if (newest === undefined || session.createdAt > newest.createdAt)
|
|
3423
|
+
newest = session;
|
|
3424
|
+
}
|
|
3425
|
+
return newest;
|
|
3426
|
+
}
|
|
3427
|
+
function sessionsByRecency(live, stored) {
|
|
3428
|
+
const byId = new Map;
|
|
3429
|
+
for (const session of stored)
|
|
3430
|
+
byId.set(session.id, session);
|
|
3431
|
+
for (const session of live)
|
|
3432
|
+
byId.set(session.id, session);
|
|
3433
|
+
return [...byId.values()].sort((a, b) => b.createdAt - a.createdAt);
|
|
2730
3434
|
}
|
|
2731
3435
|
var asGiven = (target) => target;
|
|
2732
3436
|
var init_session_scope = () => {};
|
|
@@ -2760,8 +3464,15 @@ var init_src = __esm(() => {
|
|
|
2760
3464
|
init_agent_status();
|
|
2761
3465
|
init_activity();
|
|
2762
3466
|
init_claude_hooks();
|
|
2763
|
-
|
|
3467
|
+
init_onboarding();
|
|
2764
3468
|
init_doctor();
|
|
3469
|
+
init_stall();
|
|
3470
|
+
init_codex_stream();
|
|
3471
|
+
init_keys();
|
|
3472
|
+
init_quota();
|
|
3473
|
+
init_gemini_telemetry();
|
|
3474
|
+
init_mcp_ask();
|
|
3475
|
+
init_store_quota();
|
|
2765
3476
|
init_telegram_discovery();
|
|
2766
3477
|
init_codex_hooks();
|
|
2767
3478
|
init_gemini_hooks();
|
|
@@ -2774,14 +3485,14 @@ var init_src = __esm(() => {
|
|
|
2774
3485
|
});
|
|
2775
3486
|
|
|
2776
3487
|
// packages/daemon/src/config-store.ts
|
|
2777
|
-
import
|
|
2778
|
-
import
|
|
3488
|
+
import fs5 from "fs";
|
|
3489
|
+
import path6 from "path";
|
|
2779
3490
|
function writeAtomic(target, contents, mode) {
|
|
2780
3491
|
const tmp = `${target}.tmp`;
|
|
2781
|
-
|
|
2782
|
-
|
|
3492
|
+
fs5.writeFileSync(tmp, contents, { mode });
|
|
3493
|
+
fs5.renameSync(tmp, target);
|
|
2783
3494
|
if (process.platform !== "win32")
|
|
2784
|
-
|
|
3495
|
+
fs5.chmodSync(target, mode);
|
|
2785
3496
|
}
|
|
2786
3497
|
|
|
2787
3498
|
class ConfigStore {
|
|
@@ -2797,16 +3508,16 @@ class ConfigStore {
|
|
|
2797
3508
|
}
|
|
2798
3509
|
load() {
|
|
2799
3510
|
this.loadError = undefined;
|
|
2800
|
-
|
|
3511
|
+
fs5.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
|
|
2801
3512
|
if (process.platform !== "win32") {
|
|
2802
3513
|
try {
|
|
2803
|
-
|
|
3514
|
+
fs5.chmodSync(this.paths.configDir, DIR_MODE);
|
|
2804
3515
|
} catch {}
|
|
2805
3516
|
}
|
|
2806
3517
|
this.config = structuredClone(DEFAULT_CONFIG);
|
|
2807
|
-
if (
|
|
3518
|
+
if (fs5.existsSync(this.paths.configFile)) {
|
|
2808
3519
|
try {
|
|
2809
|
-
const parsed = parseConfig(JSON.parse(
|
|
3520
|
+
const parsed = parseConfig(JSON.parse(fs5.readFileSync(this.paths.configFile, "utf8")));
|
|
2810
3521
|
if ("errors" in parsed)
|
|
2811
3522
|
this.loadError = parsed.errors.join("; ");
|
|
2812
3523
|
else
|
|
@@ -2816,10 +3527,10 @@ class ConfigStore {
|
|
|
2816
3527
|
}
|
|
2817
3528
|
}
|
|
2818
3529
|
this.secrets = {};
|
|
2819
|
-
if (
|
|
3530
|
+
if (fs5.existsSync(this.paths.secretsFile)) {
|
|
2820
3531
|
this.tightenPermissions(this.paths.secretsFile);
|
|
2821
3532
|
try {
|
|
2822
|
-
const raw = JSON.parse(
|
|
3533
|
+
const raw = JSON.parse(fs5.readFileSync(this.paths.secretsFile, "utf8"));
|
|
2823
3534
|
if (raw !== null && typeof raw === "object") {
|
|
2824
3535
|
for (const [key, value] of Object.entries(raw)) {
|
|
2825
3536
|
if (typeof value === "string")
|
|
@@ -2835,8 +3546,8 @@ class ConfigStore {
|
|
|
2835
3546
|
if (process.platform === "win32")
|
|
2836
3547
|
return;
|
|
2837
3548
|
try {
|
|
2838
|
-
if ((
|
|
2839
|
-
|
|
3549
|
+
if ((fs5.statSync(file).mode & 63) !== 0)
|
|
3550
|
+
fs5.chmodSync(file, FILE_MODE);
|
|
2840
3551
|
} catch {}
|
|
2841
3552
|
}
|
|
2842
3553
|
get() {
|
|
@@ -2844,7 +3555,7 @@ class ConfigStore {
|
|
|
2844
3555
|
}
|
|
2845
3556
|
health() {
|
|
2846
3557
|
return configHealth({
|
|
2847
|
-
fileExists:
|
|
3558
|
+
fileExists: fs5.existsSync(this.paths.configFile),
|
|
2848
3559
|
parseError: this.loadError,
|
|
2849
3560
|
config: this.resolved()
|
|
2850
3561
|
});
|
|
@@ -2889,7 +3600,7 @@ class ConfigStore {
|
|
|
2889
3600
|
delete this.secrets[dottedPath];
|
|
2890
3601
|
else
|
|
2891
3602
|
this.secrets[dottedPath] = value;
|
|
2892
|
-
if (!
|
|
3603
|
+
if (!fs5.existsSync(this.paths.configFile)) {
|
|
2893
3604
|
writeAtomic(this.paths.configFile, `${JSON.stringify(this.config, null, 2)}
|
|
2894
3605
|
`, FILE_MODE);
|
|
2895
3606
|
}
|
|
@@ -2919,7 +3630,7 @@ class ConfigStore {
|
|
|
2919
3630
|
fingerprint() {
|
|
2920
3631
|
const stamp = (file) => {
|
|
2921
3632
|
try {
|
|
2922
|
-
return String(
|
|
3633
|
+
return String(fs5.statSync(file).mtimeMs);
|
|
2923
3634
|
} catch {
|
|
2924
3635
|
return "-";
|
|
2925
3636
|
}
|
|
@@ -2941,8 +3652,8 @@ function applyPath(config, dottedPath, value) {
|
|
|
2941
3652
|
function defaultConfigStorePaths(configDir) {
|
|
2942
3653
|
return {
|
|
2943
3654
|
configDir,
|
|
2944
|
-
configFile:
|
|
2945
|
-
secretsFile:
|
|
3655
|
+
configFile: path6.join(configDir, "config.json"),
|
|
3656
|
+
secretsFile: path6.join(configDir, "secrets.json")
|
|
2946
3657
|
};
|
|
2947
3658
|
}
|
|
2948
3659
|
var ENV_ALIASES, DIR_MODE = 448, FILE_MODE = 384;
|
|
@@ -2961,13 +3672,13 @@ var init_config_store = __esm(() => {
|
|
|
2961
3672
|
});
|
|
2962
3673
|
|
|
2963
3674
|
// packages/daemon/src/shell.ts
|
|
2964
|
-
import
|
|
2965
|
-
import
|
|
3675
|
+
import fs6 from "fs";
|
|
3676
|
+
import path7 from "path";
|
|
2966
3677
|
function findExecutable(name, env) {
|
|
2967
3678
|
const isWindows = env.platform === "win32";
|
|
2968
|
-
const join = isWindows ?
|
|
3679
|
+
const join = isWindows ? path7.win32.join : path7.posix.join;
|
|
2969
3680
|
const separator = isWindows ? ";" : ":";
|
|
2970
|
-
const extensions = isWindows ?
|
|
3681
|
+
const extensions = isWindows ? path7.win32.extname(name).length > 0 ? [""] : env.pathExt.split(";").filter((e) => e.length > 0) : [""];
|
|
2971
3682
|
for (const rawDir of env.pathEnv.split(separator)) {
|
|
2972
3683
|
const dir = rawDir.trim().replace(/^"|"$/g, "");
|
|
2973
3684
|
if (dir.length === 0)
|
|
@@ -2985,16 +3696,16 @@ function lookupOnPath(name) {
|
|
|
2985
3696
|
platform: process.platform,
|
|
2986
3697
|
pathEnv: process.env["PATH"] ?? "",
|
|
2987
3698
|
pathExt: process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD",
|
|
2988
|
-
exists: (candidate) =>
|
|
3699
|
+
exists: (candidate) => fs6.existsSync(candidate)
|
|
2989
3700
|
});
|
|
2990
3701
|
}
|
|
2991
3702
|
function resolveShell(kind) {
|
|
2992
3703
|
if (process.platform !== "win32")
|
|
2993
3704
|
return resolvePosixShell(kind);
|
|
2994
3705
|
const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows";
|
|
2995
|
-
const windowsPowershell =
|
|
3706
|
+
const windowsPowershell = path7.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
2996
3707
|
if (kind === "cmd") {
|
|
2997
|
-
return { kind: "cmd", exe:
|
|
3708
|
+
return { kind: "cmd", exe: path7.join(systemRoot, "System32", "cmd.exe") };
|
|
2998
3709
|
}
|
|
2999
3710
|
if (kind === "powershell") {
|
|
3000
3711
|
return { kind: "powershell", exe: windowsPowershell };
|
|
@@ -3022,7 +3733,7 @@ function resolvePosixShell(kind) {
|
|
|
3022
3733
|
candidates.push({ kind: "bash", exe: login });
|
|
3023
3734
|
candidates.push({ kind: "bash", exe: "/bin/bash" }, { kind: "bash", exe: "/usr/bin/bash" });
|
|
3024
3735
|
}
|
|
3025
|
-
const found = candidates.find((c) =>
|
|
3736
|
+
const found = candidates.find((c) => fs6.existsSync(c.exe));
|
|
3026
3737
|
if (found)
|
|
3027
3738
|
return found;
|
|
3028
3739
|
return { kind: "sh", exe: "/bin/sh" };
|
|
@@ -3067,24 +3778,24 @@ function ipcEndpoint(platform, user, env) {
|
|
|
3067
3778
|
const suffix = `-${shortHash(env.runtimeDir)}`;
|
|
3068
3779
|
return `\\\\.\\pipe\\axterm-${safeUser}${suffix}`;
|
|
3069
3780
|
}
|
|
3070
|
-
return
|
|
3781
|
+
return path7.posix.join(env.runtimeDir, "axterm.sock");
|
|
3071
3782
|
}
|
|
3072
3783
|
var cachedPwsh;
|
|
3073
3784
|
var init_shell = () => {};
|
|
3074
3785
|
|
|
3075
3786
|
// packages/daemon/src/paths.ts
|
|
3076
|
-
import
|
|
3077
|
-
import
|
|
3078
|
-
import
|
|
3787
|
+
import fs7 from "fs";
|
|
3788
|
+
import os3 from "os";
|
|
3789
|
+
import path8 from "path";
|
|
3079
3790
|
function ensureDataDirs() {
|
|
3080
3791
|
for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir, SCROLLBACK_DIR, LOG_DIR, EXT_DIR]) {
|
|
3081
|
-
|
|
3792
|
+
fs7.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
|
|
3082
3793
|
}
|
|
3083
3794
|
if (process.platform === "win32")
|
|
3084
3795
|
return;
|
|
3085
3796
|
for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir]) {
|
|
3086
3797
|
try {
|
|
3087
|
-
|
|
3798
|
+
fs7.chmodSync(dir, DIR_MODE2);
|
|
3088
3799
|
} catch {}
|
|
3089
3800
|
}
|
|
3090
3801
|
}
|
|
@@ -3092,14 +3803,14 @@ function restrictFile(file) {
|
|
|
3092
3803
|
if (process.platform === "win32")
|
|
3093
3804
|
return;
|
|
3094
3805
|
try {
|
|
3095
|
-
|
|
3806
|
+
fs7.chmodSync(file, 384);
|
|
3096
3807
|
} catch {}
|
|
3097
3808
|
}
|
|
3098
3809
|
function log(message) {
|
|
3099
3810
|
const line = `${new Date().toISOString()} ${message}
|
|
3100
3811
|
`;
|
|
3101
3812
|
try {
|
|
3102
|
-
|
|
3813
|
+
fs7.appendFileSync(LOG_FILE, line);
|
|
3103
3814
|
} catch {}
|
|
3104
3815
|
}
|
|
3105
3816
|
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;
|
|
@@ -3116,15 +3827,15 @@ var init_paths = __esm(() => {
|
|
|
3116
3827
|
DB_PATH = PATHS.dbFile;
|
|
3117
3828
|
RUNTIME_FILE2 = PATHS.runtimeFile;
|
|
3118
3829
|
HOOK_ENDPOINT_FILE = PATHS.hookEndpointFile;
|
|
3119
|
-
PIPE_PATH = ipcEndpoint(process.platform,
|
|
3830
|
+
PIPE_PATH = ipcEndpoint(process.platform, os3.userInfo().username, {
|
|
3120
3831
|
runtimeDir: PATHS.runtimeDir
|
|
3121
3832
|
});
|
|
3122
|
-
LOG_FILE =
|
|
3833
|
+
LOG_FILE = path8.join(LOG_DIR, "daemon.log");
|
|
3123
3834
|
});
|
|
3124
3835
|
|
|
3125
3836
|
// packages/daemon/src/store.ts
|
|
3126
|
-
import
|
|
3127
|
-
import
|
|
3837
|
+
import fs8 from "fs";
|
|
3838
|
+
import path9 from "path";
|
|
3128
3839
|
import { Database } from "bun:sqlite";
|
|
3129
3840
|
function rowToInfo(row) {
|
|
3130
3841
|
return {
|
|
@@ -3155,7 +3866,7 @@ function rowToInfo(row) {
|
|
|
3155
3866
|
class Store {
|
|
3156
3867
|
db;
|
|
3157
3868
|
constructor(dbPath = DB_PATH) {
|
|
3158
|
-
|
|
3869
|
+
fs8.mkdirSync(path9.dirname(dbPath), { recursive: true });
|
|
3159
3870
|
this.db = new Database(dbPath, { strict: true });
|
|
3160
3871
|
this.db.run("PRAGMA journal_mode = WAL");
|
|
3161
3872
|
this.db.run(`
|
|
@@ -3316,11 +4027,11 @@ var init_store = __esm(() => {
|
|
|
3316
4027
|
});
|
|
3317
4028
|
|
|
3318
4029
|
// packages/daemon/src/config-migrate.ts
|
|
3319
|
-
import
|
|
3320
|
-
import
|
|
4030
|
+
import fs9 from "fs";
|
|
4031
|
+
import path10 from "path";
|
|
3321
4032
|
import { Database as Database2 } from "bun:sqlite";
|
|
3322
4033
|
function checkpointWal(dbFile) {
|
|
3323
|
-
if (!
|
|
4034
|
+
if (!fs9.existsSync(dbFile))
|
|
3324
4035
|
return;
|
|
3325
4036
|
try {
|
|
3326
4037
|
const db = new Database2(dbFile);
|
|
@@ -3330,32 +4041,32 @@ function checkpointWal(dbFile) {
|
|
|
3330
4041
|
}
|
|
3331
4042
|
function relocate(source, target) {
|
|
3332
4043
|
try {
|
|
3333
|
-
|
|
4044
|
+
fs9.renameSync(source, target);
|
|
3334
4045
|
} catch {
|
|
3335
|
-
|
|
4046
|
+
fs9.cpSync(source, target, { recursive: true });
|
|
3336
4047
|
}
|
|
3337
4048
|
}
|
|
3338
4049
|
function moveInto(from, to, notes) {
|
|
3339
|
-
checkpointWal(
|
|
3340
|
-
|
|
4050
|
+
checkpointWal(path10.join(from, "axterm.db"));
|
|
4051
|
+
fs9.mkdirSync(to, { recursive: true });
|
|
3341
4052
|
for (const entry of STATE_ENTRIES) {
|
|
3342
|
-
const source =
|
|
3343
|
-
const target =
|
|
3344
|
-
if (!
|
|
4053
|
+
const source = path10.join(from, entry);
|
|
4054
|
+
const target = path10.join(to, entry);
|
|
4055
|
+
if (!fs9.existsSync(source))
|
|
3345
4056
|
continue;
|
|
3346
|
-
if (!
|
|
4057
|
+
if (!fs9.existsSync(target)) {
|
|
3347
4058
|
relocate(source, target);
|
|
3348
4059
|
notes.push(`moved ${entry}`);
|
|
3349
4060
|
continue;
|
|
3350
4061
|
}
|
|
3351
|
-
if (!
|
|
4062
|
+
if (!fs9.statSync(source).isDirectory() || !fs9.statSync(target).isDirectory())
|
|
3352
4063
|
continue;
|
|
3353
4064
|
let moved = 0;
|
|
3354
|
-
for (const child of
|
|
3355
|
-
const childTarget =
|
|
3356
|
-
if (
|
|
4065
|
+
for (const child of fs9.readdirSync(source)) {
|
|
4066
|
+
const childTarget = path10.join(target, child);
|
|
4067
|
+
if (fs9.existsSync(childTarget))
|
|
3357
4068
|
continue;
|
|
3358
|
-
relocate(
|
|
4069
|
+
relocate(path10.join(source, child), childTarget);
|
|
3359
4070
|
moved += 1;
|
|
3360
4071
|
}
|
|
3361
4072
|
if (moved > 0)
|
|
@@ -3365,29 +4076,29 @@ function moveInto(from, to, notes) {
|
|
|
3365
4076
|
function migrateLegacyConfig(input) {
|
|
3366
4077
|
const notes = [];
|
|
3367
4078
|
const storePaths = defaultConfigStorePaths(input.configDir);
|
|
3368
|
-
if (
|
|
4079
|
+
if (fs9.existsSync(storePaths.configFile))
|
|
3369
4080
|
return { migrated: false, notes };
|
|
3370
|
-
const configSource = input.legacyRoots.find((dir) =>
|
|
4081
|
+
const configSource = input.legacyRoots.find((dir) => fs9.existsSync(path10.join(dir, "config.json")));
|
|
3371
4082
|
if (configSource !== undefined) {
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
const secrets =
|
|
3375
|
-
if (
|
|
3376
|
-
|
|
4083
|
+
fs9.mkdirSync(input.configDir, { recursive: true });
|
|
4084
|
+
fs9.copyFileSync(path10.join(configSource, "config.json"), storePaths.configFile);
|
|
4085
|
+
const secrets = path10.join(configSource, "secrets.json");
|
|
4086
|
+
if (fs9.existsSync(secrets)) {
|
|
4087
|
+
fs9.copyFileSync(secrets, storePaths.secretsFile);
|
|
3377
4088
|
try {
|
|
3378
|
-
|
|
4089
|
+
fs9.chmodSync(storePaths.secretsFile, 384);
|
|
3379
4090
|
} catch {}
|
|
3380
4091
|
}
|
|
3381
|
-
|
|
4092
|
+
fs9.writeFileSync(path10.join(configSource, "MIGRATED.txt"), `axterm now reads its configuration from:
|
|
3382
4093
|
${input.configDir}
|
|
3383
4094
|
` + `These files are no longer used. secrets.json still holds your bot token \u2014 delete this directory once you are satisfied.
|
|
3384
4095
|
`);
|
|
3385
4096
|
notes.push(`adopted configuration from ${configSource}`);
|
|
3386
4097
|
}
|
|
3387
|
-
const legacy = input.legacyRoots.find((dir) =>
|
|
4098
|
+
const legacy = input.legacyRoots.find((dir) => fs9.existsSync(path10.join(dir, "axterm.db")));
|
|
3388
4099
|
if (legacy !== undefined) {
|
|
3389
4100
|
moveInto(legacy, input.stateDir, notes);
|
|
3390
|
-
|
|
4101
|
+
fs9.writeFileSync(path10.join(legacy, "MIGRATED.txt"), `axterm now stores state in:
|
|
3391
4102
|
${input.stateDir}
|
|
3392
4103
|
and configuration in:
|
|
3393
4104
|
${input.configDir}
|
|
@@ -3396,8 +4107,8 @@ and configuration in:
|
|
|
3396
4107
|
}
|
|
3397
4108
|
if (configSource !== undefined)
|
|
3398
4109
|
return { migrated: true, ...legacy ? { from: legacy } : {}, notes };
|
|
3399
|
-
const dbFile =
|
|
3400
|
-
if (!
|
|
4110
|
+
const dbFile = path10.join(input.stateDir, "axterm.db");
|
|
4111
|
+
if (!fs9.existsSync(dbFile))
|
|
3401
4112
|
return { migrated: notes.length > 0, ...legacy ? { from: legacy } : {}, notes };
|
|
3402
4113
|
const store = new Store(dbFile);
|
|
3403
4114
|
const machine2 = store.getConfig("axterm_machine");
|
|
@@ -3442,6 +4153,7 @@ var axterm_agent_status_default = `// @axterm-managed-omp-extension\r
|
|
|
3442
4153
|
// completion) follow Orca's production orca-agent-status.ts, reviewed in\r
|
|
3443
4154
|
// docs/design/terminal-wrapper-omp-telegram.md \xA75.\r
|
|
3444
4155
|
import fs from "node:fs";\r
|
|
4156
|
+
import path from "node:path";\r
|
|
3445
4157
|
\r
|
|
3446
4158
|
const HOOK_POST_TIMEOUT_MS = 1000;\r
|
|
3447
4159
|
const MAX_ASSISTANT_TEXT = 4000;\r
|
|
@@ -3637,11 +4349,75 @@ function formatAnswers(params: unknown, answers: AskAnswerWire[]): Record<string
|
|
|
3637
4349
|
}\r
|
|
3638
4350
|
\r
|
|
3639
4351
|
export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
3640
|
-
//
|
|
4352
|
+
// Only one PROCESS may report. This catches an agent that spawns another\r
|
|
4353
|
+
// agent as a child process; it does NOT catch omp's own subagents, which are\r
|
|
4354
|
+
// \`AgentSession\`s inside THIS process \u2014 \`process.pid\` is identical for them,\r
|
|
4355
|
+
// so they fall straight through. Telling those apart needs the session id,\r
|
|
4356
|
+
// which \`isLeadSession\` below does per event.\r
|
|
3641
4357
|
const owner = process.env["AXTERM_STATUS_OWNED"];\r
|
|
3642
4358
|
const selfPid = String(process.pid);\r
|
|
3643
4359
|
if (owner && owner !== selfPid) return;\r
|
|
3644
4360
|
process.env["AXTERM_STATUS_OWNED"] = selfPid;\r
|
|
4361
|
+
\r
|
|
4362
|
+
/* ----------------------- lead versus subagent ------------------------ */\r
|
|
4363
|
+
// omp loads this extension into every AgentSession, and every handler gets\r
|
|
4364
|
+
// the same ExtensionContext \u2014 \`sessionManager.getSessionId()\` is available on\r
|
|
4365
|
+
// all of them (verified in the installed omp's types). Only the shutdown\r
|
|
4366
|
+
// handler used it, so a finishing SUBAGENT posted \`agent_end\`: the session\r
|
|
4367
|
+
// flipped to \`done\`, Telegram announced it and renamed the topic, while the\r
|
|
4368
|
+
// lead was still working.\r
|
|
4369
|
+
//\r
|
|
4370
|
+
// The first session to start in the process is the lead \u2014 a subagent cannot\r
|
|
4371
|
+
// begin before its parent. Recorded in \`process.env\` rather than a module\r
|
|
4372
|
+
// variable because that is the one channel guaranteed to be shared no matter\r
|
|
4373
|
+
// how omp instantiates the module, which is why the PID guard above uses it\r
|
|
4374
|
+
// too.\r
|
|
4375
|
+
const LEAD_KEY = "AXTERM_LEAD_SESSION";\r
|
|
4376
|
+
\r
|
|
4377
|
+
function sessionIdOf(ctx: unknown): string | undefined {\r
|
|
4378
|
+
const manager = (ctx as { sessionManager?: { getSessionId?: () => string } } | undefined)?.sessionManager;\r
|
|
4379
|
+
try {\r
|
|
4380
|
+
return manager?.getSessionId?.();\r
|
|
4381
|
+
} catch {\r
|
|
4382
|
+
return undefined;\r
|
|
4383
|
+
}\r
|
|
4384
|
+
}\r
|
|
4385
|
+
\r
|
|
4386
|
+
/**\r
|
|
4387
|
+
* Claim lead on the first \`session_start\` seen in this process. A subagent\r
|
|
4388
|
+
* cannot begin before its parent, so first is the lead \u2014 and recording it\r
|
|
4389
|
+
* here rather than lazily on the first judgement matters: a subagent's\r
|
|
4390
|
+
* completion would otherwise be the first caller and crown itself.\r
|
|
4391
|
+
*/\r
|
|
4392
|
+
function rememberLead(ctx: unknown): void {\r
|
|
4393
|
+
const id = sessionIdOf(ctx);\r
|
|
4394
|
+
if (id === undefined) return;\r
|
|
4395
|
+
if (!process.env[LEAD_KEY]) process.env[LEAD_KEY] = id;\r
|
|
4396
|
+
}\r
|
|
4397
|
+
\r
|
|
4398
|
+
/**\r
|
|
4399
|
+
* Fails OPEN twice over \u2014 no lead recorded, or no id on this event. A build\r
|
|
4400
|
+
* that exposes neither must not lose its turn-end entirely, which would leave\r
|
|
4401
|
+
* the session at \`working\` forever; the shutdown path makes the same choice\r
|
|
4402
|
+
* (\xA74.42).\r
|
|
4403
|
+
*/\r
|
|
4404
|
+
function isLeadSession(ctx: unknown): boolean {\r
|
|
4405
|
+
const lead = process.env[LEAD_KEY];\r
|
|
4406
|
+
if (!lead) return true;\r
|
|
4407
|
+
const id = sessionIdOf(ctx);\r
|
|
4408
|
+
return id === undefined || id === lead;\r
|
|
4409
|
+
}\r
|
|
4410
|
+
\r
|
|
4411
|
+
/**\r
|
|
4412
|
+
* A switch or a branch makes another session the one the operator is driving,\r
|
|
4413
|
+
* so it becomes the lead \u2014 force, not first-wins. Without this the gate would\r
|
|
4414
|
+
* treat the new foreground session as a subagent and the session would never\r
|
|
4415
|
+
* report a completed turn again, which is worse than the leak it prevents.\r
|
|
4416
|
+
*/\r
|
|
4417
|
+
function claimLead(ctx: unknown): void {\r
|
|
4418
|
+
const id = sessionIdOf(ctx);\r
|
|
4419
|
+
if (id !== undefined) process.env[LEAD_KEY] = id;\r
|
|
4420
|
+
}\r
|
|
3645
4421
|
\r
|
|
3646
4422
|
/* ---------------- endpoint re-sourcing (env-freeze fix) ---------------- */\r
|
|
3647
4423
|
let warnedBadEndpoint = false;\r
|
|
@@ -3870,6 +4646,12 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3870
4646
|
const say = (text: string, isError = false) => ({ content: [{ type: "text" as const, text }], isError });\r
|
|
3871
4647
|
const { path: filePath, caption } = (params ?? {}) as { path?: string; caption?: string };\r
|
|
3872
4648
|
if (!filePath) return say("no path given", true);\r
|
|
4649
|
+
// Resolved HERE, because this process is the only one standing in the\r
|
|
4650
|
+
// right directory. The daemon interprets what it receives against its\r
|
|
4651
|
+
// own cwd \u2014 wherever it was spawned, never the session's folder \u2014 so a\r
|
|
4652
|
+
// bare filename came back "file not found" while the file was plainly\r
|
|
4653
|
+
// there. An absolute path is passed through untouched.\r
|
|
4654
|
+
const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);\r
|
|
3873
4655
|
const coords = resolveCoords();\r
|
|
3874
4656
|
const sessionId = process.env["AXTERM_SESSION_ID"];\r
|
|
3875
4657
|
if (!coords.port || !coords.token || !sessionId) {\r
|
|
@@ -3882,7 +4664,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3882
4664
|
body: JSON.stringify({\r
|
|
3883
4665
|
sessionId,\r
|
|
3884
4666
|
launchToken: process.env["AXTERM_LAUNCH_TOKEN"] ?? "",\r
|
|
3885
|
-
path:
|
|
4667
|
+
path: resolved,\r
|
|
3886
4668
|
caption,\r
|
|
3887
4669
|
}),\r
|
|
3888
4670
|
});\r
|
|
@@ -3906,7 +4688,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3906
4688
|
return { status: "no-channel" };\r
|
|
3907
4689
|
}\r
|
|
3908
4690
|
try {\r
|
|
3909
|
-
const res = await fetch(\`http://127.0.0.1:\${coords.port}/hook/
|
|
4691
|
+
const res = await fetch(\`http://127.0.0.1:\${coords.port}/hook/ask\`, {\r
|
|
3910
4692
|
method: "POST",\r
|
|
3911
4693
|
headers: { "content-type": "application/json", "x-axterm-hook-token": coords.token },\r
|
|
3912
4694
|
body: JSON.stringify({\r
|
|
@@ -3929,7 +4711,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3929
4711
|
const sessionId = process.env["AXTERM_SESSION_ID"];\r
|
|
3930
4712
|
if (!coords.port || !coords.token || !sessionId) return;\r
|
|
3931
4713
|
try {\r
|
|
3932
|
-
await fetch(\`http://127.0.0.1:\${coords.port}/hook/
|
|
4714
|
+
await fetch(\`http://127.0.0.1:\${coords.port}/hook/ask-cancel\`, {\r
|
|
3933
4715
|
method: "POST",\r
|
|
3934
4716
|
headers: { "content-type": "application/json", "x-axterm-hook-token": coords.token },\r
|
|
3935
4717
|
body: JSON.stringify({ sessionId, launchToken: process.env["AXTERM_LAUNCH_TOKEN"] ?? "", askId }),\r
|
|
@@ -3941,6 +4723,8 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3941
4723
|
\r
|
|
3942
4724
|
/* ------------------------------ handlers ------------------------------ */\r
|
|
3943
4725
|
pi.on("session_start", (_event, ctx) => {\r
|
|
4726
|
+
// First one wins, and it decides who may end a turn from here on.\r
|
|
4727
|
+
rememberLead(ctx);\r
|
|
3944
4728
|
const manager = (ctx as { sessionManager?: Record<string, unknown> } | undefined)?.sessionManager;\r
|
|
3945
4729
|
const probe = (names: string[]): unknown => {\r
|
|
3946
4730
|
for (const name of names) {\r
|
|
@@ -3963,7 +4747,16 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3963
4747
|
});\r
|
|
3964
4748
|
});\r
|
|
3965
4749
|
\r
|
|
3966
|
-
|
|
4750
|
+
// Switching or branching inside omp changes which session the operator is\r
|
|
4751
|
+
// driving. Not reported to the daemon \u2014 the session row is unchanged \u2014 but the\r
|
|
4752
|
+
// lead has to follow, or turn ends stop arriving from here on.\r
|
|
4753
|
+
pi.on("session_switch", (_event, ctx) => claimLead(ctx));\r
|
|
4754
|
+
pi.on("session_branch", (_event, ctx) => claimLead(ctx));\r
|
|
4755
|
+
\r
|
|
4756
|
+
// Lead only: this sets the run's starting prompt, which the channel shows in\r
|
|
4757
|
+
// the topic title. A subagent's own prompt is not what the operator asked.\r
|
|
4758
|
+
pi.on("before_agent_start", (event, ctx) => {\r
|
|
4759
|
+
if (!isLeadSession(ctx)) return;\r
|
|
3967
4760
|
post("before_agent_start", { prompt: (event as { prompt?: string } | undefined)?.prompt ?? "" });\r
|
|
3968
4761
|
});\r
|
|
3969
4762
|
\r
|
|
@@ -4008,7 +4801,10 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
4008
4801
|
recheckMs = Math.min(recheckMs * 2, IDLE_RECHECK_MAX_MS);\r
|
|
4009
4802
|
}\r
|
|
4010
4803
|
\r
|
|
4011
|
-
|
|
4804
|
+
// Lead only: a subagent starting is not the session's turn restarting, and\r
|
|
4805
|
+
// resetting \`endReported\` here on its behalf would arm a second completion.\r
|
|
4806
|
+
pi.on("agent_start", (_event, ctx) => {\r
|
|
4807
|
+
if (!isLeadSession(ctx)) return;\r
|
|
4012
4808
|
clearPendingCheck();\r
|
|
4013
4809
|
endReported = false;\r
|
|
4014
4810
|
post("agent_start");\r
|
|
@@ -4093,20 +4889,29 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
4093
4889
|
});\r
|
|
4094
4890
|
});\r
|
|
4095
4891
|
\r
|
|
4096
|
-
|
|
4892
|
+
// Lead only: this becomes the session's \`lastReply\` \u2014 the answer the channel\r
|
|
4893
|
+
// shows. A subagent's internal message is not the session's answer.\r
|
|
4894
|
+
pi.on("message_end", (event, ctx) => {\r
|
|
4895
|
+
if (!isLeadSession(ctx)) return;\r
|
|
4097
4896
|
const message = (event as { message?: unknown } | undefined)?.message;\r
|
|
4098
4897
|
const text = extractAssistantText(message);\r
|
|
4099
4898
|
if (!text) return;\r
|
|
4100
4899
|
post("message_end", { role: "assistant", text });\r
|
|
4101
4900
|
});\r
|
|
4102
4901
|
\r
|
|
4103
|
-
|
|
4902
|
+
// Lead only, and this is the one the report was about: a finishing subagent\r
|
|
4903
|
+
// announced the turn as done and renamed the topic while the lead worked on.\r
|
|
4904
|
+
pi.on("agent_settled", (_event, ctx) => {\r
|
|
4905
|
+
if (!isLeadSession(ctx)) return;\r
|
|
4104
4906
|
settledSupported = true;\r
|
|
4105
4907
|
clearPendingCheck();\r
|
|
4106
4908
|
postEndOnce();\r
|
|
4107
4909
|
});\r
|
|
4108
4910
|
\r
|
|
4911
|
+
// The other route to a turn end, for builds without \`agent_settled\`. Gated the\r
|
|
4912
|
+
// same way, or a subagent whose own work is idle ends the session's turn.\r
|
|
4109
4913
|
pi.on("agent_end", (_event, ctx) => {\r
|
|
4914
|
+
if (!isLeadSession(ctx)) return;\r
|
|
4110
4915
|
if (settledSupported) return;\r
|
|
4111
4916
|
const c = ctx as { isIdle?: () => boolean } | undefined;\r
|
|
4112
4917
|
if (!c || typeof c.isIdle !== "function") {\r
|
|
@@ -4141,13 +4946,13 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
4141
4946
|
var init_axterm_agent_status = () => {};
|
|
4142
4947
|
|
|
4143
4948
|
// packages/daemon/src/ext-install.ts
|
|
4144
|
-
import
|
|
4145
|
-
import
|
|
4949
|
+
import fs10 from "fs";
|
|
4950
|
+
import path11 from "path";
|
|
4146
4951
|
function installExtensionAsset(targetDir) {
|
|
4147
|
-
|
|
4148
|
-
const target =
|
|
4952
|
+
fs10.mkdirSync(targetDir, { recursive: true });
|
|
4953
|
+
const target = path11.join(targetDir, "axterm-agent-status.mjs");
|
|
4149
4954
|
const js = new Bun.Transpiler({ loader: "ts" }).transformSync(extensionSource);
|
|
4150
|
-
|
|
4955
|
+
fs10.writeFileSync(target, MANAGED_HEADER + js);
|
|
4151
4956
|
return target;
|
|
4152
4957
|
}
|
|
4153
4958
|
var extensionSource, MANAGED_HEADER = `// @axterm-managed-omp-extension
|
|
@@ -4160,8 +4965,8 @@ var init_ext_install = __esm(() => {
|
|
|
4160
4965
|
|
|
4161
4966
|
// packages/daemon/src/workspace-local.ts
|
|
4162
4967
|
import { spawn as spawn2 } from "child_process";
|
|
4163
|
-
import
|
|
4164
|
-
import
|
|
4968
|
+
import fs11 from "fs/promises";
|
|
4969
|
+
import path12 from "path";
|
|
4165
4970
|
function isAbsent(error) {
|
|
4166
4971
|
const code = error?.code;
|
|
4167
4972
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
@@ -4174,10 +4979,10 @@ class LocalWorkspace {
|
|
|
4174
4979
|
this.stagingDir = stagingDir;
|
|
4175
4980
|
}
|
|
4176
4981
|
async list(dir) {
|
|
4177
|
-
const dirents = await
|
|
4982
|
+
const dirents = await fs11.readdir(dir, { withFileTypes: true });
|
|
4178
4983
|
const stats = await Promise.all(dirents.map(async (dirent) => {
|
|
4179
4984
|
try {
|
|
4180
|
-
return await
|
|
4985
|
+
return await fs11.lstat(path12.join(dir, dirent.name));
|
|
4181
4986
|
} catch (error) {
|
|
4182
4987
|
if (isAbsent(error))
|
|
4183
4988
|
return;
|
|
@@ -4201,7 +5006,7 @@ class LocalWorkspace {
|
|
|
4201
5006
|
}
|
|
4202
5007
|
async stat(target) {
|
|
4203
5008
|
try {
|
|
4204
|
-
const stat = await
|
|
5009
|
+
const stat = await fs11.stat(target);
|
|
4205
5010
|
const kind = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "other";
|
|
4206
5011
|
return { kind, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
4207
5012
|
} catch (error) {
|
|
@@ -4212,8 +5017,8 @@ class LocalWorkspace {
|
|
|
4212
5017
|
}
|
|
4213
5018
|
async read(file, limit) {
|
|
4214
5019
|
if (limit === undefined)
|
|
4215
|
-
return await
|
|
4216
|
-
const handle = await
|
|
5020
|
+
return await fs11.readFile(file);
|
|
5021
|
+
const handle = await fs11.open(file, "r");
|
|
4217
5022
|
try {
|
|
4218
5023
|
const buffer = Buffer.allocUnsafe(Math.min(limit, (await handle.stat()).size));
|
|
4219
5024
|
let filled = 0;
|
|
@@ -4258,9 +5063,9 @@ var init_workspace_local = () => {};
|
|
|
4258
5063
|
|
|
4259
5064
|
// packages/daemon/src/hook-host.ts
|
|
4260
5065
|
import { randomBytes } from "crypto";
|
|
4261
|
-
import
|
|
5066
|
+
import fs12 from "fs";
|
|
4262
5067
|
import http from "http";
|
|
4263
|
-
import
|
|
5068
|
+
import path13 from "path";
|
|
4264
5069
|
function header(req, name) {
|
|
4265
5070
|
const value = req.headers[name];
|
|
4266
5071
|
return Array.isArray(value) ? value[0] : value;
|
|
@@ -4284,7 +5089,7 @@ class HookHost {
|
|
|
4284
5089
|
constructor(options) {
|
|
4285
5090
|
this.endpointFile = options.endpointFile;
|
|
4286
5091
|
this.codexApprovalHoldMs = options.codexApprovalHoldMs ?? CODEX_APPROVAL_HOLD_MS;
|
|
4287
|
-
this.resolveWorkspace = options.workspaceFor ?? (() => this.local ??= new LocalWorkspace(
|
|
5092
|
+
this.resolveWorkspace = options.workspaceFor ?? (() => this.local ??= new LocalWorkspace(path13.join(RUNTIME_DIR, "materialised")));
|
|
4288
5093
|
}
|
|
4289
5094
|
start() {
|
|
4290
5095
|
const server = http.createServer((req, res) => this.handle(req, res));
|
|
@@ -4300,12 +5105,12 @@ class HookHost {
|
|
|
4300
5105
|
return promise;
|
|
4301
5106
|
}
|
|
4302
5107
|
writeEndpointFile(port) {
|
|
4303
|
-
|
|
5108
|
+
fs12.mkdirSync(path13.dirname(this.endpointFile), { recursive: true });
|
|
4304
5109
|
const tmp = `${this.endpointFile}.tmp`;
|
|
4305
|
-
|
|
5110
|
+
fs12.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
|
|
4306
5111
|
AXTERM_HOOK_TOKEN=${this.token}
|
|
4307
5112
|
`);
|
|
4308
|
-
|
|
5113
|
+
fs12.renameSync(tmp, this.endpointFile);
|
|
4309
5114
|
}
|
|
4310
5115
|
registerSession(sessionId, launchToken, onEvent) {
|
|
4311
5116
|
this.sessions.set(sessionId, { launchToken, onEvent });
|
|
@@ -4343,8 +5148,8 @@ AXTERM_HOOK_TOKEN=${this.token}
|
|
|
4343
5148
|
}
|
|
4344
5149
|
handle(req, res) {
|
|
4345
5150
|
const isEvent = req.url === "/hook/omp";
|
|
4346
|
-
const isAsk = req.url === "/hook/
|
|
4347
|
-
const isAskCancel = req.url === "/hook/
|
|
5151
|
+
const isAsk = req.url === "/hook/ask";
|
|
5152
|
+
const isAskCancel = req.url === "/hook/ask-cancel";
|
|
4348
5153
|
const isAttach = req.url === "/hook/attach";
|
|
4349
5154
|
const isClaude = req.url === "/hook/claude";
|
|
4350
5155
|
const isCodex = req.url === "/hook/codex";
|
|
@@ -4580,7 +5385,7 @@ var init_hook_host = __esm(() => {
|
|
|
4580
5385
|
});
|
|
4581
5386
|
|
|
4582
5387
|
// packages/daemon/src/rpc-server.ts
|
|
4583
|
-
import
|
|
5388
|
+
import fs13 from "fs";
|
|
4584
5389
|
import net2 from "net";
|
|
4585
5390
|
function writeFrame(socket, frame) {
|
|
4586
5391
|
if (!socket.destroyed)
|
|
@@ -4765,10 +5570,12 @@ function startRpcServer(options) {
|
|
|
4765
5570
|
}
|
|
4766
5571
|
case "config.set": {
|
|
4767
5572
|
const dottedPath = p["path"];
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
5573
|
+
const raw = p["value"];
|
|
5574
|
+
const value = Array.isArray(raw) ? raw : raw;
|
|
5575
|
+
if (isSecretPath(dottedPath)) {
|
|
5576
|
+
config.setSecret(dottedPath, Array.isArray(value) ? value.join(" ") : value);
|
|
5577
|
+
} else {
|
|
5578
|
+
const next2 = setConfigValue(config.get(), dottedPath, value);
|
|
4772
5579
|
if ("error" in next2)
|
|
4773
5580
|
throw new Error(next2.error);
|
|
4774
5581
|
config.update(() => next2.config);
|
|
@@ -4802,6 +5609,8 @@ function startRpcServer(options) {
|
|
|
4802
5609
|
return telegram.status();
|
|
4803
5610
|
case "doctor.report":
|
|
4804
5611
|
return { checks: manager.doctorReport() };
|
|
5612
|
+
case "quota.report":
|
|
5613
|
+
return { checks: manager.quotaReport() };
|
|
4805
5614
|
case "daemon.stop":
|
|
4806
5615
|
setTimeout(onStopRequested, 50);
|
|
4807
5616
|
return {};
|
|
@@ -4812,7 +5621,7 @@ function startRpcServer(options) {
|
|
|
4812
5621
|
});
|
|
4813
5622
|
if (process.platform !== "win32") {
|
|
4814
5623
|
try {
|
|
4815
|
-
|
|
5624
|
+
fs13.unlinkSync(pipePath);
|
|
4816
5625
|
} catch {}
|
|
4817
5626
|
}
|
|
4818
5627
|
server.listen(pipePath, () => {
|
|
@@ -4827,17 +5636,17 @@ var init_rpc_server = __esm(() => {
|
|
|
4827
5636
|
});
|
|
4828
5637
|
|
|
4829
5638
|
// packages/daemon/src/codex-home.ts
|
|
4830
|
-
import
|
|
4831
|
-
import
|
|
5639
|
+
import fs14 from "fs";
|
|
5640
|
+
import path14 from "path";
|
|
4832
5641
|
function codexTrustKeyPath(home) {
|
|
4833
5642
|
const resolved = (() => {
|
|
4834
5643
|
try {
|
|
4835
|
-
return
|
|
5644
|
+
return fs14.realpathSync.native(home);
|
|
4836
5645
|
} catch {
|
|
4837
|
-
return
|
|
5646
|
+
return path14.resolve(home);
|
|
4838
5647
|
}
|
|
4839
5648
|
})();
|
|
4840
|
-
return
|
|
5649
|
+
return path14.join(resolved, "hooks.json");
|
|
4841
5650
|
}
|
|
4842
5651
|
var init_codex_home = () => {};
|
|
4843
5652
|
|
|
@@ -4900,8 +5709,8 @@ function slugifyBranch(branch) {
|
|
|
4900
5709
|
var MAX_SLUG_LENGTH = 60;
|
|
4901
5710
|
|
|
4902
5711
|
// packages/daemon/src/git.ts
|
|
4903
|
-
import
|
|
4904
|
-
import
|
|
5712
|
+
import fs15 from "fs";
|
|
5713
|
+
import path15 from "path";
|
|
4905
5714
|
async function git(ws, cwd, args) {
|
|
4906
5715
|
const run = await ws.exec(["git", ...args], cwd);
|
|
4907
5716
|
return {
|
|
@@ -4918,15 +5727,15 @@ async function gitOrThrow(ws, cwd, args) {
|
|
|
4918
5727
|
return result.stdout;
|
|
4919
5728
|
}
|
|
4920
5729
|
function canonicalPath(target) {
|
|
4921
|
-
let resolved =
|
|
5730
|
+
let resolved = path15.resolve(target);
|
|
4922
5731
|
try {
|
|
4923
|
-
resolved =
|
|
5732
|
+
resolved = fs15.realpathSync.native(resolved);
|
|
4924
5733
|
} catch {}
|
|
4925
5734
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
4926
5735
|
}
|
|
4927
5736
|
async function resolveRepoRoot(ws, cwd) {
|
|
4928
5737
|
const result = await git(ws, cwd, ["rev-parse", "--show-toplevel"]);
|
|
4929
|
-
return result.ok ?
|
|
5738
|
+
return result.ok ? path15.resolve(result.stdout) : undefined;
|
|
4930
5739
|
}
|
|
4931
5740
|
async function currentBranch(ws, cwd) {
|
|
4932
5741
|
const result = await git(ws, cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
@@ -4935,8 +5744,8 @@ async function currentBranch(ws, cwd) {
|
|
|
4935
5744
|
async function listWorktrees(ws, repoRoot) {
|
|
4936
5745
|
return parseWorktreeList(await gitOrThrow(ws, repoRoot, ["worktree", "list", "--porcelain"]));
|
|
4937
5746
|
}
|
|
4938
|
-
function worktreePathFor(repoRoot, branch, root =
|
|
4939
|
-
return
|
|
5747
|
+
function worktreePathFor(repoRoot, branch, root = path15.join(STATE_DIR, "worktrees")) {
|
|
5748
|
+
return path15.join(root, slugifyBranch(path15.basename(repoRoot)), slugifyBranch(branch));
|
|
4940
5749
|
}
|
|
4941
5750
|
async function createWorktree(ws, options) {
|
|
4942
5751
|
const { repoRoot, branch, base } = options;
|
|
@@ -4944,7 +5753,7 @@ async function createWorktree(ws, options) {
|
|
|
4944
5753
|
const existing = (await listWorktrees(ws, repoRoot)).find((w) => canonicalPath(w.path) === canonicalPath(target));
|
|
4945
5754
|
if (existing)
|
|
4946
5755
|
return { path: target, branch, createdBranch: false };
|
|
4947
|
-
|
|
5756
|
+
fs15.mkdirSync(path15.dirname(target), { recursive: true });
|
|
4948
5757
|
const exists = (await git(ws, repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`])).ok;
|
|
4949
5758
|
const args = exists ? ["worktree", "add", target, branch] : ["worktree", "add", "-b", branch, target, ...base !== undefined ? [base] : []];
|
|
4950
5759
|
await gitOrThrow(ws, repoRoot, args);
|
|
@@ -4997,7 +5806,7 @@ async function diffStat(ws, worktreePath, base) {
|
|
|
4997
5806
|
const untracked = await untrackedFiles(ws, worktreePath);
|
|
4998
5807
|
let newLines = 0;
|
|
4999
5808
|
for (const file of untracked)
|
|
5000
|
-
newLines += await countLines(ws,
|
|
5809
|
+
newLines += await countLines(ws, path15.join(worktreePath, file));
|
|
5001
5810
|
return {
|
|
5002
5811
|
filesChanged: a.filesChanged + b.filesChanged + untracked.length,
|
|
5003
5812
|
insertions: a.insertions + b.insertions + newLines,
|
|
@@ -5073,7 +5882,7 @@ var init_injector = __esm(() => {
|
|
|
5073
5882
|
|
|
5074
5883
|
// packages/daemon/src/occupancy.ts
|
|
5075
5884
|
import { spawn as spawn3 } from "child_process";
|
|
5076
|
-
import
|
|
5885
|
+
import fs16 from "fs/promises";
|
|
5077
5886
|
function shellOccupied(shellPid) {
|
|
5078
5887
|
return process.platform === "win32" ? occupiedFromSnapshot(shellPid) : occupiedFromPidProbe(shellPid);
|
|
5079
5888
|
}
|
|
@@ -5118,11 +5927,11 @@ async function occupiedFromSnapshot(shellPid) {
|
|
|
5118
5927
|
}
|
|
5119
5928
|
async function childrenViaProc(pid) {
|
|
5120
5929
|
try {
|
|
5121
|
-
const children = await
|
|
5930
|
+
const children = await fs16.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
|
|
5122
5931
|
return children.trim().length > 0;
|
|
5123
5932
|
} catch {
|
|
5124
5933
|
try {
|
|
5125
|
-
await
|
|
5934
|
+
await fs16.access(`/proc/${pid}`);
|
|
5126
5935
|
return;
|
|
5127
5936
|
} catch {
|
|
5128
5937
|
return false;
|
|
@@ -5137,53 +5946,455 @@ async function childrenViaPgrep(pid) {
|
|
|
5137
5946
|
return false;
|
|
5138
5947
|
if (result.code !== 0)
|
|
5139
5948
|
return;
|
|
5140
|
-
return result.stdout.trim().length > 0;
|
|
5949
|
+
return result.stdout.trim().length > 0;
|
|
5950
|
+
}
|
|
5951
|
+
async function processTable() {
|
|
5952
|
+
const result = await capture(resolveShell("powershell").exe, [
|
|
5953
|
+
"-NoLogo",
|
|
5954
|
+
"-NoProfile",
|
|
5955
|
+
"-NonInteractive",
|
|
5956
|
+
"-Command",
|
|
5957
|
+
"(Get-CimInstance -ClassName Win32_Process -Property ProcessId,ParentProcessId,Name,CreationDate" + " | Where-Object { $_.Name -ne 'conhost.exe' }" + ` | ForEach-Object { "$($_.ProcessId),$($_.ParentProcessId),$([long]($_.CreationDate.Ticks / 10000))" }) -join ' '`
|
|
5958
|
+
]);
|
|
5959
|
+
if (result === undefined || result.code !== 0)
|
|
5960
|
+
return;
|
|
5961
|
+
const startedAt = new Map;
|
|
5962
|
+
const newestChild = new Map;
|
|
5963
|
+
for (const row of result.stdout.split(/\s+/)) {
|
|
5964
|
+
const [rawPid, rawParent, rawStarted] = row.split(",");
|
|
5965
|
+
const pid = Number(rawPid);
|
|
5966
|
+
const parent = Number(rawParent);
|
|
5967
|
+
const started = Number(rawStarted);
|
|
5968
|
+
if (!Number.isInteger(pid) || !Number.isInteger(parent) || !Number.isInteger(started))
|
|
5969
|
+
continue;
|
|
5970
|
+
if (pid === result.pid)
|
|
5971
|
+
continue;
|
|
5972
|
+
startedAt.set(pid, started);
|
|
5973
|
+
const newest = newestChild.get(parent);
|
|
5974
|
+
if (newest === undefined || started > newest)
|
|
5975
|
+
newestChild.set(parent, started);
|
|
5976
|
+
}
|
|
5977
|
+
return startedAt.size > 0 ? { startedAt, newestChild } : undefined;
|
|
5978
|
+
}
|
|
5979
|
+
function capture(exe, argv) {
|
|
5980
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
5981
|
+
const child = spawn3(exe, argv, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
|
|
5982
|
+
let stdout3 = "";
|
|
5983
|
+
child.stdout.setEncoding("utf8");
|
|
5984
|
+
child.stdout.on("data", (chunk) => {
|
|
5985
|
+
stdout3 += chunk;
|
|
5986
|
+
});
|
|
5987
|
+
child.once("error", () => resolve(undefined));
|
|
5988
|
+
child.once("close", (code) => resolve({ code, stdout: stdout3, pid: child.pid }));
|
|
5989
|
+
return promise;
|
|
5990
|
+
}
|
|
5991
|
+
var OCCUPANCY_CACHE_MS = 1500, perPid, snapshot;
|
|
5992
|
+
var init_occupancy = __esm(() => {
|
|
5993
|
+
init_shell();
|
|
5994
|
+
perPid = new Map;
|
|
5995
|
+
});
|
|
5996
|
+
|
|
5997
|
+
// packages/daemon/src/agent-store.ts
|
|
5998
|
+
import fs17 from "fs";
|
|
5999
|
+
import os4 from "os";
|
|
6000
|
+
import path16 from "path";
|
|
6001
|
+
import { Database as Database3 } from "bun:sqlite";
|
|
6002
|
+
function readTailLines(file, maxBytes) {
|
|
6003
|
+
let fd;
|
|
6004
|
+
try {
|
|
6005
|
+
fd = fs17.openSync(file, "r");
|
|
6006
|
+
const size = fs17.fstatSync(fd).size;
|
|
6007
|
+
if (size === 0)
|
|
6008
|
+
return [];
|
|
6009
|
+
const length = Math.min(size, maxBytes);
|
|
6010
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
6011
|
+
fs17.readSync(fd, buffer, 0, length, size - length);
|
|
6012
|
+
const text = buffer.toString("utf8");
|
|
6013
|
+
const lines = text.split(`
|
|
6014
|
+
`);
|
|
6015
|
+
if (length < size)
|
|
6016
|
+
lines.shift();
|
|
6017
|
+
if (lines.at(-1) === "")
|
|
6018
|
+
lines.pop();
|
|
6019
|
+
return lines;
|
|
6020
|
+
} catch {
|
|
6021
|
+
return [];
|
|
6022
|
+
} finally {
|
|
6023
|
+
if (fd !== undefined)
|
|
6024
|
+
fs17.closeSync(fd);
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
6027
|
+
function claudeConfigDir() {
|
|
6028
|
+
return process.env["CLAUDE_CONFIG_DIR"] ?? path16.join(os4.homedir(), ".claude");
|
|
6029
|
+
}
|
|
6030
|
+
function codexHome() {
|
|
6031
|
+
return path16.join(STATE_DIR, "codex-home");
|
|
6032
|
+
}
|
|
6033
|
+
function ompAgentDb() {
|
|
6034
|
+
return path16.join(os4.homedir(), ".omp", "agent", "agent.db");
|
|
6035
|
+
}
|
|
6036
|
+
function findCodexRollout(sessionId) {
|
|
6037
|
+
const root = path16.join(codexHome(), "sessions");
|
|
6038
|
+
for (const shard2 of codexRolloutShards(sessionId)) {
|
|
6039
|
+
const dir = path16.join(root, ...shard2.split("/"));
|
|
6040
|
+
let entries;
|
|
6041
|
+
try {
|
|
6042
|
+
entries = fs17.readdirSync(dir);
|
|
6043
|
+
} catch {
|
|
6044
|
+
continue;
|
|
6045
|
+
}
|
|
6046
|
+
const match2 = entries.find((name) => name.endsWith(`-${sessionId}.jsonl`));
|
|
6047
|
+
if (match2 !== undefined)
|
|
6048
|
+
return path16.join(dir, match2);
|
|
6049
|
+
}
|
|
6050
|
+
return;
|
|
6051
|
+
}
|
|
6052
|
+
function newestCodexRollout() {
|
|
6053
|
+
const root = path16.join(codexHome(), "sessions");
|
|
6054
|
+
let best;
|
|
6055
|
+
const walk = (dir, depth) => {
|
|
6056
|
+
let entries;
|
|
6057
|
+
try {
|
|
6058
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
6059
|
+
} catch {
|
|
6060
|
+
return;
|
|
6061
|
+
}
|
|
6062
|
+
for (const entry of entries) {
|
|
6063
|
+
const full = path16.join(dir, entry.name);
|
|
6064
|
+
if (entry.isDirectory()) {
|
|
6065
|
+
if (depth < 3)
|
|
6066
|
+
walk(full, depth + 1);
|
|
6067
|
+
continue;
|
|
6068
|
+
}
|
|
6069
|
+
if (!entry.name.endsWith(".jsonl"))
|
|
6070
|
+
continue;
|
|
6071
|
+
try {
|
|
6072
|
+
const { mtimeMs } = fs17.statSync(full);
|
|
6073
|
+
if (!best || mtimeMs > best.mtimeMs)
|
|
6074
|
+
best = { file: full, mtimeMs };
|
|
6075
|
+
} catch {}
|
|
6076
|
+
}
|
|
6077
|
+
};
|
|
6078
|
+
walk(root, 0);
|
|
6079
|
+
return best?.file;
|
|
6080
|
+
}
|
|
6081
|
+
function ompQuotaFacts() {
|
|
6082
|
+
const file = ompAgentDb();
|
|
6083
|
+
if (!fs17.existsSync(file))
|
|
6084
|
+
return { agent: "omp", kind: "gauge" };
|
|
6085
|
+
let db;
|
|
6086
|
+
try {
|
|
6087
|
+
db = new Database3(file, { readonly: true, strict: true });
|
|
6088
|
+
const rows = db.query(`SELECT recorded_at, limit_id, label, used_fraction, status, resets_at
|
|
6089
|
+
FROM usage_history
|
|
6090
|
+
WHERE recorded_at = (SELECT MAX(recorded_at) FROM usage_history h WHERE h.limit_id = usage_history.limit_id)
|
|
6091
|
+
AND recorded_at > ?
|
|
6092
|
+
ORDER BY limit_id`).all(Date.now() - 7 * 86400000);
|
|
6093
|
+
const reading = parseOmpQuota(rows);
|
|
6094
|
+
return reading ? { agent: "omp", kind: "gauge", reading } : { agent: "omp", kind: "gauge" };
|
|
6095
|
+
} catch (err) {
|
|
6096
|
+
log(`omp quota unreadable: ${String(err)}`);
|
|
6097
|
+
return { agent: "omp", kind: "gauge" };
|
|
6098
|
+
} finally {
|
|
6099
|
+
db?.close();
|
|
6100
|
+
}
|
|
6101
|
+
}
|
|
6102
|
+
function codexQuotaFacts() {
|
|
6103
|
+
const file = newestCodexRollout();
|
|
6104
|
+
if (file === undefined)
|
|
6105
|
+
return { agent: "codex", kind: "gauge" };
|
|
6106
|
+
const reading = parseCodexQuota(readTailLines(file, QUOTA_TAIL_BYTES));
|
|
6107
|
+
return reading ? { agent: "codex", kind: "gauge", reading } : { agent: "codex", kind: "gauge" };
|
|
6108
|
+
}
|
|
6109
|
+
function claudeQuotaFacts(sessions) {
|
|
6110
|
+
const dir = claudeConfigDir();
|
|
6111
|
+
let newest;
|
|
6112
|
+
for (const session of sessions) {
|
|
6113
|
+
if (session.agentSessionId === undefined)
|
|
6114
|
+
continue;
|
|
6115
|
+
const file = path16.join(dir, claudeTranscriptRelPath(session.cwd, session.agentSessionId));
|
|
6116
|
+
const event = parseClaudeLimitEvent(readTailLines(file, QUOTA_TAIL_BYTES));
|
|
6117
|
+
if (event && (!newest || event.at > newest.at))
|
|
6118
|
+
newest = event;
|
|
6119
|
+
}
|
|
6120
|
+
return newest ? { agent: "claude", kind: "event", event: newest } : { agent: "claude", kind: "event" };
|
|
6121
|
+
}
|
|
6122
|
+
function collectQuotaFacts(agents, sessions, onPath) {
|
|
6123
|
+
const running = new Set(sessions.map((session) => session.agent).filter((agent) => agent !== undefined));
|
|
6124
|
+
const facts = [];
|
|
6125
|
+
for (const agent of agents) {
|
|
6126
|
+
if (!running.has(agent) && onPath(agent) === undefined)
|
|
6127
|
+
continue;
|
|
6128
|
+
const kind = agentProfile(agent).quota;
|
|
6129
|
+
if (kind === undefined) {
|
|
6130
|
+
facts.push({ agent, kind: "none" });
|
|
6131
|
+
continue;
|
|
6132
|
+
}
|
|
6133
|
+
if (agent === "codex")
|
|
6134
|
+
facts.push(codexQuotaFacts());
|
|
6135
|
+
else if (agent === "omp")
|
|
6136
|
+
facts.push(ompQuotaFacts());
|
|
6137
|
+
else if (agent === "claude")
|
|
6138
|
+
facts.push(claudeQuotaFacts(sessions));
|
|
6139
|
+
else
|
|
6140
|
+
facts.push({ agent, kind: kind === "gauge" ? "gauge" : "event" });
|
|
6141
|
+
}
|
|
6142
|
+
return facts;
|
|
6143
|
+
}
|
|
6144
|
+
function storeQuietForMs(agent, cwd, agentSessionId, nowMs) {
|
|
6145
|
+
if (agentSessionId === undefined)
|
|
6146
|
+
return;
|
|
6147
|
+
const file = agent === "claude" ? path16.join(claudeConfigDir(), claudeTranscriptRelPath(cwd, agentSessionId)) : agent === "codex" ? findCodexRollout(agentSessionId) : undefined;
|
|
6148
|
+
if (file === undefined)
|
|
6149
|
+
return;
|
|
6150
|
+
try {
|
|
6151
|
+
return Math.max(0, nowMs - fs17.statSync(file).mtimeMs);
|
|
6152
|
+
} catch {
|
|
6153
|
+
return;
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6156
|
+
function readFileRange(file, from, to) {
|
|
6157
|
+
if (to <= from)
|
|
6158
|
+
return "";
|
|
6159
|
+
let fd;
|
|
6160
|
+
try {
|
|
6161
|
+
fd = fs17.openSync(file, "r");
|
|
6162
|
+
const buffer = Buffer.allocUnsafe(to - from);
|
|
6163
|
+
const read = fs17.readSync(fd, buffer, 0, to - from, from);
|
|
6164
|
+
return buffer.subarray(0, read).toString("utf8");
|
|
6165
|
+
} catch {
|
|
6166
|
+
return "";
|
|
6167
|
+
} finally {
|
|
6168
|
+
if (fd !== undefined)
|
|
6169
|
+
fs17.closeSync(fd);
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
var QUOTA_TAIL_BYTES;
|
|
6173
|
+
var init_agent_store = __esm(() => {
|
|
6174
|
+
init_src();
|
|
6175
|
+
init_paths();
|
|
6176
|
+
QUOTA_TAIL_BYTES = 64 * 1024;
|
|
6177
|
+
});
|
|
6178
|
+
|
|
6179
|
+
// packages/daemon/src/codex-app-server.ts
|
|
6180
|
+
import net3 from "net";
|
|
6181
|
+
async function freeLoopbackPort() {
|
|
6182
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
6183
|
+
const probe = net3.createServer();
|
|
6184
|
+
probe.once("error", reject);
|
|
6185
|
+
probe.listen({ host: "127.0.0.1", port: 0 }, () => {
|
|
6186
|
+
const address = probe.address();
|
|
6187
|
+
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
6188
|
+
probe.close(() => port > 0 ? resolve(port) : reject(new Error("no port")));
|
|
6189
|
+
});
|
|
6190
|
+
return promise;
|
|
6191
|
+
}
|
|
6192
|
+
async function waitForPort(port, timeoutMs = READY_TIMEOUT_MS) {
|
|
6193
|
+
const deadline = Date.now() + timeoutMs;
|
|
6194
|
+
for (;; ) {
|
|
6195
|
+
const open = await new Promise((resolve) => {
|
|
6196
|
+
const socket = net3.connect({ host: "127.0.0.1", port });
|
|
6197
|
+
const done = (value) => {
|
|
6198
|
+
socket.destroy();
|
|
6199
|
+
resolve(value);
|
|
6200
|
+
};
|
|
6201
|
+
socket.once("connect", () => done(true));
|
|
6202
|
+
socket.once("error", () => done(false));
|
|
6203
|
+
});
|
|
6204
|
+
if (open)
|
|
6205
|
+
return true;
|
|
6206
|
+
if (Date.now() >= deadline)
|
|
6207
|
+
return false;
|
|
6208
|
+
await new Promise((resolve) => setTimeout(resolve, READY_POLL_MS));
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
|
|
6212
|
+
class CodexAppServerClient {
|
|
6213
|
+
url;
|
|
6214
|
+
sink;
|
|
6215
|
+
socket;
|
|
6216
|
+
mapper = new CodexStreamMapper;
|
|
6217
|
+
joined = new Set;
|
|
6218
|
+
nextId = 1;
|
|
6219
|
+
closed = false;
|
|
6220
|
+
constructor(url, sink) {
|
|
6221
|
+
this.url = url;
|
|
6222
|
+
this.sink = sink;
|
|
6223
|
+
}
|
|
6224
|
+
get connected() {
|
|
6225
|
+
return this.socket?.readyState === WebSocket.OPEN;
|
|
6226
|
+
}
|
|
6227
|
+
connect() {
|
|
6228
|
+
if (this.closed)
|
|
6229
|
+
return;
|
|
6230
|
+
let socket;
|
|
6231
|
+
try {
|
|
6232
|
+
socket = new WebSocket(this.url);
|
|
6233
|
+
} catch (err) {
|
|
6234
|
+
log(`codex app-server: cannot connect (${String(err)}); the session keeps its hooks`);
|
|
6235
|
+
return;
|
|
6236
|
+
}
|
|
6237
|
+
this.socket = socket;
|
|
6238
|
+
socket.addEventListener("open", () => {
|
|
6239
|
+
this.send("initialize", { clientInfo: { name: "axterm", title: "axterm", version: "1" } });
|
|
6240
|
+
});
|
|
6241
|
+
socket.addEventListener("message", (event) => {
|
|
6242
|
+
this.onFrame(typeof event.data === "string" ? event.data : "");
|
|
6243
|
+
});
|
|
6244
|
+
socket.addEventListener("error", () => {});
|
|
6245
|
+
socket.addEventListener("close", () => {
|
|
6246
|
+
if (!this.closed)
|
|
6247
|
+
log("codex app-server: connection closed; the session keeps its hooks");
|
|
6248
|
+
});
|
|
6249
|
+
}
|
|
6250
|
+
close() {
|
|
6251
|
+
this.closed = true;
|
|
6252
|
+
try {
|
|
6253
|
+
this.socket?.close();
|
|
6254
|
+
} catch {}
|
|
6255
|
+
this.socket = undefined;
|
|
6256
|
+
}
|
|
6257
|
+
send(method, params) {
|
|
6258
|
+
if (this.socket?.readyState !== WebSocket.OPEN)
|
|
6259
|
+
return;
|
|
6260
|
+
this.socket.send(JSON.stringify({ jsonrpc: "2.0", id: this.nextId++, method, params }));
|
|
6261
|
+
}
|
|
6262
|
+
onFrame(raw) {
|
|
6263
|
+
if (raw === "")
|
|
6264
|
+
return;
|
|
6265
|
+
let message;
|
|
6266
|
+
try {
|
|
6267
|
+
message = JSON.parse(raw);
|
|
6268
|
+
} catch {
|
|
6269
|
+
return;
|
|
6270
|
+
}
|
|
6271
|
+
const method = message["method"];
|
|
6272
|
+
if (typeof method !== "string")
|
|
6273
|
+
return;
|
|
6274
|
+
const params = message["params"] ?? {};
|
|
6275
|
+
const threadId = params["threadId"];
|
|
6276
|
+
if (typeof threadId === "string" && !this.joined.has(threadId)) {
|
|
6277
|
+
this.joined.add(threadId);
|
|
6278
|
+
this.send("thread/resume", { threadId });
|
|
6279
|
+
log(`codex app-server: joined thread ${threadId.slice(0, 8)}`);
|
|
6280
|
+
}
|
|
6281
|
+
for (const payload of this.mapper.handle(method, params))
|
|
6282
|
+
this.sink(payload);
|
|
6283
|
+
}
|
|
6284
|
+
}
|
|
6285
|
+
var READY_TIMEOUT_MS = 1e4, READY_POLL_MS = 100;
|
|
6286
|
+
var init_codex_app_server = __esm(() => {
|
|
6287
|
+
init_src();
|
|
6288
|
+
init_paths();
|
|
6289
|
+
});
|
|
6290
|
+
|
|
6291
|
+
// packages/daemon/src/pty.ts
|
|
6292
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
6293
|
+
import path17 from "path";
|
|
6294
|
+
import { StringDecoder } from "string_decoder";
|
|
6295
|
+
|
|
6296
|
+
class Fanout {
|
|
6297
|
+
listeners = new Set;
|
|
6298
|
+
backlog = [];
|
|
6299
|
+
add(listener) {
|
|
6300
|
+
this.listeners.add(listener);
|
|
6301
|
+
for (const value of this.backlog)
|
|
6302
|
+
listener(value);
|
|
6303
|
+
}
|
|
6304
|
+
emit(value) {
|
|
6305
|
+
if (this.listeners.size === 0)
|
|
6306
|
+
this.backlog.push(value);
|
|
6307
|
+
else
|
|
6308
|
+
for (const listener of this.listeners)
|
|
6309
|
+
listener(value);
|
|
6310
|
+
}
|
|
6311
|
+
}
|
|
6312
|
+
function killTree(pid, exeName) {
|
|
6313
|
+
if (process.platform === "win32") {
|
|
6314
|
+
const check = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
|
|
6315
|
+
encoding: "utf8",
|
|
6316
|
+
windowsHide: true
|
|
6317
|
+
});
|
|
6318
|
+
const image = check.stdout?.split(",")[0]?.replace(/"/g, "").toLowerCase() ?? "";
|
|
6319
|
+
if (image === exeName) {
|
|
6320
|
+
spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
|
|
6321
|
+
}
|
|
6322
|
+
return;
|
|
6323
|
+
}
|
|
6324
|
+
try {
|
|
6325
|
+
process.kill(-pid, "SIGKILL");
|
|
6326
|
+
} catch {}
|
|
5141
6327
|
}
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
const
|
|
5159
|
-
if (
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
const
|
|
5165
|
-
if (
|
|
5166
|
-
|
|
6328
|
+
|
|
6329
|
+
class BunPtyHandle {
|
|
6330
|
+
proc;
|
|
6331
|
+
exeName;
|
|
6332
|
+
data = new Fanout;
|
|
6333
|
+
exit = new Fanout;
|
|
6334
|
+
decoder = new StringDecoder("utf8");
|
|
6335
|
+
exited = false;
|
|
6336
|
+
constructor(proc, exeName) {
|
|
6337
|
+
this.proc = proc;
|
|
6338
|
+
this.exeName = exeName;
|
|
6339
|
+
}
|
|
6340
|
+
get pid() {
|
|
6341
|
+
return this.proc.pid;
|
|
6342
|
+
}
|
|
6343
|
+
push(bytes) {
|
|
6344
|
+
const text = this.decoder.write(Buffer.from(bytes));
|
|
6345
|
+
if (text.length > 0)
|
|
6346
|
+
this.data.emit(text);
|
|
6347
|
+
}
|
|
6348
|
+
finish(code) {
|
|
6349
|
+
this.exited = true;
|
|
6350
|
+
const tail = this.decoder.end();
|
|
6351
|
+
if (tail.length > 0)
|
|
6352
|
+
this.data.emit(tail);
|
|
6353
|
+
try {
|
|
6354
|
+
this.proc.terminal.close();
|
|
6355
|
+
} catch {}
|
|
6356
|
+
this.exit.emit(code);
|
|
6357
|
+
}
|
|
6358
|
+
onData(listener) {
|
|
6359
|
+
this.data.add(listener);
|
|
6360
|
+
}
|
|
6361
|
+
onExit(listener) {
|
|
6362
|
+
this.exit.add(listener);
|
|
6363
|
+
}
|
|
6364
|
+
write(data) {
|
|
6365
|
+
this.proc.terminal.write(data);
|
|
6366
|
+
}
|
|
6367
|
+
resize(cols, rows) {
|
|
6368
|
+
this.proc.terminal.resize(cols, rows);
|
|
6369
|
+
}
|
|
6370
|
+
kill() {
|
|
6371
|
+
if (this.exited)
|
|
6372
|
+
return;
|
|
6373
|
+
killTree(this.proc.pid, this.exeName);
|
|
6374
|
+
try {
|
|
6375
|
+
this.proc.kill();
|
|
6376
|
+
} catch {}
|
|
5167
6377
|
}
|
|
5168
|
-
return startedAt.size > 0 ? { startedAt, newestChild } : undefined;
|
|
5169
6378
|
}
|
|
5170
|
-
function
|
|
5171
|
-
|
|
5172
|
-
const
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
6379
|
+
function spawnPty(options) {
|
|
6380
|
+
let handle;
|
|
6381
|
+
const proc = Bun.spawn([options.exe, ...options.argv], {
|
|
6382
|
+
cwd: options.cwd,
|
|
6383
|
+
env: { ...options.env, TERM: options.term },
|
|
6384
|
+
terminal: {
|
|
6385
|
+
cols: options.cols,
|
|
6386
|
+
rows: options.rows,
|
|
6387
|
+
name: options.term,
|
|
6388
|
+
data(_terminal, bytes) {
|
|
6389
|
+
handle?.push(bytes);
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
5177
6392
|
});
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
return
|
|
6393
|
+
handle = new BunPtyHandle(proc, path17.basename(options.exe).toLowerCase());
|
|
6394
|
+
proc.exited.then((code) => handle?.finish(code ?? null));
|
|
6395
|
+
return handle;
|
|
5181
6396
|
}
|
|
5182
|
-
var
|
|
5183
|
-
var init_occupancy = __esm(() => {
|
|
5184
|
-
init_shell();
|
|
5185
|
-
perPid = new Map;
|
|
5186
|
-
});
|
|
6397
|
+
var init_pty = () => {};
|
|
5187
6398
|
|
|
5188
6399
|
// node_modules/.bun/@xterm+headless@5.5.0/node_modules/@xterm/headless/lib-headless/xterm-headless.js
|
|
5189
6400
|
var require_xterm_headless = __commonJS((exports) => {
|
|
@@ -9470,161 +10681,53 @@ var require_addon_serialize = __commonJS((exports, module) => {
|
|
|
9470
10681
|
constructor(e3, s2, r2) {
|
|
9471
10682
|
super(e3), this._terminal = s2, this._options = r2, this._currentRow = "", this._htmlContent = "", s2._core._themeService ? this._ansiColors = s2._core._themeService.colors.ansi : this._ansiColors = t3.DEFAULT_ANSI_COLORS;
|
|
9472
10683
|
}
|
|
9473
|
-
_padStart(e3, t4, s2) {
|
|
9474
|
-
return t4 >>= 0, s2 = s2 ?? " ", e3.length > t4 ? e3 : ((t4 -= e3.length) > s2.length && (s2 += s2.repeat(t4 / s2.length)), s2.slice(0, t4) + e3);
|
|
9475
|
-
}
|
|
9476
|
-
_beforeSerialize(e3, t4, s2) {
|
|
9477
|
-
this._htmlContent += "<html><body><!--StartFragment--><pre>";
|
|
9478
|
-
let r2 = "#000000", o2 = "#ffffff";
|
|
9479
|
-
this._options.includeGlobalBackground && (r2 = this._terminal.options.theme?.foreground ?? "#ffffff", o2 = this._terminal.options.theme?.background ?? "#000000");
|
|
9480
|
-
const i2 = [];
|
|
9481
|
-
i2.push("color: " + r2 + ";"), i2.push("background-color: " + o2 + ";"), i2.push("font-family: " + this._terminal.options.fontFamily + ";"), i2.push("font-size: " + this._terminal.options.fontSize + "px;"), this._htmlContent += "<div style='" + i2.join(" ") + "'>";
|
|
9482
|
-
}
|
|
9483
|
-
_afterSerialize() {
|
|
9484
|
-
this._htmlContent += "</div>", this._htmlContent += "</pre><!--EndFragment--></body></html>";
|
|
9485
|
-
}
|
|
9486
|
-
_rowEnd(e3, t4) {
|
|
9487
|
-
this._htmlContent += "<div><span>" + this._currentRow + "</span></div>", this._currentRow = "";
|
|
9488
|
-
}
|
|
9489
|
-
_getHexColor(e3, t4) {
|
|
9490
|
-
const s2 = t4 ? e3.getFgColor() : e3.getBgColor();
|
|
9491
|
-
return (t4 ? e3.isFgRGB() : e3.isBgRGB()) ? "#" + [s2 >> 16 & 255, s2 >> 8 & 255, 255 & s2].map((e4) => this._padStart(e4.toString(16), 2, "0")).join("") : (t4 ? e3.isFgPalette() : e3.isBgPalette()) ? this._ansiColors[s2].css : undefined;
|
|
9492
|
-
}
|
|
9493
|
-
_diffStyle(e3, t4) {
|
|
9494
|
-
const s2 = [], r2 = !n(e3, t4), o2 = !l(e3, t4), i2 = !a(e3, t4);
|
|
9495
|
-
if (r2 || o2 || i2) {
|
|
9496
|
-
const t5 = this._getHexColor(e3, true);
|
|
9497
|
-
t5 && s2.push("color: " + t5 + ";");
|
|
9498
|
-
const r3 = this._getHexColor(e3, false);
|
|
9499
|
-
return r3 && s2.push("background-color: " + r3 + ";"), e3.isInverse() && s2.push("color: #000000; background-color: #BFBFBF;"), e3.isBold() && s2.push("font-weight: bold;"), e3.isUnderline() && e3.isOverline() ? s2.push("text-decoration: overline underline;") : e3.isUnderline() ? s2.push("text-decoration: underline;") : e3.isOverline() && s2.push("text-decoration: overline;"), e3.isBlink() && s2.push("text-decoration: blink;"), e3.isInvisible() && s2.push("visibility: hidden;"), e3.isItalic() && s2.push("font-style: italic;"), e3.isDim() && s2.push("opacity: 0.5;"), e3.isStrikethrough() && s2.push("text-decoration: line-through;"), s2;
|
|
9500
|
-
}
|
|
9501
|
-
}
|
|
9502
|
-
_nextCell(e3, t4, s2, r2) {
|
|
9503
|
-
if (e3.getWidth() === 0)
|
|
9504
|
-
return;
|
|
9505
|
-
const o2 = e3.getChars() === "", i2 = this._diffStyle(e3, t4);
|
|
9506
|
-
i2 && (this._currentRow += i2.length === 0 ? "</span><span>" : "</span><span style='" + i2.join(" ") + "'>"), this._currentRow += o2 ? " " : e3.getChars();
|
|
9507
|
-
}
|
|
9508
|
-
_serializeString() {
|
|
9509
|
-
return this._htmlContent;
|
|
9510
|
-
}
|
|
9511
|
-
}
|
|
9512
|
-
e2.HTMLSerializeHandler = h;
|
|
9513
|
-
})(), r;
|
|
9514
|
-
})());
|
|
9515
|
-
});
|
|
9516
|
-
|
|
9517
|
-
// packages/daemon/src/pty.ts
|
|
9518
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
9519
|
-
import path15 from "path";
|
|
9520
|
-
import { StringDecoder } from "string_decoder";
|
|
9521
|
-
|
|
9522
|
-
class Fanout {
|
|
9523
|
-
listeners = new Set;
|
|
9524
|
-
backlog = [];
|
|
9525
|
-
add(listener) {
|
|
9526
|
-
this.listeners.add(listener);
|
|
9527
|
-
for (const value of this.backlog)
|
|
9528
|
-
listener(value);
|
|
9529
|
-
}
|
|
9530
|
-
emit(value) {
|
|
9531
|
-
if (this.listeners.size === 0)
|
|
9532
|
-
this.backlog.push(value);
|
|
9533
|
-
else
|
|
9534
|
-
for (const listener of this.listeners)
|
|
9535
|
-
listener(value);
|
|
9536
|
-
}
|
|
9537
|
-
}
|
|
9538
|
-
function killTree(pid, exeName) {
|
|
9539
|
-
if (process.platform === "win32") {
|
|
9540
|
-
const check = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
|
|
9541
|
-
encoding: "utf8",
|
|
9542
|
-
windowsHide: true
|
|
9543
|
-
});
|
|
9544
|
-
const image = check.stdout?.split(",")[0]?.replace(/"/g, "").toLowerCase() ?? "";
|
|
9545
|
-
if (image === exeName) {
|
|
9546
|
-
spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
|
|
9547
|
-
}
|
|
9548
|
-
return;
|
|
9549
|
-
}
|
|
9550
|
-
try {
|
|
9551
|
-
process.kill(-pid, "SIGKILL");
|
|
9552
|
-
} catch {}
|
|
9553
|
-
}
|
|
9554
|
-
|
|
9555
|
-
class BunPtyHandle {
|
|
9556
|
-
proc;
|
|
9557
|
-
exeName;
|
|
9558
|
-
data = new Fanout;
|
|
9559
|
-
exit = new Fanout;
|
|
9560
|
-
decoder = new StringDecoder("utf8");
|
|
9561
|
-
exited = false;
|
|
9562
|
-
constructor(proc, exeName) {
|
|
9563
|
-
this.proc = proc;
|
|
9564
|
-
this.exeName = exeName;
|
|
9565
|
-
}
|
|
9566
|
-
get pid() {
|
|
9567
|
-
return this.proc.pid;
|
|
9568
|
-
}
|
|
9569
|
-
push(bytes) {
|
|
9570
|
-
const text = this.decoder.write(Buffer.from(bytes));
|
|
9571
|
-
if (text.length > 0)
|
|
9572
|
-
this.data.emit(text);
|
|
9573
|
-
}
|
|
9574
|
-
finish(code) {
|
|
9575
|
-
this.exited = true;
|
|
9576
|
-
const tail = this.decoder.end();
|
|
9577
|
-
if (tail.length > 0)
|
|
9578
|
-
this.data.emit(tail);
|
|
9579
|
-
try {
|
|
9580
|
-
this.proc.terminal.close();
|
|
9581
|
-
} catch {}
|
|
9582
|
-
this.exit.emit(code);
|
|
9583
|
-
}
|
|
9584
|
-
onData(listener) {
|
|
9585
|
-
this.data.add(listener);
|
|
9586
|
-
}
|
|
9587
|
-
onExit(listener) {
|
|
9588
|
-
this.exit.add(listener);
|
|
9589
|
-
}
|
|
9590
|
-
write(data) {
|
|
9591
|
-
this.proc.terminal.write(data);
|
|
9592
|
-
}
|
|
9593
|
-
resize(cols, rows) {
|
|
9594
|
-
this.proc.terminal.resize(cols, rows);
|
|
9595
|
-
}
|
|
9596
|
-
kill() {
|
|
9597
|
-
if (this.exited)
|
|
9598
|
-
return;
|
|
9599
|
-
killTree(this.proc.pid, this.exeName);
|
|
9600
|
-
try {
|
|
9601
|
-
this.proc.kill();
|
|
9602
|
-
} catch {}
|
|
9603
|
-
}
|
|
9604
|
-
}
|
|
9605
|
-
function spawnPty(options) {
|
|
9606
|
-
let handle;
|
|
9607
|
-
const proc = Bun.spawn([options.exe, ...options.argv], {
|
|
9608
|
-
cwd: options.cwd,
|
|
9609
|
-
env: { ...options.env, TERM: options.term },
|
|
9610
|
-
terminal: {
|
|
9611
|
-
cols: options.cols,
|
|
9612
|
-
rows: options.rows,
|
|
9613
|
-
name: options.term,
|
|
9614
|
-
data(_terminal, bytes) {
|
|
9615
|
-
handle?.push(bytes);
|
|
10684
|
+
_padStart(e3, t4, s2) {
|
|
10685
|
+
return t4 >>= 0, s2 = s2 ?? " ", e3.length > t4 ? e3 : ((t4 -= e3.length) > s2.length && (s2 += s2.repeat(t4 / s2.length)), s2.slice(0, t4) + e3);
|
|
10686
|
+
}
|
|
10687
|
+
_beforeSerialize(e3, t4, s2) {
|
|
10688
|
+
this._htmlContent += "<html><body><!--StartFragment--><pre>";
|
|
10689
|
+
let r2 = "#000000", o2 = "#ffffff";
|
|
10690
|
+
this._options.includeGlobalBackground && (r2 = this._terminal.options.theme?.foreground ?? "#ffffff", o2 = this._terminal.options.theme?.background ?? "#000000");
|
|
10691
|
+
const i2 = [];
|
|
10692
|
+
i2.push("color: " + r2 + ";"), i2.push("background-color: " + o2 + ";"), i2.push("font-family: " + this._terminal.options.fontFamily + ";"), i2.push("font-size: " + this._terminal.options.fontSize + "px;"), this._htmlContent += "<div style='" + i2.join(" ") + "'>";
|
|
10693
|
+
}
|
|
10694
|
+
_afterSerialize() {
|
|
10695
|
+
this._htmlContent += "</div>", this._htmlContent += "</pre><!--EndFragment--></body></html>";
|
|
10696
|
+
}
|
|
10697
|
+
_rowEnd(e3, t4) {
|
|
10698
|
+
this._htmlContent += "<div><span>" + this._currentRow + "</span></div>", this._currentRow = "";
|
|
10699
|
+
}
|
|
10700
|
+
_getHexColor(e3, t4) {
|
|
10701
|
+
const s2 = t4 ? e3.getFgColor() : e3.getBgColor();
|
|
10702
|
+
return (t4 ? e3.isFgRGB() : e3.isBgRGB()) ? "#" + [s2 >> 16 & 255, s2 >> 8 & 255, 255 & s2].map((e4) => this._padStart(e4.toString(16), 2, "0")).join("") : (t4 ? e3.isFgPalette() : e3.isBgPalette()) ? this._ansiColors[s2].css : undefined;
|
|
10703
|
+
}
|
|
10704
|
+
_diffStyle(e3, t4) {
|
|
10705
|
+
const s2 = [], r2 = !n(e3, t4), o2 = !l(e3, t4), i2 = !a(e3, t4);
|
|
10706
|
+
if (r2 || o2 || i2) {
|
|
10707
|
+
const t5 = this._getHexColor(e3, true);
|
|
10708
|
+
t5 && s2.push("color: " + t5 + ";");
|
|
10709
|
+
const r3 = this._getHexColor(e3, false);
|
|
10710
|
+
return r3 && s2.push("background-color: " + r3 + ";"), e3.isInverse() && s2.push("color: #000000; background-color: #BFBFBF;"), e3.isBold() && s2.push("font-weight: bold;"), e3.isUnderline() && e3.isOverline() ? s2.push("text-decoration: overline underline;") : e3.isUnderline() ? s2.push("text-decoration: underline;") : e3.isOverline() && s2.push("text-decoration: overline;"), e3.isBlink() && s2.push("text-decoration: blink;"), e3.isInvisible() && s2.push("visibility: hidden;"), e3.isItalic() && s2.push("font-style: italic;"), e3.isDim() && s2.push("opacity: 0.5;"), e3.isStrikethrough() && s2.push("text-decoration: line-through;"), s2;
|
|
10711
|
+
}
|
|
10712
|
+
}
|
|
10713
|
+
_nextCell(e3, t4, s2, r2) {
|
|
10714
|
+
if (e3.getWidth() === 0)
|
|
10715
|
+
return;
|
|
10716
|
+
const o2 = e3.getChars() === "", i2 = this._diffStyle(e3, t4);
|
|
10717
|
+
i2 && (this._currentRow += i2.length === 0 ? "</span><span>" : "</span><span style='" + i2.join(" ") + "'>"), this._currentRow += o2 ? " " : e3.getChars();
|
|
10718
|
+
}
|
|
10719
|
+
_serializeString() {
|
|
10720
|
+
return this._htmlContent;
|
|
10721
|
+
}
|
|
9616
10722
|
}
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
return handle;
|
|
9622
|
-
}
|
|
9623
|
-
var init_pty = () => {};
|
|
10723
|
+
e2.HTMLSerializeHandler = h;
|
|
10724
|
+
})(), r;
|
|
10725
|
+
})());
|
|
10726
|
+
});
|
|
9624
10727
|
|
|
9625
10728
|
// packages/daemon/src/pty-session.ts
|
|
9626
|
-
import
|
|
9627
|
-
import
|
|
10729
|
+
import fs18 from "fs";
|
|
10730
|
+
import path18 from "path";
|
|
9628
10731
|
|
|
9629
10732
|
class PtySession {
|
|
9630
10733
|
info;
|
|
@@ -9740,10 +10843,10 @@ class PtySession {
|
|
|
9740
10843
|
}
|
|
9741
10844
|
saveScrollback() {
|
|
9742
10845
|
try {
|
|
9743
|
-
const file =
|
|
10846
|
+
const file = path18.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
|
|
9744
10847
|
const tmp = `${file}.tmp`;
|
|
9745
|
-
|
|
9746
|
-
|
|
10848
|
+
fs18.writeFileSync(tmp, this.serializeBuffer(), "utf8");
|
|
10849
|
+
fs18.renameSync(tmp, file);
|
|
9747
10850
|
} catch (err) {
|
|
9748
10851
|
log(`scrollback save failed for ${this.info.shortId}: ${String(err)}`);
|
|
9749
10852
|
}
|
|
@@ -9765,9 +10868,9 @@ var init_pty_session = __esm(() => {
|
|
|
9765
10868
|
|
|
9766
10869
|
// packages/daemon/src/session-manager.ts
|
|
9767
10870
|
import { randomBytes as randomBytes2, randomUUID } from "crypto";
|
|
9768
|
-
import
|
|
10871
|
+
import fs19 from "fs";
|
|
9769
10872
|
import os5 from "os";
|
|
9770
|
-
import
|
|
10873
|
+
import path19 from "path";
|
|
9771
10874
|
function unresolvableAgent(commandOverride, agentCommand, lookup) {
|
|
9772
10875
|
if (commandOverride !== undefined)
|
|
9773
10876
|
return;
|
|
@@ -9810,6 +10913,7 @@ async function sendableNow(runtime, info, probe, now) {
|
|
|
9810
10913
|
class SessionManager {
|
|
9811
10914
|
store;
|
|
9812
10915
|
hooks;
|
|
10916
|
+
configuredDefaults;
|
|
9813
10917
|
live = new Map;
|
|
9814
10918
|
agentRuntime = new Map;
|
|
9815
10919
|
injector = new Injector;
|
|
@@ -9817,13 +10921,18 @@ class SessionManager {
|
|
|
9817
10921
|
eventListeners = new Set;
|
|
9818
10922
|
rawHookListeners = new Set;
|
|
9819
10923
|
workspace = new LocalWorkspace(MATERIALISED_DIR);
|
|
9820
|
-
constructor(store, hooks) {
|
|
10924
|
+
constructor(store, hooks, configuredDefaults = () => ({})) {
|
|
9821
10925
|
this.store = store;
|
|
9822
10926
|
this.hooks = hooks;
|
|
10927
|
+
this.configuredDefaults = configuredDefaults;
|
|
9823
10928
|
const stale = store.markStaleRunningAsExited();
|
|
9824
10929
|
if (stale > 0)
|
|
9825
10930
|
log(`boot: marked ${stale} stale session(s) as exited`);
|
|
10931
|
+
this.sessionWatch = setInterval(() => this.tickSessions(), SESSION_TICK_MS);
|
|
10932
|
+
this.sessionWatch.unref();
|
|
9826
10933
|
}
|
|
10934
|
+
sessionWatch;
|
|
10935
|
+
codexServers = new Map;
|
|
9827
10936
|
workspaceFor(_sessionId) {
|
|
9828
10937
|
return this.workspace;
|
|
9829
10938
|
}
|
|
@@ -9900,8 +11009,8 @@ class SessionManager {
|
|
|
9900
11009
|
};
|
|
9901
11010
|
}
|
|
9902
11011
|
async create(params) {
|
|
9903
|
-
const requestedCwd = params.cwd ?
|
|
9904
|
-
if (!
|
|
11012
|
+
const requestedCwd = params.cwd ? path19.resolve(params.cwd) : os5.homedir();
|
|
11013
|
+
if (!fs19.existsSync(requestedCwd)) {
|
|
9905
11014
|
throw new AxtermError("err.cwd.missing", { cwd: requestedCwd });
|
|
9906
11015
|
}
|
|
9907
11016
|
let worktree;
|
|
@@ -9918,17 +11027,29 @@ class SessionManager {
|
|
|
9918
11027
|
log(`worktree ${created.createdBranch ? "created" : "reused"} ${created.branch} at ${created.path}`);
|
|
9919
11028
|
}
|
|
9920
11029
|
const cwd = worktree?.path ?? requestedCwd;
|
|
9921
|
-
const
|
|
9922
|
-
const
|
|
11030
|
+
const configured = this.configuredDefaults();
|
|
11031
|
+
const shell = resolveShell(params.shell ?? configured.shell);
|
|
11032
|
+
const agent = params.agent ?? configured.agent ?? "omp";
|
|
9923
11033
|
if (!isAgentId(agent))
|
|
9924
11034
|
throw new AxtermError("err.agent.unknown", { agent, supported: supportedAgents().join(", ") });
|
|
9925
11035
|
const id = randomUUID().replace(/-/g, "");
|
|
11036
|
+
const extraArgs = resolveAgentArgs({
|
|
11037
|
+
bypass: agentProfile(agent).approvalBypassArgs,
|
|
11038
|
+
autoApprove: params.autoApprove,
|
|
11039
|
+
autoApproveConfigured: configured.autoApprove,
|
|
11040
|
+
configured: configured.agentArgs?.[agent],
|
|
11041
|
+
requested: params.agentArgs
|
|
11042
|
+
});
|
|
11043
|
+
const mcpContext = agent === "omp" ? undefined : this.mcpContextPath(id);
|
|
11044
|
+
const mcpConfigPath = agent === "claude" ? this.writeMcpConfig(id, mcpContext) : undefined;
|
|
9926
11045
|
const agentCommand = params.commandOverride ?? buildAgentCommand({
|
|
9927
11046
|
agent,
|
|
9928
11047
|
sessionId: id,
|
|
9929
11048
|
extensionPath: this.hooks.extensionPath,
|
|
9930
11049
|
claudeSettingsPath: this.claudeSettingsPath(id),
|
|
9931
|
-
...
|
|
11050
|
+
...mcpConfigPath === undefined ? {} : { mcpConfigPath },
|
|
11051
|
+
...agent === "codex" ? { mcpLaunch: this.mcpServerLaunch(), mcpContextPath: mcpContext } : {},
|
|
11052
|
+
...extraArgs.length > 0 ? { extraArgs } : {}
|
|
9932
11053
|
});
|
|
9933
11054
|
const missing = unresolvableAgent(params.commandOverride, agentCommand, lookupOnPath);
|
|
9934
11055
|
if (missing !== undefined)
|
|
@@ -9936,7 +11057,7 @@ class SessionManager {
|
|
|
9936
11057
|
const info = {
|
|
9937
11058
|
id,
|
|
9938
11059
|
shortId: id.slice(0, 6),
|
|
9939
|
-
name: params.name ?? worktree?.branch ??
|
|
11060
|
+
name: params.name ?? worktree?.branch ?? path19.basename(cwd),
|
|
9940
11061
|
cwd,
|
|
9941
11062
|
shell: shell.kind,
|
|
9942
11063
|
agentCommand,
|
|
@@ -9946,7 +11067,8 @@ class SessionManager {
|
|
|
9946
11067
|
createdAt: Date.now(),
|
|
9947
11068
|
...worktree !== undefined ? { worktreePath: worktree.path, branch: worktree.branch, repoRoot: worktree.repoRoot } : {}
|
|
9948
11069
|
};
|
|
9949
|
-
this.
|
|
11070
|
+
const appServerUrl = agent === "codex" ? await this.startCodexAppServer(id, this.ensureCodexHome(), cwd) : undefined;
|
|
11071
|
+
this.launch(info, shell, agentCommand, appServerUrl);
|
|
9950
11072
|
this.store.insertSession(info);
|
|
9951
11073
|
log(`session ${info.shortId} created shell=${shell.kind} cmd=${agentCommand.join(" ") || "(shell only)"}`);
|
|
9952
11074
|
this.emit({ t: "session.created", info });
|
|
@@ -9960,37 +11082,82 @@ class SessionManager {
|
|
|
9960
11082
|
launchToken
|
|
9961
11083
|
});
|
|
9962
11084
|
const file = this.claudeSettingsPath(id);
|
|
9963
|
-
|
|
11085
|
+
fs19.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
|
|
9964
11086
|
}
|
|
9965
|
-
writeGeminiSettings() {
|
|
11087
|
+
writeGeminiSettings(sessionId) {
|
|
9966
11088
|
try {
|
|
9967
11089
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "gemini" });
|
|
9968
|
-
const settings = buildGeminiSettings({
|
|
11090
|
+
const settings = buildGeminiSettings({
|
|
11091
|
+
command: bridge.command,
|
|
11092
|
+
args: bridge.args,
|
|
11093
|
+
mcp: this.mcpServerLaunch(),
|
|
11094
|
+
mcpContextPath: this.mcpContextPath(sessionId)
|
|
11095
|
+
});
|
|
9969
11096
|
const file = this.geminiSettingsPath();
|
|
9970
11097
|
const tmp = `${file}.tmp`;
|
|
9971
|
-
|
|
9972
|
-
|
|
11098
|
+
fs19.writeFileSync(tmp, JSON.stringify(settings, null, 2));
|
|
11099
|
+
fs19.renameSync(tmp, file);
|
|
9973
11100
|
} catch (err) {
|
|
9974
11101
|
log(`gemini settings write failed; session starts without hooks: ${String(err)}`);
|
|
9975
11102
|
}
|
|
9976
11103
|
}
|
|
11104
|
+
async startCodexAppServer(id, codexHome2, cwd) {
|
|
11105
|
+
try {
|
|
11106
|
+
const port = await freeLoopbackPort();
|
|
11107
|
+
const url = `ws://127.0.0.1:${port}`;
|
|
11108
|
+
const proc = Bun.spawn(["codex", "app-server", "--listen", url], {
|
|
11109
|
+
cwd,
|
|
11110
|
+
env: { ...stripHostAttribution(process.env), CODEX_HOME: codexHome2 },
|
|
11111
|
+
stdout: "ignore",
|
|
11112
|
+
stderr: "ignore",
|
|
11113
|
+
stdin: "ignore"
|
|
11114
|
+
});
|
|
11115
|
+
if (!await waitForPort(port)) {
|
|
11116
|
+
proc.kill();
|
|
11117
|
+
log(`codex app-server did not open ${port}; session ${id.slice(0, 6)} keeps its hooks`);
|
|
11118
|
+
return;
|
|
11119
|
+
}
|
|
11120
|
+
const client = new CodexAppServerClient(url, (payload) => this.ingestHookEvent(id, payload));
|
|
11121
|
+
client.connect();
|
|
11122
|
+
this.codexServers.set(id, {
|
|
11123
|
+
stop: () => {
|
|
11124
|
+
client.close();
|
|
11125
|
+
try {
|
|
11126
|
+
killTree(proc.pid, "codex.exe");
|
|
11127
|
+
} catch {}
|
|
11128
|
+
try {
|
|
11129
|
+
proc.kill();
|
|
11130
|
+
} catch {}
|
|
11131
|
+
}
|
|
11132
|
+
});
|
|
11133
|
+
log(`codex app-server on ${url} for session ${id.slice(0, 6)} in ${cwd}`);
|
|
11134
|
+
return url;
|
|
11135
|
+
} catch (err) {
|
|
11136
|
+
log(`codex app-server unavailable (${String(err)}); session ${id.slice(0, 6)} keeps its hooks`);
|
|
11137
|
+
return;
|
|
11138
|
+
}
|
|
11139
|
+
}
|
|
11140
|
+
stopCodexAppServer(id) {
|
|
11141
|
+
this.codexServers.get(id)?.stop();
|
|
11142
|
+
this.codexServers.delete(id);
|
|
11143
|
+
}
|
|
9977
11144
|
ensureCodexHome() {
|
|
9978
|
-
const home =
|
|
9979
|
-
|
|
11145
|
+
const home = path19.join(STATE_DIR, "codex-home");
|
|
11146
|
+
fs19.mkdirSync(home, { recursive: true, mode: 448 });
|
|
9980
11147
|
this.linkCodexAuth(home);
|
|
9981
11148
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
|
|
9982
11149
|
const hooksFile = buildCodexHooks({ command: bridge.command, args: bridge.args });
|
|
9983
11150
|
const hooksPath = codexTrustKeyPath(home);
|
|
9984
11151
|
const tmp = `${hooksPath}.tmp`;
|
|
9985
|
-
|
|
9986
|
-
|
|
11152
|
+
fs19.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
|
|
11153
|
+
fs19.renameSync(tmp, hooksPath);
|
|
9987
11154
|
const entries = codexHookTrust(hooksPath, hooksFile);
|
|
9988
|
-
const configPath =
|
|
9989
|
-
const existing =
|
|
11155
|
+
const configPath = path19.join(home, "config.toml");
|
|
11156
|
+
const existing = fs19.existsSync(configPath) ? fs19.readFileSync(configPath, "utf8") : "";
|
|
9990
11157
|
const missing = entries.filter((e) => !existing.includes(`"${e.key.replaceAll("\\", "\\\\")}"`));
|
|
9991
11158
|
if (missing.length > 0) {
|
|
9992
11159
|
const block = codexTrustStateToml(missing);
|
|
9993
|
-
|
|
11160
|
+
fs19.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
|
|
9994
11161
|
`) ? `
|
|
9995
11162
|
${block}` : block);
|
|
9996
11163
|
}
|
|
@@ -10003,18 +11170,18 @@ ${block}` : block);
|
|
|
10003
11170
|
return home;
|
|
10004
11171
|
}
|
|
10005
11172
|
linkCodexAuth(home) {
|
|
10006
|
-
const target =
|
|
10007
|
-
if (
|
|
11173
|
+
const target = path19.join(home, "auth.json");
|
|
11174
|
+
if (fs19.existsSync(target))
|
|
10008
11175
|
return;
|
|
10009
|
-
const operatorHome = process.env["CODEX_HOME"] ??
|
|
10010
|
-
const source =
|
|
10011
|
-
if (!
|
|
11176
|
+
const operatorHome = process.env["CODEX_HOME"] ?? path19.join(os5.homedir(), ".codex");
|
|
11177
|
+
const source = path19.join(operatorHome, "auth.json");
|
|
11178
|
+
if (!fs19.existsSync(source))
|
|
10012
11179
|
throw new AxtermError("err.codex.noAuth", { authFile: source });
|
|
10013
11180
|
try {
|
|
10014
|
-
|
|
11181
|
+
fs19.symlinkSync(source, target, "file");
|
|
10015
11182
|
} catch {
|
|
10016
11183
|
try {
|
|
10017
|
-
|
|
11184
|
+
fs19.linkSync(source, target);
|
|
10018
11185
|
} catch (err) {
|
|
10019
11186
|
throw new AxtermError("err.codex.authLink", { reason: err instanceof Error ? err.message : String(err) });
|
|
10020
11187
|
}
|
|
@@ -10022,37 +11189,45 @@ ${block}` : block);
|
|
|
10022
11189
|
}
|
|
10023
11190
|
doctorReport() {
|
|
10024
11191
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
|
|
10025
|
-
const home =
|
|
10026
|
-
const operatorHome = process.env["CODEX_HOME"] ??
|
|
10027
|
-
const authSource =
|
|
11192
|
+
const home = path19.join(STATE_DIR, "codex-home");
|
|
11193
|
+
const operatorHome = process.env["CODEX_HOME"] ?? path19.join(os5.homedir(), ".codex");
|
|
11194
|
+
const authSource = path19.join(operatorHome, "auth.json");
|
|
10028
11195
|
const geminiSettings = this.geminiSettingsPath();
|
|
10029
|
-
const configPath =
|
|
11196
|
+
const configPath = path19.join(home, "config.toml");
|
|
10030
11197
|
const read = (file) => {
|
|
10031
11198
|
try {
|
|
10032
|
-
return
|
|
11199
|
+
return fs19.existsSync(file) ? fs19.readFileSync(file, "utf8") : undefined;
|
|
10033
11200
|
} catch {
|
|
10034
11201
|
return;
|
|
10035
11202
|
}
|
|
10036
11203
|
};
|
|
10037
11204
|
return [
|
|
10038
|
-
...evaluateBridgeDoctor({ command: bridge.command, exists:
|
|
11205
|
+
...evaluateBridgeDoctor({ command: bridge.command, exists: fs19.existsSync(bridge.command) }),
|
|
10039
11206
|
...evaluateOmpDoctor({
|
|
10040
11207
|
binary: lookupOnPath("omp"),
|
|
10041
11208
|
extensionPath: this.hooks.extensionPath,
|
|
10042
|
-
extensionExists:
|
|
11209
|
+
extensionExists: fs19.existsSync(this.hooks.extensionPath)
|
|
10043
11210
|
}),
|
|
10044
11211
|
...evaluateClaudeDoctor({ binary: lookupOnPath("claude") }),
|
|
10045
11212
|
...evaluateCodexDoctor({
|
|
10046
11213
|
binary: lookupOnPath("codex"),
|
|
10047
11214
|
authSourcePath: authSource,
|
|
10048
|
-
authSourceExists:
|
|
10049
|
-
homeExists:
|
|
11215
|
+
authSourceExists: fs19.existsSync(authSource),
|
|
11216
|
+
homeExists: fs19.existsSync(home),
|
|
10050
11217
|
expected: codexHookTrust(codexTrustKeyPath(home), buildCodexHooks({ command: bridge.command, args: bridge.args })),
|
|
10051
11218
|
configToml: read(configPath)
|
|
10052
11219
|
}),
|
|
10053
11220
|
...evaluateGeminiDoctor({ binary: lookupOnPath("gemini"), settingsJson: read(geminiSettings) })
|
|
10054
11221
|
];
|
|
10055
11222
|
}
|
|
11223
|
+
quotaReport() {
|
|
11224
|
+
const sessions = this.store.listSessions().map((session) => ({
|
|
11225
|
+
cwd: session.cwd,
|
|
11226
|
+
agent: agentIdFromCommand(session.agentCommand),
|
|
11227
|
+
...session.agentSessionId === undefined ? {} : { agentSessionId: session.agentSessionId }
|
|
11228
|
+
}));
|
|
11229
|
+
return quotaReport(collectQuotaFacts(REGISTERED_AGENTS, sessions, lookupOnPath), Date.now());
|
|
11230
|
+
}
|
|
10056
11231
|
sawOutput = new Set;
|
|
10057
11232
|
idleTimers = new Map;
|
|
10058
11233
|
lastCaptured = new Map;
|
|
@@ -10079,7 +11254,7 @@ ${block}` : block);
|
|
|
10079
11254
|
if (!session || !runtime)
|
|
10080
11255
|
return;
|
|
10081
11256
|
const found = detectPrompt(session.screenText(4));
|
|
10082
|
-
const dialog = found === undefined
|
|
11257
|
+
const dialog = found === undefined ? detectOnboarding(agentIdFromCommand(session.info.agentCommand), session.screenText(30)) : undefined;
|
|
10083
11258
|
if (!found && !dialog) {
|
|
10084
11259
|
if (this.pendingPrompt.delete(id))
|
|
10085
11260
|
this.emit({ t: "choice.resolved", sessionId: id, seq: runtime.choiceSeq });
|
|
@@ -10162,51 +11337,89 @@ ${block}` : block);
|
|
|
10162
11337
|
}
|
|
10163
11338
|
writeScreenFile(idOrPrefix, lines) {
|
|
10164
11339
|
const session = this.resolveLive(idOrPrefix);
|
|
10165
|
-
const file =
|
|
10166
|
-
|
|
11340
|
+
const file = path19.join(path19.dirname(this.hooks.endpointFile), `screen-${session.info.id}.txt`);
|
|
11341
|
+
fs19.writeFileSync(file, session.screenText(lines), { mode: 384 });
|
|
10167
11342
|
return file;
|
|
10168
11343
|
}
|
|
10169
11344
|
removeScreenFile(id) {
|
|
10170
11345
|
try {
|
|
10171
|
-
|
|
11346
|
+
fs19.rmSync(path19.join(path19.dirname(this.hooks.endpointFile), `screen-${id}.txt`), { force: true });
|
|
10172
11347
|
} catch {}
|
|
10173
11348
|
}
|
|
10174
11349
|
async stageInboundFile(idOrPrefix, suggestedName, bytes) {
|
|
10175
11350
|
const info = this.resolveLive(idOrPrefix).info;
|
|
10176
|
-
const file =
|
|
10177
|
-
await
|
|
10178
|
-
await
|
|
10179
|
-
log(`session ${info.shortId} staged ${
|
|
11351
|
+
const file = path19.resolve(planMaterialisedPath(MATERIALISED_DIR, info.id, suggestedName));
|
|
11352
|
+
await fs19.promises.mkdir(path19.dirname(file), { recursive: true, mode: 448 });
|
|
11353
|
+
await fs19.promises.writeFile(file, bytes, { mode: 384 });
|
|
11354
|
+
log(`session ${info.shortId} staged ${path19.basename(file)} (${bytes.byteLength} bytes)`);
|
|
10180
11355
|
return file;
|
|
10181
11356
|
}
|
|
10182
11357
|
removeStagedFiles(id) {
|
|
10183
11358
|
try {
|
|
10184
|
-
|
|
11359
|
+
fs19.rmSync(path19.join(MATERIALISED_DIR, id), { recursive: true, force: true });
|
|
10185
11360
|
} catch {}
|
|
10186
11361
|
}
|
|
10187
11362
|
removeClaudeSettings(id) {
|
|
10188
11363
|
try {
|
|
10189
|
-
|
|
11364
|
+
fs19.rmSync(this.claudeSettingsPath(id), { force: true });
|
|
11365
|
+
fs19.rmSync(this.mcpConfigPath(id), { force: true });
|
|
11366
|
+
fs19.rmSync(this.mcpContextPath(id), { force: true });
|
|
11367
|
+
fs19.rmSync(this.geminiTelemetryPath(id), { force: true });
|
|
11368
|
+
this.stopCodexAppServer(id);
|
|
10190
11369
|
} catch {}
|
|
10191
11370
|
}
|
|
10192
11371
|
claudeSettingsPath(id) {
|
|
10193
|
-
return
|
|
11372
|
+
return path19.join(path19.dirname(this.hooks.endpointFile), `claude-${id}.json`);
|
|
11373
|
+
}
|
|
11374
|
+
mcpConfigPath(id) {
|
|
11375
|
+
return path19.join(path19.dirname(this.hooks.endpointFile), `mcp-${id}.json`);
|
|
11376
|
+
}
|
|
11377
|
+
mcpContextPath(id) {
|
|
11378
|
+
return path19.join(path19.dirname(this.hooks.endpointFile), `mcp-ctx-${id}.json`);
|
|
11379
|
+
}
|
|
11380
|
+
writeMcpContext(id, launchToken) {
|
|
11381
|
+
const file = this.mcpContextPath(id);
|
|
11382
|
+
try {
|
|
11383
|
+
fs19.writeFileSync(file, JSON.stringify({ port: this.hooks.port, token: this.hooks.host.token, sessionId: id, launchToken }), { mode: 384 });
|
|
11384
|
+
return file;
|
|
11385
|
+
} catch (err) {
|
|
11386
|
+
log(`mcp context write failed; session starts without axterm's tools: ${String(err)}`);
|
|
11387
|
+
return;
|
|
11388
|
+
}
|
|
11389
|
+
}
|
|
11390
|
+
mcpServerLaunch() {
|
|
11391
|
+
return mcpLaunch({ execPath: process.execPath, mainScript: Bun.main });
|
|
11392
|
+
}
|
|
11393
|
+
writeMcpConfig(id, contextPath) {
|
|
11394
|
+
const file = this.mcpConfigPath(id);
|
|
11395
|
+
try {
|
|
11396
|
+
fs19.writeFileSync(file, claudeMcpConfig(this.mcpServerLaunch(), contextPath), { mode: 384 });
|
|
11397
|
+
return file;
|
|
11398
|
+
} catch (err) {
|
|
11399
|
+
log(`mcp config write failed; session starts without axterm's tools: ${String(err)}`);
|
|
11400
|
+
return;
|
|
11401
|
+
}
|
|
11402
|
+
}
|
|
11403
|
+
geminiTelemetryPath(id) {
|
|
11404
|
+
return path19.join(path19.dirname(this.hooks.endpointFile), `gemini-telemetry-${id}.log`);
|
|
10194
11405
|
}
|
|
10195
11406
|
geminiSettingsPath() {
|
|
10196
|
-
return
|
|
11407
|
+
return path19.join(path19.dirname(this.hooks.endpointFile), "gemini-defaults.json");
|
|
10197
11408
|
}
|
|
10198
|
-
launch(info, shell, agentCommand) {
|
|
11409
|
+
launch(info, shell, agentCommand, appServerUrl) {
|
|
10199
11410
|
const id = info.id;
|
|
10200
11411
|
const launchToken = randomBytes2(16).toString("hex");
|
|
10201
11412
|
const agentId = agentIdFromCommand(info.agentCommand);
|
|
10202
11413
|
if (agentId === "claude")
|
|
10203
11414
|
this.writeClaudeSettings(id, launchToken);
|
|
11415
|
+
if (agentId !== "omp")
|
|
11416
|
+
this.writeMcpContext(id, launchToken);
|
|
10204
11417
|
if (agentId === "gemini")
|
|
10205
|
-
this.writeGeminiSettings();
|
|
10206
|
-
const
|
|
11418
|
+
this.writeGeminiSettings(id);
|
|
11419
|
+
const codexHome2 = agentId === "codex" ? this.ensureCodexHome() : undefined;
|
|
10207
11420
|
const session = new PtySession(info, {
|
|
10208
11421
|
exe: shell.exe,
|
|
10209
|
-
argv: buildShellArgv(shell, agentCommand),
|
|
11422
|
+
argv: buildShellArgv(shell, withAppServer(agentCommand, appServerUrl)),
|
|
10210
11423
|
cwd: info.cwd,
|
|
10211
11424
|
cols: info.cols,
|
|
10212
11425
|
rows: info.rows,
|
|
@@ -10217,8 +11430,15 @@ ${block}` : block);
|
|
|
10217
11430
|
AXTERM_HOOK_PORT: String(this.hooks.port),
|
|
10218
11431
|
AXTERM_HOOK_TOKEN: this.hooks.host.token,
|
|
10219
11432
|
AXTERM_HOOK_ENDPOINT: this.hooks.endpointFile,
|
|
10220
|
-
...agentId === "gemini" ? {
|
|
10221
|
-
|
|
11433
|
+
...agentId === "gemini" ? {
|
|
11434
|
+
GEMINI_CLI_SYSTEM_DEFAULTS_PATH: this.geminiSettingsPath(),
|
|
11435
|
+
GEMINI_TELEMETRY_ENABLED: "true",
|
|
11436
|
+
GEMINI_TELEMETRY_TARGET: "local",
|
|
11437
|
+
GEMINI_TELEMETRY_OUTFILE: this.geminiTelemetryPath(id),
|
|
11438
|
+
GEMINI_TELEMETRY_LOG_PROMPTS: "false",
|
|
11439
|
+
GEMINI_TELEMETRY_TRACES_ENABLED: "false"
|
|
11440
|
+
} : {},
|
|
11441
|
+
...codexHome2 !== undefined ? { CODEX_HOME: codexHome2 } : {}
|
|
10222
11442
|
}
|
|
10223
11443
|
});
|
|
10224
11444
|
this.agentRuntime.set(id, { agentExited: false, choiceSeq: 0, dialogOpen: false });
|
|
@@ -10340,6 +11560,9 @@ ${block}` : block);
|
|
|
10340
11560
|
this.emit({ t: "chat.message", sessionId: id, role: "assistant", text: result.assistantText });
|
|
10341
11561
|
}
|
|
10342
11562
|
if (payload.event === "before_agent_start") {
|
|
11563
|
+
const runtime2 = this.agentRuntime.get(id);
|
|
11564
|
+
if (runtime2 !== undefined)
|
|
11565
|
+
runtime2.telemetryReported = false;
|
|
10343
11566
|
const prompt = (payload.prompt ?? "").trim();
|
|
10344
11567
|
if (prompt.length > 0 && !this.echoGuard.consume(id, prompt, now)) {
|
|
10345
11568
|
this.emit({ t: "chat.message", sessionId: id, role: "user", text: prompt });
|
|
@@ -10416,14 +11639,10 @@ ${block}` : block);
|
|
|
10416
11639
|
return this.allSessions();
|
|
10417
11640
|
}
|
|
10418
11641
|
allSessions() {
|
|
10419
|
-
|
|
10420
|
-
return [
|
|
10421
|
-
...[...this.live.values()].map((s) => s.info),
|
|
10422
|
-
...this.store.listSessions().filter((s) => !liveIds.has(s.id))
|
|
10423
|
-
];
|
|
11642
|
+
return sessionsByRecency([...this.live.values()].map((s) => s.info), this.store.listSessions());
|
|
10424
11643
|
}
|
|
10425
11644
|
resumeNamed(stored, agent) {
|
|
10426
|
-
if (!
|
|
11645
|
+
if (!fs19.existsSync(stored.cwd))
|
|
10427
11646
|
throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
|
|
10428
11647
|
const agentCommand = buildAgentResumeCommand({
|
|
10429
11648
|
agent,
|
|
@@ -10442,7 +11661,7 @@ ${block}` : block);
|
|
|
10442
11661
|
this.emit({ t: "session.created", info });
|
|
10443
11662
|
return info;
|
|
10444
11663
|
}
|
|
10445
|
-
resume(idOrPrefix) {
|
|
11664
|
+
async resume(idOrPrefix) {
|
|
10446
11665
|
const stored = this.store.findByPrefix(idOrPrefix);
|
|
10447
11666
|
if (!stored)
|
|
10448
11667
|
throw new AxtermError("err.session.unknown", { id: idOrPrefix });
|
|
@@ -10463,7 +11682,7 @@ ${block}` : block);
|
|
|
10463
11682
|
if (agentCommand === undefined) {
|
|
10464
11683
|
throw new AxtermError("err.session.noResumeSupport", { agent: profile.displayName });
|
|
10465
11684
|
}
|
|
10466
|
-
if (!
|
|
11685
|
+
if (!fs19.existsSync(stored.cwd))
|
|
10467
11686
|
throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
|
|
10468
11687
|
const info = { ...stored, agentCommand, state: "running" };
|
|
10469
11688
|
delete info.exitCode;
|
|
@@ -10471,7 +11690,8 @@ ${block}` : block);
|
|
|
10471
11690
|
delete info.agentState;
|
|
10472
11691
|
delete info.agentStateSource;
|
|
10473
11692
|
const shell = resolveShell(info.shell);
|
|
10474
|
-
this.
|
|
11693
|
+
const resumedAppServer = agentIdFromCommand(agentCommand) === "codex" ? await this.startCodexAppServer(info.id, this.ensureCodexHome(), info.cwd) : undefined;
|
|
11694
|
+
this.launch(info, shell, agentCommand, resumedAppServer);
|
|
10475
11695
|
this.store.markResumed(info.id, agentCommand, info.ptyPid);
|
|
10476
11696
|
log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
|
|
10477
11697
|
this.emit({ t: "session.created", info });
|
|
@@ -10536,11 +11756,86 @@ ${block}` : block);
|
|
|
10536
11756
|
removeScrollback(id) {
|
|
10537
11757
|
for (const file of [`${id}.bin`, `${id}.bin.tmp`]) {
|
|
10538
11758
|
try {
|
|
10539
|
-
|
|
11759
|
+
fs19.rmSync(path19.join(SCROLLBACK_DIR, file), { force: true });
|
|
10540
11760
|
} catch {}
|
|
10541
11761
|
}
|
|
10542
11762
|
}
|
|
11763
|
+
tickSessions() {
|
|
11764
|
+
this.drainGeminiTelemetry();
|
|
11765
|
+
this.watchForStalls();
|
|
11766
|
+
}
|
|
11767
|
+
drainGeminiTelemetry() {
|
|
11768
|
+
for (const [id, session] of this.live) {
|
|
11769
|
+
if (agentIdFromCommand(session.info.agentCommand) !== "gemini")
|
|
11770
|
+
continue;
|
|
11771
|
+
const runtime = this.agentRuntime.get(id);
|
|
11772
|
+
if (runtime === undefined)
|
|
11773
|
+
continue;
|
|
11774
|
+
const file = this.geminiTelemetryPath(id);
|
|
11775
|
+
let size = 0;
|
|
11776
|
+
try {
|
|
11777
|
+
size = fs19.statSync(file).size;
|
|
11778
|
+
} catch {
|
|
11779
|
+
continue;
|
|
11780
|
+
}
|
|
11781
|
+
const offset = runtime.telemetryOffset ?? 0;
|
|
11782
|
+
const from = size < offset ? 0 : offset;
|
|
11783
|
+
if (size < offset)
|
|
11784
|
+
runtime.telemetryRemainder = "";
|
|
11785
|
+
if (size <= from)
|
|
11786
|
+
continue;
|
|
11787
|
+
const chunk = readFileRange(file, from, size);
|
|
11788
|
+
runtime.telemetryOffset = size;
|
|
11789
|
+
const split = splitTelemetryRecords(`${runtime.telemetryRemainder ?? ""}${chunk}`);
|
|
11790
|
+
runtime.telemetryRemainder = split.remainder;
|
|
11791
|
+
const failures = parseGeminiTelemetry(split.complete);
|
|
11792
|
+
if (failures.length === 0)
|
|
11793
|
+
continue;
|
|
11794
|
+
const first = failures[0];
|
|
11795
|
+
log(`session ${session.info.shortId} gemini api failure${first.status === undefined ? "" : ` (${first.status})`}: ${first.text.slice(0, 200)}`);
|
|
11796
|
+
if (runtime.telemetryReported === true)
|
|
11797
|
+
continue;
|
|
11798
|
+
runtime.telemetryReported = true;
|
|
11799
|
+
this.emit({
|
|
11800
|
+
t: "agent.error",
|
|
11801
|
+
sessionId: id,
|
|
11802
|
+
kind: "attempt",
|
|
11803
|
+
text: first.status === undefined ? first.text : `${first.status} ${first.text}`
|
|
11804
|
+
});
|
|
11805
|
+
}
|
|
11806
|
+
}
|
|
11807
|
+
watchForStalls() {
|
|
11808
|
+
const now = Date.now();
|
|
11809
|
+
for (const [id, session] of this.live) {
|
|
11810
|
+
const runtime = this.agentRuntime.get(id);
|
|
11811
|
+
if (runtime === undefined)
|
|
11812
|
+
continue;
|
|
11813
|
+
const quietForMs = now - session.lastOutputAt;
|
|
11814
|
+
if (quietForMs <= STALL_AFTER_MS) {
|
|
11815
|
+
runtime.stallReported = false;
|
|
11816
|
+
continue;
|
|
11817
|
+
}
|
|
11818
|
+
const storeQuiet = storeQuietForMs(agentIdFromCommand(session.info.agentCommand), session.info.cwd, session.info.agentSessionId, now);
|
|
11819
|
+
if (!shouldReportStall({
|
|
11820
|
+
agentState: runtime.entry?.state,
|
|
11821
|
+
quietForMs,
|
|
11822
|
+
thresholdMs: STALL_AFTER_MS,
|
|
11823
|
+
reported: runtime.stallReported === true,
|
|
11824
|
+
...storeQuiet === undefined ? {} : { storeQuietForMs: storeQuiet }
|
|
11825
|
+
})) {
|
|
11826
|
+
continue;
|
|
11827
|
+
}
|
|
11828
|
+
runtime.stallReported = true;
|
|
11829
|
+
const screen = quoteScreenTail(session.screenText(STALL_SCREEN_LINES), STALL_QUOTE_LINES).join(`
|
|
11830
|
+
`);
|
|
11831
|
+
log(`session ${session.info.shortId} has made no progress for ${Math.round(quietForMs / 1000)}s`);
|
|
11832
|
+
this.emit({ t: "agent.error", sessionId: id, kind: "stalled", text: screen, retryMs: quietForMs });
|
|
11833
|
+
}
|
|
11834
|
+
}
|
|
10543
11835
|
shutdownAll() {
|
|
11836
|
+
clearInterval(this.sessionWatch);
|
|
11837
|
+
for (const id of [...this.codexServers.keys()])
|
|
11838
|
+
this.stopCodexAppServer(id);
|
|
10544
11839
|
for (const session of this.live.values()) {
|
|
10545
11840
|
session.saveScrollback();
|
|
10546
11841
|
session.kill();
|
|
@@ -10549,7 +11844,7 @@ ${block}` : block);
|
|
|
10549
11844
|
this.agentRuntime.clear();
|
|
10550
11845
|
}
|
|
10551
11846
|
}
|
|
10552
|
-
var DEFAULT_COLS = 120, DEFAULT_ROWS = 30, MATERIALISED_DIR, ACTIVITY_ARG_CHARS = 160, PROMPT_SETTLE_MS = 400, PROMPT_OPTIONS, AGENT_EXIT_CONFIRM_MS = 5000;
|
|
11847
|
+
var DEFAULT_COLS = 120, DEFAULT_ROWS = 30, MATERIALISED_DIR, ACTIVITY_ARG_CHARS = 160, PROMPT_SETTLE_MS = 400, STALL_AFTER_MS = 180000, STALL_SCREEN_LINES = 24, STALL_QUOTE_LINES = 6, SESSION_TICK_MS = 5000, PROMPT_OPTIONS, AGENT_EXIT_CONFIRM_MS = 5000;
|
|
10553
11848
|
var init_session_manager = __esm(() => {
|
|
10554
11849
|
init_src();
|
|
10555
11850
|
init_codex_home();
|
|
@@ -10557,11 +11852,14 @@ var init_session_manager = __esm(() => {
|
|
|
10557
11852
|
init_git();
|
|
10558
11853
|
init_injector();
|
|
10559
11854
|
init_occupancy();
|
|
11855
|
+
init_agent_store();
|
|
11856
|
+
init_codex_app_server();
|
|
11857
|
+
init_pty();
|
|
10560
11858
|
init_paths();
|
|
10561
11859
|
init_pty_session();
|
|
10562
11860
|
init_shell();
|
|
10563
11861
|
init_workspace_local();
|
|
10564
|
-
MATERIALISED_DIR =
|
|
11862
|
+
MATERIALISED_DIR = path19.join(RUNTIME_DIR, "materialised");
|
|
10565
11863
|
PROMPT_OPTIONS = {
|
|
10566
11864
|
hostKey: [{ label: "yes" }, { label: "no" }],
|
|
10567
11865
|
password: [],
|
|
@@ -10710,11 +12008,11 @@ var init_acknowledge = __esm(() => {
|
|
|
10710
12008
|
});
|
|
10711
12009
|
|
|
10712
12010
|
// packages/channel-telegram/src/attach.ts
|
|
10713
|
-
import
|
|
10714
|
-
import
|
|
12011
|
+
import fs20 from "fs";
|
|
12012
|
+
import path20 from "path";
|
|
10715
12013
|
function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELEGRAM_LIMITS) {
|
|
10716
|
-
const name =
|
|
10717
|
-
const bytes =
|
|
12014
|
+
const name = path20.basename(filePath);
|
|
12015
|
+
const bytes = fs20.statSync(filePath).size;
|
|
10718
12016
|
const plan = planAttachment({ name, bytes }, limits);
|
|
10719
12017
|
if (plan.kind === "rejected")
|
|
10720
12018
|
throw new Error(plan.reason);
|
|
@@ -10724,7 +12022,7 @@ function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELE
|
|
|
10724
12022
|
form.append("message_thread_id", String(topicId));
|
|
10725
12023
|
if (caption !== undefined && caption.length > 0)
|
|
10726
12024
|
form.append("caption", caption);
|
|
10727
|
-
form.append(field, new Blob([
|
|
12025
|
+
form.append(field, new Blob([fs20.readFileSync(filePath)]), name);
|
|
10728
12026
|
return { method: plan.kind === "photo" ? "sendPhoto" : "sendDocument", field, form };
|
|
10729
12027
|
}
|
|
10730
12028
|
function isPhotoRejection(error) {
|
|
@@ -10732,13 +12030,13 @@ function isPhotoRejection(error) {
|
|
|
10732
12030
|
return /IMAGE_PROCESS_FAILED|PHOTO_INVALID_DIMENSIONS|PHOTO_EXT_INVALID|PHOTO_SAVE_FILE_INVALID/i.test(message);
|
|
10733
12031
|
}
|
|
10734
12032
|
function asDocumentUpload(filePath, chatId, topicId, caption) {
|
|
10735
|
-
const name =
|
|
12033
|
+
const name = path20.basename(filePath);
|
|
10736
12034
|
const form = new FormData;
|
|
10737
12035
|
form.append("chat_id", String(chatId));
|
|
10738
12036
|
form.append("message_thread_id", String(topicId));
|
|
10739
12037
|
if (caption !== undefined && caption.length > 0)
|
|
10740
12038
|
form.append("caption", caption);
|
|
10741
|
-
form.append("document", new Blob([
|
|
12039
|
+
form.append("document", new Blob([fs20.readFileSync(filePath)]), name);
|
|
10742
12040
|
return { method: "sendDocument", field: "document", form };
|
|
10743
12041
|
}
|
|
10744
12042
|
var TELEGRAM_LIMITS;
|
|
@@ -11505,6 +12803,14 @@ class TelegramChannel {
|
|
|
11505
12803
|
await this.sendToSession(sessionId, message);
|
|
11506
12804
|
}
|
|
11507
12805
|
}
|
|
12806
|
+
quotaText() {
|
|
12807
|
+
const checks = this.control.quota();
|
|
12808
|
+
if (checks.length === 0)
|
|
12809
|
+
return escapeHtml(t("cli.quota.none"));
|
|
12810
|
+
const GLYPHS = { ok: "\uD83D\uDFE2", info: "\u26AA", warn: "\uD83D\uDD34", fail: "\uD83D\uDD34" };
|
|
12811
|
+
return checks.map((check) => `${GLYPHS[check.status]} ${escapeHtml(t(check.key, check.params ?? {}))}`).join(`
|
|
12812
|
+
`);
|
|
12813
|
+
}
|
|
11508
12814
|
async onAgentStatus(sessionId, state, toolName) {
|
|
11509
12815
|
const binding = this.control.findBindingBySession(sessionId);
|
|
11510
12816
|
if (!binding)
|
|
@@ -11625,7 +12931,7 @@ ${code(prompt.line)}`,
|
|
|
11625
12931
|
});
|
|
11626
12932
|
}
|
|
11627
12933
|
awaitingSecret = new Map;
|
|
11628
|
-
async onChoiceRequest(sessionId, seq, questions, askId,
|
|
12934
|
+
async onChoiceRequest(sessionId, seq, questions, askId, onboarding2 = false) {
|
|
11629
12935
|
const binding = this.control.findBindingBySession(sessionId);
|
|
11630
12936
|
if (!binding || questions.length === 0)
|
|
11631
12937
|
return;
|
|
@@ -11642,7 +12948,7 @@ ${code(prompt.line)}`,
|
|
|
11642
12948
|
answers: [],
|
|
11643
12949
|
picked: [],
|
|
11644
12950
|
...askId !== undefined ? { askId } : {},
|
|
11645
|
-
...
|
|
12951
|
+
...onboarding2 ? { onboarding: true } : {}
|
|
11646
12952
|
});
|
|
11647
12953
|
await this.renderChoiceStep(sessionId);
|
|
11648
12954
|
}
|
|
@@ -11707,7 +13013,7 @@ ${code(prompt.line)}`,
|
|
|
11707
13013
|
}
|
|
11708
13014
|
async onAgentError(sessionId, kind, text, retryMs) {
|
|
11709
13015
|
const reason = clipText(text, AGENT_ERROR_CHARS);
|
|
11710
|
-
const message = kind === "retry" && retryMs !== undefined && retryMs > 0 ? tHtml("tg.agent.retry", { wait: humanDuration(retryMs), error: reason }) : tHtml("tg.agent.failed", { error: reason });
|
|
13016
|
+
const message = kind === "stalled" ? tHtml("tg.agent.stalled", { wait: humanDuration(retryMs ?? 0), screen: reason }) : kind === "attempt" ? tHtml("tg.agent.attempt", { error: reason }) : kind === "retry" && retryMs !== undefined && retryMs > 0 ? tHtml("tg.agent.retry", { wait: humanDuration(retryMs), error: reason }) : tHtml("tg.agent.failed", { error: reason });
|
|
11711
13017
|
await this.sendToSession(sessionId, message);
|
|
11712
13018
|
}
|
|
11713
13019
|
async onChatMessage(sessionId, role, text) {
|
|
@@ -11871,8 +13177,8 @@ ${code(prompt.line)}`,
|
|
|
11871
13177
|
throw new Error("this Telegram client cannot download files");
|
|
11872
13178
|
const files = [];
|
|
11873
13179
|
for (const item of batch.items) {
|
|
11874
|
-
const
|
|
11875
|
-
files.push({ path:
|
|
13180
|
+
const path21 = await this.control.stageInboundFile(batch.sessionId, item.name, await download2(item.fileId));
|
|
13181
|
+
files.push({ path: path21, kind: item.kind });
|
|
11876
13182
|
}
|
|
11877
13183
|
await this.control.prompt(batch.sessionId, buildAttachmentPrompt(files, batch.caption));
|
|
11878
13184
|
this.log(`staged ${files.length} inbound file(s) for ${batch.sessionId}`);
|
|
@@ -11898,6 +13204,7 @@ ${code(prompt.line)}`,
|
|
|
11898
13204
|
}
|
|
11899
13205
|
async handleCommand(text, binding, msg) {
|
|
11900
13206
|
const [rawCmd = "", ...args] = text.split(/\s+/);
|
|
13207
|
+
const rawTail = text.slice(rawCmd.length);
|
|
11901
13208
|
const cmd = rawCmd.replace(/@\w+$/, "");
|
|
11902
13209
|
if (binding) {
|
|
11903
13210
|
switch (cmd) {
|
|
@@ -11907,9 +13214,34 @@ ${code(prompt.line)}`,
|
|
|
11907
13214
|
case "/kill":
|
|
11908
13215
|
this.control.kill(binding.sessionId);
|
|
11909
13216
|
return;
|
|
13217
|
+
case "/key": {
|
|
13218
|
+
const parsed = parseKeyNames(args.join(" "));
|
|
13219
|
+
if ("unknown" in parsed) {
|
|
13220
|
+
await this.sendToSession(binding.sessionId, tHtml("tg.raw.unknownKey", { name: parsed.unknown, keys: KEY_NAMES.join(" ") }));
|
|
13221
|
+
return;
|
|
13222
|
+
}
|
|
13223
|
+
for (const sequence of parsed.keys)
|
|
13224
|
+
this.control.write(binding.sessionId, sequence);
|
|
13225
|
+
await this.sendToSession(binding.sessionId, tHtml("tg.raw.keysSent", { keys: args.join(" ") }));
|
|
13226
|
+
return;
|
|
13227
|
+
}
|
|
13228
|
+
case "/type": {
|
|
13229
|
+
const typed = rawTail.replace(/^ /, "");
|
|
13230
|
+
if (typed === "") {
|
|
13231
|
+
await this.sendToSession(binding.sessionId, tHtml("tg.raw.needText"));
|
|
13232
|
+
return;
|
|
13233
|
+
}
|
|
13234
|
+
if (typed.length > RAW_TYPE_LIMIT) {
|
|
13235
|
+
await this.sendToSession(binding.sessionId, tHtml("tg.raw.tooLong", { limit: RAW_TYPE_LIMIT }));
|
|
13236
|
+
return;
|
|
13237
|
+
}
|
|
13238
|
+
this.control.write(binding.sessionId, typed);
|
|
13239
|
+
await this.sendToSession(binding.sessionId, tHtml("tg.raw.typed", { screen: this.control.screen(binding.sessionId, RAW_SCREEN_LINES) }));
|
|
13240
|
+
return;
|
|
13241
|
+
}
|
|
11910
13242
|
case "/resume": {
|
|
11911
13243
|
try {
|
|
11912
|
-
const info = this.control.resume(binding.sessionId);
|
|
13244
|
+
const info = await this.control.resume(binding.sessionId);
|
|
11913
13245
|
await this.sendToSession(binding.sessionId, tHtml("tg.resume.done", { command: info.agentCommand.join(" ") }));
|
|
11914
13246
|
} catch (err) {
|
|
11915
13247
|
await this.sendToSession(binding.sessionId, tHtml("tg.resume.failed", { error: localizeError(err) }));
|
|
@@ -11924,6 +13256,10 @@ ${code(prompt.line)}`,
|
|
|
11924
13256
|
case "/diff":
|
|
11925
13257
|
await this.postDiffSummary(binding.sessionId);
|
|
11926
13258
|
return;
|
|
13259
|
+
case "/quota": {
|
|
13260
|
+
await this.sendToSession(binding.sessionId, this.quotaText());
|
|
13261
|
+
return;
|
|
13262
|
+
}
|
|
11927
13263
|
default:
|
|
11928
13264
|
await this.sendToSession(binding.sessionId, helpText());
|
|
11929
13265
|
return;
|
|
@@ -12122,8 +13458,8 @@ ${code(prompt.line)}`,
|
|
|
12122
13458
|
return;
|
|
12123
13459
|
}
|
|
12124
13460
|
if (flow.askId === undefined) {
|
|
12125
|
-
const
|
|
12126
|
-
this.control.write(session.id,
|
|
13461
|
+
const keys2 = agentProfile(agentIdFromCommand(session.agentCommand)).approvalKeys;
|
|
13462
|
+
this.control.write(session.id, keys2 !== undefined && decoded.optionIndex <= 1 ? decoded.optionIndex === 0 ? keys2.allow : keys2.deny : buildMenuSelectKeys(decoded.optionIndex));
|
|
12127
13463
|
}
|
|
12128
13464
|
flow.answers.push({ questionIndex: flow.cursor, selected: [label] });
|
|
12129
13465
|
flow.cursor += 1;
|
|
@@ -12201,7 +13537,7 @@ ${code(prompt.line)}`,
|
|
|
12201
13537
|
}
|
|
12202
13538
|
}
|
|
12203
13539
|
}
|
|
12204
|
-
var SCREEN_MESSAGE_BUDGET = 2, AGENT_ERROR_CHARS = 1200, HELP_KEYS, helpText = () => HELP_KEYS.map((key) => escapeHtml(t(key))).join(`
|
|
13540
|
+
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(`
|
|
12205
13541
|
`), ALBUM_DEBOUNCE_MS = 2000, UNKNOWN_SIZE = 1, REFUSAL_KEYS, megabytes2 = (bytes) => `${Math.round(bytes / (1024 * 1024))} MB`;
|
|
12206
13542
|
var init_adapter = __esm(() => {
|
|
12207
13543
|
init_src();
|
|
@@ -12268,6 +13604,7 @@ class TelegramService {
|
|
|
12268
13604
|
return {
|
|
12269
13605
|
...telegram?.botToken !== undefined ? { botToken: telegram.botToken } : {},
|
|
12270
13606
|
...telegram?.groupId !== undefined ? { groupId: telegram.groupId } : {},
|
|
13607
|
+
...telegram?.enabled !== undefined ? { enabled: telegram.enabled } : {},
|
|
12271
13608
|
...telegram?.allowUserIds?.[0] !== undefined ? { allowUserId: telegram.allowUserIds[0] } : {},
|
|
12272
13609
|
...machine2 !== undefined ? { machine: machine2 } : {}
|
|
12273
13610
|
};
|
|
@@ -12314,11 +13651,15 @@ class TelegramService {
|
|
|
12314
13651
|
}
|
|
12315
13652
|
start() {
|
|
12316
13653
|
const s = this.readSettings();
|
|
13654
|
+
this.stop();
|
|
13655
|
+
if (s.enabled === false) {
|
|
13656
|
+
log("telegram: disabled by configuration, channel not started");
|
|
13657
|
+
return false;
|
|
13658
|
+
}
|
|
12317
13659
|
if (!s.botToken || s.groupId === undefined || s.allowUserId === undefined) {
|
|
12318
13660
|
log("telegram: not configured, channel not started");
|
|
12319
13661
|
return false;
|
|
12320
13662
|
}
|
|
12321
|
-
this.stop();
|
|
12322
13663
|
const api = new FetchTelegramApi(s.botToken);
|
|
12323
13664
|
const config = {
|
|
12324
13665
|
groupId: s.groupId,
|
|
@@ -12360,6 +13701,7 @@ class TelegramService {
|
|
|
12360
13701
|
answerShellPrompt: (id, seq, text) => this.manager.answerShellPrompt(id, seq, text),
|
|
12361
13702
|
answerOnboarding: (id, seq, optionIndex) => this.manager.answerOnboardingChoice(id, seq, optionIndex),
|
|
12362
13703
|
diff: (id) => this.manager.sessionDiff(id),
|
|
13704
|
+
quota: () => this.manager.quotaReport(),
|
|
12363
13705
|
saveBinding: (b) => this.store.saveBinding(b),
|
|
12364
13706
|
findBindingBySession: (id) => this.store.findBindingBySession(id),
|
|
12365
13707
|
findBindingByTopic: (chatId, topicId) => this.store.findBindingByTopic(chatId, topicId),
|
|
@@ -12466,8 +13808,8 @@ var init_telegram_service = __esm(() => {
|
|
|
12466
13808
|
// packages/daemon/src/index.ts
|
|
12467
13809
|
var exports_src = {};
|
|
12468
13810
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
12469
|
-
import
|
|
12470
|
-
import
|
|
13811
|
+
import fs21 from "fs";
|
|
13812
|
+
import net4 from "net";
|
|
12471
13813
|
function isPidAlive(pid) {
|
|
12472
13814
|
try {
|
|
12473
13815
|
process.kill(pid, 0);
|
|
@@ -12478,9 +13820,9 @@ function isPidAlive(pid) {
|
|
|
12478
13820
|
}
|
|
12479
13821
|
async function main() {
|
|
12480
13822
|
ensureDataDirs();
|
|
12481
|
-
if (
|
|
13823
|
+
if (fs21.existsSync(RUNTIME_FILE2)) {
|
|
12482
13824
|
try {
|
|
12483
|
-
const existing = JSON.parse(
|
|
13825
|
+
const existing = JSON.parse(fs21.readFileSync(RUNTIME_FILE2, "utf8"));
|
|
12484
13826
|
if (existing.pid !== process.pid && isPidAlive(existing.pid)) {
|
|
12485
13827
|
log(`daemon already running (pid ${existing.pid}); exiting`);
|
|
12486
13828
|
process.exit(0);
|
|
@@ -12503,7 +13845,7 @@ async function main() {
|
|
|
12503
13845
|
version: productVersion(),
|
|
12504
13846
|
startedAt: Date.now()
|
|
12505
13847
|
};
|
|
12506
|
-
|
|
13848
|
+
fs21.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
|
|
12507
13849
|
restrictFile(RUNTIME_FILE2);
|
|
12508
13850
|
const config = new ConfigStore(defaultConfigStorePaths(CONFIG_DIR));
|
|
12509
13851
|
if (config.loadError !== undefined)
|
|
@@ -12521,7 +13863,7 @@ async function main() {
|
|
|
12521
13863
|
port: hookPort,
|
|
12522
13864
|
endpointFile: HOOK_ENDPOINT_FILE,
|
|
12523
13865
|
extensionPath
|
|
12524
|
-
});
|
|
13866
|
+
}, () => config.resolved().defaults ?? {});
|
|
12525
13867
|
const telegram = new TelegramService(config, store, manager);
|
|
12526
13868
|
hookHost.onAttachment(async (sessionId, filePath, caption) => {
|
|
12527
13869
|
await telegram.sendAttachment(sessionId, filePath, caption);
|
|
@@ -12555,9 +13897,9 @@ async function main() {
|
|
|
12555
13897
|
hookHost.stop();
|
|
12556
13898
|
store.close();
|
|
12557
13899
|
try {
|
|
12558
|
-
const current = JSON.parse(
|
|
13900
|
+
const current = JSON.parse(fs21.readFileSync(RUNTIME_FILE2, "utf8"));
|
|
12559
13901
|
if (current.pid === process.pid)
|
|
12560
|
-
|
|
13902
|
+
fs21.rmSync(RUNTIME_FILE2);
|
|
12561
13903
|
} catch {}
|
|
12562
13904
|
process.exit(0);
|
|
12563
13905
|
};
|
|
@@ -12590,14 +13932,14 @@ var init_src3 = __esm(() => {
|
|
|
12590
13932
|
init_session_manager();
|
|
12591
13933
|
init_telegram_service();
|
|
12592
13934
|
init_store();
|
|
12593
|
-
|
|
13935
|
+
net4.setDefaultAutoSelectFamilyAttemptTimeout(3000);
|
|
12594
13936
|
main();
|
|
12595
13937
|
});
|
|
12596
13938
|
|
|
12597
13939
|
// packages/cli/src/index.ts
|
|
12598
13940
|
init_src();
|
|
12599
|
-
import
|
|
12600
|
-
import
|
|
13941
|
+
import fs22 from "fs";
|
|
13942
|
+
import path21 from "path";
|
|
12601
13943
|
|
|
12602
13944
|
// packages/cli/src/attach.ts
|
|
12603
13945
|
import fs from "fs";
|
|
@@ -13237,18 +14579,289 @@ async function relayHookEvent(agent) {
|
|
|
13237
14579
|
} catch {}
|
|
13238
14580
|
}
|
|
13239
14581
|
|
|
14582
|
+
// packages/cli/src/mcp-server.ts
|
|
14583
|
+
init_src();
|
|
14584
|
+
import fs3 from "fs";
|
|
14585
|
+
import path4 from "path";
|
|
14586
|
+
var ASK_TIMEOUT_MS = 10 * 60000;
|
|
14587
|
+
var POST_TIMEOUT_MS2 = 5000;
|
|
14588
|
+
var TOOLS = [
|
|
14589
|
+
{
|
|
14590
|
+
name: "axterm_send_file",
|
|
14591
|
+
description: "Send a file from this machine to the operator's messaging channel (the chat they are watching " + "this session from). Use when asked to send, share, deliver, or show a file, image, screenshot, " + "log, or document. Images arrive inline; anything else as a download.",
|
|
14592
|
+
inputSchema: {
|
|
14593
|
+
type: "object",
|
|
14594
|
+
properties: {
|
|
14595
|
+
path: { type: "string", description: "Path to the file; relative paths resolve against the session's folder" },
|
|
14596
|
+
caption: { type: "string", description: "Optional one-line caption" }
|
|
14597
|
+
},
|
|
14598
|
+
required: ["path"]
|
|
14599
|
+
}
|
|
14600
|
+
},
|
|
14601
|
+
{
|
|
14602
|
+
name: "axterm_ask",
|
|
14603
|
+
description: "Ask the operator a question with a fixed set of choices. The question appears both in this " + "terminal and in the operator's messaging channel, and whichever they answer first is used. " + "Prefer this over guessing when a decision is genuinely the operator's to make.",
|
|
14604
|
+
inputSchema: {
|
|
14605
|
+
type: "object",
|
|
14606
|
+
properties: {
|
|
14607
|
+
questions: {
|
|
14608
|
+
type: "array",
|
|
14609
|
+
description: "One or more questions to put to the operator",
|
|
14610
|
+
items: {
|
|
14611
|
+
type: "object",
|
|
14612
|
+
properties: {
|
|
14613
|
+
question: { type: "string" },
|
|
14614
|
+
options: { type: "array", items: { type: "string" }, description: "Omit for a free-text answer" },
|
|
14615
|
+
multi: { type: "boolean", description: "Allow more than one choice" }
|
|
14616
|
+
},
|
|
14617
|
+
required: ["question"]
|
|
14618
|
+
}
|
|
14619
|
+
}
|
|
14620
|
+
},
|
|
14621
|
+
required: ["questions"]
|
|
14622
|
+
}
|
|
14623
|
+
}
|
|
14624
|
+
];
|
|
14625
|
+
|
|
14626
|
+
class McpServer {
|
|
14627
|
+
io;
|
|
14628
|
+
clientElicits = false;
|
|
14629
|
+
nextRequestId = 1;
|
|
14630
|
+
pending = new Map;
|
|
14631
|
+
constructor(io) {
|
|
14632
|
+
this.io = io;
|
|
14633
|
+
}
|
|
14634
|
+
handle(raw) {
|
|
14635
|
+
let message;
|
|
14636
|
+
try {
|
|
14637
|
+
message = JSON.parse(raw);
|
|
14638
|
+
} catch {
|
|
14639
|
+
return;
|
|
14640
|
+
}
|
|
14641
|
+
if (message.method === undefined && message.id !== undefined) {
|
|
14642
|
+
const resolve = this.pending.get(Number(message.id));
|
|
14643
|
+
this.pending.delete(Number(message.id));
|
|
14644
|
+
resolve?.(message.result ?? message.error);
|
|
14645
|
+
return;
|
|
14646
|
+
}
|
|
14647
|
+
this.dispatch(message);
|
|
14648
|
+
}
|
|
14649
|
+
send(message) {
|
|
14650
|
+
this.io.write(`${JSON.stringify(message)}
|
|
14651
|
+
`);
|
|
14652
|
+
}
|
|
14653
|
+
async dispatch(message) {
|
|
14654
|
+
const { id, method } = message;
|
|
14655
|
+
switch (method) {
|
|
14656
|
+
case "initialize": {
|
|
14657
|
+
const params = message.params ?? {};
|
|
14658
|
+
const capabilities = params["capabilities"];
|
|
14659
|
+
this.clientElicits = typeof capabilities === "object" && capabilities !== null && "elicitation" in capabilities;
|
|
14660
|
+
this.send({
|
|
14661
|
+
jsonrpc: "2.0",
|
|
14662
|
+
id,
|
|
14663
|
+
result: {
|
|
14664
|
+
protocolVersion: typeof params["protocolVersion"] === "string" ? params["protocolVersion"] : "2025-06-18",
|
|
14665
|
+
capabilities: { tools: {} },
|
|
14666
|
+
serverInfo: { name: "axterm", version: "1" }
|
|
14667
|
+
}
|
|
14668
|
+
});
|
|
14669
|
+
return;
|
|
14670
|
+
}
|
|
14671
|
+
case "tools/list":
|
|
14672
|
+
this.send({ jsonrpc: "2.0", id, result: { tools: TOOLS } });
|
|
14673
|
+
return;
|
|
14674
|
+
case "resources/list":
|
|
14675
|
+
this.send({ jsonrpc: "2.0", id, result: { resources: [] } });
|
|
14676
|
+
return;
|
|
14677
|
+
case "prompts/list":
|
|
14678
|
+
this.send({ jsonrpc: "2.0", id, result: { prompts: [] } });
|
|
14679
|
+
return;
|
|
14680
|
+
case "tools/call": {
|
|
14681
|
+
const params = message.params ?? {};
|
|
14682
|
+
const name = params["name"];
|
|
14683
|
+
const args = params["arguments"] ?? {};
|
|
14684
|
+
const outcome = name === "axterm_send_file" ? await this.sendFile(args) : name === "axterm_ask" ? await this.ask(args) : { text: `unknown tool: ${String(name)}`, isError: true };
|
|
14685
|
+
this.send({
|
|
14686
|
+
jsonrpc: "2.0",
|
|
14687
|
+
id,
|
|
14688
|
+
result: { content: [{ type: "text", text: outcome.text }], isError: outcome.isError }
|
|
14689
|
+
});
|
|
14690
|
+
return;
|
|
14691
|
+
}
|
|
14692
|
+
default:
|
|
14693
|
+
if (id !== undefined)
|
|
14694
|
+
this.send({ jsonrpc: "2.0", id, error: { code: -32601, message: `unknown: ${String(method)}` } });
|
|
14695
|
+
}
|
|
14696
|
+
}
|
|
14697
|
+
target() {
|
|
14698
|
+
const contextFile = this.io.env[MCP_CONTEXT_ENV];
|
|
14699
|
+
if (contextFile !== undefined) {
|
|
14700
|
+
try {
|
|
14701
|
+
const context = JSON.parse(fs3.readFileSync(contextFile, "utf8"));
|
|
14702
|
+
if (context.port && context.token && context.sessionId) {
|
|
14703
|
+
return {
|
|
14704
|
+
url: `http://127.0.0.1:${context.port}`,
|
|
14705
|
+
headers: { "x-axterm-hook-token": context.token },
|
|
14706
|
+
sessionId: context.sessionId,
|
|
14707
|
+
launchToken: context.launchToken ?? ""
|
|
14708
|
+
};
|
|
14709
|
+
}
|
|
14710
|
+
} catch {}
|
|
14711
|
+
return;
|
|
14712
|
+
}
|
|
14713
|
+
const base = hookPostTarget(this.io.env, "omp");
|
|
14714
|
+
const sessionId = this.io.env["AXTERM_SESSION_ID"];
|
|
14715
|
+
if (!base || !sessionId)
|
|
14716
|
+
return;
|
|
14717
|
+
return {
|
|
14718
|
+
url: base.url.replace(/\/hook\/[^/]+$/, ""),
|
|
14719
|
+
headers: base.headers,
|
|
14720
|
+
sessionId,
|
|
14721
|
+
launchToken: this.io.env["AXTERM_LAUNCH_TOKEN"] ?? ""
|
|
14722
|
+
};
|
|
14723
|
+
}
|
|
14724
|
+
async post(route, body, timeoutMs) {
|
|
14725
|
+
const target = this.target();
|
|
14726
|
+
if (!target)
|
|
14727
|
+
return;
|
|
14728
|
+
try {
|
|
14729
|
+
return await this.io.fetch(`${target.url}${route}`, {
|
|
14730
|
+
method: "POST",
|
|
14731
|
+
headers: { "content-type": "application/json", ...target.headers },
|
|
14732
|
+
body: JSON.stringify(body),
|
|
14733
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
14734
|
+
});
|
|
14735
|
+
} catch {
|
|
14736
|
+
return;
|
|
14737
|
+
}
|
|
14738
|
+
}
|
|
14739
|
+
async sendFile(args) {
|
|
14740
|
+
const file = typeof args["path"] === "string" ? args["path"] : "";
|
|
14741
|
+
if (file === "")
|
|
14742
|
+
return { text: "no path given", isError: true };
|
|
14743
|
+
const target = this.target();
|
|
14744
|
+
if (!target)
|
|
14745
|
+
return { text: "axterm is not reachable from this session", isError: true };
|
|
14746
|
+
const resolved = path4.isAbsolute(file) ? file : path4.resolve(this.io.cwd, file);
|
|
14747
|
+
const caption = typeof args["caption"] === "string" ? args["caption"] : undefined;
|
|
14748
|
+
const res = await this.post("/hook/attach", { sessionId: target.sessionId, launchToken: target.launchToken, path: resolved, caption }, POST_TIMEOUT_MS2);
|
|
14749
|
+
if (!res)
|
|
14750
|
+
return { text: "axterm did not answer", isError: true };
|
|
14751
|
+
if (!res.ok)
|
|
14752
|
+
return { text: `axterm could not deliver the file (${res.status})`, isError: true };
|
|
14753
|
+
return { text: `sent ${resolved} to the operator's channel`, isError: false };
|
|
14754
|
+
}
|
|
14755
|
+
async ask(args) {
|
|
14756
|
+
const questions = parseQuestions(args["questions"]);
|
|
14757
|
+
if (questions.length === 0)
|
|
14758
|
+
return { text: "no questions given", isError: true };
|
|
14759
|
+
const target = this.target();
|
|
14760
|
+
if (!target)
|
|
14761
|
+
return { text: "axterm is not reachable from this session", isError: true };
|
|
14762
|
+
const askId = `mcp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
14763
|
+
const channel = this.post("/hook/ask", { sessionId: target.sessionId, launchToken: target.launchToken, askId, questions }, ASK_TIMEOUT_MS).then(async (res) => {
|
|
14764
|
+
if (!res?.ok)
|
|
14765
|
+
return;
|
|
14766
|
+
const body = await res.json().catch(() => {
|
|
14767
|
+
return;
|
|
14768
|
+
});
|
|
14769
|
+
return body?.status === "answered" ? body.answers : undefined;
|
|
14770
|
+
});
|
|
14771
|
+
const terminal = this.clientElicits ? this.elicit(questions) : undefined;
|
|
14772
|
+
const winner = await Promise.race([
|
|
14773
|
+
channel.then((answers) => ({ from: "channel", answers })),
|
|
14774
|
+
...terminal ? [terminal.promise.then((answers) => ({ from: "terminal", answers }))] : []
|
|
14775
|
+
]);
|
|
14776
|
+
if (winner.from === "channel") {
|
|
14777
|
+
if (winner.answers) {
|
|
14778
|
+
terminal?.cancel();
|
|
14779
|
+
return { text: formatAskResult(questions, winner.answers).text, isError: false };
|
|
14780
|
+
}
|
|
14781
|
+
const settled = terminal ? await terminal.promise : undefined;
|
|
14782
|
+
if (settled)
|
|
14783
|
+
return { text: formatAskResult(questions, settled).text, isError: false };
|
|
14784
|
+
return { text: "nobody answered the question", isError: true };
|
|
14785
|
+
}
|
|
14786
|
+
this.post("/hook/ask-cancel", { sessionId: target.sessionId, launchToken: target.launchToken, askId }, POST_TIMEOUT_MS2);
|
|
14787
|
+
if (winner.answers)
|
|
14788
|
+
return { text: formatAskResult(questions, winner.answers).text, isError: false };
|
|
14789
|
+
return { text: "the operator dismissed the question in the terminal", isError: true };
|
|
14790
|
+
}
|
|
14791
|
+
elicit(questions) {
|
|
14792
|
+
const id = this.nextRequestId++;
|
|
14793
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
14794
|
+
this.pending.set(id, resolve);
|
|
14795
|
+
this.send({
|
|
14796
|
+
jsonrpc: "2.0",
|
|
14797
|
+
id,
|
|
14798
|
+
method: "elicitation/create",
|
|
14799
|
+
params: {
|
|
14800
|
+
message: questions.length === 1 ? questions[0].question : "axterm needs your input",
|
|
14801
|
+
requestedSchema: elicitationSchema(questions)
|
|
14802
|
+
}
|
|
14803
|
+
});
|
|
14804
|
+
return {
|
|
14805
|
+
promise: promise.then((result) => answersFromElicitation(questions, result)),
|
|
14806
|
+
cancel: () => {
|
|
14807
|
+
if (!this.pending.has(id))
|
|
14808
|
+
return;
|
|
14809
|
+
this.pending.delete(id);
|
|
14810
|
+
this.send({ jsonrpc: "2.0", method: "notifications/cancelled", params: { requestId: id, reason: "answered on the channel" } });
|
|
14811
|
+
}
|
|
14812
|
+
};
|
|
14813
|
+
}
|
|
14814
|
+
}
|
|
14815
|
+
function parseQuestions(value) {
|
|
14816
|
+
if (!Array.isArray(value))
|
|
14817
|
+
return [];
|
|
14818
|
+
const questions = [];
|
|
14819
|
+
for (const raw of value) {
|
|
14820
|
+
if (typeof raw !== "object" || raw === null)
|
|
14821
|
+
continue;
|
|
14822
|
+
const entry = raw;
|
|
14823
|
+
if (typeof entry.question !== "string" || entry.question === "")
|
|
14824
|
+
continue;
|
|
14825
|
+
const options = Array.isArray(entry.options) ? entry.options.filter((option) => typeof option === "string").map((label) => ({ label })) : [];
|
|
14826
|
+
questions.push({ question: entry.question, options, multi: entry.multi === true });
|
|
14827
|
+
}
|
|
14828
|
+
return questions;
|
|
14829
|
+
}
|
|
14830
|
+
function runMcpServer() {
|
|
14831
|
+
const server = new McpServer({
|
|
14832
|
+
write: (line) => process.stdout.write(line),
|
|
14833
|
+
fetch,
|
|
14834
|
+
env: process.env,
|
|
14835
|
+
cwd: process.cwd()
|
|
14836
|
+
});
|
|
14837
|
+
let buffer = "";
|
|
14838
|
+
process.stdin.on("data", (chunk) => {
|
|
14839
|
+
buffer += chunk.toString("utf8");
|
|
14840
|
+
for (;; ) {
|
|
14841
|
+
const newline = buffer.indexOf(`
|
|
14842
|
+
`);
|
|
14843
|
+
if (newline < 0)
|
|
14844
|
+
break;
|
|
14845
|
+
const line = buffer.slice(0, newline).trim();
|
|
14846
|
+
buffer = buffer.slice(newline + 1);
|
|
14847
|
+
if (line !== "")
|
|
14848
|
+
server.handle(line);
|
|
14849
|
+
}
|
|
14850
|
+
});
|
|
14851
|
+
process.stdin.resume();
|
|
14852
|
+
}
|
|
14853
|
+
|
|
13240
14854
|
// packages/cli/src/update.ts
|
|
13241
14855
|
init_src();
|
|
13242
14856
|
import { spawnSync } from "child_process";
|
|
13243
14857
|
import { createHash as createHash2 } from "crypto";
|
|
13244
|
-
import
|
|
13245
|
-
import
|
|
13246
|
-
import path4 from "path";
|
|
14858
|
+
import fs4 from "fs";
|
|
14859
|
+
import path5 from "path";
|
|
13247
14860
|
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
13248
14861
|
// packages/cli/package.json
|
|
13249
14862
|
var package_default = {
|
|
13250
14863
|
name: "@layorixinc/axterm",
|
|
13251
|
-
version: "0.
|
|
14864
|
+
version: "0.4.0",
|
|
13252
14865
|
type: "module",
|
|
13253
14866
|
bin: {
|
|
13254
14867
|
axterm: "dist/cli.js"
|
|
@@ -13322,7 +14935,7 @@ function assetName(platform, arch, isMusl) {
|
|
|
13322
14935
|
function usesMusl() {
|
|
13323
14936
|
if (process.platform !== "linux")
|
|
13324
14937
|
return false;
|
|
13325
|
-
if (
|
|
14938
|
+
if (fs4.existsSync("/etc/alpine-release"))
|
|
13326
14939
|
return true;
|
|
13327
14940
|
const ldd = spawnSync("ldd", ["--version"], { encoding: "utf8" });
|
|
13328
14941
|
return `${ldd.stdout ?? ""}${ldd.stderr ?? ""}`.toLowerCase().includes("musl");
|
|
@@ -13336,7 +14949,7 @@ function firstLine(command, args) {
|
|
|
13336
14949
|
function detectOrigin(execPath) {
|
|
13337
14950
|
const resolved = (() => {
|
|
13338
14951
|
try {
|
|
13339
|
-
return
|
|
14952
|
+
return fs4.realpathSync(execPath);
|
|
13340
14953
|
} catch {
|
|
13341
14954
|
return execPath;
|
|
13342
14955
|
}
|
|
@@ -13348,7 +14961,7 @@ function detectOrigin(execPath) {
|
|
|
13348
14961
|
brewPrefix: firstLine("brew", ["--prefix"]),
|
|
13349
14962
|
misePaths: misePaths === undefined ? [] : [misePaths],
|
|
13350
14963
|
bunBin: firstLine("bun", ["pm", "bin", "-g"]),
|
|
13351
|
-
npmBin:
|
|
14964
|
+
npmBin: npmGlobalBin()
|
|
13352
14965
|
});
|
|
13353
14966
|
}
|
|
13354
14967
|
var MANAGER_COMMAND = {
|
|
@@ -13357,8 +14970,11 @@ var MANAGER_COMMAND = {
|
|
|
13357
14970
|
bun: (v) => `bun install -g ${PACKAGE}@${v}`,
|
|
13358
14971
|
npm: (v) => `npm install -g ${PACKAGE}@${v}`
|
|
13359
14972
|
};
|
|
13360
|
-
function
|
|
13361
|
-
|
|
14973
|
+
function npmGlobalBin() {
|
|
14974
|
+
const prefix = firstLine("npm", ["prefix", "-g"]);
|
|
14975
|
+
if (prefix === undefined)
|
|
14976
|
+
return;
|
|
14977
|
+
return process.platform === "win32" ? prefix : path5.join(prefix, "bin");
|
|
13362
14978
|
}
|
|
13363
14979
|
async function download(url, into) {
|
|
13364
14980
|
const response = await fetch(url, { signal: AbortSignal.timeout(600000) });
|
|
@@ -13375,7 +14991,7 @@ function checksumFor(sums, asset) {
|
|
|
13375
14991
|
return;
|
|
13376
14992
|
}
|
|
13377
14993
|
function sha256(file) {
|
|
13378
|
-
return createHash2("sha256").update(
|
|
14994
|
+
return createHash2("sha256").update(fs4.readFileSync(file)).digest("hex");
|
|
13379
14995
|
}
|
|
13380
14996
|
function ask2(question) {
|
|
13381
14997
|
if (stdin2.isTTY !== true)
|
|
@@ -13404,21 +15020,26 @@ async function runUpdate(client, current, options) {
|
|
|
13404
15020
|
console.log(t("cli.update.available", { latest: latest.version, current }));
|
|
13405
15021
|
if (options.checkOnly)
|
|
13406
15022
|
return;
|
|
13407
|
-
|
|
15023
|
+
const lane = classifyRuntimeLane(Bun.main);
|
|
15024
|
+
if (lane === "checkout") {
|
|
13408
15025
|
console.log(t("cli.update.notBinary"));
|
|
13409
15026
|
return;
|
|
13410
15027
|
}
|
|
15028
|
+
if (lane === "bun" || lane === "npm") {
|
|
15029
|
+
console.log(t("cli.update.viaManager", { manager: lane }));
|
|
15030
|
+
console.log(t("cli.update.managerHint", { command: MANAGER_COMMAND[lane](latest.version) }));
|
|
15031
|
+
return;
|
|
15032
|
+
}
|
|
13411
15033
|
const origin = detectOrigin(process.execPath);
|
|
13412
15034
|
if (origin !== "binary") {
|
|
13413
|
-
const command = MANAGER_COMMAND[origin](latest.version);
|
|
13414
15035
|
console.log(t("cli.update.viaManager", { manager: origin }));
|
|
13415
|
-
console.log(t("cli.update.managerHint", { command }));
|
|
15036
|
+
console.log(t("cli.update.managerHint", { command: MANAGER_COMMAND[origin](latest.version) }));
|
|
13416
15037
|
return;
|
|
13417
15038
|
}
|
|
13418
15039
|
const asset = assetName(process.platform, process.arch, usesMusl());
|
|
13419
15040
|
const base = `${RELEASES}/v${latest.version}`;
|
|
13420
|
-
const staging =
|
|
13421
|
-
const downloaded =
|
|
15041
|
+
const staging = fs4.mkdtempSync(`${process.execPath}.update-`);
|
|
15042
|
+
const downloaded = path5.join(staging, asset);
|
|
13422
15043
|
try {
|
|
13423
15044
|
console.log(t("cli.update.downloading", { asset }));
|
|
13424
15045
|
await download(`${base}/${asset}`, downloaded);
|
|
@@ -13431,7 +15052,7 @@ async function runUpdate(client, current, options) {
|
|
|
13431
15052
|
return;
|
|
13432
15053
|
}
|
|
13433
15054
|
if (process.platform !== "win32")
|
|
13434
|
-
|
|
15055
|
+
fs4.chmodSync(downloaded, 493);
|
|
13435
15056
|
const reported = spawnSync(downloaded, ["--version"], { encoding: "utf8" }).stdout?.trim();
|
|
13436
15057
|
if (reported !== latest.version) {
|
|
13437
15058
|
console.error(t("cli.update.wrongVersion", { got: reported ?? "nothing", want: latest.version }));
|
|
@@ -13450,6 +15071,9 @@ async function runUpdate(client, current, options) {
|
|
|
13450
15071
|
return;
|
|
13451
15072
|
}
|
|
13452
15073
|
}
|
|
15074
|
+
console.log(t("cli.update.swapping"));
|
|
15075
|
+
applySwap(process.execPath, downloaded);
|
|
15076
|
+
console.log(t("cli.update.done", { version: latest.version }));
|
|
13453
15077
|
if (plan.stopDaemon) {
|
|
13454
15078
|
console.log(t("cli.update.stopping"));
|
|
13455
15079
|
await client.call("daemon.stop").catch(() => {
|
|
@@ -13458,9 +15082,6 @@ async function runUpdate(client, current, options) {
|
|
|
13458
15082
|
client.close();
|
|
13459
15083
|
await Bun.sleep(1500);
|
|
13460
15084
|
}
|
|
13461
|
-
console.log(t("cli.update.swapping"));
|
|
13462
|
-
applySwap(process.execPath, downloaded);
|
|
13463
|
-
console.log(t("cli.update.done", { version: latest.version }));
|
|
13464
15085
|
if (plan.stale > 0)
|
|
13465
15086
|
console.log(t("cli.update.staleDaemon", { count: plan.stale }));
|
|
13466
15087
|
for (const id of plan.resume) {
|
|
@@ -13471,7 +15092,7 @@ async function runUpdate(client, current, options) {
|
|
|
13471
15092
|
console.log(t("cli.update.restoreFailed", { id, reason: (result.stderr ?? "").trim() || "unknown" }));
|
|
13472
15093
|
}
|
|
13473
15094
|
} finally {
|
|
13474
|
-
|
|
15095
|
+
fs4.rmSync(staging, { recursive: true, force: true });
|
|
13475
15096
|
}
|
|
13476
15097
|
}
|
|
13477
15098
|
function applySwap(target, downloaded) {
|
|
@@ -13480,16 +15101,16 @@ function applySwap(target, downloaded) {
|
|
|
13480
15101
|
try {
|
|
13481
15102
|
for (const step of steps) {
|
|
13482
15103
|
if (step.op === "chmod")
|
|
13483
|
-
|
|
15104
|
+
fs4.chmodSync(step.from, 493);
|
|
13484
15105
|
else if (step.op === "backup") {
|
|
13485
|
-
|
|
15106
|
+
fs4.renameSync(step.from, step.to);
|
|
13486
15107
|
backup = step.to;
|
|
13487
15108
|
} else
|
|
13488
|
-
|
|
15109
|
+
fs4.renameSync(step.from, step.to);
|
|
13489
15110
|
}
|
|
13490
15111
|
} catch (err) {
|
|
13491
|
-
if (backup !== undefined && !
|
|
13492
|
-
|
|
15112
|
+
if (backup !== undefined && !fs4.existsSync(target))
|
|
15113
|
+
fs4.renameSync(backup, target);
|
|
13493
15114
|
throw err;
|
|
13494
15115
|
}
|
|
13495
15116
|
}
|
|
@@ -13562,7 +15183,7 @@ function printSessionTable(sessions) {
|
|
|
13562
15183
|
console.log(row.map((cell, col) => cell.padEnd(widths[col])).join(" "));
|
|
13563
15184
|
}
|
|
13564
15185
|
}
|
|
13565
|
-
var realPath = (target) =>
|
|
15186
|
+
var realPath = (target) => fs22.realpathSync.native(target);
|
|
13566
15187
|
function scopedHere(sessions) {
|
|
13567
15188
|
return sessionsInScope(sessions, process.cwd(), realPath);
|
|
13568
15189
|
}
|
|
@@ -13572,22 +15193,10 @@ async function exitedIds(client, all) {
|
|
|
13572
15193
|
return scoped.filter((s) => s.state === "exited").map((s) => s.id);
|
|
13573
15194
|
}
|
|
13574
15195
|
var help = () => t("cli.help");
|
|
13575
|
-
var KEY_SEQUENCES = {
|
|
13576
|
-
approve: "\r",
|
|
13577
|
-
deny: "\x1B[B\r",
|
|
13578
|
-
enter: "\r",
|
|
13579
|
-
esc: "\x1B",
|
|
13580
|
-
up: "\x1B[A",
|
|
13581
|
-
down: "\x1B[B",
|
|
13582
|
-
left: "\x1B[D",
|
|
13583
|
-
right: "\x1B[C",
|
|
13584
|
-
tab: "\t",
|
|
13585
|
-
space: " "
|
|
13586
|
-
};
|
|
13587
15196
|
function readDotEnv(file = ".env") {
|
|
13588
15197
|
const out = {};
|
|
13589
15198
|
try {
|
|
13590
|
-
for (const line of
|
|
15199
|
+
for (const line of fs22.readFileSync(file, "utf8").split(/\r?\n/)) {
|
|
13591
15200
|
const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
13592
15201
|
if (m)
|
|
13593
15202
|
out[m[1]] = m[2].trim();
|
|
@@ -13598,7 +15207,7 @@ function readDotEnv(file = ".env") {
|
|
|
13598
15207
|
function configuredLocale() {
|
|
13599
15208
|
try {
|
|
13600
15209
|
const file = resolvePaths(currentPathEnv()).configFile;
|
|
13601
|
-
const raw = JSON.parse(
|
|
15210
|
+
const raw = JSON.parse(fs22.readFileSync(file, "utf8"));
|
|
13602
15211
|
return typeof raw.locale === "string" ? raw.locale : undefined;
|
|
13603
15212
|
} catch {
|
|
13604
15213
|
return;
|
|
@@ -13615,6 +15224,10 @@ async function main2() {
|
|
|
13615
15224
|
await relayHookEvent(argv[1]);
|
|
13616
15225
|
return;
|
|
13617
15226
|
}
|
|
15227
|
+
if (isMcpInvocation(argv)) {
|
|
15228
|
+
runMcpServer();
|
|
15229
|
+
return;
|
|
15230
|
+
}
|
|
13618
15231
|
const [command, ...rest] = argv;
|
|
13619
15232
|
setLocale(resolveLocale({ configured: configuredLocale(), env: process.env }));
|
|
13620
15233
|
const { flags, positional, passthrough } = parseArgs(rest);
|
|
@@ -13648,6 +15261,7 @@ async function main2() {
|
|
|
13648
15261
|
name: flags.get("name"),
|
|
13649
15262
|
shell: flags.get("shell"),
|
|
13650
15263
|
agent: flags.get("agent"),
|
|
15264
|
+
autoApprove: typeof flags.get("auto-approve") === "boolean" ? flags.get("auto-approve") : undefined,
|
|
13651
15265
|
agentArgs: passthrough,
|
|
13652
15266
|
worktreeBranch: flags.get("worktree"),
|
|
13653
15267
|
worktreeBase: flags.get("base"),
|
|
@@ -13676,7 +15290,16 @@ async function main2() {
|
|
|
13676
15290
|
break;
|
|
13677
15291
|
}
|
|
13678
15292
|
case "attach": {
|
|
13679
|
-
|
|
15293
|
+
let id = positional[0];
|
|
15294
|
+
if (flags.get("last") === true) {
|
|
15295
|
+
const all = await client.call("session.list");
|
|
15296
|
+
const pool = flags.get("all") === true ? all : scopedHere(all);
|
|
15297
|
+
const last = mostRecentSession(pool);
|
|
15298
|
+
if (!last)
|
|
15299
|
+
throw new Error(t("cli.attach.noRecent"));
|
|
15300
|
+
console.log(t("cli.attach.picked", { id: last.shortId, name: last.name }));
|
|
15301
|
+
id = last.id;
|
|
15302
|
+
}
|
|
13680
15303
|
if (!id)
|
|
13681
15304
|
throw new Error(t("cli.usage.attach"));
|
|
13682
15305
|
await attachSession(client, id, flags.get("host-status") === "orca");
|
|
@@ -13716,8 +15339,8 @@ async function main2() {
|
|
|
13716
15339
|
case "key": {
|
|
13717
15340
|
const [id, name] = positional;
|
|
13718
15341
|
if (!id || !name)
|
|
13719
|
-
throw new Error(t("cli.usage.key", { keys:
|
|
13720
|
-
const seq =
|
|
15342
|
+
throw new Error(t("cli.usage.key", { keys: KEY_NAMES.join("|") }));
|
|
15343
|
+
const seq = keySequence(name);
|
|
13721
15344
|
if (seq === undefined)
|
|
13722
15345
|
throw new Error(t("cli.key.unknown", { name }));
|
|
13723
15346
|
await client.call("terminal.write", { id, dataB64: Buffer.from(seq, "utf8").toString("base64") });
|
|
@@ -13728,8 +15351,8 @@ async function main2() {
|
|
|
13728
15351
|
const [id, indexArg] = positional;
|
|
13729
15352
|
if (!id || indexArg === undefined)
|
|
13730
15353
|
throw new Error(t("cli.usage.choose"));
|
|
13731
|
-
const
|
|
13732
|
-
await client.call("terminal.write", { id, dataB64: Buffer.from(
|
|
15354
|
+
const keys2 = buildMenuSelectKeys(Number(indexArg));
|
|
15355
|
+
await client.call("terminal.write", { id, dataB64: Buffer.from(keys2, "utf8").toString("base64") });
|
|
13733
15356
|
console.log(t("cli.session.selected", { index: indexArg }));
|
|
13734
15357
|
break;
|
|
13735
15358
|
}
|
|
@@ -13764,9 +15387,10 @@ async function main2() {
|
|
|
13764
15387
|
console.log(value === undefined ? "" : String(value));
|
|
13765
15388
|
} else if (sub === "set") {
|
|
13766
15389
|
const [, dottedPath, ...rest2] = positional;
|
|
13767
|
-
|
|
15390
|
+
const value = passthrough.length > 0 ? passthrough : rest2.join(" ");
|
|
15391
|
+
if (!dottedPath || value.length === 0)
|
|
13768
15392
|
throw new Error(t("cli.usage.configSet"));
|
|
13769
|
-
await client.call("config.set", { path: dottedPath, value
|
|
15393
|
+
await client.call("config.set", { path: dottedPath, value });
|
|
13770
15394
|
console.log(t("cli.config.set", { path: dottedPath }));
|
|
13771
15395
|
} else if (sub === "unset") {
|
|
13772
15396
|
const dottedPath = positional[1];
|
|
@@ -13805,8 +15429,8 @@ async function main2() {
|
|
|
13805
15429
|
case "project": {
|
|
13806
15430
|
const sub = positional[0] ?? "list";
|
|
13807
15431
|
if (sub === "add") {
|
|
13808
|
-
const dir = positional[1] ?
|
|
13809
|
-
const alias = positional[2] ??
|
|
15432
|
+
const dir = positional[1] ? path21.resolve(positional[1]) : process.cwd();
|
|
15433
|
+
const alias = positional[2] ?? path21.basename(dir);
|
|
13810
15434
|
const projects = await client.call("project.add", {
|
|
13811
15435
|
alias,
|
|
13812
15436
|
path: dir
|
|
@@ -13865,6 +15489,24 @@ async function main2() {
|
|
|
13865
15489
|
}
|
|
13866
15490
|
break;
|
|
13867
15491
|
}
|
|
15492
|
+
case "quota": {
|
|
15493
|
+
const report = await client.call("quota.report").catch(() => {
|
|
15494
|
+
return;
|
|
15495
|
+
});
|
|
15496
|
+
if (report === undefined) {
|
|
15497
|
+
console.log(t("doctor.daemon.old"));
|
|
15498
|
+
break;
|
|
15499
|
+
}
|
|
15500
|
+
if (report.checks.length === 0) {
|
|
15501
|
+
console.log(t("cli.quota.none"));
|
|
15502
|
+
break;
|
|
15503
|
+
}
|
|
15504
|
+
const GLYPHS = { ok: "\u2713", info: "\u2023", warn: "!", fail: "\u2717" };
|
|
15505
|
+
for (const check of report.checks) {
|
|
15506
|
+
console.log(` ${GLYPHS[check.status]} ${t(check.key, check.params ?? {})}`);
|
|
15507
|
+
}
|
|
15508
|
+
break;
|
|
15509
|
+
}
|
|
13868
15510
|
case "worktree": {
|
|
13869
15511
|
const sub = positional[0] ?? "list";
|
|
13870
15512
|
if (sub === "list") {
|