@layorixinc/axterm 0.1.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 +2442 -670
- package/package.json +50 -50
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)
|
|
@@ -684,9 +854,11 @@ usage:
|
|
|
684
854
|
axterm fanout --count 3 [--prefix try] "prompt"
|
|
685
855
|
axterm worktree list|rm <branch> [--force]
|
|
686
856
|
axterm diff <id> [--base REF]
|
|
687
|
-
axterm telegram link|doctor|status
|
|
857
|
+
axterm telegram link|doctor|status|discover
|
|
858
|
+
discover lists the group and user ids your bot can see
|
|
688
859
|
axterm setup interactive configuration (runs automatically on first use)
|
|
689
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
|
|
690
862
|
axterm config list|get <path>|set <path> <v>|unset <path>|path|import-env [file]
|
|
691
863
|
|
|
692
864
|
locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
@@ -697,7 +869,9 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
697
869
|
"cli.sessions.removed": "removed {count} session(s)",
|
|
698
870
|
"cli.sessions.noneExited": "no exited sessions to remove",
|
|
699
871
|
"cli.session.removeFailed": "could not remove {id}: {error}",
|
|
700
|
-
"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",
|
|
701
875
|
"cli.session.killed": "killed",
|
|
702
876
|
"cli.session.sent": "sent",
|
|
703
877
|
"cli.session.sentKey": "sent {key}",
|
|
@@ -725,7 +899,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
725
899
|
"cli.update.notBinary": "this axterm runs from source, so there is nothing to replace \u2014 use git pull",
|
|
726
900
|
"cli.flag.needsValue": "--{flag} requires a value",
|
|
727
901
|
"cli.key.unknown": "unknown key: {name}",
|
|
728
|
-
"cli.usage.attach": "usage: axterm attach <id>",
|
|
902
|
+
"cli.usage.attach": "usage: axterm attach <id> (or `axterm attach --last`)",
|
|
729
903
|
"cli.usage.send": "usage: axterm send [--raw] <id> <text...>",
|
|
730
904
|
"cli.usage.screen": "usage: axterm screen <id>",
|
|
731
905
|
"cli.usage.esc": "usage: axterm esc <id>",
|
|
@@ -738,14 +912,14 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
738
912
|
"cli.usage.diff": "usage: axterm diff <id> [--base REF]",
|
|
739
913
|
"cli.usage.config": "usage: axterm config list|get|set|unset|path|import-env",
|
|
740
914
|
"cli.usage.configGet": "usage: axterm config get <path> [--reveal]",
|
|
741
|
-
"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 `--`)",
|
|
742
916
|
"cli.usage.configUnset": "usage: axterm config unset <path>",
|
|
743
917
|
"cli.usage.project": "usage: axterm project add|list|rm",
|
|
744
918
|
"cli.usage.projectRm": "usage: axterm project rm <alias>",
|
|
745
919
|
"cli.usage.worktree": "usage: axterm worktree list|rm <branch> [--force]",
|
|
746
920
|
"cli.usage.worktreeRm": "usage: axterm worktree rm <branch> [--force]",
|
|
747
921
|
"cli.usage.daemon": "usage: axterm daemon status|stop",
|
|
748
|
-
"cli.usage.telegram": "usage: axterm telegram link|doctor|status",
|
|
922
|
+
"cli.usage.telegram": "usage: axterm telegram link|doctor|status|discover",
|
|
749
923
|
"cli.usage.fanout": "usage: axterm fanout --count N <prompt>",
|
|
750
924
|
"cli.config.set": "set {path}",
|
|
751
925
|
"cli.config.unset": "unset {path}",
|
|
@@ -762,6 +936,13 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
762
936
|
"cli.daemon.stopping": "daemon stopping",
|
|
763
937
|
"cli.daemon.notReady": "daemon did not become ready within 15s (see {log})",
|
|
764
938
|
"cli.telegram.missingSettings": "telegram link: no token/group/user found. Provide --token/--group/--user flags, TELEGRAM_* environment variables, or a .env file in the current directory.",
|
|
939
|
+
"cli.telegram.discoverHint": "Create a group with Topics enabled, add your bot to it, then run this again. Being added is enough \u2014 you do not have to send anything.",
|
|
940
|
+
"cli.telegram.discoverNone": "the bot cannot see any group yet.",
|
|
941
|
+
"cli.telegram.discoverChats": "groups this bot can see (use the id as --group):",
|
|
942
|
+
"cli.telegram.discoverUsers": "people it has seen (use the id as --user):",
|
|
943
|
+
"cli.telegram.topicsOff": "Topics are OFF \u2014 axterm needs them; enable Topics in the group settings",
|
|
944
|
+
"err.telegram.discoverWhileRunning": "the telegram channel is already running, and Telegram allows one reader per bot token \u2014 stop it with `axterm config set channels.telegram.enabled false` before discovering, or read the ids from `axterm telegram status`",
|
|
945
|
+
"err.telegram.noToken": "no bot token is configured \u2014 run `axterm setup` or pass --token",
|
|
765
946
|
"tg.help.general.header": "axterm commands (General topic):",
|
|
766
947
|
"tg.help.general.projects": "/projects \u2014 registered projects",
|
|
767
948
|
"tg.help.general.new": "/new <project> [name] \u2014 new omp session",
|
|
@@ -770,7 +951,7 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
770
951
|
"tg.help.general.worktrees": "/worktrees <project> \xB7 /rm <project> <branch> [force]",
|
|
771
952
|
"tg.help.general.sessions": "/sessions \u2014 session list",
|
|
772
953
|
"tg.help.topic.header": "Inside a session topic, a plain message is delivered as a prompt.",
|
|
773
|
-
"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",
|
|
774
955
|
"tg.session.started": `\u25B6 Session {id} started
|
|
775
956
|
{command}
|
|
776
957
|
\uD83D\uDCC1 {cwd}`,
|
|
@@ -794,6 +975,15 @@ locale: set AXTERM_LANG=en|ko, or \`axterm config set locale ko\`
|
|
|
794
975
|
"tg.shell.only": "(shell)",
|
|
795
976
|
"tg.agent.failed": "\uD83D\uDED1 The agent could not finish this turn: {error}",
|
|
796
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>`,
|
|
797
987
|
"tg.inbound.tooLarge": "\u26A0\uFE0F That file is larger than the {limit} limit \u2014 nothing was downloaded.",
|
|
798
988
|
"tg.inbound.empty": "\u26A0\uFE0F That file is empty \u2014 nothing was downloaded.",
|
|
799
989
|
"tg.inbound.noSession": "\u26A0\uFE0F Send the file inside a session topic \u2014 no session is bound to this one.",
|
|
@@ -869,6 +1059,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
869
1059
|
"doctor.telegram.ok": "telegram: connected",
|
|
870
1060
|
"doctor.telegram.off": "telegram: not running",
|
|
871
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",
|
|
872
1071
|
"fanout.needCountOrBranches": "a branch count or a branch list is required",
|
|
873
1072
|
"fanout.needPrompt": "a prompt is required",
|
|
874
1073
|
"fanout.outOfRange": "must be between 1 and {max}",
|
|
@@ -892,11 +1091,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
892
1091
|
"health.telegram.noUsers": "Telegram is enabled but no user is allowed to control it",
|
|
893
1092
|
"wizard.prompt.locale": "Language",
|
|
894
1093
|
"wizard.prompt.machine": "Name for this machine (used to address it from a channel)",
|
|
895
|
-
"wizard.prompt.agent": "Default agent
|
|
1094
|
+
"wizard.prompt.agent": "Default agent (extra flags go in defaults.agentArgs.<agent>)",
|
|
896
1095
|
"wizard.prompt.telegram": "Set up Telegram control now?",
|
|
897
1096
|
"wizard.prompt.telegramToken": "Bot token from @BotFather",
|
|
898
|
-
"wizard.prompt.
|
|
899
|
-
"wizard.prompt.
|
|
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`.",
|
|
1098
|
+
"wizard.prompt.telegramGroup": "Group id (negative, starts with -100; `axterm telegram discover` lists them)",
|
|
1099
|
+
"wizard.prompt.telegramUsers": "Telegram user ids allowed to control axterm (comma separated; `axterm telegram discover` lists them)",
|
|
900
1100
|
"wizard.prompt.review": "Save this configuration?",
|
|
901
1101
|
"wizard.error.locale": "pick one of the offered languages",
|
|
902
1102
|
"wizard.error.machine": "letters, digits, dot, dash and underscore only",
|
|
@@ -926,8 +1126,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
926
1126
|
\uC0AC\uC6A9\uBC95:
|
|
927
1127
|
axterm run [--cwd \uD3F4\uB354] [--name \uC774\uB984] [--shell pwsh|powershell|cmd|bash|zsh]
|
|
928
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
|
|
929
1131
|
axterm list [--all] \uC774 \uD3F4\uB354\uC5D0\uC11C \uB9CC\uB4E0 \uC138\uC158. --all \uC740 \uC804\uCCB4
|
|
930
|
-
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+])
|
|
931
1135
|
\uD638\uC2A4\uD2B8 \uD130\uBBF8\uB110 \uC81C\uBAA9\uC740 \uD56D\uC0C1 \uC5D0\uC774\uC804\uD2B8 \uC0C1\uD0DC\uB97C \uBC18\uC601\uD55C\uB2E4;
|
|
932
1136
|
--host-status orca \uB294 \uD32C\uC5D0\uB3C4 \uC804\uCCB4 \uC0C1\uD0DC\uB97C \uBCF4\uACE0\uD55C\uB2E4
|
|
933
1137
|
axterm esc <id> ESC \uC804\uC1A1 (\uC911\uB2E8)
|
|
@@ -946,9 +1150,11 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
946
1150
|
axterm fanout --count 3 [--prefix try] "\uD504\uB86C\uD504\uD2B8"
|
|
947
1151
|
axterm worktree list|rm <\uBE0C\uB79C\uCE58> [--force]
|
|
948
1152
|
axterm diff <id> [--base REF]
|
|
949
|
-
axterm telegram link|doctor|status
|
|
1153
|
+
axterm telegram link|doctor|status|discover
|
|
1154
|
+
discover \u2014 \uBD07\uC774 \uBCFC \uC218 \uC788\uB294 \uADF8\uB8F9\xB7\uC0AC\uC6A9\uC790 id \uBAA9\uB85D
|
|
950
1155
|
axterm setup \uB300\uD654\uD615 \uC124\uC815 (\uCD5C\uCD08 \uC2E4\uD589 \uC2DC \uC790\uB3D9)
|
|
951
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)
|
|
952
1158
|
axterm config list|get <\uACBD\uB85C>|set <\uACBD\uB85C> <\uAC12>|unset <\uACBD\uB85C>|path|import-env [\uD30C\uC77C]
|
|
953
1159
|
|
|
954
1160
|
\uC5B8\uC5B4: AXTERM_LANG=en|ko \uB610\uB294 \`axterm config set locale ko\`
|
|
@@ -959,7 +1165,9 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
959
1165
|
"cli.sessions.removed": "\uC138\uC158 {count}\uAC1C \uC0AD\uC81C\uB428",
|
|
960
1166
|
"cli.sessions.noneExited": "\uC0AD\uC81C\uD560 \uC885\uB8CC\uB41C \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
961
1167
|
"cli.session.removeFailed": "{id} \uC0AD\uC81C \uC2E4\uD328: {error}",
|
|
962
|
-
"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",
|
|
963
1171
|
"cli.session.killed": "\uC885\uB8CC\uB428",
|
|
964
1172
|
"cli.session.sent": "\uC804\uB2EC\uB428",
|
|
965
1173
|
"cli.session.sentKey": "{key} \uC804\uB2EC\uB428",
|
|
@@ -987,7 +1195,7 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
987
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",
|
|
988
1196
|
"cli.flag.needsValue": "--{flag} \uC5D0\uB294 \uAC12\uC774 \uD544\uC694\uD569\uB2C8\uB2E4",
|
|
989
1197
|
"cli.key.unknown": "\uC54C \uC218 \uC5C6\uB294 \uD0A4: {name}",
|
|
990
|
-
"cli.usage.attach": "\uC0AC\uC6A9\uBC95: axterm attach <id>",
|
|
1198
|
+
"cli.usage.attach": "\uC0AC\uC6A9\uBC95: axterm attach <id> (\uB610\uB294 `axterm attach --last`)",
|
|
991
1199
|
"cli.usage.send": "\uC0AC\uC6A9\uBC95: axterm send [--raw] <id> <\uD14D\uC2A4\uD2B8...>",
|
|
992
1200
|
"cli.usage.screen": "\uC0AC\uC6A9\uBC95: axterm screen <id>",
|
|
993
1201
|
"cli.usage.esc": "\uC0AC\uC6A9\uBC95: axterm esc <id>",
|
|
@@ -1000,14 +1208,14 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1000
1208
|
"cli.usage.diff": "\uC0AC\uC6A9\uBC95: axterm diff <id> [--base REF]",
|
|
1001
1209
|
"cli.usage.config": "\uC0AC\uC6A9\uBC95: axterm config list|get|set|unset|path|import-env",
|
|
1002
1210
|
"cli.usage.configGet": "\uC0AC\uC6A9\uBC95: axterm config get <\uACBD\uB85C> [--reveal]",
|
|
1003
|
-
"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)",
|
|
1004
1212
|
"cli.usage.configUnset": "\uC0AC\uC6A9\uBC95: axterm config unset <\uACBD\uB85C>",
|
|
1005
1213
|
"cli.usage.project": "\uC0AC\uC6A9\uBC95: axterm project add|list|rm",
|
|
1006
1214
|
"cli.usage.projectRm": "\uC0AC\uC6A9\uBC95: axterm project rm <\uBCC4\uCE6D>",
|
|
1007
1215
|
"cli.usage.worktree": "\uC0AC\uC6A9\uBC95: axterm worktree list|rm <\uBE0C\uB79C\uCE58> [--force]",
|
|
1008
1216
|
"cli.usage.worktreeRm": "\uC0AC\uC6A9\uBC95: axterm worktree rm <\uBE0C\uB79C\uCE58> [--force]",
|
|
1009
1217
|
"cli.usage.daemon": "\uC0AC\uC6A9\uBC95: axterm daemon status|stop",
|
|
1010
|
-
"cli.usage.telegram": "\uC0AC\uC6A9\uBC95: axterm telegram link|doctor|status",
|
|
1218
|
+
"cli.usage.telegram": "\uC0AC\uC6A9\uBC95: axterm telegram link|doctor|status|discover",
|
|
1011
1219
|
"cli.usage.fanout": "\uC0AC\uC6A9\uBC95: axterm fanout --count N <\uD504\uB86C\uD504\uD2B8>",
|
|
1012
1220
|
"cli.config.set": "{path} \uC124\uC815\uB428",
|
|
1013
1221
|
"cli.config.unset": "{path} \uD574\uC81C\uB428",
|
|
@@ -1030,9 +1238,16 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1030
1238
|
"tg.help.general.wt": "/wt <\uD504\uB85C\uC81D\uD2B8> <\uBE0C\uB79C\uCE58> \u2014 worktree \uC138\uC158",
|
|
1031
1239
|
"tg.help.general.fanout": "/fanout <\uD504\uB85C\uC81D\uD2B8> <\uAC1C\uC218|\uBE0C\uB79C\uCE58\uB4E4> <\uD504\uB86C\uD504\uD2B8> \u2014 \uBCD1\uB82C \uC2E4\uD589",
|
|
1032
1240
|
"tg.help.general.worktrees": "/worktrees <\uD504\uB85C\uC81D\uD2B8> \xB7 /rm <\uD504\uB85C\uC81D\uD2B8> <\uBE0C\uB79C\uCE58> [force]",
|
|
1241
|
+
"cli.telegram.discoverHint": "\uD1A0\uD53D\uC744 \uCF20 \uADF8\uB8F9\uC744 \uB9CC\uB4E4\uACE0 \uBD07\uC744 \uCD08\uB300\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694. \uCD08\uB300\uB9CC \uD558\uBA74 \uCDA9\uBD84\uD569\uB2C8\uB2E4 \u2014 \uBA54\uC2DC\uC9C0\uB97C \uBCF4\uB0BC \uD544\uC694\uB294 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
1242
|
+
"cli.telegram.discoverNone": "\uBD07\uC774 \uC544\uC9C1 \uC5B4\uB5A4 \uADF8\uB8F9\uB3C4 \uBCFC \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
1243
|
+
"cli.telegram.discoverChats": "\uBD07\uC774 \uBCFC \uC218 \uC788\uB294 \uADF8\uB8F9 (\uC774 id\uB97C --group \uC5D0 \uC0AC\uC6A9):",
|
|
1244
|
+
"cli.telegram.discoverUsers": "\uBD07\uC774 \uBCF8 \uC0AC\uC6A9\uC790 (\uC774 id\uB97C --user \uC5D0 \uC0AC\uC6A9):",
|
|
1245
|
+
"cli.telegram.topicsOff": "\uD1A0\uD53D\uC774 \uAEBC\uC838 \uC788\uC2B5\uB2C8\uB2E4 \u2014 axterm\uC5D0 \uD544\uC694\uD569\uB2C8\uB2E4. \uADF8\uB8F9 \uC124\uC815\uC5D0\uC11C \uD1A0\uD53D\uC744 \uCF1C\uC138\uC694",
|
|
1246
|
+
"err.telegram.discoverWhileRunning": "\uD154\uB808\uADF8\uB7A8 \uCC44\uB110\uC774 \uC774\uBBF8 \uC2E4\uD589 \uC911\uC774\uACE0, \uD154\uB808\uADF8\uB7A8\uC740 \uBD07 \uD1A0\uD070\uB2F9 \uB9AC\uB354\uB97C \uD558\uB098\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4 \u2014 `axterm config set channels.telegram.enabled false` \uB85C \uBA48\uCD98 \uB4A4 \uD0D0\uC0C9\uD558\uAC70\uB098, `axterm telegram status` \uC5D0\uC11C id\uB97C \uD655\uC778\uD558\uC138\uC694",
|
|
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",
|
|
1033
1248
|
"tg.help.general.sessions": "/sessions \u2014 \uC138\uC158 \uBAA9\uB85D",
|
|
1034
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.",
|
|
1035
|
-
"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",
|
|
1036
1251
|
"tg.session.started": `\u25B6 \uC138\uC158 {id} \uC2DC\uC791
|
|
1037
1252
|
{command}
|
|
1038
1253
|
\uD83D\uDCC1 {cwd}`,
|
|
@@ -1056,6 +1271,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1056
1271
|
"tg.shell.only": "(\uC178)",
|
|
1057
1272
|
"tg.agent.failed": "\uD83D\uDED1 \uC5D0\uC774\uC804\uD2B8\uAC00 \uC774\uBC88 \uD134\uC744 \uB05D\uB0B4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: {error}",
|
|
1058
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>`,
|
|
1059
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.",
|
|
1060
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.",
|
|
1061
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.",
|
|
@@ -1131,6 +1355,15 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1131
1355
|
"doctor.telegram.ok": "\uD154\uB808\uADF8\uB7A8: \uC5F0\uACB0\uB428",
|
|
1132
1356
|
"doctor.telegram.off": "\uD154\uB808\uADF8\uB7A8: \uBBF8\uC2E4\uD589",
|
|
1133
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",
|
|
1134
1367
|
"fanout.needCountOrBranches": "\uBE0C\uB79C\uCE58 \uC218\uB098 \uBE0C\uB79C\uCE58 \uBAA9\uB85D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4",
|
|
1135
1368
|
"fanout.needPrompt": "\uD504\uB86C\uD504\uD2B8\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4",
|
|
1136
1369
|
"fanout.outOfRange": "1..{max} \uBC94\uC704\uC5EC\uC57C \uD569\uB2C8\uB2E4",
|
|
@@ -1154,11 +1387,12 @@ usage: /fanout <project> <count|branches> <prompt>`,
|
|
|
1154
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",
|
|
1155
1388
|
"wizard.prompt.locale": "\uC5B8\uC5B4",
|
|
1156
1389
|
"wizard.prompt.machine": "\uC774 \uBA38\uC2E0\uC758 \uC774\uB984 (\uCC44\uB110\uC5D0\uC11C \uC9C0\uC815\uD560 \uB54C \uC500)",
|
|
1157
|
-
"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)",
|
|
1158
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.",
|
|
1159
1393
|
"wizard.prompt.telegramToken": "@BotFather \uC5D0\uC11C \uBC1B\uC740 \uBD07 \uD1A0\uD070",
|
|
1160
|
-
"wizard.prompt.telegramGroup": "\uADF8\uB8F9 id (\uC74C\uC218, \uBCF4\uD1B5 -100 \uC73C\uB85C \uC2DC\uC791)",
|
|
1161
|
-
"wizard.prompt.telegramUsers": "axterm \uC870\uC791\uC744 \uD5C8\uC6A9\uD560 Telegram user id (\uC27C\uD45C\uB85C \uAD6C\uBD84)",
|
|
1394
|
+
"wizard.prompt.telegramGroup": "\uADF8\uB8F9 id (\uC74C\uC218, \uBCF4\uD1B5 -100 \uC73C\uB85C \uC2DC\uC791. `axterm telegram discover` \uB85C \uD655\uC778 \uAC00\uB2A5)",
|
|
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)",
|
|
1162
1396
|
"wizard.prompt.review": "\uC774 \uC124\uC815\uC744 \uC800\uC7A5\uD560\uAE4C\uC694?",
|
|
1163
1397
|
"wizard.error.locale": "\uC81C\uC2DC\uB41C \uC5B8\uC5B4 \uC911\uC5D0\uC11C \uACE0\uB974\uC138\uC694",
|
|
1164
1398
|
"wizard.error.machine": "\uC601\uBB38\xB7\uC22B\uC790\uC640 . - _ \uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4",
|
|
@@ -1195,6 +1429,8 @@ function defaultFor(step, config, hostname) {
|
|
|
1195
1429
|
return config.machine?.name ?? hostname;
|
|
1196
1430
|
case "agent":
|
|
1197
1431
|
return config.defaults?.agent ?? "omp";
|
|
1432
|
+
case "agentApproval":
|
|
1433
|
+
return "no";
|
|
1198
1434
|
case "telegram":
|
|
1199
1435
|
return telegram?.enabled === true ? "yes" : "no";
|
|
1200
1436
|
case "telegramGroup":
|
|
@@ -1228,6 +1464,7 @@ function collect(answers) {
|
|
|
1228
1464
|
["machine.name", answers["machine"] ?? ""],
|
|
1229
1465
|
["defaults.agent", answers["agent"] ?? "omp"]
|
|
1230
1466
|
];
|
|
1467
|
+
settings.push(["defaults.autoApprove", YES.has(answers["agentApproval"] ?? "") ? "true" : "false"]);
|
|
1231
1468
|
const secrets = [];
|
|
1232
1469
|
const wantsTelegram = YES.has(answers["telegram"] ?? "");
|
|
1233
1470
|
settings.push(["channels.telegram.enabled", wantsTelegram ? "true" : "false"]);
|
|
@@ -1265,6 +1502,7 @@ var YES, NO, numericList = (value) => {
|
|
|
1265
1502
|
return parts.length > 0 && parts.every((p) => /^-?\d+$/.test(p)) ? undefined : "wizard.error.numberList";
|
|
1266
1503
|
}, STEPS, wizardConfig;
|
|
1267
1504
|
var init_setup_wizard = __esm(() => {
|
|
1505
|
+
init_agent_launch();
|
|
1268
1506
|
init_i18n();
|
|
1269
1507
|
YES = new Set(["y", "yes", "true", "1"]);
|
|
1270
1508
|
NO = new Set(["n", "no", "false", "0"]);
|
|
@@ -1283,9 +1521,17 @@ var init_setup_wizard = __esm(() => {
|
|
|
1283
1521
|
next: () => "agent"
|
|
1284
1522
|
},
|
|
1285
1523
|
agent: {
|
|
1286
|
-
kind: "
|
|
1524
|
+
kind: "select",
|
|
1287
1525
|
promptKey: "wizard.prompt.agent",
|
|
1288
|
-
|
|
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",
|
|
1289
1535
|
next: () => "telegram"
|
|
1290
1536
|
},
|
|
1291
1537
|
telegram: {
|
|
@@ -1355,7 +1601,7 @@ function oscTitle(title) {
|
|
|
1355
1601
|
const safe = title.replace(/[\x00-\x1f\x7f]/g, "").slice(0, MAX_TITLE_LENGTH);
|
|
1356
1602
|
return `\x1B]0;${safe}\x07`;
|
|
1357
1603
|
}
|
|
1358
|
-
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;
|
|
1359
1605
|
var init_agent_registry = __esm(() => {
|
|
1360
1606
|
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1361
1607
|
PROFILES = {
|
|
@@ -1364,6 +1610,8 @@ var init_agent_registry = __esm(() => {
|
|
|
1364
1610
|
displayName: "OMP",
|
|
1365
1611
|
title: { workingLabel: "OMP", permissionLabel: "OMP - action required", idleLabel: "OMP ready" },
|
|
1366
1612
|
hostHookSource: "omp",
|
|
1613
|
+
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
1614
|
+
quota: "gauge",
|
|
1367
1615
|
resume: { argv: ["--resume", "{ref}"], ref: "sessionFile" }
|
|
1368
1616
|
},
|
|
1369
1617
|
pi: {
|
|
@@ -1383,6 +1631,8 @@ var init_agent_registry = __esm(() => {
|
|
|
1383
1631
|
synthesizeWorking: false
|
|
1384
1632
|
},
|
|
1385
1633
|
hostHookSource: "codex",
|
|
1634
|
+
quota: "gauge",
|
|
1635
|
+
approvalBypassArgs: ["--dangerously-bypass-approvals-and-sandbox"],
|
|
1386
1636
|
structuredApprovals: true,
|
|
1387
1637
|
resume: { argv: ["resume", "{ref}"], ref: "sessionId", form: "subcommand" }
|
|
1388
1638
|
},
|
|
@@ -1427,6 +1677,8 @@ var init_agent_registry = __esm(() => {
|
|
|
1427
1677
|
synthesizeTerminal: false
|
|
1428
1678
|
},
|
|
1429
1679
|
hostHookSource: "claude",
|
|
1680
|
+
quota: "event",
|
|
1681
|
+
approvalBypassArgs: ["--permission-mode", "bypassPermissions"],
|
|
1430
1682
|
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1431
1683
|
},
|
|
1432
1684
|
gemini: {
|
|
@@ -1440,11 +1692,13 @@ var init_agent_registry = __esm(() => {
|
|
|
1440
1692
|
synthesizeTerminal: false
|
|
1441
1693
|
},
|
|
1442
1694
|
hostHookSource: "gemini",
|
|
1695
|
+
approvalBypassArgs: ["--approval-mode", "yolo"],
|
|
1443
1696
|
hookAck: "{}",
|
|
1444
1697
|
approvalKeys: { allow: MENU_APPROVE, deny: "\x1B" },
|
|
1445
1698
|
resume: { argv: ["--resume", "{ref}"], ref: "sessionId" }
|
|
1446
1699
|
}
|
|
1447
1700
|
};
|
|
1701
|
+
REGISTERED_AGENTS = Object.keys(PROFILES);
|
|
1448
1702
|
});
|
|
1449
1703
|
|
|
1450
1704
|
// packages/core/src/agent-resume.ts
|
|
@@ -1502,6 +1756,13 @@ function hookLaunch(input) {
|
|
|
1502
1756
|
function isHookInvocation(args) {
|
|
1503
1757
|
return args[0] === HOOK_ARGV_FLAG && typeof args[1] === "string" && args[1] !== "";
|
|
1504
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
|
+
}
|
|
1505
1766
|
function hookPostTarget(env, agent) {
|
|
1506
1767
|
const port = Number(env["AXTERM_HOOK_PORT"]);
|
|
1507
1768
|
const token = env["AXTERM_HOOK_TOKEN"];
|
|
@@ -1524,7 +1785,7 @@ function hookPostTarget(env, agent) {
|
|
|
1524
1785
|
function isDaemonInvocation(args) {
|
|
1525
1786
|
return args[0] === DAEMON_ARGV_FLAG;
|
|
1526
1787
|
}
|
|
1527
|
-
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;
|
|
1528
1789
|
var init_self_spawn = __esm(() => {
|
|
1529
1790
|
RUNTIME_NAMES = new Set(["bun", "bun.exe", "bun-debug", "bun-debug.exe", "node", "node.exe"]);
|
|
1530
1791
|
});
|
|
@@ -1603,6 +1864,15 @@ function updateNeeded(current, latest) {
|
|
|
1603
1864
|
return false;
|
|
1604
1865
|
return Bun.semver.order(latest, current) > 0;
|
|
1605
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
|
+
}
|
|
1606
1876
|
function planBinarySwap(input) {
|
|
1607
1877
|
const steps = [];
|
|
1608
1878
|
if (input.platform !== "win32")
|
|
@@ -1612,16 +1882,16 @@ function planBinarySwap(input) {
|
|
|
1612
1882
|
return steps;
|
|
1613
1883
|
}
|
|
1614
1884
|
function planSessionRestore(sessions) {
|
|
1615
|
-
const
|
|
1885
|
+
const live = sessions.filter((s) => s.state !== "exited");
|
|
1616
1886
|
const resumable = (s) => agentProfile(agentIdFromCommand(s.agentCommand)).resume !== undefined;
|
|
1617
1887
|
return {
|
|
1618
|
-
resume:
|
|
1619
|
-
cannotResume:
|
|
1620
|
-
needsConfirmation:
|
|
1888
|
+
resume: live.filter(resumable).map((s) => s.id),
|
|
1889
|
+
cannotResume: live.filter((s) => !resumable(s)),
|
|
1890
|
+
needsConfirmation: live.length > 0
|
|
1621
1891
|
};
|
|
1622
1892
|
}
|
|
1623
1893
|
function planUpdateRestart(input) {
|
|
1624
|
-
const live = input.sessions.filter((s) => s.state
|
|
1894
|
+
const live = input.sessions.filter((s) => s.state !== "exited").length;
|
|
1625
1895
|
if (!input.restart && live > 0) {
|
|
1626
1896
|
return { stopDaemon: false, resume: [], lost: [], stale: live, needsConfirmation: false };
|
|
1627
1897
|
}
|
|
@@ -1769,6 +2039,44 @@ class EchoGuard {
|
|
|
1769
2039
|
this.pending.delete(sessionId);
|
|
1770
2040
|
}
|
|
1771
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
|
+
|
|
1772
2080
|
// packages/core/src/choice.ts
|
|
1773
2081
|
function buildApprovalQuestion(toolName) {
|
|
1774
2082
|
return {
|
|
@@ -1999,8 +2307,7 @@ function mapClaudeHook(input) {
|
|
|
1999
2307
|
...input.tool_input !== undefined ? { tool_input: input.tool_input } : {}
|
|
2000
2308
|
}
|
|
2001
2309
|
];
|
|
2002
|
-
case "Stop":
|
|
2003
|
-
case "StopFailure": {
|
|
2310
|
+
case "Stop": {
|
|
2004
2311
|
const out = [];
|
|
2005
2312
|
const text = input.last_assistant_message;
|
|
2006
2313
|
if (typeof text === "string" && text.trim() !== "") {
|
|
@@ -2009,6 +2316,19 @@ function mapClaudeHook(input) {
|
|
|
2009
2316
|
out.push({ event: "agent_end" });
|
|
2010
2317
|
return out;
|
|
2011
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
|
+
}
|
|
2012
2332
|
case "SessionEnd":
|
|
2013
2333
|
return [
|
|
2014
2334
|
{ event: "session_shutdown", ...input.session_id !== undefined ? { session_id: input.session_id } : {} }
|
|
@@ -2041,16 +2361,26 @@ function buildClaudeSettings(input) {
|
|
|
2041
2361
|
var MAX_RESULT_TEXT = 2000, HOOK_TIMEOUT_SECONDS = 5, TOOL_EVENTS, PLAIN_EVENTS;
|
|
2042
2362
|
var init_claude_hooks = __esm(() => {
|
|
2043
2363
|
TOOL_EVENTS = ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PermissionRequest"];
|
|
2044
|
-
PLAIN_EVENTS = [
|
|
2364
|
+
PLAIN_EVENTS = [
|
|
2365
|
+
"SessionStart",
|
|
2366
|
+
"UserPromptSubmit",
|
|
2367
|
+
"Stop",
|
|
2368
|
+
"StopFailure",
|
|
2369
|
+
"MessageDisplay",
|
|
2370
|
+
"SessionEnd"
|
|
2371
|
+
];
|
|
2045
2372
|
});
|
|
2046
2373
|
|
|
2047
|
-
// packages/core/src/
|
|
2048
|
-
function
|
|
2374
|
+
// packages/core/src/onboarding.ts
|
|
2375
|
+
function detectOnboarding(agent, screen) {
|
|
2376
|
+
const dialogs = DIALOGS[agent];
|
|
2377
|
+
if (dialogs === undefined)
|
|
2378
|
+
return;
|
|
2049
2379
|
const lines = screen.split(`
|
|
2050
2380
|
`).map((line) => line.replace(/^\s*\u2502/, "").replace(/\u2502\s*$/, "").trim());
|
|
2051
2381
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
2052
2382
|
const line = lines[i];
|
|
2053
|
-
const dialog =
|
|
2383
|
+
const dialog = dialogs.find((d) => line.startsWith(d.question));
|
|
2054
2384
|
if (!dialog)
|
|
2055
2385
|
continue;
|
|
2056
2386
|
const options = [];
|
|
@@ -2066,12 +2396,19 @@ function detectGeminiOnboarding(screen) {
|
|
|
2066
2396
|
return;
|
|
2067
2397
|
}
|
|
2068
2398
|
var DIALOGS, OPTION_ROW, OPTION_WINDOW_LINES = 8;
|
|
2069
|
-
var
|
|
2070
|
-
DIALOGS =
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
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+(.+)$/;
|
|
2075
2412
|
});
|
|
2076
2413
|
|
|
2077
2414
|
// packages/core/src/doctor.ts
|
|
@@ -2206,87 +2543,587 @@ var init_doctor = __esm(() => {
|
|
|
2206
2543
|
GEMINI_FORBIDDEN_EVENTS = ["AfterModel", "BeforeModel"];
|
|
2207
2544
|
});
|
|
2208
2545
|
|
|
2209
|
-
// packages/core/src/
|
|
2210
|
-
function
|
|
2211
|
-
|
|
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;
|
|
2212
2557
|
}
|
|
2213
|
-
function
|
|
2214
|
-
|
|
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();
|
|
2215
2572
|
}
|
|
2216
|
-
var
|
|
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
|
+
});
|
|
2217
2578
|
|
|
2218
|
-
// packages/core/src/codex-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
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
|
+
}
|
|
2241
2624
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
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`;
|
|
2248
2679
|
}
|
|
2249
|
-
|
|
2680
|
+
const days = Math.floor(hours / 24);
|
|
2681
|
+
const restHours = hours % 24;
|
|
2682
|
+
return restHours === 0 ? `${days}d` : `${days}d ${restHours}h`;
|
|
2250
2683
|
}
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
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 } });
|
|
2256
2691
|
continue;
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
entries.push({ key: `${hooksJsonPath}:${label}:${groupIndex}:${hookIndex}`, hash });
|
|
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
|
+
}
|
|
2273
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 });
|
|
2274
2735
|
});
|
|
2275
2736
|
}
|
|
2276
|
-
return
|
|
2277
|
-
}
|
|
2278
|
-
function codexTrustStateToml(entries) {
|
|
2279
|
-
return entries.map((e) => {
|
|
2280
|
-
const key = e.key.replaceAll("\\", "\\\\").replaceAll('"', "\\\"");
|
|
2281
|
-
return `[hooks.state."${key}"]
|
|
2282
|
-
trusted_hash = "${e.hash}"
|
|
2283
|
-
`;
|
|
2284
|
-
}).join(`
|
|
2285
|
-
`);
|
|
2737
|
+
return out;
|
|
2286
2738
|
}
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
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
|
+
|
|
2967
|
+
// packages/core/src/version.ts
|
|
2968
|
+
function setProductVersion(version) {
|
|
2969
|
+
product = version;
|
|
2970
|
+
}
|
|
2971
|
+
function productVersion() {
|
|
2972
|
+
return product;
|
|
2973
|
+
}
|
|
2974
|
+
var product = "";
|
|
2975
|
+
|
|
2976
|
+
// packages/core/src/telegram-discovery.ts
|
|
2977
|
+
function readChat(value) {
|
|
2978
|
+
if (!isRecord(value))
|
|
2979
|
+
return;
|
|
2980
|
+
const id = value["id"];
|
|
2981
|
+
const type = value["type"];
|
|
2982
|
+
if (typeof id !== "number" || typeof type !== "string")
|
|
2983
|
+
return;
|
|
2984
|
+
const title = value["title"];
|
|
2985
|
+
const isForum = value["is_forum"];
|
|
2986
|
+
return {
|
|
2987
|
+
id,
|
|
2988
|
+
type,
|
|
2989
|
+
...typeof title === "string" ? { title } : {},
|
|
2990
|
+
...isForum === true ? { isForum: true } : {}
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
function readUser(value) {
|
|
2994
|
+
if (!isRecord(value))
|
|
2995
|
+
return;
|
|
2996
|
+
const id = value["id"];
|
|
2997
|
+
if (typeof id !== "number")
|
|
2998
|
+
return;
|
|
2999
|
+
if (value["is_bot"] === true)
|
|
3000
|
+
return;
|
|
3001
|
+
const username = value["username"];
|
|
3002
|
+
const name = value["first_name"];
|
|
3003
|
+
return {
|
|
3004
|
+
id,
|
|
3005
|
+
...typeof username === "string" ? { username } : {},
|
|
3006
|
+
...typeof name === "string" ? { name } : {}
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
function detail(candidate) {
|
|
3010
|
+
return (candidate.title !== undefined ? 1 : 0) + (candidate.isForum !== undefined ? 1 : 0);
|
|
3011
|
+
}
|
|
3012
|
+
function discoverTelegramTargets(updates) {
|
|
3013
|
+
const chats = new Map;
|
|
3014
|
+
const users = new Map;
|
|
3015
|
+
if (!Array.isArray(updates))
|
|
3016
|
+
return { chats: [], users: [] };
|
|
3017
|
+
const noteChat = (raw) => {
|
|
3018
|
+
const chat = readChat(raw);
|
|
3019
|
+
if (chat === undefined || !chat.type.includes("group"))
|
|
3020
|
+
return;
|
|
3021
|
+
const seen = chats.get(chat.id);
|
|
3022
|
+
if (seen === undefined || detail(chat) > detail(seen))
|
|
3023
|
+
chats.set(chat.id, chat);
|
|
3024
|
+
};
|
|
3025
|
+
const noteUser = (raw) => {
|
|
3026
|
+
const user = readUser(raw);
|
|
3027
|
+
if (user !== undefined && !users.has(user.id))
|
|
3028
|
+
users.set(user.id, user);
|
|
3029
|
+
};
|
|
3030
|
+
for (const update of updates) {
|
|
3031
|
+
if (!isRecord(update))
|
|
3032
|
+
continue;
|
|
3033
|
+
for (const key of CHAT_CARRIERS) {
|
|
3034
|
+
const carrier = update[key];
|
|
3035
|
+
if (!isRecord(carrier))
|
|
3036
|
+
continue;
|
|
3037
|
+
noteChat(carrier["chat"]);
|
|
3038
|
+
noteUser(carrier["from"]);
|
|
3039
|
+
}
|
|
3040
|
+
const callback = update["callback_query"];
|
|
3041
|
+
if (isRecord(callback)) {
|
|
3042
|
+
noteUser(callback["from"]);
|
|
3043
|
+
const message = callback["message"];
|
|
3044
|
+
if (isRecord(message))
|
|
3045
|
+
noteChat(message["chat"]);
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
return { chats: [...chats.values()], users: [...users.values()] };
|
|
3049
|
+
}
|
|
3050
|
+
var CHAT_CARRIERS;
|
|
3051
|
+
var init_telegram_discovery = __esm(() => {
|
|
3052
|
+
CHAT_CARRIERS = ["message", "edited_message", "channel_post", "edited_channel_post", "my_chat_member"];
|
|
3053
|
+
});
|
|
3054
|
+
|
|
3055
|
+
// packages/core/src/codex-hooks.ts
|
|
3056
|
+
import { createHash } from "crypto";
|
|
3057
|
+
function quoteToken(token) {
|
|
3058
|
+
if (token !== "" && !/[ \t"]/.test(token))
|
|
3059
|
+
return token;
|
|
3060
|
+
return `"${token.replaceAll('"', "\\\"")}"`;
|
|
3061
|
+
}
|
|
3062
|
+
function buildCodexHooks(input) {
|
|
3063
|
+
const command = [input.command, ...input.args].map(quoteToken).join(" ");
|
|
3064
|
+
const group = (timeout) => [
|
|
3065
|
+
{ hooks: [{ type: "command", command, timeout, async: false }] }
|
|
3066
|
+
];
|
|
3067
|
+
const hooks = {};
|
|
3068
|
+
for (const event of OBSERVATION_EVENTS)
|
|
3069
|
+
hooks[event] = group(input.timeoutSeconds ?? OBSERVATION_TIMEOUT_SECONDS);
|
|
3070
|
+
hooks["PermissionRequest"] = group(CODEX_PERMISSION_TIMEOUT_SECONDS);
|
|
3071
|
+
hooks["SessionEnd"] = group(SESSION_END_TIMEOUT_SECONDS);
|
|
3072
|
+
return { hooks };
|
|
3073
|
+
}
|
|
3074
|
+
function normalizedTimeout(event, timeout) {
|
|
3075
|
+
if (event === "SessionEnd")
|
|
3076
|
+
return Math.min(3, Math.max(1, timeout));
|
|
3077
|
+
return Math.max(1, timeout);
|
|
3078
|
+
}
|
|
3079
|
+
function canonicalJson(value) {
|
|
3080
|
+
if (Array.isArray(value))
|
|
3081
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
3082
|
+
if (typeof value === "object" && value !== null) {
|
|
3083
|
+
const entries = Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`);
|
|
3084
|
+
return `{${entries.join(",")}}`;
|
|
3085
|
+
}
|
|
3086
|
+
return JSON.stringify(value);
|
|
3087
|
+
}
|
|
3088
|
+
function codexHookTrust(hooksJsonPath, file) {
|
|
3089
|
+
const entries = [];
|
|
3090
|
+
for (const [event, groups] of Object.entries(file.hooks)) {
|
|
3091
|
+
const label = EVENT_KEY_LABELS[event];
|
|
3092
|
+
if (label === undefined)
|
|
3093
|
+
continue;
|
|
3094
|
+
groups.forEach((group, groupIndex) => {
|
|
3095
|
+
group.hooks.forEach((hook, hookIndex) => {
|
|
3096
|
+
const identity = {
|
|
3097
|
+
event_name: label,
|
|
3098
|
+
...group.matcher !== undefined ? { matcher: group.matcher } : {},
|
|
3099
|
+
hooks: [
|
|
3100
|
+
{
|
|
3101
|
+
type: "command",
|
|
3102
|
+
command: hook.command,
|
|
3103
|
+
timeout: normalizedTimeout(event, hook.timeout),
|
|
3104
|
+
async: hook.async
|
|
3105
|
+
}
|
|
3106
|
+
]
|
|
3107
|
+
};
|
|
3108
|
+
const hash = `sha256:${createHash("sha256").update(canonicalJson(identity)).digest("hex")}`;
|
|
3109
|
+
entries.push({ key: `${hooksJsonPath}:${label}:${groupIndex}:${hookIndex}`, hash });
|
|
3110
|
+
});
|
|
3111
|
+
});
|
|
3112
|
+
}
|
|
3113
|
+
return entries;
|
|
3114
|
+
}
|
|
3115
|
+
function codexTrustStateToml(entries) {
|
|
3116
|
+
return entries.map((e) => {
|
|
3117
|
+
const key = e.key.replaceAll("\\", "\\\\").replaceAll('"', "\\\"");
|
|
3118
|
+
return `[hooks.state."${key}"]
|
|
3119
|
+
trusted_hash = "${e.hash}"
|
|
3120
|
+
`;
|
|
3121
|
+
}).join(`
|
|
3122
|
+
`);
|
|
3123
|
+
}
|
|
3124
|
+
function codexPermissionDecision(question, answers, denyMessage) {
|
|
3125
|
+
const picked = answers?.find((a) => a.questionIndex === 0)?.selected[0];
|
|
3126
|
+
if (picked === undefined)
|
|
2290
3127
|
return;
|
|
2291
3128
|
if (picked === question.options[0]?.label) {
|
|
2292
3129
|
return { hookSpecificOutput: { hookEventName: "PermissionRequest", decision: { behavior: "allow" } } };
|
|
@@ -2346,6 +3183,19 @@ function errorText(value) {
|
|
|
2346
3183
|
return message.slice(0, MAX_RESULT_TEXT2);
|
|
2347
3184
|
return llmContentText(value);
|
|
2348
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
|
+
}
|
|
2349
3199
|
function mapGeminiHook(input) {
|
|
2350
3200
|
switch (input.hook_event_name) {
|
|
2351
3201
|
case "SessionStart":
|
|
@@ -2378,15 +3228,17 @@ function mapGeminiHook(input) {
|
|
|
2378
3228
|
}
|
|
2379
3229
|
return out;
|
|
2380
3230
|
}
|
|
2381
|
-
case "Notification":
|
|
3231
|
+
case "Notification": {
|
|
2382
3232
|
if (input.notification_type !== "ToolPermission")
|
|
2383
3233
|
return [];
|
|
3234
|
+
const toolName = permissionToolName(input.details);
|
|
2384
3235
|
return [
|
|
2385
3236
|
{
|
|
2386
3237
|
event: "tool_approval_requested",
|
|
2387
|
-
...
|
|
3238
|
+
...toolName !== undefined ? { tool_name: toolName } : {}
|
|
2388
3239
|
}
|
|
2389
3240
|
];
|
|
3241
|
+
}
|
|
2390
3242
|
case "AfterAgent": {
|
|
2391
3243
|
const out = [];
|
|
2392
3244
|
const text = input.prompt_response;
|
|
@@ -2418,7 +3270,10 @@ function buildGeminiSettings(input) {
|
|
|
2418
3270
|
const hooks = {};
|
|
2419
3271
|
for (const event of GEMINI_EVENTS)
|
|
2420
3272
|
hooks[event] = [{ hooks: [handler] }];
|
|
2421
|
-
return {
|
|
3273
|
+
return {
|
|
3274
|
+
hooks,
|
|
3275
|
+
...input.mcp === undefined ? {} : { mcpServers: { [MCP_SERVER_NAME]: mcpServerEntry(input.mcp, input.mcpContextPath) } }
|
|
3276
|
+
};
|
|
2422
3277
|
}
|
|
2423
3278
|
var MAX_RESULT_TEXT2 = 2000, HOOK_TIMEOUT_MS = 2000, GEMINI_EVENTS;
|
|
2424
3279
|
var init_gemini_hooks = __esm(() => {
|
|
@@ -2433,65 +3288,6 @@ var init_gemini_hooks = __esm(() => {
|
|
|
2433
3288
|
];
|
|
2434
3289
|
});
|
|
2435
3290
|
|
|
2436
|
-
// packages/core/src/agent-launch.ts
|
|
2437
|
-
function uuidFromSessionId(sessionId) {
|
|
2438
|
-
const hex = sessionId.replace(/-/g, "");
|
|
2439
|
-
if (hex.length !== 32)
|
|
2440
|
-
throw new Error(`session id is not 32 hex characters: ${sessionId}`);
|
|
2441
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
2442
|
-
}
|
|
2443
|
-
function buildAgentCommand(input) {
|
|
2444
|
-
const extra = [...input.extraArgs ?? []];
|
|
2445
|
-
if (input.agent === "claude") {
|
|
2446
|
-
return [
|
|
2447
|
-
"claude",
|
|
2448
|
-
"--settings",
|
|
2449
|
-
input.claudeSettingsPath,
|
|
2450
|
-
"--session-id",
|
|
2451
|
-
uuidFromSessionId(input.sessionId),
|
|
2452
|
-
...extra
|
|
2453
|
-
];
|
|
2454
|
-
}
|
|
2455
|
-
if (input.agent === "gemini") {
|
|
2456
|
-
return [
|
|
2457
|
-
"gemini",
|
|
2458
|
-
"--session-id",
|
|
2459
|
-
input.sessionId,
|
|
2460
|
-
...extra
|
|
2461
|
-
];
|
|
2462
|
-
}
|
|
2463
|
-
if (input.agent === "codex") {
|
|
2464
|
-
return ["codex", ...extra];
|
|
2465
|
-
}
|
|
2466
|
-
return ["omp", "--extension", input.extensionPath, ...extra];
|
|
2467
|
-
}
|
|
2468
|
-
function buildAgentResumeCommand(input) {
|
|
2469
|
-
if (input.agent === "claude") {
|
|
2470
|
-
return [
|
|
2471
|
-
"claude",
|
|
2472
|
-
"--settings",
|
|
2473
|
-
input.claudeSettingsPath,
|
|
2474
|
-
"--resume",
|
|
2475
|
-
uuidFromSessionId(input.sessionId),
|
|
2476
|
-
...input.extraArgs ?? []
|
|
2477
|
-
];
|
|
2478
|
-
}
|
|
2479
|
-
if (input.agent === "gemini") {
|
|
2480
|
-
return ["gemini", "--resume", input.sessionId, ...input.extraArgs ?? []];
|
|
2481
|
-
}
|
|
2482
|
-
return buildAgentCommand(input);
|
|
2483
|
-
}
|
|
2484
|
-
function isAgentId(value) {
|
|
2485
|
-
return AGENT_IDS.includes(value);
|
|
2486
|
-
}
|
|
2487
|
-
function supportedAgents() {
|
|
2488
|
-
return AGENT_IDS;
|
|
2489
|
-
}
|
|
2490
|
-
var AGENT_IDS;
|
|
2491
|
-
var init_agent_launch = __esm(() => {
|
|
2492
|
-
AGENT_IDS = ["omp", "claude", "codex", "gemini"];
|
|
2493
|
-
});
|
|
2494
|
-
|
|
2495
3291
|
// packages/core/src/dotenv-scrub.ts
|
|
2496
3292
|
function dotenvCandidates(nodeEnv) {
|
|
2497
3293
|
const mode = nodeEnv === undefined || nodeEnv === "" ? "development" : nodeEnv;
|
|
@@ -2543,21 +3339,6 @@ var init_dotenv_scrub = __esm(() => {
|
|
|
2543
3339
|
LINE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
|
|
2544
3340
|
});
|
|
2545
3341
|
|
|
2546
|
-
// packages/core/src/duration.ts
|
|
2547
|
-
function humanDuration(ms) {
|
|
2548
|
-
if (!Number.isFinite(ms) || ms <= 0)
|
|
2549
|
-
return "0s";
|
|
2550
|
-
const seconds = Math.max(1, Math.round(ms / 1000));
|
|
2551
|
-
if (seconds < 60)
|
|
2552
|
-
return `${seconds}s`;
|
|
2553
|
-
const minutes = Math.round(seconds / 60);
|
|
2554
|
-
if (minutes < 60)
|
|
2555
|
-
return `${minutes}m`;
|
|
2556
|
-
const hours = Math.floor(minutes / 60);
|
|
2557
|
-
const rest = minutes % 60;
|
|
2558
|
-
return rest === 0 ? `${hours}h` : `${hours}h ${rest}m`;
|
|
2559
|
-
}
|
|
2560
|
-
|
|
2561
3342
|
// packages/core/src/idle-screen.ts
|
|
2562
3343
|
function screenHash(screen) {
|
|
2563
3344
|
const normalised = screen.split(`
|
|
@@ -2633,6 +3414,24 @@ function sessionInScope(session, scope, resolve = asGiven) {
|
|
|
2633
3414
|
function sessionsInScope(sessions, scope, resolve = asGiven) {
|
|
2634
3415
|
return sessions.filter((session) => sessionInScope(session, scope, resolve));
|
|
2635
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);
|
|
3434
|
+
}
|
|
2636
3435
|
var asGiven = (target) => target;
|
|
2637
3436
|
var init_session_scope = () => {};
|
|
2638
3437
|
|
|
@@ -2665,8 +3464,16 @@ var init_src = __esm(() => {
|
|
|
2665
3464
|
init_agent_status();
|
|
2666
3465
|
init_activity();
|
|
2667
3466
|
init_claude_hooks();
|
|
2668
|
-
|
|
3467
|
+
init_onboarding();
|
|
2669
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();
|
|
3476
|
+
init_telegram_discovery();
|
|
2670
3477
|
init_codex_hooks();
|
|
2671
3478
|
init_gemini_hooks();
|
|
2672
3479
|
init_agent_launch();
|
|
@@ -2678,14 +3485,14 @@ var init_src = __esm(() => {
|
|
|
2678
3485
|
});
|
|
2679
3486
|
|
|
2680
3487
|
// packages/daemon/src/config-store.ts
|
|
2681
|
-
import
|
|
2682
|
-
import
|
|
3488
|
+
import fs5 from "fs";
|
|
3489
|
+
import path6 from "path";
|
|
2683
3490
|
function writeAtomic(target, contents, mode) {
|
|
2684
3491
|
const tmp = `${target}.tmp`;
|
|
2685
|
-
|
|
2686
|
-
|
|
3492
|
+
fs5.writeFileSync(tmp, contents, { mode });
|
|
3493
|
+
fs5.renameSync(tmp, target);
|
|
2687
3494
|
if (process.platform !== "win32")
|
|
2688
|
-
|
|
3495
|
+
fs5.chmodSync(target, mode);
|
|
2689
3496
|
}
|
|
2690
3497
|
|
|
2691
3498
|
class ConfigStore {
|
|
@@ -2701,16 +3508,16 @@ class ConfigStore {
|
|
|
2701
3508
|
}
|
|
2702
3509
|
load() {
|
|
2703
3510
|
this.loadError = undefined;
|
|
2704
|
-
|
|
3511
|
+
fs5.mkdirSync(this.paths.configDir, { recursive: true, mode: DIR_MODE });
|
|
2705
3512
|
if (process.platform !== "win32") {
|
|
2706
3513
|
try {
|
|
2707
|
-
|
|
3514
|
+
fs5.chmodSync(this.paths.configDir, DIR_MODE);
|
|
2708
3515
|
} catch {}
|
|
2709
3516
|
}
|
|
2710
3517
|
this.config = structuredClone(DEFAULT_CONFIG);
|
|
2711
|
-
if (
|
|
3518
|
+
if (fs5.existsSync(this.paths.configFile)) {
|
|
2712
3519
|
try {
|
|
2713
|
-
const parsed = parseConfig(JSON.parse(
|
|
3520
|
+
const parsed = parseConfig(JSON.parse(fs5.readFileSync(this.paths.configFile, "utf8")));
|
|
2714
3521
|
if ("errors" in parsed)
|
|
2715
3522
|
this.loadError = parsed.errors.join("; ");
|
|
2716
3523
|
else
|
|
@@ -2720,10 +3527,10 @@ class ConfigStore {
|
|
|
2720
3527
|
}
|
|
2721
3528
|
}
|
|
2722
3529
|
this.secrets = {};
|
|
2723
|
-
if (
|
|
3530
|
+
if (fs5.existsSync(this.paths.secretsFile)) {
|
|
2724
3531
|
this.tightenPermissions(this.paths.secretsFile);
|
|
2725
3532
|
try {
|
|
2726
|
-
const raw = JSON.parse(
|
|
3533
|
+
const raw = JSON.parse(fs5.readFileSync(this.paths.secretsFile, "utf8"));
|
|
2727
3534
|
if (raw !== null && typeof raw === "object") {
|
|
2728
3535
|
for (const [key, value] of Object.entries(raw)) {
|
|
2729
3536
|
if (typeof value === "string")
|
|
@@ -2739,8 +3546,8 @@ class ConfigStore {
|
|
|
2739
3546
|
if (process.platform === "win32")
|
|
2740
3547
|
return;
|
|
2741
3548
|
try {
|
|
2742
|
-
if ((
|
|
2743
|
-
|
|
3549
|
+
if ((fs5.statSync(file).mode & 63) !== 0)
|
|
3550
|
+
fs5.chmodSync(file, FILE_MODE);
|
|
2744
3551
|
} catch {}
|
|
2745
3552
|
}
|
|
2746
3553
|
get() {
|
|
@@ -2748,7 +3555,7 @@ class ConfigStore {
|
|
|
2748
3555
|
}
|
|
2749
3556
|
health() {
|
|
2750
3557
|
return configHealth({
|
|
2751
|
-
fileExists:
|
|
3558
|
+
fileExists: fs5.existsSync(this.paths.configFile),
|
|
2752
3559
|
parseError: this.loadError,
|
|
2753
3560
|
config: this.resolved()
|
|
2754
3561
|
});
|
|
@@ -2793,7 +3600,7 @@ class ConfigStore {
|
|
|
2793
3600
|
delete this.secrets[dottedPath];
|
|
2794
3601
|
else
|
|
2795
3602
|
this.secrets[dottedPath] = value;
|
|
2796
|
-
if (!
|
|
3603
|
+
if (!fs5.existsSync(this.paths.configFile)) {
|
|
2797
3604
|
writeAtomic(this.paths.configFile, `${JSON.stringify(this.config, null, 2)}
|
|
2798
3605
|
`, FILE_MODE);
|
|
2799
3606
|
}
|
|
@@ -2823,7 +3630,7 @@ class ConfigStore {
|
|
|
2823
3630
|
fingerprint() {
|
|
2824
3631
|
const stamp = (file) => {
|
|
2825
3632
|
try {
|
|
2826
|
-
return String(
|
|
3633
|
+
return String(fs5.statSync(file).mtimeMs);
|
|
2827
3634
|
} catch {
|
|
2828
3635
|
return "-";
|
|
2829
3636
|
}
|
|
@@ -2845,8 +3652,8 @@ function applyPath(config, dottedPath, value) {
|
|
|
2845
3652
|
function defaultConfigStorePaths(configDir) {
|
|
2846
3653
|
return {
|
|
2847
3654
|
configDir,
|
|
2848
|
-
configFile:
|
|
2849
|
-
secretsFile:
|
|
3655
|
+
configFile: path6.join(configDir, "config.json"),
|
|
3656
|
+
secretsFile: path6.join(configDir, "secrets.json")
|
|
2850
3657
|
};
|
|
2851
3658
|
}
|
|
2852
3659
|
var ENV_ALIASES, DIR_MODE = 448, FILE_MODE = 384;
|
|
@@ -2865,13 +3672,13 @@ var init_config_store = __esm(() => {
|
|
|
2865
3672
|
});
|
|
2866
3673
|
|
|
2867
3674
|
// packages/daemon/src/shell.ts
|
|
2868
|
-
import
|
|
2869
|
-
import
|
|
3675
|
+
import fs6 from "fs";
|
|
3676
|
+
import path7 from "path";
|
|
2870
3677
|
function findExecutable(name, env) {
|
|
2871
3678
|
const isWindows = env.platform === "win32";
|
|
2872
|
-
const join = isWindows ?
|
|
3679
|
+
const join = isWindows ? path7.win32.join : path7.posix.join;
|
|
2873
3680
|
const separator = isWindows ? ";" : ":";
|
|
2874
|
-
const extensions = isWindows ?
|
|
3681
|
+
const extensions = isWindows ? path7.win32.extname(name).length > 0 ? [""] : env.pathExt.split(";").filter((e) => e.length > 0) : [""];
|
|
2875
3682
|
for (const rawDir of env.pathEnv.split(separator)) {
|
|
2876
3683
|
const dir = rawDir.trim().replace(/^"|"$/g, "");
|
|
2877
3684
|
if (dir.length === 0)
|
|
@@ -2889,16 +3696,16 @@ function lookupOnPath(name) {
|
|
|
2889
3696
|
platform: process.platform,
|
|
2890
3697
|
pathEnv: process.env["PATH"] ?? "",
|
|
2891
3698
|
pathExt: process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD",
|
|
2892
|
-
exists: (candidate) =>
|
|
3699
|
+
exists: (candidate) => fs6.existsSync(candidate)
|
|
2893
3700
|
});
|
|
2894
3701
|
}
|
|
2895
3702
|
function resolveShell(kind) {
|
|
2896
3703
|
if (process.platform !== "win32")
|
|
2897
3704
|
return resolvePosixShell(kind);
|
|
2898
3705
|
const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows";
|
|
2899
|
-
const windowsPowershell =
|
|
3706
|
+
const windowsPowershell = path7.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
2900
3707
|
if (kind === "cmd") {
|
|
2901
|
-
return { kind: "cmd", exe:
|
|
3708
|
+
return { kind: "cmd", exe: path7.join(systemRoot, "System32", "cmd.exe") };
|
|
2902
3709
|
}
|
|
2903
3710
|
if (kind === "powershell") {
|
|
2904
3711
|
return { kind: "powershell", exe: windowsPowershell };
|
|
@@ -2926,7 +3733,7 @@ function resolvePosixShell(kind) {
|
|
|
2926
3733
|
candidates.push({ kind: "bash", exe: login });
|
|
2927
3734
|
candidates.push({ kind: "bash", exe: "/bin/bash" }, { kind: "bash", exe: "/usr/bin/bash" });
|
|
2928
3735
|
}
|
|
2929
|
-
const found = candidates.find((c) =>
|
|
3736
|
+
const found = candidates.find((c) => fs6.existsSync(c.exe));
|
|
2930
3737
|
if (found)
|
|
2931
3738
|
return found;
|
|
2932
3739
|
return { kind: "sh", exe: "/bin/sh" };
|
|
@@ -2971,24 +3778,24 @@ function ipcEndpoint(platform, user, env) {
|
|
|
2971
3778
|
const suffix = `-${shortHash(env.runtimeDir)}`;
|
|
2972
3779
|
return `\\\\.\\pipe\\axterm-${safeUser}${suffix}`;
|
|
2973
3780
|
}
|
|
2974
|
-
return
|
|
3781
|
+
return path7.posix.join(env.runtimeDir, "axterm.sock");
|
|
2975
3782
|
}
|
|
2976
3783
|
var cachedPwsh;
|
|
2977
3784
|
var init_shell = () => {};
|
|
2978
|
-
|
|
2979
|
-
// packages/daemon/src/paths.ts
|
|
2980
|
-
import
|
|
2981
|
-
import
|
|
2982
|
-
import
|
|
3785
|
+
|
|
3786
|
+
// packages/daemon/src/paths.ts
|
|
3787
|
+
import fs7 from "fs";
|
|
3788
|
+
import os3 from "os";
|
|
3789
|
+
import path8 from "path";
|
|
2983
3790
|
function ensureDataDirs() {
|
|
2984
3791
|
for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir, SCROLLBACK_DIR, LOG_DIR, EXT_DIR]) {
|
|
2985
|
-
|
|
3792
|
+
fs7.mkdirSync(dir, { recursive: true, mode: DIR_MODE2 });
|
|
2986
3793
|
}
|
|
2987
3794
|
if (process.platform === "win32")
|
|
2988
3795
|
return;
|
|
2989
3796
|
for (const dir of [PATHS.configDir, PATHS.stateDir, PATHS.runtimeDir]) {
|
|
2990
3797
|
try {
|
|
2991
|
-
|
|
3798
|
+
fs7.chmodSync(dir, DIR_MODE2);
|
|
2992
3799
|
} catch {}
|
|
2993
3800
|
}
|
|
2994
3801
|
}
|
|
@@ -2996,14 +3803,14 @@ function restrictFile(file) {
|
|
|
2996
3803
|
if (process.platform === "win32")
|
|
2997
3804
|
return;
|
|
2998
3805
|
try {
|
|
2999
|
-
|
|
3806
|
+
fs7.chmodSync(file, 384);
|
|
3000
3807
|
} catch {}
|
|
3001
3808
|
}
|
|
3002
3809
|
function log(message) {
|
|
3003
3810
|
const line = `${new Date().toISOString()} ${message}
|
|
3004
3811
|
`;
|
|
3005
3812
|
try {
|
|
3006
|
-
|
|
3813
|
+
fs7.appendFileSync(LOG_FILE, line);
|
|
3007
3814
|
} catch {}
|
|
3008
3815
|
}
|
|
3009
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;
|
|
@@ -3020,15 +3827,15 @@ var init_paths = __esm(() => {
|
|
|
3020
3827
|
DB_PATH = PATHS.dbFile;
|
|
3021
3828
|
RUNTIME_FILE2 = PATHS.runtimeFile;
|
|
3022
3829
|
HOOK_ENDPOINT_FILE = PATHS.hookEndpointFile;
|
|
3023
|
-
PIPE_PATH = ipcEndpoint(process.platform,
|
|
3830
|
+
PIPE_PATH = ipcEndpoint(process.platform, os3.userInfo().username, {
|
|
3024
3831
|
runtimeDir: PATHS.runtimeDir
|
|
3025
3832
|
});
|
|
3026
|
-
LOG_FILE =
|
|
3833
|
+
LOG_FILE = path8.join(LOG_DIR, "daemon.log");
|
|
3027
3834
|
});
|
|
3028
3835
|
|
|
3029
3836
|
// packages/daemon/src/store.ts
|
|
3030
|
-
import
|
|
3031
|
-
import
|
|
3837
|
+
import fs8 from "fs";
|
|
3838
|
+
import path9 from "path";
|
|
3032
3839
|
import { Database } from "bun:sqlite";
|
|
3033
3840
|
function rowToInfo(row) {
|
|
3034
3841
|
return {
|
|
@@ -3059,7 +3866,7 @@ function rowToInfo(row) {
|
|
|
3059
3866
|
class Store {
|
|
3060
3867
|
db;
|
|
3061
3868
|
constructor(dbPath = DB_PATH) {
|
|
3062
|
-
|
|
3869
|
+
fs8.mkdirSync(path9.dirname(dbPath), { recursive: true });
|
|
3063
3870
|
this.db = new Database(dbPath, { strict: true });
|
|
3064
3871
|
this.db.run("PRAGMA journal_mode = WAL");
|
|
3065
3872
|
this.db.run(`
|
|
@@ -3220,11 +4027,11 @@ var init_store = __esm(() => {
|
|
|
3220
4027
|
});
|
|
3221
4028
|
|
|
3222
4029
|
// packages/daemon/src/config-migrate.ts
|
|
3223
|
-
import
|
|
3224
|
-
import
|
|
4030
|
+
import fs9 from "fs";
|
|
4031
|
+
import path10 from "path";
|
|
3225
4032
|
import { Database as Database2 } from "bun:sqlite";
|
|
3226
4033
|
function checkpointWal(dbFile) {
|
|
3227
|
-
if (!
|
|
4034
|
+
if (!fs9.existsSync(dbFile))
|
|
3228
4035
|
return;
|
|
3229
4036
|
try {
|
|
3230
4037
|
const db = new Database2(dbFile);
|
|
@@ -3234,32 +4041,32 @@ function checkpointWal(dbFile) {
|
|
|
3234
4041
|
}
|
|
3235
4042
|
function relocate(source, target) {
|
|
3236
4043
|
try {
|
|
3237
|
-
|
|
4044
|
+
fs9.renameSync(source, target);
|
|
3238
4045
|
} catch {
|
|
3239
|
-
|
|
4046
|
+
fs9.cpSync(source, target, { recursive: true });
|
|
3240
4047
|
}
|
|
3241
4048
|
}
|
|
3242
4049
|
function moveInto(from, to, notes) {
|
|
3243
|
-
checkpointWal(
|
|
3244
|
-
|
|
4050
|
+
checkpointWal(path10.join(from, "axterm.db"));
|
|
4051
|
+
fs9.mkdirSync(to, { recursive: true });
|
|
3245
4052
|
for (const entry of STATE_ENTRIES) {
|
|
3246
|
-
const source =
|
|
3247
|
-
const target =
|
|
3248
|
-
if (!
|
|
4053
|
+
const source = path10.join(from, entry);
|
|
4054
|
+
const target = path10.join(to, entry);
|
|
4055
|
+
if (!fs9.existsSync(source))
|
|
3249
4056
|
continue;
|
|
3250
|
-
if (!
|
|
4057
|
+
if (!fs9.existsSync(target)) {
|
|
3251
4058
|
relocate(source, target);
|
|
3252
4059
|
notes.push(`moved ${entry}`);
|
|
3253
4060
|
continue;
|
|
3254
4061
|
}
|
|
3255
|
-
if (!
|
|
4062
|
+
if (!fs9.statSync(source).isDirectory() || !fs9.statSync(target).isDirectory())
|
|
3256
4063
|
continue;
|
|
3257
4064
|
let moved = 0;
|
|
3258
|
-
for (const child of
|
|
3259
|
-
const childTarget =
|
|
3260
|
-
if (
|
|
4065
|
+
for (const child of fs9.readdirSync(source)) {
|
|
4066
|
+
const childTarget = path10.join(target, child);
|
|
4067
|
+
if (fs9.existsSync(childTarget))
|
|
3261
4068
|
continue;
|
|
3262
|
-
relocate(
|
|
4069
|
+
relocate(path10.join(source, child), childTarget);
|
|
3263
4070
|
moved += 1;
|
|
3264
4071
|
}
|
|
3265
4072
|
if (moved > 0)
|
|
@@ -3269,29 +4076,29 @@ function moveInto(from, to, notes) {
|
|
|
3269
4076
|
function migrateLegacyConfig(input) {
|
|
3270
4077
|
const notes = [];
|
|
3271
4078
|
const storePaths = defaultConfigStorePaths(input.configDir);
|
|
3272
|
-
if (
|
|
4079
|
+
if (fs9.existsSync(storePaths.configFile))
|
|
3273
4080
|
return { migrated: false, notes };
|
|
3274
|
-
const configSource = input.legacyRoots.find((dir) =>
|
|
4081
|
+
const configSource = input.legacyRoots.find((dir) => fs9.existsSync(path10.join(dir, "config.json")));
|
|
3275
4082
|
if (configSource !== undefined) {
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
const secrets =
|
|
3279
|
-
if (
|
|
3280
|
-
|
|
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);
|
|
3281
4088
|
try {
|
|
3282
|
-
|
|
4089
|
+
fs9.chmodSync(storePaths.secretsFile, 384);
|
|
3283
4090
|
} catch {}
|
|
3284
4091
|
}
|
|
3285
|
-
|
|
4092
|
+
fs9.writeFileSync(path10.join(configSource, "MIGRATED.txt"), `axterm now reads its configuration from:
|
|
3286
4093
|
${input.configDir}
|
|
3287
4094
|
` + `These files are no longer used. secrets.json still holds your bot token \u2014 delete this directory once you are satisfied.
|
|
3288
4095
|
`);
|
|
3289
4096
|
notes.push(`adopted configuration from ${configSource}`);
|
|
3290
4097
|
}
|
|
3291
|
-
const legacy = input.legacyRoots.find((dir) =>
|
|
4098
|
+
const legacy = input.legacyRoots.find((dir) => fs9.existsSync(path10.join(dir, "axterm.db")));
|
|
3292
4099
|
if (legacy !== undefined) {
|
|
3293
4100
|
moveInto(legacy, input.stateDir, notes);
|
|
3294
|
-
|
|
4101
|
+
fs9.writeFileSync(path10.join(legacy, "MIGRATED.txt"), `axterm now stores state in:
|
|
3295
4102
|
${input.stateDir}
|
|
3296
4103
|
and configuration in:
|
|
3297
4104
|
${input.configDir}
|
|
@@ -3300,8 +4107,8 @@ and configuration in:
|
|
|
3300
4107
|
}
|
|
3301
4108
|
if (configSource !== undefined)
|
|
3302
4109
|
return { migrated: true, ...legacy ? { from: legacy } : {}, notes };
|
|
3303
|
-
const dbFile =
|
|
3304
|
-
if (!
|
|
4110
|
+
const dbFile = path10.join(input.stateDir, "axterm.db");
|
|
4111
|
+
if (!fs9.existsSync(dbFile))
|
|
3305
4112
|
return { migrated: notes.length > 0, ...legacy ? { from: legacy } : {}, notes };
|
|
3306
4113
|
const store = new Store(dbFile);
|
|
3307
4114
|
const machine2 = store.getConfig("axterm_machine");
|
|
@@ -3346,6 +4153,7 @@ var axterm_agent_status_default = `// @axterm-managed-omp-extension\r
|
|
|
3346
4153
|
// completion) follow Orca's production orca-agent-status.ts, reviewed in\r
|
|
3347
4154
|
// docs/design/terminal-wrapper-omp-telegram.md \xA75.\r
|
|
3348
4155
|
import fs from "node:fs";\r
|
|
4156
|
+
import path from "node:path";\r
|
|
3349
4157
|
\r
|
|
3350
4158
|
const HOOK_POST_TIMEOUT_MS = 1000;\r
|
|
3351
4159
|
const MAX_ASSISTANT_TEXT = 4000;\r
|
|
@@ -3541,11 +4349,75 @@ function formatAnswers(params: unknown, answers: AskAnswerWire[]): Record<string
|
|
|
3541
4349
|
}\r
|
|
3542
4350
|
\r
|
|
3543
4351
|
export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
3544
|
-
//
|
|
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
|
|
3545
4357
|
const owner = process.env["AXTERM_STATUS_OWNED"];\r
|
|
3546
4358
|
const selfPid = String(process.pid);\r
|
|
3547
4359
|
if (owner && owner !== selfPid) return;\r
|
|
3548
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
|
|
3549
4421
|
\r
|
|
3550
4422
|
/* ---------------- endpoint re-sourcing (env-freeze fix) ---------------- */\r
|
|
3551
4423
|
let warnedBadEndpoint = false;\r
|
|
@@ -3774,6 +4646,12 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3774
4646
|
const say = (text: string, isError = false) => ({ content: [{ type: "text" as const, text }], isError });\r
|
|
3775
4647
|
const { path: filePath, caption } = (params ?? {}) as { path?: string; caption?: string };\r
|
|
3776
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
|
|
3777
4655
|
const coords = resolveCoords();\r
|
|
3778
4656
|
const sessionId = process.env["AXTERM_SESSION_ID"];\r
|
|
3779
4657
|
if (!coords.port || !coords.token || !sessionId) {\r
|
|
@@ -3786,7 +4664,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3786
4664
|
body: JSON.stringify({\r
|
|
3787
4665
|
sessionId,\r
|
|
3788
4666
|
launchToken: process.env["AXTERM_LAUNCH_TOKEN"] ?? "",\r
|
|
3789
|
-
path:
|
|
4667
|
+
path: resolved,\r
|
|
3790
4668
|
caption,\r
|
|
3791
4669
|
}),\r
|
|
3792
4670
|
});\r
|
|
@@ -3810,7 +4688,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3810
4688
|
return { status: "no-channel" };\r
|
|
3811
4689
|
}\r
|
|
3812
4690
|
try {\r
|
|
3813
|
-
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
|
|
3814
4692
|
method: "POST",\r
|
|
3815
4693
|
headers: { "content-type": "application/json", "x-axterm-hook-token": coords.token },\r
|
|
3816
4694
|
body: JSON.stringify({\r
|
|
@@ -3833,7 +4711,7 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3833
4711
|
const sessionId = process.env["AXTERM_SESSION_ID"];\r
|
|
3834
4712
|
if (!coords.port || !coords.token || !sessionId) return;\r
|
|
3835
4713
|
try {\r
|
|
3836
|
-
await fetch(\`http://127.0.0.1:\${coords.port}/hook/
|
|
4714
|
+
await fetch(\`http://127.0.0.1:\${coords.port}/hook/ask-cancel\`, {\r
|
|
3837
4715
|
method: "POST",\r
|
|
3838
4716
|
headers: { "content-type": "application/json", "x-axterm-hook-token": coords.token },\r
|
|
3839
4717
|
body: JSON.stringify({ sessionId, launchToken: process.env["AXTERM_LAUNCH_TOKEN"] ?? "", askId }),\r
|
|
@@ -3845,6 +4723,8 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3845
4723
|
\r
|
|
3846
4724
|
/* ------------------------------ handlers ------------------------------ */\r
|
|
3847
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
|
|
3848
4728
|
const manager = (ctx as { sessionManager?: Record<string, unknown> } | undefined)?.sessionManager;\r
|
|
3849
4729
|
const probe = (names: string[]): unknown => {\r
|
|
3850
4730
|
for (const name of names) {\r
|
|
@@ -3867,7 +4747,16 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3867
4747
|
});\r
|
|
3868
4748
|
});\r
|
|
3869
4749
|
\r
|
|
3870
|
-
|
|
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
|
|
3871
4760
|
post("before_agent_start", { prompt: (event as { prompt?: string } | undefined)?.prompt ?? "" });\r
|
|
3872
4761
|
});\r
|
|
3873
4762
|
\r
|
|
@@ -3912,7 +4801,10 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3912
4801
|
recheckMs = Math.min(recheckMs * 2, IDLE_RECHECK_MAX_MS);\r
|
|
3913
4802
|
}\r
|
|
3914
4803
|
\r
|
|
3915
|
-
|
|
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
|
|
3916
4808
|
clearPendingCheck();\r
|
|
3917
4809
|
endReported = false;\r
|
|
3918
4810
|
post("agent_start");\r
|
|
@@ -3997,20 +4889,29 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
3997
4889
|
});\r
|
|
3998
4890
|
});\r
|
|
3999
4891
|
\r
|
|
4000
|
-
|
|
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
|
|
4001
4896
|
const message = (event as { message?: unknown } | undefined)?.message;\r
|
|
4002
4897
|
const text = extractAssistantText(message);\r
|
|
4003
4898
|
if (!text) return;\r
|
|
4004
4899
|
post("message_end", { role: "assistant", text });\r
|
|
4005
4900
|
});\r
|
|
4006
4901
|
\r
|
|
4007
|
-
|
|
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
|
|
4008
4906
|
settledSupported = true;\r
|
|
4009
4907
|
clearPendingCheck();\r
|
|
4010
4908
|
postEndOnce();\r
|
|
4011
4909
|
});\r
|
|
4012
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
|
|
4013
4913
|
pi.on("agent_end", (_event, ctx) => {\r
|
|
4914
|
+
if (!isLeadSession(ctx)) return;\r
|
|
4014
4915
|
if (settledSupported) return;\r
|
|
4015
4916
|
const c = ctx as { isIdle?: () => boolean } | undefined;\r
|
|
4016
4917
|
if (!c || typeof c.isIdle !== "function") {\r
|
|
@@ -4045,13 +4946,13 @@ export default function axtermAgentStatus(pi: PiLike): void {\r
|
|
|
4045
4946
|
var init_axterm_agent_status = () => {};
|
|
4046
4947
|
|
|
4047
4948
|
// packages/daemon/src/ext-install.ts
|
|
4048
|
-
import
|
|
4049
|
-
import
|
|
4949
|
+
import fs10 from "fs";
|
|
4950
|
+
import path11 from "path";
|
|
4050
4951
|
function installExtensionAsset(targetDir) {
|
|
4051
|
-
|
|
4052
|
-
const target =
|
|
4952
|
+
fs10.mkdirSync(targetDir, { recursive: true });
|
|
4953
|
+
const target = path11.join(targetDir, "axterm-agent-status.mjs");
|
|
4053
4954
|
const js = new Bun.Transpiler({ loader: "ts" }).transformSync(extensionSource);
|
|
4054
|
-
|
|
4955
|
+
fs10.writeFileSync(target, MANAGED_HEADER + js);
|
|
4055
4956
|
return target;
|
|
4056
4957
|
}
|
|
4057
4958
|
var extensionSource, MANAGED_HEADER = `// @axterm-managed-omp-extension
|
|
@@ -4064,8 +4965,8 @@ var init_ext_install = __esm(() => {
|
|
|
4064
4965
|
|
|
4065
4966
|
// packages/daemon/src/workspace-local.ts
|
|
4066
4967
|
import { spawn as spawn2 } from "child_process";
|
|
4067
|
-
import
|
|
4068
|
-
import
|
|
4968
|
+
import fs11 from "fs/promises";
|
|
4969
|
+
import path12 from "path";
|
|
4069
4970
|
function isAbsent(error) {
|
|
4070
4971
|
const code = error?.code;
|
|
4071
4972
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
@@ -4078,10 +4979,10 @@ class LocalWorkspace {
|
|
|
4078
4979
|
this.stagingDir = stagingDir;
|
|
4079
4980
|
}
|
|
4080
4981
|
async list(dir) {
|
|
4081
|
-
const dirents = await
|
|
4982
|
+
const dirents = await fs11.readdir(dir, { withFileTypes: true });
|
|
4082
4983
|
const stats = await Promise.all(dirents.map(async (dirent) => {
|
|
4083
4984
|
try {
|
|
4084
|
-
return await
|
|
4985
|
+
return await fs11.lstat(path12.join(dir, dirent.name));
|
|
4085
4986
|
} catch (error) {
|
|
4086
4987
|
if (isAbsent(error))
|
|
4087
4988
|
return;
|
|
@@ -4105,7 +5006,7 @@ class LocalWorkspace {
|
|
|
4105
5006
|
}
|
|
4106
5007
|
async stat(target) {
|
|
4107
5008
|
try {
|
|
4108
|
-
const stat = await
|
|
5009
|
+
const stat = await fs11.stat(target);
|
|
4109
5010
|
const kind = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "other";
|
|
4110
5011
|
return { kind, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
4111
5012
|
} catch (error) {
|
|
@@ -4116,8 +5017,8 @@ class LocalWorkspace {
|
|
|
4116
5017
|
}
|
|
4117
5018
|
async read(file, limit) {
|
|
4118
5019
|
if (limit === undefined)
|
|
4119
|
-
return await
|
|
4120
|
-
const handle = await
|
|
5020
|
+
return await fs11.readFile(file);
|
|
5021
|
+
const handle = await fs11.open(file, "r");
|
|
4121
5022
|
try {
|
|
4122
5023
|
const buffer = Buffer.allocUnsafe(Math.min(limit, (await handle.stat()).size));
|
|
4123
5024
|
let filled = 0;
|
|
@@ -4162,9 +5063,9 @@ var init_workspace_local = () => {};
|
|
|
4162
5063
|
|
|
4163
5064
|
// packages/daemon/src/hook-host.ts
|
|
4164
5065
|
import { randomBytes } from "crypto";
|
|
4165
|
-
import
|
|
5066
|
+
import fs12 from "fs";
|
|
4166
5067
|
import http from "http";
|
|
4167
|
-
import
|
|
5068
|
+
import path13 from "path";
|
|
4168
5069
|
function header(req, name) {
|
|
4169
5070
|
const value = req.headers[name];
|
|
4170
5071
|
return Array.isArray(value) ? value[0] : value;
|
|
@@ -4188,7 +5089,7 @@ class HookHost {
|
|
|
4188
5089
|
constructor(options) {
|
|
4189
5090
|
this.endpointFile = options.endpointFile;
|
|
4190
5091
|
this.codexApprovalHoldMs = options.codexApprovalHoldMs ?? CODEX_APPROVAL_HOLD_MS;
|
|
4191
|
-
this.resolveWorkspace = options.workspaceFor ?? (() => this.local ??= new LocalWorkspace(
|
|
5092
|
+
this.resolveWorkspace = options.workspaceFor ?? (() => this.local ??= new LocalWorkspace(path13.join(RUNTIME_DIR, "materialised")));
|
|
4192
5093
|
}
|
|
4193
5094
|
start() {
|
|
4194
5095
|
const server = http.createServer((req, res) => this.handle(req, res));
|
|
@@ -4204,12 +5105,12 @@ class HookHost {
|
|
|
4204
5105
|
return promise;
|
|
4205
5106
|
}
|
|
4206
5107
|
writeEndpointFile(port) {
|
|
4207
|
-
|
|
5108
|
+
fs12.mkdirSync(path13.dirname(this.endpointFile), { recursive: true });
|
|
4208
5109
|
const tmp = `${this.endpointFile}.tmp`;
|
|
4209
|
-
|
|
5110
|
+
fs12.writeFileSync(tmp, `AXTERM_HOOK_PORT=${port}
|
|
4210
5111
|
AXTERM_HOOK_TOKEN=${this.token}
|
|
4211
5112
|
`);
|
|
4212
|
-
|
|
5113
|
+
fs12.renameSync(tmp, this.endpointFile);
|
|
4213
5114
|
}
|
|
4214
5115
|
registerSession(sessionId, launchToken, onEvent) {
|
|
4215
5116
|
this.sessions.set(sessionId, { launchToken, onEvent });
|
|
@@ -4247,8 +5148,8 @@ AXTERM_HOOK_TOKEN=${this.token}
|
|
|
4247
5148
|
}
|
|
4248
5149
|
handle(req, res) {
|
|
4249
5150
|
const isEvent = req.url === "/hook/omp";
|
|
4250
|
-
const isAsk = req.url === "/hook/
|
|
4251
|
-
const isAskCancel = req.url === "/hook/
|
|
5151
|
+
const isAsk = req.url === "/hook/ask";
|
|
5152
|
+
const isAskCancel = req.url === "/hook/ask-cancel";
|
|
4252
5153
|
const isAttach = req.url === "/hook/attach";
|
|
4253
5154
|
const isClaude = req.url === "/hook/claude";
|
|
4254
5155
|
const isCodex = req.url === "/hook/codex";
|
|
@@ -4484,7 +5385,7 @@ var init_hook_host = __esm(() => {
|
|
|
4484
5385
|
});
|
|
4485
5386
|
|
|
4486
5387
|
// packages/daemon/src/rpc-server.ts
|
|
4487
|
-
import
|
|
5388
|
+
import fs13 from "fs";
|
|
4488
5389
|
import net2 from "net";
|
|
4489
5390
|
function writeFrame(socket, frame) {
|
|
4490
5391
|
if (!socket.destroyed)
|
|
@@ -4669,10 +5570,12 @@ function startRpcServer(options) {
|
|
|
4669
5570
|
}
|
|
4670
5571
|
case "config.set": {
|
|
4671
5572
|
const dottedPath = p["path"];
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
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);
|
|
4676
5579
|
if ("error" in next2)
|
|
4677
5580
|
throw new Error(next2.error);
|
|
4678
5581
|
config.update(() => next2.config);
|
|
@@ -4700,10 +5603,14 @@ function startRpcServer(options) {
|
|
|
4700
5603
|
}
|
|
4701
5604
|
case "telegram.doctor":
|
|
4702
5605
|
return telegram.doctor();
|
|
5606
|
+
case "telegram.discover":
|
|
5607
|
+
return telegram.discover(p["botToken"]);
|
|
4703
5608
|
case "telegram.status":
|
|
4704
5609
|
return telegram.status();
|
|
4705
5610
|
case "doctor.report":
|
|
4706
5611
|
return { checks: manager.doctorReport() };
|
|
5612
|
+
case "quota.report":
|
|
5613
|
+
return { checks: manager.quotaReport() };
|
|
4707
5614
|
case "daemon.stop":
|
|
4708
5615
|
setTimeout(onStopRequested, 50);
|
|
4709
5616
|
return {};
|
|
@@ -4714,7 +5621,7 @@ function startRpcServer(options) {
|
|
|
4714
5621
|
});
|
|
4715
5622
|
if (process.platform !== "win32") {
|
|
4716
5623
|
try {
|
|
4717
|
-
|
|
5624
|
+
fs13.unlinkSync(pipePath);
|
|
4718
5625
|
} catch {}
|
|
4719
5626
|
}
|
|
4720
5627
|
server.listen(pipePath, () => {
|
|
@@ -4729,17 +5636,17 @@ var init_rpc_server = __esm(() => {
|
|
|
4729
5636
|
});
|
|
4730
5637
|
|
|
4731
5638
|
// packages/daemon/src/codex-home.ts
|
|
4732
|
-
import
|
|
4733
|
-
import
|
|
5639
|
+
import fs14 from "fs";
|
|
5640
|
+
import path14 from "path";
|
|
4734
5641
|
function codexTrustKeyPath(home) {
|
|
4735
5642
|
const resolved = (() => {
|
|
4736
5643
|
try {
|
|
4737
|
-
return
|
|
5644
|
+
return fs14.realpathSync.native(home);
|
|
4738
5645
|
} catch {
|
|
4739
|
-
return
|
|
5646
|
+
return path14.resolve(home);
|
|
4740
5647
|
}
|
|
4741
5648
|
})();
|
|
4742
|
-
return
|
|
5649
|
+
return path14.join(resolved, "hooks.json");
|
|
4743
5650
|
}
|
|
4744
5651
|
var init_codex_home = () => {};
|
|
4745
5652
|
|
|
@@ -4802,8 +5709,8 @@ function slugifyBranch(branch) {
|
|
|
4802
5709
|
var MAX_SLUG_LENGTH = 60;
|
|
4803
5710
|
|
|
4804
5711
|
// packages/daemon/src/git.ts
|
|
4805
|
-
import
|
|
4806
|
-
import
|
|
5712
|
+
import fs15 from "fs";
|
|
5713
|
+
import path15 from "path";
|
|
4807
5714
|
async function git(ws, cwd, args) {
|
|
4808
5715
|
const run = await ws.exec(["git", ...args], cwd);
|
|
4809
5716
|
return {
|
|
@@ -4820,15 +5727,15 @@ async function gitOrThrow(ws, cwd, args) {
|
|
|
4820
5727
|
return result.stdout;
|
|
4821
5728
|
}
|
|
4822
5729
|
function canonicalPath(target) {
|
|
4823
|
-
let resolved =
|
|
5730
|
+
let resolved = path15.resolve(target);
|
|
4824
5731
|
try {
|
|
4825
|
-
resolved =
|
|
5732
|
+
resolved = fs15.realpathSync.native(resolved);
|
|
4826
5733
|
} catch {}
|
|
4827
5734
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
4828
5735
|
}
|
|
4829
5736
|
async function resolveRepoRoot(ws, cwd) {
|
|
4830
5737
|
const result = await git(ws, cwd, ["rev-parse", "--show-toplevel"]);
|
|
4831
|
-
return result.ok ?
|
|
5738
|
+
return result.ok ? path15.resolve(result.stdout) : undefined;
|
|
4832
5739
|
}
|
|
4833
5740
|
async function currentBranch(ws, cwd) {
|
|
4834
5741
|
const result = await git(ws, cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
@@ -4837,8 +5744,8 @@ async function currentBranch(ws, cwd) {
|
|
|
4837
5744
|
async function listWorktrees(ws, repoRoot) {
|
|
4838
5745
|
return parseWorktreeList(await gitOrThrow(ws, repoRoot, ["worktree", "list", "--porcelain"]));
|
|
4839
5746
|
}
|
|
4840
|
-
function worktreePathFor(repoRoot, branch, root =
|
|
4841
|
-
return
|
|
5747
|
+
function worktreePathFor(repoRoot, branch, root = path15.join(STATE_DIR, "worktrees")) {
|
|
5748
|
+
return path15.join(root, slugifyBranch(path15.basename(repoRoot)), slugifyBranch(branch));
|
|
4842
5749
|
}
|
|
4843
5750
|
async function createWorktree(ws, options) {
|
|
4844
5751
|
const { repoRoot, branch, base } = options;
|
|
@@ -4846,7 +5753,7 @@ async function createWorktree(ws, options) {
|
|
|
4846
5753
|
const existing = (await listWorktrees(ws, repoRoot)).find((w) => canonicalPath(w.path) === canonicalPath(target));
|
|
4847
5754
|
if (existing)
|
|
4848
5755
|
return { path: target, branch, createdBranch: false };
|
|
4849
|
-
|
|
5756
|
+
fs15.mkdirSync(path15.dirname(target), { recursive: true });
|
|
4850
5757
|
const exists = (await git(ws, repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`])).ok;
|
|
4851
5758
|
const args = exists ? ["worktree", "add", target, branch] : ["worktree", "add", "-b", branch, target, ...base !== undefined ? [base] : []];
|
|
4852
5759
|
await gitOrThrow(ws, repoRoot, args);
|
|
@@ -4899,7 +5806,7 @@ async function diffStat(ws, worktreePath, base) {
|
|
|
4899
5806
|
const untracked = await untrackedFiles(ws, worktreePath);
|
|
4900
5807
|
let newLines = 0;
|
|
4901
5808
|
for (const file of untracked)
|
|
4902
|
-
newLines += await countLines(ws,
|
|
5809
|
+
newLines += await countLines(ws, path15.join(worktreePath, file));
|
|
4903
5810
|
return {
|
|
4904
5811
|
filesChanged: a.filesChanged + b.filesChanged + untracked.length,
|
|
4905
5812
|
insertions: a.insertions + b.insertions + newLines,
|
|
@@ -4975,7 +5882,7 @@ var init_injector = __esm(() => {
|
|
|
4975
5882
|
|
|
4976
5883
|
// packages/daemon/src/occupancy.ts
|
|
4977
5884
|
import { spawn as spawn3 } from "child_process";
|
|
4978
|
-
import
|
|
5885
|
+
import fs16 from "fs/promises";
|
|
4979
5886
|
function shellOccupied(shellPid) {
|
|
4980
5887
|
return process.platform === "win32" ? occupiedFromSnapshot(shellPid) : occupiedFromPidProbe(shellPid);
|
|
4981
5888
|
}
|
|
@@ -5020,72 +5927,474 @@ async function occupiedFromSnapshot(shellPid) {
|
|
|
5020
5927
|
}
|
|
5021
5928
|
async function childrenViaProc(pid) {
|
|
5022
5929
|
try {
|
|
5023
|
-
const children = await
|
|
5930
|
+
const children = await fs16.readFile(`/proc/${pid}/task/${pid}/children`, "utf8");
|
|
5024
5931
|
return children.trim().length > 0;
|
|
5025
5932
|
} catch {
|
|
5026
5933
|
try {
|
|
5027
|
-
await
|
|
5934
|
+
await fs16.access(`/proc/${pid}`);
|
|
5028
5935
|
return;
|
|
5029
5936
|
} catch {
|
|
5030
5937
|
return false;
|
|
5031
5938
|
}
|
|
5032
|
-
}
|
|
5033
|
-
}
|
|
5034
|
-
async function childrenViaPgrep(pid) {
|
|
5035
|
-
const result = await capture("pgrep", ["-P", String(pid)]);
|
|
5036
|
-
if (result === undefined)
|
|
5037
|
-
return;
|
|
5038
|
-
if (result.code === 1)
|
|
5039
|
-
return false;
|
|
5040
|
-
if (result.code !== 0)
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
async function childrenViaPgrep(pid) {
|
|
5942
|
+
const result = await capture("pgrep", ["-P", String(pid)]);
|
|
5943
|
+
if (result === undefined)
|
|
5944
|
+
return;
|
|
5945
|
+
if (result.code === 1)
|
|
5946
|
+
return false;
|
|
5947
|
+
if (result.code !== 0)
|
|
5948
|
+
return;
|
|
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
|
+
}
|
|
5041
6322
|
return;
|
|
5042
|
-
|
|
6323
|
+
}
|
|
6324
|
+
try {
|
|
6325
|
+
process.kill(-pid, "SIGKILL");
|
|
6326
|
+
} catch {}
|
|
5043
6327
|
}
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
const
|
|
5061
|
-
if (
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
const
|
|
5067
|
-
if (
|
|
5068
|
-
|
|
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 {}
|
|
5069
6377
|
}
|
|
5070
|
-
return startedAt.size > 0 ? { startedAt, newestChild } : undefined;
|
|
5071
6378
|
}
|
|
5072
|
-
function
|
|
5073
|
-
|
|
5074
|
-
const
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
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
|
+
}
|
|
5079
6392
|
});
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
return
|
|
6393
|
+
handle = new BunPtyHandle(proc, path17.basename(options.exe).toLowerCase());
|
|
6394
|
+
proc.exited.then((code) => handle?.finish(code ?? null));
|
|
6395
|
+
return handle;
|
|
5083
6396
|
}
|
|
5084
|
-
var
|
|
5085
|
-
var init_occupancy = __esm(() => {
|
|
5086
|
-
init_shell();
|
|
5087
|
-
perPid = new Map;
|
|
5088
|
-
});
|
|
6397
|
+
var init_pty = () => {};
|
|
5089
6398
|
|
|
5090
6399
|
// node_modules/.bun/@xterm+headless@5.5.0/node_modules/@xterm/headless/lib-headless/xterm-headless.js
|
|
5091
6400
|
var require_xterm_headless = __commonJS((exports) => {
|
|
@@ -9379,154 +10688,46 @@ var require_addon_serialize = __commonJS((exports, module) => {
|
|
|
9379
10688
|
this._htmlContent += "<html><body><!--StartFragment--><pre>";
|
|
9380
10689
|
let r2 = "#000000", o2 = "#ffffff";
|
|
9381
10690
|
this._options.includeGlobalBackground && (r2 = this._terminal.options.theme?.foreground ?? "#ffffff", o2 = this._terminal.options.theme?.background ?? "#000000");
|
|
9382
|
-
const i2 = [];
|
|
9383
|
-
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(" ") + "'>";
|
|
9384
|
-
}
|
|
9385
|
-
_afterSerialize() {
|
|
9386
|
-
this._htmlContent += "</div>", this._htmlContent += "</pre><!--EndFragment--></body></html>";
|
|
9387
|
-
}
|
|
9388
|
-
_rowEnd(e3, t4) {
|
|
9389
|
-
this._htmlContent += "<div><span>" + this._currentRow + "</span></div>", this._currentRow = "";
|
|
9390
|
-
}
|
|
9391
|
-
_getHexColor(e3, t4) {
|
|
9392
|
-
const s2 = t4 ? e3.getFgColor() : e3.getBgColor();
|
|
9393
|
-
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;
|
|
9394
|
-
}
|
|
9395
|
-
_diffStyle(e3, t4) {
|
|
9396
|
-
const s2 = [], r2 = !n(e3, t4), o2 = !l(e3, t4), i2 = !a(e3, t4);
|
|
9397
|
-
if (r2 || o2 || i2) {
|
|
9398
|
-
const t5 = this._getHexColor(e3, true);
|
|
9399
|
-
t5 && s2.push("color: " + t5 + ";");
|
|
9400
|
-
const r3 = this._getHexColor(e3, false);
|
|
9401
|
-
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;
|
|
9402
|
-
}
|
|
9403
|
-
}
|
|
9404
|
-
_nextCell(e3, t4, s2, r2) {
|
|
9405
|
-
if (e3.getWidth() === 0)
|
|
9406
|
-
return;
|
|
9407
|
-
const o2 = e3.getChars() === "", i2 = this._diffStyle(e3, t4);
|
|
9408
|
-
i2 && (this._currentRow += i2.length === 0 ? "</span><span>" : "</span><span style='" + i2.join(" ") + "'>"), this._currentRow += o2 ? " " : e3.getChars();
|
|
9409
|
-
}
|
|
9410
|
-
_serializeString() {
|
|
9411
|
-
return this._htmlContent;
|
|
9412
|
-
}
|
|
9413
|
-
}
|
|
9414
|
-
e2.HTMLSerializeHandler = h;
|
|
9415
|
-
})(), r;
|
|
9416
|
-
})());
|
|
9417
|
-
});
|
|
9418
|
-
|
|
9419
|
-
// packages/daemon/src/pty.ts
|
|
9420
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
9421
|
-
import path15 from "path";
|
|
9422
|
-
import { StringDecoder } from "string_decoder";
|
|
9423
|
-
|
|
9424
|
-
class Fanout {
|
|
9425
|
-
listeners = new Set;
|
|
9426
|
-
backlog = [];
|
|
9427
|
-
add(listener) {
|
|
9428
|
-
this.listeners.add(listener);
|
|
9429
|
-
for (const value of this.backlog)
|
|
9430
|
-
listener(value);
|
|
9431
|
-
}
|
|
9432
|
-
emit(value) {
|
|
9433
|
-
if (this.listeners.size === 0)
|
|
9434
|
-
this.backlog.push(value);
|
|
9435
|
-
else
|
|
9436
|
-
for (const listener of this.listeners)
|
|
9437
|
-
listener(value);
|
|
9438
|
-
}
|
|
9439
|
-
}
|
|
9440
|
-
function killTree(pid, exeName) {
|
|
9441
|
-
if (process.platform === "win32") {
|
|
9442
|
-
const check = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
|
|
9443
|
-
encoding: "utf8",
|
|
9444
|
-
windowsHide: true
|
|
9445
|
-
});
|
|
9446
|
-
const image = check.stdout?.split(",")[0]?.replace(/"/g, "").toLowerCase() ?? "";
|
|
9447
|
-
if (image === exeName) {
|
|
9448
|
-
spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
|
|
9449
|
-
}
|
|
9450
|
-
return;
|
|
9451
|
-
}
|
|
9452
|
-
try {
|
|
9453
|
-
process.kill(-pid, "SIGKILL");
|
|
9454
|
-
} catch {}
|
|
9455
|
-
}
|
|
9456
|
-
|
|
9457
|
-
class BunPtyHandle {
|
|
9458
|
-
proc;
|
|
9459
|
-
exeName;
|
|
9460
|
-
data = new Fanout;
|
|
9461
|
-
exit = new Fanout;
|
|
9462
|
-
decoder = new StringDecoder("utf8");
|
|
9463
|
-
exited = false;
|
|
9464
|
-
constructor(proc, exeName) {
|
|
9465
|
-
this.proc = proc;
|
|
9466
|
-
this.exeName = exeName;
|
|
9467
|
-
}
|
|
9468
|
-
get pid() {
|
|
9469
|
-
return this.proc.pid;
|
|
9470
|
-
}
|
|
9471
|
-
push(bytes) {
|
|
9472
|
-
const text = this.decoder.write(Buffer.from(bytes));
|
|
9473
|
-
if (text.length > 0)
|
|
9474
|
-
this.data.emit(text);
|
|
9475
|
-
}
|
|
9476
|
-
finish(code) {
|
|
9477
|
-
this.exited = true;
|
|
9478
|
-
const tail = this.decoder.end();
|
|
9479
|
-
if (tail.length > 0)
|
|
9480
|
-
this.data.emit(tail);
|
|
9481
|
-
try {
|
|
9482
|
-
this.proc.terminal.close();
|
|
9483
|
-
} catch {}
|
|
9484
|
-
this.exit.emit(code);
|
|
9485
|
-
}
|
|
9486
|
-
onData(listener) {
|
|
9487
|
-
this.data.add(listener);
|
|
9488
|
-
}
|
|
9489
|
-
onExit(listener) {
|
|
9490
|
-
this.exit.add(listener);
|
|
9491
|
-
}
|
|
9492
|
-
write(data) {
|
|
9493
|
-
this.proc.terminal.write(data);
|
|
9494
|
-
}
|
|
9495
|
-
resize(cols, rows) {
|
|
9496
|
-
this.proc.terminal.resize(cols, rows);
|
|
9497
|
-
}
|
|
9498
|
-
kill() {
|
|
9499
|
-
if (this.exited)
|
|
9500
|
-
return;
|
|
9501
|
-
killTree(this.proc.pid, this.exeName);
|
|
9502
|
-
try {
|
|
9503
|
-
this.proc.kill();
|
|
9504
|
-
} catch {}
|
|
9505
|
-
}
|
|
9506
|
-
}
|
|
9507
|
-
function spawnPty(options) {
|
|
9508
|
-
let handle;
|
|
9509
|
-
const proc = Bun.spawn([options.exe, ...options.argv], {
|
|
9510
|
-
cwd: options.cwd,
|
|
9511
|
-
env: { ...options.env, TERM: options.term },
|
|
9512
|
-
terminal: {
|
|
9513
|
-
cols: options.cols,
|
|
9514
|
-
rows: options.rows,
|
|
9515
|
-
name: options.term,
|
|
9516
|
-
data(_terminal, bytes) {
|
|
9517
|
-
handle?.push(bytes);
|
|
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
|
+
}
|
|
9518
10722
|
}
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
return handle;
|
|
9524
|
-
}
|
|
9525
|
-
var init_pty = () => {};
|
|
10723
|
+
e2.HTMLSerializeHandler = h;
|
|
10724
|
+
})(), r;
|
|
10725
|
+
})());
|
|
10726
|
+
});
|
|
9526
10727
|
|
|
9527
10728
|
// packages/daemon/src/pty-session.ts
|
|
9528
|
-
import
|
|
9529
|
-
import
|
|
10729
|
+
import fs18 from "fs";
|
|
10730
|
+
import path18 from "path";
|
|
9530
10731
|
|
|
9531
10732
|
class PtySession {
|
|
9532
10733
|
info;
|
|
@@ -9642,10 +10843,10 @@ class PtySession {
|
|
|
9642
10843
|
}
|
|
9643
10844
|
saveScrollback() {
|
|
9644
10845
|
try {
|
|
9645
|
-
const file =
|
|
10846
|
+
const file = path18.join(SCROLLBACK_DIR, `${this.info.id}.bin`);
|
|
9646
10847
|
const tmp = `${file}.tmp`;
|
|
9647
|
-
|
|
9648
|
-
|
|
10848
|
+
fs18.writeFileSync(tmp, this.serializeBuffer(), "utf8");
|
|
10849
|
+
fs18.renameSync(tmp, file);
|
|
9649
10850
|
} catch (err) {
|
|
9650
10851
|
log(`scrollback save failed for ${this.info.shortId}: ${String(err)}`);
|
|
9651
10852
|
}
|
|
@@ -9667,9 +10868,9 @@ var init_pty_session = __esm(() => {
|
|
|
9667
10868
|
|
|
9668
10869
|
// packages/daemon/src/session-manager.ts
|
|
9669
10870
|
import { randomBytes as randomBytes2, randomUUID } from "crypto";
|
|
9670
|
-
import
|
|
10871
|
+
import fs19 from "fs";
|
|
9671
10872
|
import os5 from "os";
|
|
9672
|
-
import
|
|
10873
|
+
import path19 from "path";
|
|
9673
10874
|
function unresolvableAgent(commandOverride, agentCommand, lookup) {
|
|
9674
10875
|
if (commandOverride !== undefined)
|
|
9675
10876
|
return;
|
|
@@ -9712,6 +10913,7 @@ async function sendableNow(runtime, info, probe, now) {
|
|
|
9712
10913
|
class SessionManager {
|
|
9713
10914
|
store;
|
|
9714
10915
|
hooks;
|
|
10916
|
+
configuredDefaults;
|
|
9715
10917
|
live = new Map;
|
|
9716
10918
|
agentRuntime = new Map;
|
|
9717
10919
|
injector = new Injector;
|
|
@@ -9719,13 +10921,18 @@ class SessionManager {
|
|
|
9719
10921
|
eventListeners = new Set;
|
|
9720
10922
|
rawHookListeners = new Set;
|
|
9721
10923
|
workspace = new LocalWorkspace(MATERIALISED_DIR);
|
|
9722
|
-
constructor(store, hooks) {
|
|
10924
|
+
constructor(store, hooks, configuredDefaults = () => ({})) {
|
|
9723
10925
|
this.store = store;
|
|
9724
10926
|
this.hooks = hooks;
|
|
10927
|
+
this.configuredDefaults = configuredDefaults;
|
|
9725
10928
|
const stale = store.markStaleRunningAsExited();
|
|
9726
10929
|
if (stale > 0)
|
|
9727
10930
|
log(`boot: marked ${stale} stale session(s) as exited`);
|
|
10931
|
+
this.sessionWatch = setInterval(() => this.tickSessions(), SESSION_TICK_MS);
|
|
10932
|
+
this.sessionWatch.unref();
|
|
9728
10933
|
}
|
|
10934
|
+
sessionWatch;
|
|
10935
|
+
codexServers = new Map;
|
|
9729
10936
|
workspaceFor(_sessionId) {
|
|
9730
10937
|
return this.workspace;
|
|
9731
10938
|
}
|
|
@@ -9802,8 +11009,8 @@ class SessionManager {
|
|
|
9802
11009
|
};
|
|
9803
11010
|
}
|
|
9804
11011
|
async create(params) {
|
|
9805
|
-
const requestedCwd = params.cwd ?
|
|
9806
|
-
if (!
|
|
11012
|
+
const requestedCwd = params.cwd ? path19.resolve(params.cwd) : os5.homedir();
|
|
11013
|
+
if (!fs19.existsSync(requestedCwd)) {
|
|
9807
11014
|
throw new AxtermError("err.cwd.missing", { cwd: requestedCwd });
|
|
9808
11015
|
}
|
|
9809
11016
|
let worktree;
|
|
@@ -9820,17 +11027,29 @@ class SessionManager {
|
|
|
9820
11027
|
log(`worktree ${created.createdBranch ? "created" : "reused"} ${created.branch} at ${created.path}`);
|
|
9821
11028
|
}
|
|
9822
11029
|
const cwd = worktree?.path ?? requestedCwd;
|
|
9823
|
-
const
|
|
9824
|
-
const
|
|
11030
|
+
const configured = this.configuredDefaults();
|
|
11031
|
+
const shell = resolveShell(params.shell ?? configured.shell);
|
|
11032
|
+
const agent = params.agent ?? configured.agent ?? "omp";
|
|
9825
11033
|
if (!isAgentId(agent))
|
|
9826
11034
|
throw new AxtermError("err.agent.unknown", { agent, supported: supportedAgents().join(", ") });
|
|
9827
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;
|
|
9828
11045
|
const agentCommand = params.commandOverride ?? buildAgentCommand({
|
|
9829
11046
|
agent,
|
|
9830
11047
|
sessionId: id,
|
|
9831
11048
|
extensionPath: this.hooks.extensionPath,
|
|
9832
11049
|
claudeSettingsPath: this.claudeSettingsPath(id),
|
|
9833
|
-
...
|
|
11050
|
+
...mcpConfigPath === undefined ? {} : { mcpConfigPath },
|
|
11051
|
+
...agent === "codex" ? { mcpLaunch: this.mcpServerLaunch(), mcpContextPath: mcpContext } : {},
|
|
11052
|
+
...extraArgs.length > 0 ? { extraArgs } : {}
|
|
9834
11053
|
});
|
|
9835
11054
|
const missing = unresolvableAgent(params.commandOverride, agentCommand, lookupOnPath);
|
|
9836
11055
|
if (missing !== undefined)
|
|
@@ -9838,7 +11057,7 @@ class SessionManager {
|
|
|
9838
11057
|
const info = {
|
|
9839
11058
|
id,
|
|
9840
11059
|
shortId: id.slice(0, 6),
|
|
9841
|
-
name: params.name ?? worktree?.branch ??
|
|
11060
|
+
name: params.name ?? worktree?.branch ?? path19.basename(cwd),
|
|
9842
11061
|
cwd,
|
|
9843
11062
|
shell: shell.kind,
|
|
9844
11063
|
agentCommand,
|
|
@@ -9848,7 +11067,8 @@ class SessionManager {
|
|
|
9848
11067
|
createdAt: Date.now(),
|
|
9849
11068
|
...worktree !== undefined ? { worktreePath: worktree.path, branch: worktree.branch, repoRoot: worktree.repoRoot } : {}
|
|
9850
11069
|
};
|
|
9851
|
-
this.
|
|
11070
|
+
const appServerUrl = agent === "codex" ? await this.startCodexAppServer(id, this.ensureCodexHome(), cwd) : undefined;
|
|
11071
|
+
this.launch(info, shell, agentCommand, appServerUrl);
|
|
9852
11072
|
this.store.insertSession(info);
|
|
9853
11073
|
log(`session ${info.shortId} created shell=${shell.kind} cmd=${agentCommand.join(" ") || "(shell only)"}`);
|
|
9854
11074
|
this.emit({ t: "session.created", info });
|
|
@@ -9862,37 +11082,82 @@ class SessionManager {
|
|
|
9862
11082
|
launchToken
|
|
9863
11083
|
});
|
|
9864
11084
|
const file = this.claudeSettingsPath(id);
|
|
9865
|
-
|
|
11085
|
+
fs19.writeFileSync(file, JSON.stringify(settings, null, 2), { mode: 384 });
|
|
9866
11086
|
}
|
|
9867
|
-
writeGeminiSettings() {
|
|
11087
|
+
writeGeminiSettings(sessionId) {
|
|
9868
11088
|
try {
|
|
9869
11089
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "gemini" });
|
|
9870
|
-
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
|
+
});
|
|
9871
11096
|
const file = this.geminiSettingsPath();
|
|
9872
11097
|
const tmp = `${file}.tmp`;
|
|
9873
|
-
|
|
9874
|
-
|
|
11098
|
+
fs19.writeFileSync(tmp, JSON.stringify(settings, null, 2));
|
|
11099
|
+
fs19.renameSync(tmp, file);
|
|
9875
11100
|
} catch (err) {
|
|
9876
11101
|
log(`gemini settings write failed; session starts without hooks: ${String(err)}`);
|
|
9877
11102
|
}
|
|
9878
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
|
+
}
|
|
9879
11144
|
ensureCodexHome() {
|
|
9880
|
-
const home =
|
|
9881
|
-
|
|
11145
|
+
const home = path19.join(STATE_DIR, "codex-home");
|
|
11146
|
+
fs19.mkdirSync(home, { recursive: true, mode: 448 });
|
|
9882
11147
|
this.linkCodexAuth(home);
|
|
9883
11148
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
|
|
9884
11149
|
const hooksFile = buildCodexHooks({ command: bridge.command, args: bridge.args });
|
|
9885
11150
|
const hooksPath = codexTrustKeyPath(home);
|
|
9886
11151
|
const tmp = `${hooksPath}.tmp`;
|
|
9887
|
-
|
|
9888
|
-
|
|
11152
|
+
fs19.writeFileSync(tmp, JSON.stringify(hooksFile, null, 2));
|
|
11153
|
+
fs19.renameSync(tmp, hooksPath);
|
|
9889
11154
|
const entries = codexHookTrust(hooksPath, hooksFile);
|
|
9890
|
-
const configPath =
|
|
9891
|
-
const existing =
|
|
11155
|
+
const configPath = path19.join(home, "config.toml");
|
|
11156
|
+
const existing = fs19.existsSync(configPath) ? fs19.readFileSync(configPath, "utf8") : "";
|
|
9892
11157
|
const missing = entries.filter((e) => !existing.includes(`"${e.key.replaceAll("\\", "\\\\")}"`));
|
|
9893
11158
|
if (missing.length > 0) {
|
|
9894
11159
|
const block = codexTrustStateToml(missing);
|
|
9895
|
-
|
|
11160
|
+
fs19.appendFileSync(configPath, existing !== "" && !existing.endsWith(`
|
|
9896
11161
|
`) ? `
|
|
9897
11162
|
${block}` : block);
|
|
9898
11163
|
}
|
|
@@ -9905,18 +11170,18 @@ ${block}` : block);
|
|
|
9905
11170
|
return home;
|
|
9906
11171
|
}
|
|
9907
11172
|
linkCodexAuth(home) {
|
|
9908
|
-
const target =
|
|
9909
|
-
if (
|
|
11173
|
+
const target = path19.join(home, "auth.json");
|
|
11174
|
+
if (fs19.existsSync(target))
|
|
9910
11175
|
return;
|
|
9911
|
-
const operatorHome = process.env["CODEX_HOME"] ??
|
|
9912
|
-
const source =
|
|
9913
|
-
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))
|
|
9914
11179
|
throw new AxtermError("err.codex.noAuth", { authFile: source });
|
|
9915
11180
|
try {
|
|
9916
|
-
|
|
11181
|
+
fs19.symlinkSync(source, target, "file");
|
|
9917
11182
|
} catch {
|
|
9918
11183
|
try {
|
|
9919
|
-
|
|
11184
|
+
fs19.linkSync(source, target);
|
|
9920
11185
|
} catch (err) {
|
|
9921
11186
|
throw new AxtermError("err.codex.authLink", { reason: err instanceof Error ? err.message : String(err) });
|
|
9922
11187
|
}
|
|
@@ -9924,37 +11189,45 @@ ${block}` : block);
|
|
|
9924
11189
|
}
|
|
9925
11190
|
doctorReport() {
|
|
9926
11191
|
const bridge = hookLaunch({ execPath: process.execPath, mainScript: Bun.main, agent: "codex" });
|
|
9927
|
-
const home =
|
|
9928
|
-
const operatorHome = process.env["CODEX_HOME"] ??
|
|
9929
|
-
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");
|
|
9930
11195
|
const geminiSettings = this.geminiSettingsPath();
|
|
9931
|
-
const configPath =
|
|
11196
|
+
const configPath = path19.join(home, "config.toml");
|
|
9932
11197
|
const read = (file) => {
|
|
9933
11198
|
try {
|
|
9934
|
-
return
|
|
11199
|
+
return fs19.existsSync(file) ? fs19.readFileSync(file, "utf8") : undefined;
|
|
9935
11200
|
} catch {
|
|
9936
11201
|
return;
|
|
9937
11202
|
}
|
|
9938
11203
|
};
|
|
9939
11204
|
return [
|
|
9940
|
-
...evaluateBridgeDoctor({ command: bridge.command, exists:
|
|
11205
|
+
...evaluateBridgeDoctor({ command: bridge.command, exists: fs19.existsSync(bridge.command) }),
|
|
9941
11206
|
...evaluateOmpDoctor({
|
|
9942
11207
|
binary: lookupOnPath("omp"),
|
|
9943
11208
|
extensionPath: this.hooks.extensionPath,
|
|
9944
|
-
extensionExists:
|
|
11209
|
+
extensionExists: fs19.existsSync(this.hooks.extensionPath)
|
|
9945
11210
|
}),
|
|
9946
11211
|
...evaluateClaudeDoctor({ binary: lookupOnPath("claude") }),
|
|
9947
11212
|
...evaluateCodexDoctor({
|
|
9948
11213
|
binary: lookupOnPath("codex"),
|
|
9949
11214
|
authSourcePath: authSource,
|
|
9950
|
-
authSourceExists:
|
|
9951
|
-
homeExists:
|
|
11215
|
+
authSourceExists: fs19.existsSync(authSource),
|
|
11216
|
+
homeExists: fs19.existsSync(home),
|
|
9952
11217
|
expected: codexHookTrust(codexTrustKeyPath(home), buildCodexHooks({ command: bridge.command, args: bridge.args })),
|
|
9953
11218
|
configToml: read(configPath)
|
|
9954
11219
|
}),
|
|
9955
11220
|
...evaluateGeminiDoctor({ binary: lookupOnPath("gemini"), settingsJson: read(geminiSettings) })
|
|
9956
11221
|
];
|
|
9957
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
|
+
}
|
|
9958
11231
|
sawOutput = new Set;
|
|
9959
11232
|
idleTimers = new Map;
|
|
9960
11233
|
lastCaptured = new Map;
|
|
@@ -9981,7 +11254,7 @@ ${block}` : block);
|
|
|
9981
11254
|
if (!session || !runtime)
|
|
9982
11255
|
return;
|
|
9983
11256
|
const found = detectPrompt(session.screenText(4));
|
|
9984
|
-
const dialog = found === undefined
|
|
11257
|
+
const dialog = found === undefined ? detectOnboarding(agentIdFromCommand(session.info.agentCommand), session.screenText(30)) : undefined;
|
|
9985
11258
|
if (!found && !dialog) {
|
|
9986
11259
|
if (this.pendingPrompt.delete(id))
|
|
9987
11260
|
this.emit({ t: "choice.resolved", sessionId: id, seq: runtime.choiceSeq });
|
|
@@ -10064,51 +11337,89 @@ ${block}` : block);
|
|
|
10064
11337
|
}
|
|
10065
11338
|
writeScreenFile(idOrPrefix, lines) {
|
|
10066
11339
|
const session = this.resolveLive(idOrPrefix);
|
|
10067
|
-
const file =
|
|
10068
|
-
|
|
11340
|
+
const file = path19.join(path19.dirname(this.hooks.endpointFile), `screen-${session.info.id}.txt`);
|
|
11341
|
+
fs19.writeFileSync(file, session.screenText(lines), { mode: 384 });
|
|
10069
11342
|
return file;
|
|
10070
11343
|
}
|
|
10071
11344
|
removeScreenFile(id) {
|
|
10072
11345
|
try {
|
|
10073
|
-
|
|
11346
|
+
fs19.rmSync(path19.join(path19.dirname(this.hooks.endpointFile), `screen-${id}.txt`), { force: true });
|
|
10074
11347
|
} catch {}
|
|
10075
11348
|
}
|
|
10076
11349
|
async stageInboundFile(idOrPrefix, suggestedName, bytes) {
|
|
10077
11350
|
const info = this.resolveLive(idOrPrefix).info;
|
|
10078
|
-
const file =
|
|
10079
|
-
await
|
|
10080
|
-
await
|
|
10081
|
-
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)`);
|
|
10082
11355
|
return file;
|
|
10083
11356
|
}
|
|
10084
11357
|
removeStagedFiles(id) {
|
|
10085
11358
|
try {
|
|
10086
|
-
|
|
11359
|
+
fs19.rmSync(path19.join(MATERIALISED_DIR, id), { recursive: true, force: true });
|
|
10087
11360
|
} catch {}
|
|
10088
11361
|
}
|
|
10089
11362
|
removeClaudeSettings(id) {
|
|
10090
11363
|
try {
|
|
10091
|
-
|
|
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);
|
|
10092
11369
|
} catch {}
|
|
10093
11370
|
}
|
|
10094
11371
|
claudeSettingsPath(id) {
|
|
10095
|
-
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`);
|
|
10096
11405
|
}
|
|
10097
11406
|
geminiSettingsPath() {
|
|
10098
|
-
return
|
|
11407
|
+
return path19.join(path19.dirname(this.hooks.endpointFile), "gemini-defaults.json");
|
|
10099
11408
|
}
|
|
10100
|
-
launch(info, shell, agentCommand) {
|
|
11409
|
+
launch(info, shell, agentCommand, appServerUrl) {
|
|
10101
11410
|
const id = info.id;
|
|
10102
11411
|
const launchToken = randomBytes2(16).toString("hex");
|
|
10103
11412
|
const agentId = agentIdFromCommand(info.agentCommand);
|
|
10104
11413
|
if (agentId === "claude")
|
|
10105
11414
|
this.writeClaudeSettings(id, launchToken);
|
|
11415
|
+
if (agentId !== "omp")
|
|
11416
|
+
this.writeMcpContext(id, launchToken);
|
|
10106
11417
|
if (agentId === "gemini")
|
|
10107
|
-
this.writeGeminiSettings();
|
|
10108
|
-
const
|
|
11418
|
+
this.writeGeminiSettings(id);
|
|
11419
|
+
const codexHome2 = agentId === "codex" ? this.ensureCodexHome() : undefined;
|
|
10109
11420
|
const session = new PtySession(info, {
|
|
10110
11421
|
exe: shell.exe,
|
|
10111
|
-
argv: buildShellArgv(shell, agentCommand),
|
|
11422
|
+
argv: buildShellArgv(shell, withAppServer(agentCommand, appServerUrl)),
|
|
10112
11423
|
cwd: info.cwd,
|
|
10113
11424
|
cols: info.cols,
|
|
10114
11425
|
rows: info.rows,
|
|
@@ -10119,8 +11430,15 @@ ${block}` : block);
|
|
|
10119
11430
|
AXTERM_HOOK_PORT: String(this.hooks.port),
|
|
10120
11431
|
AXTERM_HOOK_TOKEN: this.hooks.host.token,
|
|
10121
11432
|
AXTERM_HOOK_ENDPOINT: this.hooks.endpointFile,
|
|
10122
|
-
...agentId === "gemini" ? {
|
|
10123
|
-
|
|
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 } : {}
|
|
10124
11442
|
}
|
|
10125
11443
|
});
|
|
10126
11444
|
this.agentRuntime.set(id, { agentExited: false, choiceSeq: 0, dialogOpen: false });
|
|
@@ -10242,6 +11560,9 @@ ${block}` : block);
|
|
|
10242
11560
|
this.emit({ t: "chat.message", sessionId: id, role: "assistant", text: result.assistantText });
|
|
10243
11561
|
}
|
|
10244
11562
|
if (payload.event === "before_agent_start") {
|
|
11563
|
+
const runtime2 = this.agentRuntime.get(id);
|
|
11564
|
+
if (runtime2 !== undefined)
|
|
11565
|
+
runtime2.telemetryReported = false;
|
|
10245
11566
|
const prompt = (payload.prompt ?? "").trim();
|
|
10246
11567
|
if (prompt.length > 0 && !this.echoGuard.consume(id, prompt, now)) {
|
|
10247
11568
|
this.emit({ t: "chat.message", sessionId: id, role: "user", text: prompt });
|
|
@@ -10318,14 +11639,10 @@ ${block}` : block);
|
|
|
10318
11639
|
return this.allSessions();
|
|
10319
11640
|
}
|
|
10320
11641
|
allSessions() {
|
|
10321
|
-
|
|
10322
|
-
return [
|
|
10323
|
-
...[...this.live.values()].map((s) => s.info),
|
|
10324
|
-
...this.store.listSessions().filter((s) => !liveIds.has(s.id))
|
|
10325
|
-
];
|
|
11642
|
+
return sessionsByRecency([...this.live.values()].map((s) => s.info), this.store.listSessions());
|
|
10326
11643
|
}
|
|
10327
11644
|
resumeNamed(stored, agent) {
|
|
10328
|
-
if (!
|
|
11645
|
+
if (!fs19.existsSync(stored.cwd))
|
|
10329
11646
|
throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
|
|
10330
11647
|
const agentCommand = buildAgentResumeCommand({
|
|
10331
11648
|
agent,
|
|
@@ -10344,7 +11661,7 @@ ${block}` : block);
|
|
|
10344
11661
|
this.emit({ t: "session.created", info });
|
|
10345
11662
|
return info;
|
|
10346
11663
|
}
|
|
10347
|
-
resume(idOrPrefix) {
|
|
11664
|
+
async resume(idOrPrefix) {
|
|
10348
11665
|
const stored = this.store.findByPrefix(idOrPrefix);
|
|
10349
11666
|
if (!stored)
|
|
10350
11667
|
throw new AxtermError("err.session.unknown", { id: idOrPrefix });
|
|
@@ -10365,7 +11682,7 @@ ${block}` : block);
|
|
|
10365
11682
|
if (agentCommand === undefined) {
|
|
10366
11683
|
throw new AxtermError("err.session.noResumeSupport", { agent: profile.displayName });
|
|
10367
11684
|
}
|
|
10368
|
-
if (!
|
|
11685
|
+
if (!fs19.existsSync(stored.cwd))
|
|
10369
11686
|
throw new AxtermError("err.session.cwdGone", { cwd: stored.cwd });
|
|
10370
11687
|
const info = { ...stored, agentCommand, state: "running" };
|
|
10371
11688
|
delete info.exitCode;
|
|
@@ -10373,7 +11690,8 @@ ${block}` : block);
|
|
|
10373
11690
|
delete info.agentState;
|
|
10374
11691
|
delete info.agentStateSource;
|
|
10375
11692
|
const shell = resolveShell(info.shell);
|
|
10376
|
-
this.
|
|
11693
|
+
const resumedAppServer = agentIdFromCommand(agentCommand) === "codex" ? await this.startCodexAppServer(info.id, this.ensureCodexHome(), info.cwd) : undefined;
|
|
11694
|
+
this.launch(info, shell, agentCommand, resumedAppServer);
|
|
10377
11695
|
this.store.markResumed(info.id, agentCommand, info.ptyPid);
|
|
10378
11696
|
log(`session ${info.shortId} resumed cmd=${agentCommand.join(" ")}`);
|
|
10379
11697
|
this.emit({ t: "session.created", info });
|
|
@@ -10438,11 +11756,86 @@ ${block}` : block);
|
|
|
10438
11756
|
removeScrollback(id) {
|
|
10439
11757
|
for (const file of [`${id}.bin`, `${id}.bin.tmp`]) {
|
|
10440
11758
|
try {
|
|
10441
|
-
|
|
11759
|
+
fs19.rmSync(path19.join(SCROLLBACK_DIR, file), { force: true });
|
|
10442
11760
|
} catch {}
|
|
10443
11761
|
}
|
|
10444
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
|
+
}
|
|
10445
11835
|
shutdownAll() {
|
|
11836
|
+
clearInterval(this.sessionWatch);
|
|
11837
|
+
for (const id of [...this.codexServers.keys()])
|
|
11838
|
+
this.stopCodexAppServer(id);
|
|
10446
11839
|
for (const session of this.live.values()) {
|
|
10447
11840
|
session.saveScrollback();
|
|
10448
11841
|
session.kill();
|
|
@@ -10451,7 +11844,7 @@ ${block}` : block);
|
|
|
10451
11844
|
this.agentRuntime.clear();
|
|
10452
11845
|
}
|
|
10453
11846
|
}
|
|
10454
|
-
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;
|
|
10455
11848
|
var init_session_manager = __esm(() => {
|
|
10456
11849
|
init_src();
|
|
10457
11850
|
init_codex_home();
|
|
@@ -10459,11 +11852,14 @@ var init_session_manager = __esm(() => {
|
|
|
10459
11852
|
init_git();
|
|
10460
11853
|
init_injector();
|
|
10461
11854
|
init_occupancy();
|
|
11855
|
+
init_agent_store();
|
|
11856
|
+
init_codex_app_server();
|
|
11857
|
+
init_pty();
|
|
10462
11858
|
init_paths();
|
|
10463
11859
|
init_pty_session();
|
|
10464
11860
|
init_shell();
|
|
10465
11861
|
init_workspace_local();
|
|
10466
|
-
MATERIALISED_DIR =
|
|
11862
|
+
MATERIALISED_DIR = path19.join(RUNTIME_DIR, "materialised");
|
|
10467
11863
|
PROMPT_OPTIONS = {
|
|
10468
11864
|
hostKey: [{ label: "yes" }, { label: "no" }],
|
|
10469
11865
|
password: [],
|
|
@@ -10612,11 +12008,11 @@ var init_acknowledge = __esm(() => {
|
|
|
10612
12008
|
});
|
|
10613
12009
|
|
|
10614
12010
|
// packages/channel-telegram/src/attach.ts
|
|
10615
|
-
import
|
|
10616
|
-
import
|
|
12011
|
+
import fs20 from "fs";
|
|
12012
|
+
import path20 from "path";
|
|
10617
12013
|
function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELEGRAM_LIMITS) {
|
|
10618
|
-
const name =
|
|
10619
|
-
const bytes =
|
|
12014
|
+
const name = path20.basename(filePath);
|
|
12015
|
+
const bytes = fs20.statSync(filePath).size;
|
|
10620
12016
|
const plan = planAttachment({ name, bytes }, limits);
|
|
10621
12017
|
if (plan.kind === "rejected")
|
|
10622
12018
|
throw new Error(plan.reason);
|
|
@@ -10626,7 +12022,7 @@ function buildAttachmentUpload(filePath, chatId, topicId, caption, limits = TELE
|
|
|
10626
12022
|
form.append("message_thread_id", String(topicId));
|
|
10627
12023
|
if (caption !== undefined && caption.length > 0)
|
|
10628
12024
|
form.append("caption", caption);
|
|
10629
|
-
form.append(field, new Blob([
|
|
12025
|
+
form.append(field, new Blob([fs20.readFileSync(filePath)]), name);
|
|
10630
12026
|
return { method: plan.kind === "photo" ? "sendPhoto" : "sendDocument", field, form };
|
|
10631
12027
|
}
|
|
10632
12028
|
function isPhotoRejection(error) {
|
|
@@ -10634,13 +12030,13 @@ function isPhotoRejection(error) {
|
|
|
10634
12030
|
return /IMAGE_PROCESS_FAILED|PHOTO_INVALID_DIMENSIONS|PHOTO_EXT_INVALID|PHOTO_SAVE_FILE_INVALID/i.test(message);
|
|
10635
12031
|
}
|
|
10636
12032
|
function asDocumentUpload(filePath, chatId, topicId, caption) {
|
|
10637
|
-
const name =
|
|
12033
|
+
const name = path20.basename(filePath);
|
|
10638
12034
|
const form = new FormData;
|
|
10639
12035
|
form.append("chat_id", String(chatId));
|
|
10640
12036
|
form.append("message_thread_id", String(topicId));
|
|
10641
12037
|
if (caption !== undefined && caption.length > 0)
|
|
10642
12038
|
form.append("caption", caption);
|
|
10643
|
-
form.append("document", new Blob([
|
|
12039
|
+
form.append("document", new Blob([fs20.readFileSync(filePath)]), name);
|
|
10644
12040
|
return { method: "sendDocument", field: "document", form };
|
|
10645
12041
|
}
|
|
10646
12042
|
var TELEGRAM_LIMITS;
|
|
@@ -11407,6 +12803,14 @@ class TelegramChannel {
|
|
|
11407
12803
|
await this.sendToSession(sessionId, message);
|
|
11408
12804
|
}
|
|
11409
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
|
+
}
|
|
11410
12814
|
async onAgentStatus(sessionId, state, toolName) {
|
|
11411
12815
|
const binding = this.control.findBindingBySession(sessionId);
|
|
11412
12816
|
if (!binding)
|
|
@@ -11527,7 +12931,7 @@ ${code(prompt.line)}`,
|
|
|
11527
12931
|
});
|
|
11528
12932
|
}
|
|
11529
12933
|
awaitingSecret = new Map;
|
|
11530
|
-
async onChoiceRequest(sessionId, seq, questions, askId,
|
|
12934
|
+
async onChoiceRequest(sessionId, seq, questions, askId, onboarding2 = false) {
|
|
11531
12935
|
const binding = this.control.findBindingBySession(sessionId);
|
|
11532
12936
|
if (!binding || questions.length === 0)
|
|
11533
12937
|
return;
|
|
@@ -11544,7 +12948,7 @@ ${code(prompt.line)}`,
|
|
|
11544
12948
|
answers: [],
|
|
11545
12949
|
picked: [],
|
|
11546
12950
|
...askId !== undefined ? { askId } : {},
|
|
11547
|
-
...
|
|
12951
|
+
...onboarding2 ? { onboarding: true } : {}
|
|
11548
12952
|
});
|
|
11549
12953
|
await this.renderChoiceStep(sessionId);
|
|
11550
12954
|
}
|
|
@@ -11609,7 +13013,7 @@ ${code(prompt.line)}`,
|
|
|
11609
13013
|
}
|
|
11610
13014
|
async onAgentError(sessionId, kind, text, retryMs) {
|
|
11611
13015
|
const reason = clipText(text, AGENT_ERROR_CHARS);
|
|
11612
|
-
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 });
|
|
11613
13017
|
await this.sendToSession(sessionId, message);
|
|
11614
13018
|
}
|
|
11615
13019
|
async onChatMessage(sessionId, role, text) {
|
|
@@ -11773,8 +13177,8 @@ ${code(prompt.line)}`,
|
|
|
11773
13177
|
throw new Error("this Telegram client cannot download files");
|
|
11774
13178
|
const files = [];
|
|
11775
13179
|
for (const item of batch.items) {
|
|
11776
|
-
const
|
|
11777
|
-
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 });
|
|
11778
13182
|
}
|
|
11779
13183
|
await this.control.prompt(batch.sessionId, buildAttachmentPrompt(files, batch.caption));
|
|
11780
13184
|
this.log(`staged ${files.length} inbound file(s) for ${batch.sessionId}`);
|
|
@@ -11800,6 +13204,7 @@ ${code(prompt.line)}`,
|
|
|
11800
13204
|
}
|
|
11801
13205
|
async handleCommand(text, binding, msg) {
|
|
11802
13206
|
const [rawCmd = "", ...args] = text.split(/\s+/);
|
|
13207
|
+
const rawTail = text.slice(rawCmd.length);
|
|
11803
13208
|
const cmd = rawCmd.replace(/@\w+$/, "");
|
|
11804
13209
|
if (binding) {
|
|
11805
13210
|
switch (cmd) {
|
|
@@ -11809,9 +13214,34 @@ ${code(prompt.line)}`,
|
|
|
11809
13214
|
case "/kill":
|
|
11810
13215
|
this.control.kill(binding.sessionId);
|
|
11811
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
|
+
}
|
|
11812
13242
|
case "/resume": {
|
|
11813
13243
|
try {
|
|
11814
|
-
const info = this.control.resume(binding.sessionId);
|
|
13244
|
+
const info = await this.control.resume(binding.sessionId);
|
|
11815
13245
|
await this.sendToSession(binding.sessionId, tHtml("tg.resume.done", { command: info.agentCommand.join(" ") }));
|
|
11816
13246
|
} catch (err) {
|
|
11817
13247
|
await this.sendToSession(binding.sessionId, tHtml("tg.resume.failed", { error: localizeError(err) }));
|
|
@@ -11826,6 +13256,10 @@ ${code(prompt.line)}`,
|
|
|
11826
13256
|
case "/diff":
|
|
11827
13257
|
await this.postDiffSummary(binding.sessionId);
|
|
11828
13258
|
return;
|
|
13259
|
+
case "/quota": {
|
|
13260
|
+
await this.sendToSession(binding.sessionId, this.quotaText());
|
|
13261
|
+
return;
|
|
13262
|
+
}
|
|
11829
13263
|
default:
|
|
11830
13264
|
await this.sendToSession(binding.sessionId, helpText());
|
|
11831
13265
|
return;
|
|
@@ -12024,8 +13458,8 @@ ${code(prompt.line)}`,
|
|
|
12024
13458
|
return;
|
|
12025
13459
|
}
|
|
12026
13460
|
if (flow.askId === undefined) {
|
|
12027
|
-
const
|
|
12028
|
-
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));
|
|
12029
13463
|
}
|
|
12030
13464
|
flow.answers.push({ questionIndex: flow.cursor, selected: [label] });
|
|
12031
13465
|
flow.cursor += 1;
|
|
@@ -12103,7 +13537,7 @@ ${code(prompt.line)}`,
|
|
|
12103
13537
|
}
|
|
12104
13538
|
}
|
|
12105
13539
|
}
|
|
12106
|
-
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(`
|
|
12107
13541
|
`), ALBUM_DEBOUNCE_MS = 2000, UNKNOWN_SIZE = 1, REFUSAL_KEYS, megabytes2 = (bytes) => `${Math.round(bytes / (1024 * 1024))} MB`;
|
|
12108
13542
|
var init_adapter = __esm(() => {
|
|
12109
13543
|
init_src();
|
|
@@ -12170,6 +13604,7 @@ class TelegramService {
|
|
|
12170
13604
|
return {
|
|
12171
13605
|
...telegram?.botToken !== undefined ? { botToken: telegram.botToken } : {},
|
|
12172
13606
|
...telegram?.groupId !== undefined ? { groupId: telegram.groupId } : {},
|
|
13607
|
+
...telegram?.enabled !== undefined ? { enabled: telegram.enabled } : {},
|
|
12173
13608
|
...telegram?.allowUserIds?.[0] !== undefined ? { allowUserId: telegram.allowUserIds[0] } : {},
|
|
12174
13609
|
...machine2 !== undefined ? { machine: machine2 } : {}
|
|
12175
13610
|
};
|
|
@@ -12204,13 +13639,27 @@ class TelegramService {
|
|
|
12204
13639
|
hasToken: s.botToken !== undefined
|
|
12205
13640
|
};
|
|
12206
13641
|
}
|
|
13642
|
+
async discover(botToken) {
|
|
13643
|
+
if (this.channel !== undefined)
|
|
13644
|
+
throw new AxtermError("err.telegram.discoverWhileRunning");
|
|
13645
|
+
const token = botToken ?? this.readSettings().botToken;
|
|
13646
|
+
if (!token)
|
|
13647
|
+
throw new AxtermError("err.telegram.noToken");
|
|
13648
|
+
const api = new FetchTelegramApi(token);
|
|
13649
|
+
const updates = await api.call("getUpdates", { timeout: 0 });
|
|
13650
|
+
return discoverTelegramTargets(updates);
|
|
13651
|
+
}
|
|
12207
13652
|
start() {
|
|
12208
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
|
+
}
|
|
12209
13659
|
if (!s.botToken || s.groupId === undefined || s.allowUserId === undefined) {
|
|
12210
13660
|
log("telegram: not configured, channel not started");
|
|
12211
13661
|
return false;
|
|
12212
13662
|
}
|
|
12213
|
-
this.stop();
|
|
12214
13663
|
const api = new FetchTelegramApi(s.botToken);
|
|
12215
13664
|
const config = {
|
|
12216
13665
|
groupId: s.groupId,
|
|
@@ -12252,6 +13701,7 @@ class TelegramService {
|
|
|
12252
13701
|
answerShellPrompt: (id, seq, text) => this.manager.answerShellPrompt(id, seq, text),
|
|
12253
13702
|
answerOnboarding: (id, seq, optionIndex) => this.manager.answerOnboardingChoice(id, seq, optionIndex),
|
|
12254
13703
|
diff: (id) => this.manager.sessionDiff(id),
|
|
13704
|
+
quota: () => this.manager.quotaReport(),
|
|
12255
13705
|
saveBinding: (b) => this.store.saveBinding(b),
|
|
12256
13706
|
findBindingBySession: (id) => this.store.findBindingBySession(id),
|
|
12257
13707
|
findBindingByTopic: (chatId, topicId) => this.store.findBindingByTopic(chatId, topicId),
|
|
@@ -12358,8 +13808,8 @@ var init_telegram_service = __esm(() => {
|
|
|
12358
13808
|
// packages/daemon/src/index.ts
|
|
12359
13809
|
var exports_src = {};
|
|
12360
13810
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
12361
|
-
import
|
|
12362
|
-
import
|
|
13811
|
+
import fs21 from "fs";
|
|
13812
|
+
import net4 from "net";
|
|
12363
13813
|
function isPidAlive(pid) {
|
|
12364
13814
|
try {
|
|
12365
13815
|
process.kill(pid, 0);
|
|
@@ -12370,9 +13820,9 @@ function isPidAlive(pid) {
|
|
|
12370
13820
|
}
|
|
12371
13821
|
async function main() {
|
|
12372
13822
|
ensureDataDirs();
|
|
12373
|
-
if (
|
|
13823
|
+
if (fs21.existsSync(RUNTIME_FILE2)) {
|
|
12374
13824
|
try {
|
|
12375
|
-
const existing = JSON.parse(
|
|
13825
|
+
const existing = JSON.parse(fs21.readFileSync(RUNTIME_FILE2, "utf8"));
|
|
12376
13826
|
if (existing.pid !== process.pid && isPidAlive(existing.pid)) {
|
|
12377
13827
|
log(`daemon already running (pid ${existing.pid}); exiting`);
|
|
12378
13828
|
process.exit(0);
|
|
@@ -12395,7 +13845,7 @@ async function main() {
|
|
|
12395
13845
|
version: productVersion(),
|
|
12396
13846
|
startedAt: Date.now()
|
|
12397
13847
|
};
|
|
12398
|
-
|
|
13848
|
+
fs21.writeFileSync(RUNTIME_FILE2, JSON.stringify(runtime, null, 2), { mode: 384 });
|
|
12399
13849
|
restrictFile(RUNTIME_FILE2);
|
|
12400
13850
|
const config = new ConfigStore(defaultConfigStorePaths(CONFIG_DIR));
|
|
12401
13851
|
if (config.loadError !== undefined)
|
|
@@ -12413,7 +13863,7 @@ async function main() {
|
|
|
12413
13863
|
port: hookPort,
|
|
12414
13864
|
endpointFile: HOOK_ENDPOINT_FILE,
|
|
12415
13865
|
extensionPath
|
|
12416
|
-
});
|
|
13866
|
+
}, () => config.resolved().defaults ?? {});
|
|
12417
13867
|
const telegram = new TelegramService(config, store, manager);
|
|
12418
13868
|
hookHost.onAttachment(async (sessionId, filePath, caption) => {
|
|
12419
13869
|
await telegram.sendAttachment(sessionId, filePath, caption);
|
|
@@ -12447,9 +13897,9 @@ async function main() {
|
|
|
12447
13897
|
hookHost.stop();
|
|
12448
13898
|
store.close();
|
|
12449
13899
|
try {
|
|
12450
|
-
const current = JSON.parse(
|
|
13900
|
+
const current = JSON.parse(fs21.readFileSync(RUNTIME_FILE2, "utf8"));
|
|
12451
13901
|
if (current.pid === process.pid)
|
|
12452
|
-
|
|
13902
|
+
fs21.rmSync(RUNTIME_FILE2);
|
|
12453
13903
|
} catch {}
|
|
12454
13904
|
process.exit(0);
|
|
12455
13905
|
};
|
|
@@ -12482,14 +13932,14 @@ var init_src3 = __esm(() => {
|
|
|
12482
13932
|
init_session_manager();
|
|
12483
13933
|
init_telegram_service();
|
|
12484
13934
|
init_store();
|
|
12485
|
-
|
|
13935
|
+
net4.setDefaultAutoSelectFamilyAttemptTimeout(3000);
|
|
12486
13936
|
main();
|
|
12487
13937
|
});
|
|
12488
13938
|
|
|
12489
13939
|
// packages/cli/src/index.ts
|
|
12490
13940
|
init_src();
|
|
12491
|
-
import
|
|
12492
|
-
import
|
|
13941
|
+
import fs22 from "fs";
|
|
13942
|
+
import path21 from "path";
|
|
12493
13943
|
|
|
12494
13944
|
// packages/cli/src/attach.ts
|
|
12495
13945
|
import fs from "fs";
|
|
@@ -13129,18 +14579,289 @@ async function relayHookEvent(agent) {
|
|
|
13129
14579
|
} catch {}
|
|
13130
14580
|
}
|
|
13131
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
|
+
|
|
13132
14854
|
// packages/cli/src/update.ts
|
|
13133
14855
|
init_src();
|
|
13134
14856
|
import { spawnSync } from "child_process";
|
|
13135
14857
|
import { createHash as createHash2 } from "crypto";
|
|
13136
|
-
import
|
|
13137
|
-
import
|
|
13138
|
-
import path4 from "path";
|
|
14858
|
+
import fs4 from "fs";
|
|
14859
|
+
import path5 from "path";
|
|
13139
14860
|
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
13140
14861
|
// packages/cli/package.json
|
|
13141
14862
|
var package_default = {
|
|
13142
14863
|
name: "@layorixinc/axterm",
|
|
13143
|
-
version: "0.
|
|
14864
|
+
version: "0.4.0",
|
|
13144
14865
|
type: "module",
|
|
13145
14866
|
bin: {
|
|
13146
14867
|
axterm: "dist/cli.js"
|
|
@@ -13214,7 +14935,7 @@ function assetName(platform, arch, isMusl) {
|
|
|
13214
14935
|
function usesMusl() {
|
|
13215
14936
|
if (process.platform !== "linux")
|
|
13216
14937
|
return false;
|
|
13217
|
-
if (
|
|
14938
|
+
if (fs4.existsSync("/etc/alpine-release"))
|
|
13218
14939
|
return true;
|
|
13219
14940
|
const ldd = spawnSync("ldd", ["--version"], { encoding: "utf8" });
|
|
13220
14941
|
return `${ldd.stdout ?? ""}${ldd.stderr ?? ""}`.toLowerCase().includes("musl");
|
|
@@ -13228,7 +14949,7 @@ function firstLine(command, args) {
|
|
|
13228
14949
|
function detectOrigin(execPath) {
|
|
13229
14950
|
const resolved = (() => {
|
|
13230
14951
|
try {
|
|
13231
|
-
return
|
|
14952
|
+
return fs4.realpathSync(execPath);
|
|
13232
14953
|
} catch {
|
|
13233
14954
|
return execPath;
|
|
13234
14955
|
}
|
|
@@ -13240,7 +14961,7 @@ function detectOrigin(execPath) {
|
|
|
13240
14961
|
brewPrefix: firstLine("brew", ["--prefix"]),
|
|
13241
14962
|
misePaths: misePaths === undefined ? [] : [misePaths],
|
|
13242
14963
|
bunBin: firstLine("bun", ["pm", "bin", "-g"]),
|
|
13243
|
-
npmBin:
|
|
14964
|
+
npmBin: npmGlobalBin()
|
|
13244
14965
|
});
|
|
13245
14966
|
}
|
|
13246
14967
|
var MANAGER_COMMAND = {
|
|
@@ -13249,8 +14970,11 @@ var MANAGER_COMMAND = {
|
|
|
13249
14970
|
bun: (v) => `bun install -g ${PACKAGE}@${v}`,
|
|
13250
14971
|
npm: (v) => `npm install -g ${PACKAGE}@${v}`
|
|
13251
14972
|
};
|
|
13252
|
-
function
|
|
13253
|
-
|
|
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");
|
|
13254
14978
|
}
|
|
13255
14979
|
async function download(url, into) {
|
|
13256
14980
|
const response = await fetch(url, { signal: AbortSignal.timeout(600000) });
|
|
@@ -13267,7 +14991,7 @@ function checksumFor(sums, asset) {
|
|
|
13267
14991
|
return;
|
|
13268
14992
|
}
|
|
13269
14993
|
function sha256(file) {
|
|
13270
|
-
return createHash2("sha256").update(
|
|
14994
|
+
return createHash2("sha256").update(fs4.readFileSync(file)).digest("hex");
|
|
13271
14995
|
}
|
|
13272
14996
|
function ask2(question) {
|
|
13273
14997
|
if (stdin2.isTTY !== true)
|
|
@@ -13296,21 +15020,26 @@ async function runUpdate(client, current, options) {
|
|
|
13296
15020
|
console.log(t("cli.update.available", { latest: latest.version, current }));
|
|
13297
15021
|
if (options.checkOnly)
|
|
13298
15022
|
return;
|
|
13299
|
-
|
|
15023
|
+
const lane = classifyRuntimeLane(Bun.main);
|
|
15024
|
+
if (lane === "checkout") {
|
|
13300
15025
|
console.log(t("cli.update.notBinary"));
|
|
13301
15026
|
return;
|
|
13302
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
|
+
}
|
|
13303
15033
|
const origin = detectOrigin(process.execPath);
|
|
13304
15034
|
if (origin !== "binary") {
|
|
13305
|
-
const command = MANAGER_COMMAND[origin](latest.version);
|
|
13306
15035
|
console.log(t("cli.update.viaManager", { manager: origin }));
|
|
13307
|
-
console.log(t("cli.update.managerHint", { command }));
|
|
15036
|
+
console.log(t("cli.update.managerHint", { command: MANAGER_COMMAND[origin](latest.version) }));
|
|
13308
15037
|
return;
|
|
13309
15038
|
}
|
|
13310
15039
|
const asset = assetName(process.platform, process.arch, usesMusl());
|
|
13311
15040
|
const base = `${RELEASES}/v${latest.version}`;
|
|
13312
|
-
const staging =
|
|
13313
|
-
const downloaded =
|
|
15041
|
+
const staging = fs4.mkdtempSync(`${process.execPath}.update-`);
|
|
15042
|
+
const downloaded = path5.join(staging, asset);
|
|
13314
15043
|
try {
|
|
13315
15044
|
console.log(t("cli.update.downloading", { asset }));
|
|
13316
15045
|
await download(`${base}/${asset}`, downloaded);
|
|
@@ -13323,7 +15052,7 @@ async function runUpdate(client, current, options) {
|
|
|
13323
15052
|
return;
|
|
13324
15053
|
}
|
|
13325
15054
|
if (process.platform !== "win32")
|
|
13326
|
-
|
|
15055
|
+
fs4.chmodSync(downloaded, 493);
|
|
13327
15056
|
const reported = spawnSync(downloaded, ["--version"], { encoding: "utf8" }).stdout?.trim();
|
|
13328
15057
|
if (reported !== latest.version) {
|
|
13329
15058
|
console.error(t("cli.update.wrongVersion", { got: reported ?? "nothing", want: latest.version }));
|
|
@@ -13342,6 +15071,9 @@ async function runUpdate(client, current, options) {
|
|
|
13342
15071
|
return;
|
|
13343
15072
|
}
|
|
13344
15073
|
}
|
|
15074
|
+
console.log(t("cli.update.swapping"));
|
|
15075
|
+
applySwap(process.execPath, downloaded);
|
|
15076
|
+
console.log(t("cli.update.done", { version: latest.version }));
|
|
13345
15077
|
if (plan.stopDaemon) {
|
|
13346
15078
|
console.log(t("cli.update.stopping"));
|
|
13347
15079
|
await client.call("daemon.stop").catch(() => {
|
|
@@ -13350,9 +15082,6 @@ async function runUpdate(client, current, options) {
|
|
|
13350
15082
|
client.close();
|
|
13351
15083
|
await Bun.sleep(1500);
|
|
13352
15084
|
}
|
|
13353
|
-
console.log(t("cli.update.swapping"));
|
|
13354
|
-
applySwap(process.execPath, downloaded);
|
|
13355
|
-
console.log(t("cli.update.done", { version: latest.version }));
|
|
13356
15085
|
if (plan.stale > 0)
|
|
13357
15086
|
console.log(t("cli.update.staleDaemon", { count: plan.stale }));
|
|
13358
15087
|
for (const id of plan.resume) {
|
|
@@ -13363,7 +15092,7 @@ async function runUpdate(client, current, options) {
|
|
|
13363
15092
|
console.log(t("cli.update.restoreFailed", { id, reason: (result.stderr ?? "").trim() || "unknown" }));
|
|
13364
15093
|
}
|
|
13365
15094
|
} finally {
|
|
13366
|
-
|
|
15095
|
+
fs4.rmSync(staging, { recursive: true, force: true });
|
|
13367
15096
|
}
|
|
13368
15097
|
}
|
|
13369
15098
|
function applySwap(target, downloaded) {
|
|
@@ -13372,16 +15101,16 @@ function applySwap(target, downloaded) {
|
|
|
13372
15101
|
try {
|
|
13373
15102
|
for (const step of steps) {
|
|
13374
15103
|
if (step.op === "chmod")
|
|
13375
|
-
|
|
15104
|
+
fs4.chmodSync(step.from, 493);
|
|
13376
15105
|
else if (step.op === "backup") {
|
|
13377
|
-
|
|
15106
|
+
fs4.renameSync(step.from, step.to);
|
|
13378
15107
|
backup = step.to;
|
|
13379
15108
|
} else
|
|
13380
|
-
|
|
15109
|
+
fs4.renameSync(step.from, step.to);
|
|
13381
15110
|
}
|
|
13382
15111
|
} catch (err) {
|
|
13383
|
-
if (backup !== undefined && !
|
|
13384
|
-
|
|
15112
|
+
if (backup !== undefined && !fs4.existsSync(target))
|
|
15113
|
+
fs4.renameSync(backup, target);
|
|
13385
15114
|
throw err;
|
|
13386
15115
|
}
|
|
13387
15116
|
}
|
|
@@ -13454,7 +15183,7 @@ function printSessionTable(sessions) {
|
|
|
13454
15183
|
console.log(row.map((cell, col) => cell.padEnd(widths[col])).join(" "));
|
|
13455
15184
|
}
|
|
13456
15185
|
}
|
|
13457
|
-
var realPath = (target) =>
|
|
15186
|
+
var realPath = (target) => fs22.realpathSync.native(target);
|
|
13458
15187
|
function scopedHere(sessions) {
|
|
13459
15188
|
return sessionsInScope(sessions, process.cwd(), realPath);
|
|
13460
15189
|
}
|
|
@@ -13464,22 +15193,10 @@ async function exitedIds(client, all) {
|
|
|
13464
15193
|
return scoped.filter((s) => s.state === "exited").map((s) => s.id);
|
|
13465
15194
|
}
|
|
13466
15195
|
var help = () => t("cli.help");
|
|
13467
|
-
var KEY_SEQUENCES = {
|
|
13468
|
-
approve: "\r",
|
|
13469
|
-
deny: "\x1B[B\r",
|
|
13470
|
-
enter: "\r",
|
|
13471
|
-
esc: "\x1B",
|
|
13472
|
-
up: "\x1B[A",
|
|
13473
|
-
down: "\x1B[B",
|
|
13474
|
-
left: "\x1B[D",
|
|
13475
|
-
right: "\x1B[C",
|
|
13476
|
-
tab: "\t",
|
|
13477
|
-
space: " "
|
|
13478
|
-
};
|
|
13479
15196
|
function readDotEnv(file = ".env") {
|
|
13480
15197
|
const out = {};
|
|
13481
15198
|
try {
|
|
13482
|
-
for (const line of
|
|
15199
|
+
for (const line of fs22.readFileSync(file, "utf8").split(/\r?\n/)) {
|
|
13483
15200
|
const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
13484
15201
|
if (m)
|
|
13485
15202
|
out[m[1]] = m[2].trim();
|
|
@@ -13490,7 +15207,7 @@ function readDotEnv(file = ".env") {
|
|
|
13490
15207
|
function configuredLocale() {
|
|
13491
15208
|
try {
|
|
13492
15209
|
const file = resolvePaths(currentPathEnv()).configFile;
|
|
13493
|
-
const raw = JSON.parse(
|
|
15210
|
+
const raw = JSON.parse(fs22.readFileSync(file, "utf8"));
|
|
13494
15211
|
return typeof raw.locale === "string" ? raw.locale : undefined;
|
|
13495
15212
|
} catch {
|
|
13496
15213
|
return;
|
|
@@ -13507,6 +15224,10 @@ async function main2() {
|
|
|
13507
15224
|
await relayHookEvent(argv[1]);
|
|
13508
15225
|
return;
|
|
13509
15226
|
}
|
|
15227
|
+
if (isMcpInvocation(argv)) {
|
|
15228
|
+
runMcpServer();
|
|
15229
|
+
return;
|
|
15230
|
+
}
|
|
13510
15231
|
const [command, ...rest] = argv;
|
|
13511
15232
|
setLocale(resolveLocale({ configured: configuredLocale(), env: process.env }));
|
|
13512
15233
|
const { flags, positional, passthrough } = parseArgs(rest);
|
|
@@ -13525,9 +15246,9 @@ async function main2() {
|
|
|
13525
15246
|
if (health !== undefined && shouldOfferSetup(health, command, flags)) {
|
|
13526
15247
|
const header2 = health.status === "invalid" ? "wizard.invalid" : health.status === "incomplete" ? "wizard.incomplete" : "wizard.needed";
|
|
13527
15248
|
console.log(t(header2));
|
|
13528
|
-
const
|
|
13529
|
-
if (
|
|
13530
|
-
console.log(
|
|
15249
|
+
const detail2 = describeHealth(health);
|
|
15250
|
+
if (detail2.length > 0)
|
|
15251
|
+
console.log(detail2);
|
|
13531
15252
|
console.log(t("wizard.hint"));
|
|
13532
15253
|
await runSetup(client, await client.call("config.raw"));
|
|
13533
15254
|
}
|
|
@@ -13540,6 +15261,7 @@ async function main2() {
|
|
|
13540
15261
|
name: flags.get("name"),
|
|
13541
15262
|
shell: flags.get("shell"),
|
|
13542
15263
|
agent: flags.get("agent"),
|
|
15264
|
+
autoApprove: typeof flags.get("auto-approve") === "boolean" ? flags.get("auto-approve") : undefined,
|
|
13543
15265
|
agentArgs: passthrough,
|
|
13544
15266
|
worktreeBranch: flags.get("worktree"),
|
|
13545
15267
|
worktreeBase: flags.get("base"),
|
|
@@ -13568,7 +15290,16 @@ async function main2() {
|
|
|
13568
15290
|
break;
|
|
13569
15291
|
}
|
|
13570
15292
|
case "attach": {
|
|
13571
|
-
|
|
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
|
+
}
|
|
13572
15303
|
if (!id)
|
|
13573
15304
|
throw new Error(t("cli.usage.attach"));
|
|
13574
15305
|
await attachSession(client, id, flags.get("host-status") === "orca");
|
|
@@ -13608,8 +15339,8 @@ async function main2() {
|
|
|
13608
15339
|
case "key": {
|
|
13609
15340
|
const [id, name] = positional;
|
|
13610
15341
|
if (!id || !name)
|
|
13611
|
-
throw new Error(t("cli.usage.key", { keys:
|
|
13612
|
-
const seq =
|
|
15342
|
+
throw new Error(t("cli.usage.key", { keys: KEY_NAMES.join("|") }));
|
|
15343
|
+
const seq = keySequence(name);
|
|
13613
15344
|
if (seq === undefined)
|
|
13614
15345
|
throw new Error(t("cli.key.unknown", { name }));
|
|
13615
15346
|
await client.call("terminal.write", { id, dataB64: Buffer.from(seq, "utf8").toString("base64") });
|
|
@@ -13620,8 +15351,8 @@ async function main2() {
|
|
|
13620
15351
|
const [id, indexArg] = positional;
|
|
13621
15352
|
if (!id || indexArg === undefined)
|
|
13622
15353
|
throw new Error(t("cli.usage.choose"));
|
|
13623
|
-
const
|
|
13624
|
-
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") });
|
|
13625
15356
|
console.log(t("cli.session.selected", { index: indexArg }));
|
|
13626
15357
|
break;
|
|
13627
15358
|
}
|
|
@@ -13656,9 +15387,10 @@ async function main2() {
|
|
|
13656
15387
|
console.log(value === undefined ? "" : String(value));
|
|
13657
15388
|
} else if (sub === "set") {
|
|
13658
15389
|
const [, dottedPath, ...rest2] = positional;
|
|
13659
|
-
|
|
15390
|
+
const value = passthrough.length > 0 ? passthrough : rest2.join(" ");
|
|
15391
|
+
if (!dottedPath || value.length === 0)
|
|
13660
15392
|
throw new Error(t("cli.usage.configSet"));
|
|
13661
|
-
await client.call("config.set", { path: dottedPath, value
|
|
15393
|
+
await client.call("config.set", { path: dottedPath, value });
|
|
13662
15394
|
console.log(t("cli.config.set", { path: dottedPath }));
|
|
13663
15395
|
} else if (sub === "unset") {
|
|
13664
15396
|
const dottedPath = positional[1];
|
|
@@ -13697,8 +15429,8 @@ async function main2() {
|
|
|
13697
15429
|
case "project": {
|
|
13698
15430
|
const sub = positional[0] ?? "list";
|
|
13699
15431
|
if (sub === "add") {
|
|
13700
|
-
const dir = positional[1] ?
|
|
13701
|
-
const alias = positional[2] ??
|
|
15432
|
+
const dir = positional[1] ? path21.resolve(positional[1]) : process.cwd();
|
|
15433
|
+
const alias = positional[2] ?? path21.basename(dir);
|
|
13702
15434
|
const projects = await client.call("project.add", {
|
|
13703
15435
|
alias,
|
|
13704
15436
|
path: dir
|
|
@@ -13757,6 +15489,24 @@ async function main2() {
|
|
|
13757
15489
|
}
|
|
13758
15490
|
break;
|
|
13759
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
|
+
}
|
|
13760
15510
|
case "worktree": {
|
|
13761
15511
|
const sub = positional[0] ?? "list";
|
|
13762
15512
|
if (sub === "list") {
|
|
@@ -13913,6 +15663,28 @@ async function main2() {
|
|
|
13913
15663
|
console.log(JSON.stringify(await client.call("telegram.doctor"), null, 2));
|
|
13914
15664
|
} else if (sub === "status") {
|
|
13915
15665
|
console.log(JSON.stringify(await client.call("telegram.status"), null, 2));
|
|
15666
|
+
} else if (sub === "discover") {
|
|
15667
|
+
const token = flags.get("token");
|
|
15668
|
+
const found = await client.call("telegram.discover", {
|
|
15669
|
+
...token !== undefined ? { botToken: token } : {}
|
|
15670
|
+
});
|
|
15671
|
+
if (found.chats.length > 0) {
|
|
15672
|
+
console.log(t("cli.telegram.discoverChats"));
|
|
15673
|
+
for (const chat of found.chats) {
|
|
15674
|
+
const marks = [chat.type, chat.isForum ? undefined : t("cli.telegram.topicsOff")].filter((m) => m !== undefined).join(" \u2014 ");
|
|
15675
|
+
console.log(` ${String(chat.id).padEnd(16)} ${(chat.title ?? "").padEnd(24)} ${marks}`);
|
|
15676
|
+
}
|
|
15677
|
+
} else {
|
|
15678
|
+
console.log(t("cli.telegram.discoverNone"));
|
|
15679
|
+
console.log(t("cli.telegram.discoverHint"));
|
|
15680
|
+
}
|
|
15681
|
+
if (found.users.length > 0) {
|
|
15682
|
+
console.log(t("cli.telegram.discoverUsers"));
|
|
15683
|
+
for (const user of found.users) {
|
|
15684
|
+
const label = [user.username !== undefined ? `@${user.username}` : undefined, user.name].filter((part) => part !== undefined).join(" ");
|
|
15685
|
+
console.log(` ${String(user.id).padEnd(16)} ${label}`);
|
|
15686
|
+
}
|
|
15687
|
+
}
|
|
13916
15688
|
} else {
|
|
13917
15689
|
throw new Error(t("cli.usage.telegram"));
|
|
13918
15690
|
}
|