@hydra-acp/cli 0.1.23 → 0.1.24
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 +495 -70
- package/dist/index.d.ts +8 -1
- package/dist/index.js +134 -19
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4067,7 +4067,7 @@ var init_session_row = __esm({
|
|
|
4067
4067
|
|
|
4068
4068
|
// src/cli/commands/sessions.ts
|
|
4069
4069
|
import * as fs17 from "fs/promises";
|
|
4070
|
-
import * as
|
|
4070
|
+
import * as path11 from "path";
|
|
4071
4071
|
async function runSessionsList(opts = {}) {
|
|
4072
4072
|
const config = await loadConfig();
|
|
4073
4073
|
const serviceToken = await loadServiceToken();
|
|
@@ -4195,7 +4195,7 @@ async function runSessionsExport(id, outPath) {
|
|
|
4195
4195
|
return;
|
|
4196
4196
|
}
|
|
4197
4197
|
const resolved = outPath === "." ? deriveFilenameFrom(response, id) : outPath;
|
|
4198
|
-
await fs17.mkdir(
|
|
4198
|
+
await fs17.mkdir(path11.dirname(path11.resolve(resolved)), { recursive: true });
|
|
4199
4199
|
await fs17.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
|
|
4200
4200
|
process.stdout.write(`Wrote ${resolved}
|
|
4201
4201
|
`);
|
|
@@ -4214,7 +4214,7 @@ async function runSessionsTranscript(idOrFile, outPath) {
|
|
|
4214
4214
|
const bundle = decodeBundleOrExit(localFile.raw);
|
|
4215
4215
|
body = bundleToMarkdown(bundle);
|
|
4216
4216
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4217
|
-
defaultName = `${
|
|
4217
|
+
defaultName = `${path11.basename(idOrFile, path11.extname(idOrFile))}-${stamp}.md`;
|
|
4218
4218
|
} else {
|
|
4219
4219
|
const config = await loadConfig();
|
|
4220
4220
|
const serviceToken = await loadServiceToken();
|
|
@@ -4243,7 +4243,7 @@ async function runSessionsTranscript(idOrFile, outPath) {
|
|
|
4243
4243
|
return;
|
|
4244
4244
|
}
|
|
4245
4245
|
const resolved = outPath === "." ? defaultName : outPath;
|
|
4246
|
-
await fs17.mkdir(
|
|
4246
|
+
await fs17.mkdir(path11.dirname(path11.resolve(resolved)), { recursive: true });
|
|
4247
4247
|
await fs17.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
|
|
4248
4248
|
process.stdout.write(`Wrote ${resolved}
|
|
4249
4249
|
`);
|
|
@@ -4284,7 +4284,7 @@ async function runSessionsImport(file, opts = {}) {
|
|
|
4284
4284
|
}
|
|
4285
4285
|
let cwdOverride;
|
|
4286
4286
|
if (opts.cwd !== void 0) {
|
|
4287
|
-
const resolved =
|
|
4287
|
+
const resolved = path11.resolve(opts.cwd);
|
|
4288
4288
|
try {
|
|
4289
4289
|
const stat4 = await fs17.stat(resolved);
|
|
4290
4290
|
if (!stat4.isDirectory()) {
|
|
@@ -4955,6 +4955,10 @@ async function pickSession(term, opts) {
|
|
|
4955
4955
|
const indicatorRow = () => startRow + 3 + viewportSize;
|
|
4956
4956
|
const sessionRow = (sessionIdx) => startRow + 3 + (sessionIdx - scrollOffset);
|
|
4957
4957
|
const renderFromScratch = () => {
|
|
4958
|
+
if (mode === "help") {
|
|
4959
|
+
renderHelp();
|
|
4960
|
+
return;
|
|
4961
|
+
}
|
|
4958
4962
|
computeLayout();
|
|
4959
4963
|
adjustScroll();
|
|
4960
4964
|
startRow = 1;
|
|
@@ -4969,6 +4973,21 @@ async function pickSession(term, opts) {
|
|
|
4969
4973
|
paintIndicator();
|
|
4970
4974
|
term("\n");
|
|
4971
4975
|
};
|
|
4976
|
+
const renderHelp = () => {
|
|
4977
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
4978
|
+
term.brightWhite.bold.noFormat(" Picker hotkeys")("\n\n");
|
|
4979
|
+
for (const entry of HELP_ENTRIES) {
|
|
4980
|
+
if (entry === null) {
|
|
4981
|
+
term("\n");
|
|
4982
|
+
continue;
|
|
4983
|
+
}
|
|
4984
|
+
const [keys, desc] = entry;
|
|
4985
|
+
term.brightCyan.noFormat(` ${keys.padEnd(HELP_KEYS_WIDTH)}`);
|
|
4986
|
+
term.noFormat(desc)("\n");
|
|
4987
|
+
}
|
|
4988
|
+
term("\n");
|
|
4989
|
+
term.dim.noFormat(" press any key to dismiss")("\n");
|
|
4990
|
+
};
|
|
4972
4991
|
const repaintNewItem = () => {
|
|
4973
4992
|
term.moveTo(1, startRow).eraseLineAfter();
|
|
4974
4993
|
paintNewItem();
|
|
@@ -5099,6 +5118,16 @@ async function pickSession(term, opts) {
|
|
|
5099
5118
|
if (mode === "busy") {
|
|
5100
5119
|
return;
|
|
5101
5120
|
}
|
|
5121
|
+
if (mode === "help") {
|
|
5122
|
+
if (name === "CTRL_C") {
|
|
5123
|
+
cleanup();
|
|
5124
|
+
resolve5({ kind: "abort" });
|
|
5125
|
+
return;
|
|
5126
|
+
}
|
|
5127
|
+
mode = "normal";
|
|
5128
|
+
renderFromScratch();
|
|
5129
|
+
return;
|
|
5130
|
+
}
|
|
5102
5131
|
if (mode === "confirm-kill" || mode === "confirm-delete") {
|
|
5103
5132
|
if (data?.isCharacter && (name === "y" || name === "Y")) {
|
|
5104
5133
|
const kind = mode === "confirm-kill" ? "kill" : "delete";
|
|
@@ -5114,6 +5143,11 @@ async function pickSession(term, opts) {
|
|
|
5114
5143
|
return;
|
|
5115
5144
|
}
|
|
5116
5145
|
clearTransient();
|
|
5146
|
+
if (!searchActive && data?.isCharacter && name === "?") {
|
|
5147
|
+
mode = "help";
|
|
5148
|
+
renderHelp();
|
|
5149
|
+
return;
|
|
5150
|
+
}
|
|
5117
5151
|
if (searchActive) {
|
|
5118
5152
|
if (data?.isCharacter) {
|
|
5119
5153
|
searchTerm += name;
|
|
@@ -5262,6 +5296,7 @@ async function pickSession(term, opts) {
|
|
|
5262
5296
|
}
|
|
5263
5297
|
case "ESCAPE":
|
|
5264
5298
|
case "CTRL_C":
|
|
5299
|
+
case "CTRL_D":
|
|
5265
5300
|
cleanup();
|
|
5266
5301
|
resolve5({ kind: "abort" });
|
|
5267
5302
|
return;
|
|
@@ -5303,7 +5338,7 @@ function matchesSearch(s, term) {
|
|
|
5303
5338
|
}
|
|
5304
5339
|
return false;
|
|
5305
5340
|
}
|
|
5306
|
-
var ROW_PREFIX_WIDTH;
|
|
5341
|
+
var ROW_PREFIX_WIDTH, HELP_KEYS_WIDTH, HELP_ENTRIES;
|
|
5307
5342
|
var init_picker = __esm({
|
|
5308
5343
|
"src/tui/picker.ts"() {
|
|
5309
5344
|
"use strict";
|
|
@@ -5312,13 +5347,31 @@ var init_picker = __esm({
|
|
|
5312
5347
|
init_session();
|
|
5313
5348
|
init_discovery();
|
|
5314
5349
|
ROW_PREFIX_WIDTH = 2;
|
|
5350
|
+
HELP_KEYS_WIDTH = 20;
|
|
5351
|
+
HELP_ENTRIES = [
|
|
5352
|
+
["\u2191 / \u2193 or n / p", "navigate"],
|
|
5353
|
+
["PgUp / PgDn", "page up / page down"],
|
|
5354
|
+
["Home / End", "first / last"],
|
|
5355
|
+
["Enter", "open selected session (or create new)"],
|
|
5356
|
+
null,
|
|
5357
|
+
["/", "search sessions"],
|
|
5358
|
+
["o", "toggle cwd-only filter"],
|
|
5359
|
+
["r", "refresh from daemon"],
|
|
5360
|
+
null,
|
|
5361
|
+
["k", "kill the selected live session"],
|
|
5362
|
+
["d", "delete the selected cold session"],
|
|
5363
|
+
null,
|
|
5364
|
+
["c", "create new session"],
|
|
5365
|
+
["?", "toggle this help"],
|
|
5366
|
+
["q / Esc / ^C / ^D", "quit picker (detach)"]
|
|
5367
|
+
];
|
|
5315
5368
|
}
|
|
5316
5369
|
});
|
|
5317
5370
|
|
|
5318
5371
|
// src/tui/attachments.ts
|
|
5319
|
-
import
|
|
5372
|
+
import path12 from "path";
|
|
5320
5373
|
function mimeFromExtension(p) {
|
|
5321
|
-
return EXTENSION_TO_MIME[
|
|
5374
|
+
return EXTENSION_TO_MIME[path12.extname(p).toLowerCase()] ?? null;
|
|
5322
5375
|
}
|
|
5323
5376
|
function isSupportedImagePath(p) {
|
|
5324
5377
|
return mimeFromExtension(p) !== null;
|
|
@@ -5894,6 +5947,12 @@ function mapKeyName(name) {
|
|
|
5894
5947
|
case "ALT_ENTER":
|
|
5895
5948
|
case "META_ENTER":
|
|
5896
5949
|
return "alt-enter";
|
|
5950
|
+
case "ALT_B":
|
|
5951
|
+
case "META_B":
|
|
5952
|
+
return "alt-b";
|
|
5953
|
+
case "ALT_F":
|
|
5954
|
+
case "META_F":
|
|
5955
|
+
return "alt-f";
|
|
5897
5956
|
case "CTRL_T":
|
|
5898
5957
|
return "ctrl-t";
|
|
5899
5958
|
case "SHIFT_TAB":
|
|
@@ -5928,6 +5987,8 @@ function mapKeyName(name) {
|
|
|
5928
5987
|
return "ctrl-e";
|
|
5929
5988
|
case "CTRL_F":
|
|
5930
5989
|
return "ctrl-f";
|
|
5990
|
+
case "CTRL_G":
|
|
5991
|
+
return "ctrl-g";
|
|
5931
5992
|
case "CTRL_K":
|
|
5932
5993
|
return "ctrl-k";
|
|
5933
5994
|
case "CTRL_L":
|
|
@@ -5956,7 +6017,7 @@ function mapKeyName(name) {
|
|
|
5956
6017
|
return null;
|
|
5957
6018
|
}
|
|
5958
6019
|
}
|
|
5959
|
-
var SESSIONBAR_ROWS, BANNER_ROWS, SEPARATOR_ROWS, MAX_PROMPT_ROWS, MAX_QUEUED_ROWS, MAX_PERMISSION_ROWS, MAX_COMPLETION_ROWS, MAX_CHIP_ROWS, CONFIRM_PROMPT_ROWS, DEFAULT_CONTENT_REPAINT_THROTTLE_MS, DEFAULT_MAX_SCROLLBACK_LINES, BARE_URL_RE, Screen, NON_ASCII, SEGMENTER, TK_MARKUP_STYLE_CHAR, shortId;
|
|
6020
|
+
var SESSIONBAR_ROWS, BANNER_ROWS, SEPARATOR_ROWS, MAX_PROMPT_ROWS, MAX_QUEUED_ROWS, MAX_PERMISSION_ROWS, MAX_HELP_ROWS, MAX_COMPLETION_ROWS, MAX_CHIP_ROWS, CONFIRM_PROMPT_ROWS, DEFAULT_CONTENT_REPAINT_THROTTLE_MS, DEFAULT_MAX_SCROLLBACK_LINES, BARE_URL_RE, Screen, NON_ASCII, SEGMENTER, TK_MARKUP_STYLE_CHAR, shortId;
|
|
5960
6021
|
var init_screen = __esm({
|
|
5961
6022
|
"src/tui/screen.ts"() {
|
|
5962
6023
|
"use strict";
|
|
@@ -5970,6 +6031,7 @@ var init_screen = __esm({
|
|
|
5970
6031
|
MAX_PROMPT_ROWS = 8;
|
|
5971
6032
|
MAX_QUEUED_ROWS = 5;
|
|
5972
6033
|
MAX_PERMISSION_ROWS = 12;
|
|
6034
|
+
MAX_HELP_ROWS = 30;
|
|
5973
6035
|
MAX_COMPLETION_ROWS = 6;
|
|
5974
6036
|
MAX_CHIP_ROWS = 4;
|
|
5975
6037
|
CONFIRM_PROMPT_ROWS = 2;
|
|
@@ -6032,6 +6094,7 @@ var init_screen = __esm({
|
|
|
6032
6094
|
lastFrameH = 0;
|
|
6033
6095
|
permissionPrompt = null;
|
|
6034
6096
|
confirmPrompt = null;
|
|
6097
|
+
helpPrompt = null;
|
|
6035
6098
|
completions = [];
|
|
6036
6099
|
// Scrollback offset: 0 = pinned to bottom (live), N = N wrapped lines
|
|
6037
6100
|
// above the bottom. Mouse wheel and PgUp/PgDn adjust this; new content
|
|
@@ -6060,7 +6123,7 @@ var init_screen = __esm({
|
|
|
6060
6123
|
banner = {
|
|
6061
6124
|
status: "ready",
|
|
6062
6125
|
currentMode: void 0,
|
|
6063
|
-
hint: "\u21E7\u21E5 mode \xB7 \
|
|
6126
|
+
hint: "\u21E7\u21E5 mode \xB7 \u2303P pick \xB7 \u2303G guide \xB7 \u2303D detach",
|
|
6064
6127
|
queued: 0
|
|
6065
6128
|
};
|
|
6066
6129
|
sessionbar = { agent: "?", cwd: "?", sessionId: "?" };
|
|
@@ -6623,6 +6686,16 @@ var init_screen = __esm({
|
|
|
6623
6686
|
this.confirmPrompt = spec ? { ...spec } : null;
|
|
6624
6687
|
this.repaint();
|
|
6625
6688
|
}
|
|
6689
|
+
// Multi-row help cheatsheet that takes over the prompt area. Used by
|
|
6690
|
+
// the ^G hotkey to surface every binding without dropping the user
|
|
6691
|
+
// out of the session. Pass null to dismiss.
|
|
6692
|
+
setHelpPrompt(spec) {
|
|
6693
|
+
this.helpPrompt = spec ? { ...spec, entries: [...spec.entries] } : null;
|
|
6694
|
+
this.repaint();
|
|
6695
|
+
}
|
|
6696
|
+
isHelpPromptActive() {
|
|
6697
|
+
return this.helpPrompt !== null;
|
|
6698
|
+
}
|
|
6626
6699
|
// Slash-command completion list shown directly above the separator. App
|
|
6627
6700
|
// calls this after each keystroke; pass [] to dismiss. Suppressed when
|
|
6628
6701
|
// the permission modal is active (the modal owns the prompt area).
|
|
@@ -7158,7 +7231,7 @@ var init_screen = __esm({
|
|
|
7158
7231
|
this.repaint();
|
|
7159
7232
|
}
|
|
7160
7233
|
completionRows() {
|
|
7161
|
-
if (this.permissionPrompt) {
|
|
7234
|
+
if (this.permissionPrompt || this.confirmPrompt || this.helpPrompt) {
|
|
7162
7235
|
return 0;
|
|
7163
7236
|
}
|
|
7164
7237
|
return Math.min(MAX_COMPLETION_ROWS, this.completions.length);
|
|
@@ -7302,6 +7375,10 @@ var init_screen = __esm({
|
|
|
7302
7375
|
this.drawConfirmPrompt();
|
|
7303
7376
|
return;
|
|
7304
7377
|
}
|
|
7378
|
+
if (this.helpPrompt) {
|
|
7379
|
+
this.drawHelpPrompt();
|
|
7380
|
+
return;
|
|
7381
|
+
}
|
|
7305
7382
|
const w = this.term.width;
|
|
7306
7383
|
const room = Math.max(1, w - 2);
|
|
7307
7384
|
const state = this.dispatcher.state();
|
|
@@ -7351,6 +7428,58 @@ var init_screen = __esm({
|
|
|
7351
7428
|
this.term.dim(` ${truncate(spec.hint, w - 2)}`);
|
|
7352
7429
|
});
|
|
7353
7430
|
}
|
|
7431
|
+
drawHelpPrompt() {
|
|
7432
|
+
const spec = this.helpPrompt;
|
|
7433
|
+
if (!spec) {
|
|
7434
|
+
return;
|
|
7435
|
+
}
|
|
7436
|
+
const w = this.term.width;
|
|
7437
|
+
const rows = this.helpRows();
|
|
7438
|
+
const top = this.term.height - rows - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
7439
|
+
let row = top;
|
|
7440
|
+
const writeRow = (sig, paint) => {
|
|
7441
|
+
if (row >= top + rows) {
|
|
7442
|
+
return;
|
|
7443
|
+
}
|
|
7444
|
+
this.paintRow(row, sig, paint);
|
|
7445
|
+
row += 1;
|
|
7446
|
+
};
|
|
7447
|
+
writeRow(`help|t|${w}|${spec.title}`, () => {
|
|
7448
|
+
this.term.brightYellow(` \u2753 ${truncate(spec.title, w - 5)}`);
|
|
7449
|
+
});
|
|
7450
|
+
const keysWidth = Math.min(
|
|
7451
|
+
24,
|
|
7452
|
+
Math.max(
|
|
7453
|
+
...spec.entries.map((e) => e === null ? 0 : e[0].length),
|
|
7454
|
+
4
|
|
7455
|
+
)
|
|
7456
|
+
);
|
|
7457
|
+
for (const entry of spec.entries) {
|
|
7458
|
+
if (row >= top + rows - 1) {
|
|
7459
|
+
break;
|
|
7460
|
+
}
|
|
7461
|
+
if (entry === null) {
|
|
7462
|
+
writeRow(`help|sep|${w}|${row}`, () => void 0);
|
|
7463
|
+
continue;
|
|
7464
|
+
}
|
|
7465
|
+
const [keys, desc] = entry;
|
|
7466
|
+
const paddedKeys = keys.padEnd(keysWidth);
|
|
7467
|
+
writeRow(`help|e|${w}|${keys}|${desc}`, () => {
|
|
7468
|
+
this.term(" ");
|
|
7469
|
+
this.term.brightCyan.noFormat(paddedKeys);
|
|
7470
|
+
this.term.noFormat(` ${truncate(desc, w - 2 - keysWidth - 1)}`);
|
|
7471
|
+
});
|
|
7472
|
+
}
|
|
7473
|
+
writeRow(`help|hint|${w}|${spec.hint}`, () => {
|
|
7474
|
+
this.term.dim(` ${truncate(spec.hint, w - 2)}`);
|
|
7475
|
+
});
|
|
7476
|
+
}
|
|
7477
|
+
helpRows() {
|
|
7478
|
+
if (!this.helpPrompt) {
|
|
7479
|
+
return 0;
|
|
7480
|
+
}
|
|
7481
|
+
return Math.min(MAX_HELP_ROWS, 2 + this.helpPrompt.entries.length);
|
|
7482
|
+
}
|
|
7354
7483
|
drawPermissionPrompt() {
|
|
7355
7484
|
const spec = this.permissionPrompt;
|
|
7356
7485
|
if (!spec) {
|
|
@@ -7460,6 +7589,12 @@ var init_screen = __esm({
|
|
|
7460
7589
|
this.term.moveTo(2, top2);
|
|
7461
7590
|
return;
|
|
7462
7591
|
}
|
|
7592
|
+
if (this.helpPrompt) {
|
|
7593
|
+
const rows = this.helpRows();
|
|
7594
|
+
const top2 = this.term.height - rows - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
7595
|
+
this.term.moveTo(2, top2);
|
|
7596
|
+
return;
|
|
7597
|
+
}
|
|
7463
7598
|
if (this.scrollbackSearch) {
|
|
7464
7599
|
this.term.hideCursor(true);
|
|
7465
7600
|
return;
|
|
@@ -7486,6 +7621,9 @@ var init_screen = __esm({
|
|
|
7486
7621
|
if (this.confirmPrompt) {
|
|
7487
7622
|
return CONFIRM_PROMPT_ROWS;
|
|
7488
7623
|
}
|
|
7624
|
+
if (this.helpPrompt) {
|
|
7625
|
+
return this.helpRows();
|
|
7626
|
+
}
|
|
7489
7627
|
const w = this.term.width;
|
|
7490
7628
|
const room = Math.max(1, w - 2);
|
|
7491
7629
|
const state = this.dispatcher.state();
|
|
@@ -7844,6 +7982,14 @@ var init_input = __esm({
|
|
|
7844
7982
|
case "ctrl-f":
|
|
7845
7983
|
this.moveRight();
|
|
7846
7984
|
return [];
|
|
7985
|
+
case "ctrl-g":
|
|
7986
|
+
return [{ type: "show-help" }];
|
|
7987
|
+
case "alt-b":
|
|
7988
|
+
this.moveWordBackward();
|
|
7989
|
+
return [];
|
|
7990
|
+
case "alt-f":
|
|
7991
|
+
this.moveWordForward();
|
|
7992
|
+
return [];
|
|
7847
7993
|
case "ctrl-k":
|
|
7848
7994
|
this.killToEnd();
|
|
7849
7995
|
return [];
|
|
@@ -8047,6 +8193,44 @@ var init_input = __esm({
|
|
|
8047
8193
|
this.col = 0;
|
|
8048
8194
|
}
|
|
8049
8195
|
}
|
|
8196
|
+
moveWordBackward() {
|
|
8197
|
+
if (this.col === 0) {
|
|
8198
|
+
if (this.row === 0) {
|
|
8199
|
+
return;
|
|
8200
|
+
}
|
|
8201
|
+
this.row -= 1;
|
|
8202
|
+
this.col = this.currentLine().length;
|
|
8203
|
+
return;
|
|
8204
|
+
}
|
|
8205
|
+
const line = this.currentLine();
|
|
8206
|
+
let i = this.col;
|
|
8207
|
+
while (i > 0 && /\s/.test(line[i - 1] ?? "")) {
|
|
8208
|
+
i -= 1;
|
|
8209
|
+
}
|
|
8210
|
+
while (i > 0 && !/\s/.test(line[i - 1] ?? "")) {
|
|
8211
|
+
i -= 1;
|
|
8212
|
+
}
|
|
8213
|
+
this.col = i;
|
|
8214
|
+
}
|
|
8215
|
+
moveWordForward() {
|
|
8216
|
+
const line = this.currentLine();
|
|
8217
|
+
if (this.col >= line.length) {
|
|
8218
|
+
if (this.row >= this.buffer.length - 1) {
|
|
8219
|
+
return;
|
|
8220
|
+
}
|
|
8221
|
+
this.row += 1;
|
|
8222
|
+
this.col = 0;
|
|
8223
|
+
return;
|
|
8224
|
+
}
|
|
8225
|
+
let i = this.col;
|
|
8226
|
+
while (i < line.length && /\s/.test(line[i] ?? "")) {
|
|
8227
|
+
i += 1;
|
|
8228
|
+
}
|
|
8229
|
+
while (i < line.length && !/\s/.test(line[i] ?? "")) {
|
|
8230
|
+
i += 1;
|
|
8231
|
+
}
|
|
8232
|
+
this.col = i;
|
|
8233
|
+
}
|
|
8050
8234
|
// Up walks the navigation stack from newest to oldest: pending queue
|
|
8051
8235
|
// items first (so the user can edit something they just enqueued),
|
|
8052
8236
|
// then prompt history. Cursor movement within a multi-line buffer
|
|
@@ -8362,7 +8546,7 @@ var init_input = __esm({
|
|
|
8362
8546
|
import { spawn as nodeSpawn } from "child_process";
|
|
8363
8547
|
import fs18 from "fs/promises";
|
|
8364
8548
|
import os4 from "os";
|
|
8365
|
-
import
|
|
8549
|
+
import path13 from "path";
|
|
8366
8550
|
async function readClipboard(envIn = {}) {
|
|
8367
8551
|
const env = { ...defaultEnv, ...envIn };
|
|
8368
8552
|
if (env.platform === "darwin") {
|
|
@@ -8377,7 +8561,7 @@ async function readClipboard(envIn = {}) {
|
|
|
8377
8561
|
};
|
|
8378
8562
|
}
|
|
8379
8563
|
async function readMacOS(env) {
|
|
8380
|
-
const tmpPath =
|
|
8564
|
+
const tmpPath = path13.join(
|
|
8381
8565
|
env.tmpdir(),
|
|
8382
8566
|
`hydra-clipboard-${Date.now()}-${process.pid}.png`
|
|
8383
8567
|
);
|
|
@@ -8981,7 +9165,7 @@ import { appendFileSync, statSync, renameSync } from "fs";
|
|
|
8981
9165
|
import { nanoid as nanoid3 } from "nanoid";
|
|
8982
9166
|
import termkit from "terminal-kit";
|
|
8983
9167
|
import fs19 from "fs/promises";
|
|
8984
|
-
import
|
|
9168
|
+
import path14 from "path";
|
|
8985
9169
|
async function runTuiApp(opts) {
|
|
8986
9170
|
const config = await loadConfig();
|
|
8987
9171
|
const serviceToken = await ensureServiceToken();
|
|
@@ -9087,14 +9271,27 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9087
9271
|
};
|
|
9088
9272
|
let screenRef = null;
|
|
9089
9273
|
let dispatcherRef = null;
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9274
|
+
let lastSeenMessageId = void 0;
|
|
9275
|
+
let reconnectReplayBuffer = null;
|
|
9276
|
+
const STATE_UPDATE_KINDS2 = /* @__PURE__ */ new Set([
|
|
9277
|
+
"session_info_update",
|
|
9278
|
+
"current_model_update",
|
|
9279
|
+
"current_mode_update",
|
|
9280
|
+
"available_commands_update",
|
|
9281
|
+
"available_modes_update",
|
|
9282
|
+
"usage_update"
|
|
9283
|
+
]);
|
|
9284
|
+
const handleSessionUpdate = (params) => {
|
|
9094
9285
|
const { update } = params ?? {};
|
|
9095
9286
|
const event = mapUpdate(update);
|
|
9096
9287
|
debugLogUpdate(update, event);
|
|
9097
9288
|
const rawTag = update?.sessionUpdate;
|
|
9289
|
+
if (typeof rawTag === "string" && !STATE_UPDATE_KINDS2.has(rawTag)) {
|
|
9290
|
+
const u = update ?? {};
|
|
9291
|
+
if (typeof u.messageId === "string") {
|
|
9292
|
+
lastSeenMessageId = u.messageId;
|
|
9293
|
+
}
|
|
9294
|
+
}
|
|
9098
9295
|
if (rawTag === "prompt_received") {
|
|
9099
9296
|
adjustPendingTurns(1);
|
|
9100
9297
|
} else if (event?.kind === "turn-complete") {
|
|
@@ -9106,6 +9303,16 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9106
9303
|
}
|
|
9107
9304
|
appendRender(event);
|
|
9108
9305
|
maybeDismissPermissionByToolUpdate(update);
|
|
9306
|
+
};
|
|
9307
|
+
conn.onNotification("session/update", (params) => {
|
|
9308
|
+
if (teardownStarted) {
|
|
9309
|
+
return;
|
|
9310
|
+
}
|
|
9311
|
+
if (reconnectReplayBuffer !== null) {
|
|
9312
|
+
reconnectReplayBuffer.push(params);
|
|
9313
|
+
return;
|
|
9314
|
+
}
|
|
9315
|
+
handleSessionUpdate(params);
|
|
9109
9316
|
});
|
|
9110
9317
|
conn.onNotification("hydra-acp/session_closed", () => {
|
|
9111
9318
|
if (teardownStarted) {
|
|
@@ -9184,6 +9391,7 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9184
9391
|
text: echo.text,
|
|
9185
9392
|
attachments: echo.attachments
|
|
9186
9393
|
});
|
|
9394
|
+
currentTurnEcho = echo;
|
|
9187
9395
|
}
|
|
9188
9396
|
}
|
|
9189
9397
|
});
|
|
@@ -9420,6 +9628,9 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9420
9628
|
if (exitConfirmation && tryHandleExitConfirmKey(ev)) {
|
|
9421
9629
|
continue;
|
|
9422
9630
|
}
|
|
9631
|
+
if (tryHandleHelpKey(ev)) {
|
|
9632
|
+
continue;
|
|
9633
|
+
}
|
|
9423
9634
|
if (tryHandleScrollbackSearchKey(ev)) {
|
|
9424
9635
|
continue;
|
|
9425
9636
|
}
|
|
@@ -9705,6 +9916,28 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9705
9916
|
}
|
|
9706
9917
|
return true;
|
|
9707
9918
|
};
|
|
9919
|
+
const toggleHelpModal = () => {
|
|
9920
|
+
if (screen.isHelpPromptActive()) {
|
|
9921
|
+
screen.setHelpPrompt(null);
|
|
9922
|
+
return;
|
|
9923
|
+
}
|
|
9924
|
+
screen.setHelpPrompt({
|
|
9925
|
+
title: "Hotkeys",
|
|
9926
|
+
entries: HELP_ENTRIES2,
|
|
9927
|
+
hint: "any key dismisses \xB7 /help lists commands"
|
|
9928
|
+
});
|
|
9929
|
+
};
|
|
9930
|
+
const tryHandleHelpKey = (ev) => {
|
|
9931
|
+
if (!screen.isHelpPromptActive()) {
|
|
9932
|
+
return false;
|
|
9933
|
+
}
|
|
9934
|
+
if (ev.type === "key" && ev.name === "ctrl-g") {
|
|
9935
|
+
screen.setHelpPrompt(null);
|
|
9936
|
+
return true;
|
|
9937
|
+
}
|
|
9938
|
+
screen.setHelpPrompt(null);
|
|
9939
|
+
return true;
|
|
9940
|
+
};
|
|
9708
9941
|
const teardown = () => {
|
|
9709
9942
|
teardownStarted = true;
|
|
9710
9943
|
process.off("SIGINT", sigintHandler);
|
|
@@ -9885,6 +10118,9 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9885
10118
|
toolsExpanded = !toolsExpanded;
|
|
9886
10119
|
renderToolsBlock();
|
|
9887
10120
|
return;
|
|
10121
|
+
case "show-help":
|
|
10122
|
+
toggleHelpModal();
|
|
10123
|
+
return;
|
|
9888
10124
|
case "escalate-search":
|
|
9889
10125
|
screen.enterScrollbackSearch();
|
|
9890
10126
|
screen.updateScrollbackSearchTerm(effect.query);
|
|
@@ -9924,7 +10160,7 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9924
10160
|
}
|
|
9925
10161
|
const mimeType = mimeFromExtension(token);
|
|
9926
10162
|
if (!mimeType) {
|
|
9927
|
-
screen.notify(`unsupported image type: ${
|
|
10163
|
+
screen.notify(`unsupported image type: ${path14.basename(token)}`);
|
|
9928
10164
|
continue;
|
|
9929
10165
|
}
|
|
9930
10166
|
try {
|
|
@@ -9938,13 +10174,13 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9938
10174
|
dispatcher.addAttachment({
|
|
9939
10175
|
mimeType,
|
|
9940
10176
|
data: buf.toString("base64"),
|
|
9941
|
-
name:
|
|
10177
|
+
name: path14.basename(token),
|
|
9942
10178
|
sizeBytes: buf.length
|
|
9943
10179
|
});
|
|
9944
10180
|
added++;
|
|
9945
10181
|
} catch (err) {
|
|
9946
10182
|
screen.notify(
|
|
9947
|
-
`cannot read ${
|
|
10183
|
+
`cannot read ${path14.basename(token)}: ${err.message}`
|
|
9948
10184
|
);
|
|
9949
10185
|
}
|
|
9950
10186
|
}
|
|
@@ -9998,6 +10234,7 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9998
10234
|
const queueCache = /* @__PURE__ */ new Map();
|
|
9999
10235
|
const pendingEchoes = [];
|
|
10000
10236
|
const ownPendingByMid = /* @__PURE__ */ new Map();
|
|
10237
|
+
let currentTurnEcho = null;
|
|
10001
10238
|
const refreshQueueDisplay = () => {
|
|
10002
10239
|
const entries = [...queueCache.values()];
|
|
10003
10240
|
const displayTexts = entries.map(formatQueueChipText);
|
|
@@ -10254,10 +10491,11 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10254
10491
|
} finally {
|
|
10255
10492
|
turnInFlight = null;
|
|
10256
10493
|
adjustPendingTurns(-1);
|
|
10257
|
-
if (echo.flushed) {
|
|
10494
|
+
if (echo.flushed && currentTurnEcho === echo) {
|
|
10258
10495
|
appendRender(
|
|
10259
10496
|
stopReason !== void 0 ? { kind: "turn-complete", stopReason } : { kind: "turn-complete" }
|
|
10260
10497
|
);
|
|
10498
|
+
currentTurnEcho = null;
|
|
10261
10499
|
}
|
|
10262
10500
|
if (pendingPrefill !== null) {
|
|
10263
10501
|
const { text: pt, attachments: pa } = pendingPrefill;
|
|
@@ -10440,6 +10678,11 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10440
10678
|
}
|
|
10441
10679
|
if (event.kind === "user-text") {
|
|
10442
10680
|
closeAgentText();
|
|
10681
|
+
if (toolsBlockStartedAt !== null) {
|
|
10682
|
+
toolsBlockEndedAt = Date.now();
|
|
10683
|
+
renderToolsBlock();
|
|
10684
|
+
}
|
|
10685
|
+
currentTurnEcho = null;
|
|
10443
10686
|
screen.ensureSeparator();
|
|
10444
10687
|
const formatted2 = formatEvent(event);
|
|
10445
10688
|
if (formatted2.length > 0) {
|
|
@@ -10508,6 +10751,15 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10508
10751
|
toolsBlockStopReason = event.stopReason ?? null;
|
|
10509
10752
|
renderToolsBlock();
|
|
10510
10753
|
screen.clearKey("tools");
|
|
10754
|
+
} else if (event.stopReason !== void 0 && event.stopReason !== "end_turn") {
|
|
10755
|
+
screen.appendLines([
|
|
10756
|
+
{
|
|
10757
|
+
prefix: "\u26A0 ",
|
|
10758
|
+
prefixStyle: "tool-status-fail",
|
|
10759
|
+
body: `turn ended: ${event.stopReason}`,
|
|
10760
|
+
bodyStyle: "tool-status-fail"
|
|
10761
|
+
}
|
|
10762
|
+
]);
|
|
10511
10763
|
}
|
|
10512
10764
|
toolStates.clear();
|
|
10513
10765
|
toolCallOrder.length = 0;
|
|
@@ -10555,23 +10807,21 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10555
10807
|
resolve5({ outcome: { outcome: "cancelled" } });
|
|
10556
10808
|
}
|
|
10557
10809
|
closeAgentText();
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
screen.clearKey("tools");
|
|
10563
|
-
toolStates.clear();
|
|
10564
|
-
toolCallOrder.length = 0;
|
|
10565
|
-
toolsBlockStartedAt = null;
|
|
10566
|
-
toolsBlockEndedAt = null;
|
|
10567
|
-
toolsBlockStopReason = null;
|
|
10568
|
-
toolsExpanded = false;
|
|
10569
|
-
}
|
|
10570
|
-
screen.clearKey("plan");
|
|
10571
|
-
lastPlanEvent = null;
|
|
10572
|
-
if (pendingTurns > 0) {
|
|
10573
|
-
adjustPendingTurns(-pendingTurns);
|
|
10810
|
+
};
|
|
10811
|
+
const markToolsBlockRecoveryFailed = () => {
|
|
10812
|
+
if (toolsBlockStartedAt === null) {
|
|
10813
|
+
return;
|
|
10574
10814
|
}
|
|
10815
|
+
toolsBlockEndedAt = Date.now();
|
|
10816
|
+
toolsBlockStopReason = "reconnect-recovery-failed";
|
|
10817
|
+
renderToolsBlock();
|
|
10818
|
+
screen.clearKey("tools");
|
|
10819
|
+
toolStates.clear();
|
|
10820
|
+
toolCallOrder.length = 0;
|
|
10821
|
+
toolsBlockStartedAt = null;
|
|
10822
|
+
toolsBlockEndedAt = null;
|
|
10823
|
+
toolsBlockStopReason = null;
|
|
10824
|
+
toolsExpanded = false;
|
|
10575
10825
|
};
|
|
10576
10826
|
onDisconnectHook = () => {
|
|
10577
10827
|
screen.setBanner({ status: "disconnected", elapsedMs: void 0 });
|
|
@@ -10595,13 +10845,15 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10595
10845
|
await stream.request(initReq);
|
|
10596
10846
|
} catch {
|
|
10597
10847
|
}
|
|
10848
|
+
const useAfterMessage = lastSeenMessageId !== void 0;
|
|
10598
10849
|
const attachReq = {
|
|
10599
10850
|
jsonrpc: "2.0",
|
|
10600
10851
|
id: `tui-reattach-${nanoid3()}`,
|
|
10601
10852
|
method: "session/attach",
|
|
10602
10853
|
params: {
|
|
10603
10854
|
sessionId: resolvedSessionId,
|
|
10604
|
-
historyPolicy: "none",
|
|
10855
|
+
historyPolicy: useAfterMessage ? "after_message" : "none",
|
|
10856
|
+
...useAfterMessage ? { afterMessageId: lastSeenMessageId } : {},
|
|
10605
10857
|
clientInfo: { name: "hydra-acp-tui", version: HYDRA_VERSION },
|
|
10606
10858
|
...upstreamSessionId !== void 0 ? {
|
|
10607
10859
|
_meta: {
|
|
@@ -10616,19 +10868,46 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10616
10868
|
} : {}
|
|
10617
10869
|
}
|
|
10618
10870
|
};
|
|
10871
|
+
reconnectReplayBuffer = [];
|
|
10872
|
+
let appliedPolicy;
|
|
10873
|
+
let attachErr;
|
|
10619
10874
|
try {
|
|
10620
10875
|
const resp = await stream.request(attachReq);
|
|
10621
10876
|
if (resp.error) {
|
|
10622
10877
|
throw new Error(resp.error.message);
|
|
10623
10878
|
}
|
|
10879
|
+
const result = resp.result ?? {};
|
|
10880
|
+
if (typeof result.historyPolicy === "string") {
|
|
10881
|
+
appliedPolicy = result.historyPolicy;
|
|
10882
|
+
}
|
|
10624
10883
|
} catch (err) {
|
|
10884
|
+
attachErr = err;
|
|
10885
|
+
}
|
|
10886
|
+
const buffered2 = reconnectReplayBuffer ?? [];
|
|
10887
|
+
reconnectReplayBuffer = null;
|
|
10888
|
+
if (attachErr) {
|
|
10889
|
+
markToolsBlockRecoveryFailed();
|
|
10625
10890
|
screen.appendLines([
|
|
10626
10891
|
{
|
|
10627
10892
|
prefix: " ",
|
|
10628
|
-
body: `reattach failed: ${
|
|
10893
|
+
body: `reattach failed: ${attachErr.message}`,
|
|
10629
10894
|
bodyStyle: "tool-status-fail"
|
|
10630
10895
|
}
|
|
10631
10896
|
]);
|
|
10897
|
+
} else if (useAfterMessage && appliedPolicy !== "after_message") {
|
|
10898
|
+
markToolsBlockRecoveryFailed();
|
|
10899
|
+
screen.appendLines([
|
|
10900
|
+
{
|
|
10901
|
+
prefix: "\u26A0 ",
|
|
10902
|
+
prefixStyle: "tool-status-fail",
|
|
10903
|
+
body: "reconnect couldn't replay events since last seen \u2014 scrollback may be incomplete",
|
|
10904
|
+
bodyStyle: "tool-status-fail"
|
|
10905
|
+
}
|
|
10906
|
+
]);
|
|
10907
|
+
} else {
|
|
10908
|
+
for (const params of buffered2) {
|
|
10909
|
+
handleSessionUpdate(params);
|
|
10910
|
+
}
|
|
10632
10911
|
}
|
|
10633
10912
|
screen.setBanner({
|
|
10634
10913
|
status: pendingTurns > 0 ? "busy" : "ready",
|
|
@@ -10735,7 +11014,7 @@ function rotateIfBig(target) {
|
|
|
10735
11014
|
} catch {
|
|
10736
11015
|
}
|
|
10737
11016
|
}
|
|
10738
|
-
var logMaxBytes;
|
|
11017
|
+
var HELP_ENTRIES2, logMaxBytes;
|
|
10739
11018
|
var init_app = __esm({
|
|
10740
11019
|
"src/tui/app.ts"() {
|
|
10741
11020
|
"use strict";
|
|
@@ -10759,6 +11038,34 @@ var init_app = __esm({
|
|
|
10759
11038
|
init_completion();
|
|
10760
11039
|
init_render_update();
|
|
10761
11040
|
init_format();
|
|
11041
|
+
HELP_ENTRIES2 = [
|
|
11042
|
+
["Enter", "send prompt (or queue while a turn is running)"],
|
|
11043
|
+
["Alt+Enter", "newline in prompt"],
|
|
11044
|
+
["Shift+Tab", "cycle agent modes (plan / accept-edits / etc.)"],
|
|
11045
|
+
["Tab", "indent \xB7 slash-command completion"],
|
|
11046
|
+
null,
|
|
11047
|
+
["\u2191 / \u2193", "prompt history \xB7 queue navigation"],
|
|
11048
|
+
["\u2190/\u2192 Home/End", "cursor movement"],
|
|
11049
|
+
["Alt+B / Alt+F", "word back / forward"],
|
|
11050
|
+
["^A / ^E", "line start / end"],
|
|
11051
|
+
["^W / ^U / ^K", "kill word / line / to end"],
|
|
11052
|
+
["^Y", "yank last kill"],
|
|
11053
|
+
null,
|
|
11054
|
+
["^P", "switch session (picker)"],
|
|
11055
|
+
["^T", "next live session"],
|
|
11056
|
+
["^V", "paste image from clipboard"],
|
|
11057
|
+
["^O", "expand / collapse tools block"],
|
|
11058
|
+
null,
|
|
11059
|
+
["^R / ^S", "history reverse / forward search"],
|
|
11060
|
+
["PgUp / PgDn", "scroll scrollback"],
|
|
11061
|
+
["Mouse wheel", "scroll scrollback (when mouse capture is on)"],
|
|
11062
|
+
null,
|
|
11063
|
+
["^C", "cancel turn (twice to exit)"],
|
|
11064
|
+
["Esc", "cancel turn and prefill draft"],
|
|
11065
|
+
["^D", "exit (or delete-forward in prompt)"],
|
|
11066
|
+
["^L", "force full redraw"],
|
|
11067
|
+
["^G", "toggle this help"]
|
|
11068
|
+
];
|
|
10762
11069
|
logMaxBytes = 5 * 1024 * 1024;
|
|
10763
11070
|
}
|
|
10764
11071
|
});
|
|
@@ -10891,13 +11198,13 @@ New token: ${newToken}
|
|
|
10891
11198
|
init_paths();
|
|
10892
11199
|
init_config();
|
|
10893
11200
|
init_service_token();
|
|
10894
|
-
import * as
|
|
11201
|
+
import * as fsp7 from "fs/promises";
|
|
10895
11202
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
10896
11203
|
|
|
10897
11204
|
// src/daemon/server.ts
|
|
10898
11205
|
init_config();
|
|
10899
11206
|
import * as fs15 from "fs";
|
|
10900
|
-
import * as
|
|
11207
|
+
import * as fsp5 from "fs/promises";
|
|
10901
11208
|
import Fastify from "fastify";
|
|
10902
11209
|
import websocketPlugin from "@fastify/websocket";
|
|
10903
11210
|
import pino from "pino";
|
|
@@ -11284,10 +11591,12 @@ var RegistryDocument = z2.object({
|
|
|
11284
11591
|
extensions: z2.array(z2.unknown()).optional()
|
|
11285
11592
|
});
|
|
11286
11593
|
var Registry = class {
|
|
11287
|
-
constructor(config) {
|
|
11594
|
+
constructor(config, options = {}) {
|
|
11288
11595
|
this.config = config;
|
|
11596
|
+
this.options = options;
|
|
11289
11597
|
}
|
|
11290
11598
|
config;
|
|
11599
|
+
options;
|
|
11291
11600
|
cache;
|
|
11292
11601
|
async load() {
|
|
11293
11602
|
if (this.cache && this.isFresh(this.cache.fetchedAt)) {
|
|
@@ -11337,7 +11646,12 @@ var Registry = class {
|
|
|
11337
11646
|
}
|
|
11338
11647
|
const raw = await response.json();
|
|
11339
11648
|
const data = RegistryDocument.parse(raw);
|
|
11340
|
-
|
|
11649
|
+
const cached2 = { fetchedAt: Date.now(), raw, data };
|
|
11650
|
+
const hook = this.options.onFetched;
|
|
11651
|
+
if (hook) {
|
|
11652
|
+
void Promise.resolve().then(() => hook(data)).catch(() => void 0);
|
|
11653
|
+
}
|
|
11654
|
+
return cached2;
|
|
11341
11655
|
}
|
|
11342
11656
|
async readDiskCache() {
|
|
11343
11657
|
let text;
|
|
@@ -11399,6 +11713,7 @@ function npxPackageBasename(agent) {
|
|
|
11399
11713
|
return atIdx <= 0 ? afterSlash : afterSlash.slice(0, atIdx);
|
|
11400
11714
|
}
|
|
11401
11715
|
async function planSpawn(agent, callerArgs = [], options = {}) {
|
|
11716
|
+
const version = agent.version ?? "current";
|
|
11402
11717
|
if (agent.distribution.npx) {
|
|
11403
11718
|
const npx = agent.distribution.npx;
|
|
11404
11719
|
const tail = callerArgs.length > 0 ? callerArgs : npx.args ?? [];
|
|
@@ -11406,13 +11721,14 @@ async function planSpawn(agent, callerArgs = [], options = {}) {
|
|
|
11406
11721
|
return {
|
|
11407
11722
|
command: "npx",
|
|
11408
11723
|
args: ["-y", npx.package, ...tail],
|
|
11409
|
-
env: npx.env ?? {}
|
|
11724
|
+
env: npx.env ?? {},
|
|
11725
|
+
version
|
|
11410
11726
|
};
|
|
11411
11727
|
}
|
|
11412
11728
|
const bin = npx.bin ?? npxPackageBasename(agent) ?? npx.package;
|
|
11413
11729
|
const binPath = await ensureNpmPackage({
|
|
11414
11730
|
agentId: agent.id,
|
|
11415
|
-
version
|
|
11731
|
+
version,
|
|
11416
11732
|
packageSpec: npx.package,
|
|
11417
11733
|
bin,
|
|
11418
11734
|
registry: options.npmRegistry
|
|
@@ -11420,7 +11736,8 @@ async function planSpawn(agent, callerArgs = [], options = {}) {
|
|
|
11420
11736
|
return {
|
|
11421
11737
|
command: binPath,
|
|
11422
11738
|
args: tail,
|
|
11423
|
-
env: npx.env ?? {}
|
|
11739
|
+
env: npx.env ?? {},
|
|
11740
|
+
version
|
|
11424
11741
|
};
|
|
11425
11742
|
}
|
|
11426
11743
|
if (agent.distribution.binary) {
|
|
@@ -11432,14 +11749,15 @@ async function planSpawn(agent, callerArgs = [], options = {}) {
|
|
|
11432
11749
|
}
|
|
11433
11750
|
const cmdPath = await ensureBinary({
|
|
11434
11751
|
agentId: agent.id,
|
|
11435
|
-
version
|
|
11752
|
+
version,
|
|
11436
11753
|
target
|
|
11437
11754
|
});
|
|
11438
11755
|
const tail = callerArgs.length > 0 ? callerArgs : target.args ?? [];
|
|
11439
11756
|
return {
|
|
11440
11757
|
command: cmdPath,
|
|
11441
11758
|
args: tail,
|
|
11442
|
-
env: target.env ?? {}
|
|
11759
|
+
env: target.env ?? {},
|
|
11760
|
+
version
|
|
11443
11761
|
};
|
|
11444
11762
|
}
|
|
11445
11763
|
if (agent.distribution.uvx) {
|
|
@@ -11448,7 +11766,8 @@ async function planSpawn(agent, callerArgs = [], options = {}) {
|
|
|
11448
11766
|
return {
|
|
11449
11767
|
command: "uvx",
|
|
11450
11768
|
args: [uvx.package, ...tail],
|
|
11451
|
-
env: uvx.env ?? {}
|
|
11769
|
+
env: uvx.env ?? {},
|
|
11770
|
+
version
|
|
11452
11771
|
};
|
|
11453
11772
|
}
|
|
11454
11773
|
throw new Error(`Agent ${agent.id} has no usable distribution method.`);
|
|
@@ -11539,6 +11858,9 @@ init_connection();
|
|
|
11539
11858
|
var DEFAULT_STDERR_TAIL_BYTES = 4096;
|
|
11540
11859
|
var AgentInstance = class _AgentInstance {
|
|
11541
11860
|
agentId;
|
|
11861
|
+
// Version this process was spawned from — used by the registry-fetch
|
|
11862
|
+
// prune sweep to skip install dirs belonging to a live agent.
|
|
11863
|
+
version;
|
|
11542
11864
|
cwd;
|
|
11543
11865
|
connection;
|
|
11544
11866
|
child;
|
|
@@ -11550,6 +11872,7 @@ var AgentInstance = class _AgentInstance {
|
|
|
11550
11872
|
exitHandlers = [];
|
|
11551
11873
|
constructor(opts, child) {
|
|
11552
11874
|
this.agentId = opts.agentId;
|
|
11875
|
+
this.version = opts.plan.version;
|
|
11553
11876
|
this.cwd = opts.cwd;
|
|
11554
11877
|
this.child = child;
|
|
11555
11878
|
this.stderrTailBytes = opts.stderrTailBytes ?? DEFAULT_STDERR_TAIL_BYTES;
|
|
@@ -12213,6 +12536,23 @@ var SessionManager = class {
|
|
|
12213
12536
|
get(sessionId) {
|
|
12214
12537
|
return this.sessions.get(sessionId);
|
|
12215
12538
|
}
|
|
12539
|
+
// Snapshot of which agent versions are currently in use by live
|
|
12540
|
+
// sessions, keyed by agentId. Read by the registry-fetch prune sweep
|
|
12541
|
+
// so it can skip install dirs that still back a running process.
|
|
12542
|
+
activeAgentVersions() {
|
|
12543
|
+
const out = /* @__PURE__ */ new Map();
|
|
12544
|
+
for (const session of this.sessions.values()) {
|
|
12545
|
+
const id = session.agent.agentId;
|
|
12546
|
+
const version = session.agent.version;
|
|
12547
|
+
let set = out.get(id);
|
|
12548
|
+
if (!set) {
|
|
12549
|
+
set = /* @__PURE__ */ new Set();
|
|
12550
|
+
out.set(id, set);
|
|
12551
|
+
}
|
|
12552
|
+
set.add(version);
|
|
12553
|
+
}
|
|
12554
|
+
return out;
|
|
12555
|
+
}
|
|
12216
12556
|
// Resolve a user-typed session id (which may have the hydra_session_
|
|
12217
12557
|
// prefix stripped — that's what `sessions list` and the picker show) to
|
|
12218
12558
|
// the canonical form that actually exists. Tries the input as-given
|
|
@@ -13189,12 +13529,91 @@ function withCode2(err, code) {
|
|
|
13189
13529
|
|
|
13190
13530
|
// src/daemon/server.ts
|
|
13191
13531
|
init_paths();
|
|
13532
|
+
|
|
13533
|
+
// src/core/agent-prune.ts
|
|
13534
|
+
init_paths();
|
|
13535
|
+
import * as fsp4 from "fs/promises";
|
|
13536
|
+
import * as path8 from "path";
|
|
13537
|
+
var logSink3 = (msg) => {
|
|
13538
|
+
process.stderr.write(msg + "\n");
|
|
13539
|
+
};
|
|
13540
|
+
function setAgentPruneLogger(log) {
|
|
13541
|
+
logSink3 = log ?? ((msg) => process.stderr.write(msg + "\n"));
|
|
13542
|
+
}
|
|
13543
|
+
async function pruneStaleAgentVersions(registry, sessionManager) {
|
|
13544
|
+
const platformKey = currentPlatformKey();
|
|
13545
|
+
if (!platformKey) {
|
|
13546
|
+
return;
|
|
13547
|
+
}
|
|
13548
|
+
const doc = await registry.load();
|
|
13549
|
+
const desiredByAgent = /* @__PURE__ */ new Map();
|
|
13550
|
+
for (const a of doc.agents) {
|
|
13551
|
+
desiredByAgent.set(a.id, a.version ?? "current");
|
|
13552
|
+
}
|
|
13553
|
+
const activeByAgent = sessionManager.activeAgentVersions();
|
|
13554
|
+
const platformDir = path8.join(paths.agentsDir(), platformKey);
|
|
13555
|
+
let agentEntries;
|
|
13556
|
+
try {
|
|
13557
|
+
agentEntries = await fsp4.readdir(platformDir, { withFileTypes: true });
|
|
13558
|
+
} catch (err) {
|
|
13559
|
+
const e = err;
|
|
13560
|
+
if (e.code === "ENOENT") {
|
|
13561
|
+
return;
|
|
13562
|
+
}
|
|
13563
|
+
logSink3(`hydra-acp: prune: failed to read ${platformDir}: ${e.message}`);
|
|
13564
|
+
return;
|
|
13565
|
+
}
|
|
13566
|
+
for (const agentEntry of agentEntries) {
|
|
13567
|
+
if (!agentEntry.isDirectory()) {
|
|
13568
|
+
continue;
|
|
13569
|
+
}
|
|
13570
|
+
const agentId = agentEntry.name;
|
|
13571
|
+
const desired = desiredByAgent.get(agentId);
|
|
13572
|
+
if (desired === void 0) {
|
|
13573
|
+
continue;
|
|
13574
|
+
}
|
|
13575
|
+
const activeVersions = activeByAgent.get(agentId) ?? /* @__PURE__ */ new Set();
|
|
13576
|
+
const agentDir = path8.join(platformDir, agentId);
|
|
13577
|
+
let versionEntries;
|
|
13578
|
+
try {
|
|
13579
|
+
versionEntries = await fsp4.readdir(agentDir, { withFileTypes: true });
|
|
13580
|
+
} catch (err) {
|
|
13581
|
+
logSink3(
|
|
13582
|
+
`hydra-acp: prune: failed to read ${agentDir}: ${err.message}`
|
|
13583
|
+
);
|
|
13584
|
+
continue;
|
|
13585
|
+
}
|
|
13586
|
+
for (const versionEntry of versionEntries) {
|
|
13587
|
+
if (!versionEntry.isDirectory()) {
|
|
13588
|
+
continue;
|
|
13589
|
+
}
|
|
13590
|
+
const version = versionEntry.name;
|
|
13591
|
+
if (version === desired) {
|
|
13592
|
+
continue;
|
|
13593
|
+
}
|
|
13594
|
+
if (activeVersions.has(version)) {
|
|
13595
|
+
continue;
|
|
13596
|
+
}
|
|
13597
|
+
const versionDir = path8.join(agentDir, version);
|
|
13598
|
+
try {
|
|
13599
|
+
await fsp4.rm(versionDir, { recursive: true, force: true });
|
|
13600
|
+
logSink3(`hydra-acp: pruned stale ${agentId} ${version} (${versionDir})`);
|
|
13601
|
+
} catch (err) {
|
|
13602
|
+
logSink3(
|
|
13603
|
+
`hydra-acp: prune: failed to remove ${versionDir}: ${err.message}`
|
|
13604
|
+
);
|
|
13605
|
+
}
|
|
13606
|
+
}
|
|
13607
|
+
}
|
|
13608
|
+
}
|
|
13609
|
+
|
|
13610
|
+
// src/daemon/server.ts
|
|
13192
13611
|
init_hydra_version();
|
|
13193
13612
|
|
|
13194
13613
|
// src/core/session-tokens.ts
|
|
13195
13614
|
init_paths();
|
|
13196
13615
|
import * as fs13 from "fs/promises";
|
|
13197
|
-
import * as
|
|
13616
|
+
import * as path9 from "path";
|
|
13198
13617
|
import { createHash, randomBytes, timingSafeEqual } from "crypto";
|
|
13199
13618
|
var TOKEN_PREFIX = "hydra_session_";
|
|
13200
13619
|
var DEFAULT_TTL_SEC = 60 * 60 * 24 * 30;
|
|
@@ -13202,7 +13621,7 @@ var ID_LENGTH = 12;
|
|
|
13202
13621
|
var TOKEN_BYTES = 32;
|
|
13203
13622
|
var WRITE_DEBOUNCE_MS = 50;
|
|
13204
13623
|
function tokensFilePath() {
|
|
13205
|
-
return
|
|
13624
|
+
return path9.join(paths.home(), "session-tokens.json");
|
|
13206
13625
|
}
|
|
13207
13626
|
function sha256Hex(input) {
|
|
13208
13627
|
return createHash("sha256").update(input).digest("hex");
|
|
@@ -13872,12 +14291,12 @@ import { z as z6 } from "zod";
|
|
|
13872
14291
|
// src/core/password.ts
|
|
13873
14292
|
init_paths();
|
|
13874
14293
|
import * as fs14 from "fs/promises";
|
|
13875
|
-
import * as
|
|
14294
|
+
import * as path10 from "path";
|
|
13876
14295
|
import { randomBytes as randomBytes2, scrypt, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
13877
14296
|
import { promisify } from "util";
|
|
13878
14297
|
var scryptAsync = promisify(scrypt);
|
|
13879
14298
|
function passwordHashPath() {
|
|
13880
|
-
return
|
|
14299
|
+
return path10.join(paths.home(), "password-hash");
|
|
13881
14300
|
}
|
|
13882
14301
|
var DEFAULT_N = 1 << 15;
|
|
13883
14302
|
var DEFAULT_R = 8;
|
|
@@ -14464,10 +14883,10 @@ function bindClientToSession(connection, session, state, clientInfo, callerClien
|
|
|
14464
14883
|
async function startDaemon(config, serviceToken) {
|
|
14465
14884
|
ensureLoopbackOrTls(config);
|
|
14466
14885
|
const httpsOptions = config.daemon.tls ? {
|
|
14467
|
-
key: await
|
|
14468
|
-
cert: await
|
|
14886
|
+
key: await fsp5.readFile(config.daemon.tls.key),
|
|
14887
|
+
cert: await fsp5.readFile(config.daemon.tls.cert)
|
|
14469
14888
|
} : void 0;
|
|
14470
|
-
await
|
|
14889
|
+
await fsp5.mkdir(paths.home(), { recursive: true });
|
|
14471
14890
|
const { stream: logStream, fileStream } = await buildLogStream(
|
|
14472
14891
|
config.daemon.logLevel
|
|
14473
14892
|
);
|
|
@@ -14511,7 +14930,12 @@ async function startDaemon(config, serviceToken) {
|
|
|
14511
14930
|
5 * 60 * 1e3
|
|
14512
14931
|
);
|
|
14513
14932
|
sweepInterval.unref();
|
|
14514
|
-
const registry = new Registry(config
|
|
14933
|
+
const registry = new Registry(config, {
|
|
14934
|
+
onFetched: () => {
|
|
14935
|
+
void pruneStaleAgentVersions(registry, manager);
|
|
14936
|
+
}
|
|
14937
|
+
});
|
|
14938
|
+
setAgentPruneLogger((msg) => app.log.info(msg));
|
|
14515
14939
|
const agentLogger = {
|
|
14516
14940
|
info: (msg) => app.log.info(msg),
|
|
14517
14941
|
warn: (msg) => app.log.warn(msg)
|
|
@@ -14552,8 +14976,8 @@ async function startDaemon(config, serviceToken) {
|
|
|
14552
14976
|
await app.listen({ host: config.daemon.host, port: config.daemon.port });
|
|
14553
14977
|
const address = app.server.address();
|
|
14554
14978
|
const boundPort = address && typeof address === "object" ? address.port : config.daemon.port;
|
|
14555
|
-
await
|
|
14556
|
-
await
|
|
14979
|
+
await fsp5.mkdir(paths.home(), { recursive: true });
|
|
14980
|
+
await fsp5.writeFile(
|
|
14557
14981
|
paths.pidFile(),
|
|
14558
14982
|
JSON.stringify({
|
|
14559
14983
|
pid: process.pid,
|
|
@@ -14587,6 +15011,7 @@ async function startDaemon(config, serviceToken) {
|
|
|
14587
15011
|
await manager.flushMetaWrites();
|
|
14588
15012
|
setBinaryInstallLogger(null);
|
|
14589
15013
|
setNpmInstallLogger(null);
|
|
15014
|
+
setAgentPruneLogger(null);
|
|
14590
15015
|
await app.close();
|
|
14591
15016
|
try {
|
|
14592
15017
|
fs15.unlinkSync(paths.pidFile());
|
|
@@ -14629,12 +15054,12 @@ init_daemon_bootstrap();
|
|
|
14629
15054
|
|
|
14630
15055
|
// src/cli/commands/log-tail.ts
|
|
14631
15056
|
import * as fs16 from "fs";
|
|
14632
|
-
import * as
|
|
15057
|
+
import * as fsp6 from "fs/promises";
|
|
14633
15058
|
async function runLogTail(logPath, argv, notFoundMessage) {
|
|
14634
15059
|
const opts = parseLogTailFlags(argv);
|
|
14635
15060
|
let stat4;
|
|
14636
15061
|
try {
|
|
14637
|
-
stat4 = await
|
|
15062
|
+
stat4 = await fsp6.stat(logPath);
|
|
14638
15063
|
} catch (err) {
|
|
14639
15064
|
const e = err;
|
|
14640
15065
|
if (e.code === "ENOENT") {
|
|
@@ -14660,14 +15085,14 @@ async function runLogTail(logPath, argv, notFoundMessage) {
|
|
|
14660
15085
|
setImmediate(async () => {
|
|
14661
15086
|
pending = false;
|
|
14662
15087
|
try {
|
|
14663
|
-
const s = await
|
|
15088
|
+
const s = await fsp6.stat(logPath);
|
|
14664
15089
|
if (s.size <= position) {
|
|
14665
15090
|
if (s.size < position) {
|
|
14666
15091
|
position = s.size;
|
|
14667
15092
|
}
|
|
14668
15093
|
return;
|
|
14669
15094
|
}
|
|
14670
|
-
const fd = await
|
|
15095
|
+
const fd = await fsp6.open(logPath, "r");
|
|
14671
15096
|
try {
|
|
14672
15097
|
const buf = Buffer.alloc(s.size - position);
|
|
14673
15098
|
await fd.read(buf, 0, buf.length, position);
|
|
@@ -14694,7 +15119,7 @@ async function printTail(logPath, fileSize, lines) {
|
|
|
14694
15119
|
return fileSize;
|
|
14695
15120
|
}
|
|
14696
15121
|
const CHUNK = 64 * 1024;
|
|
14697
|
-
const fd = await
|
|
15122
|
+
const fd = await fsp6.open(logPath, "r");
|
|
14698
15123
|
try {
|
|
14699
15124
|
let position = fileSize;
|
|
14700
15125
|
let collected = "";
|
|
@@ -14866,7 +15291,7 @@ async function runDaemonStatus() {
|
|
|
14866
15291
|
}
|
|
14867
15292
|
async function readPidFile() {
|
|
14868
15293
|
try {
|
|
14869
|
-
const raw = await
|
|
15294
|
+
const raw = await fsp7.readFile(paths.pidFile(), "utf8");
|
|
14870
15295
|
return JSON.parse(raw);
|
|
14871
15296
|
} catch (err) {
|
|
14872
15297
|
const e = err;
|
|
@@ -14892,7 +15317,7 @@ init_sessions();
|
|
|
14892
15317
|
init_config();
|
|
14893
15318
|
init_service_token();
|
|
14894
15319
|
init_paths();
|
|
14895
|
-
import * as
|
|
15320
|
+
import * as fsp8 from "fs/promises";
|
|
14896
15321
|
init_sessions();
|
|
14897
15322
|
async function runExtensionsList() {
|
|
14898
15323
|
const config = await loadConfig();
|
|
@@ -15091,11 +15516,11 @@ async function runExtensionsRemove(name) {
|
|
|
15091
15516
|
}
|
|
15092
15517
|
}
|
|
15093
15518
|
async function readRawConfig() {
|
|
15094
|
-
const raw = await
|
|
15519
|
+
const raw = await fsp8.readFile(paths.config(), "utf8");
|
|
15095
15520
|
return JSON.parse(raw);
|
|
15096
15521
|
}
|
|
15097
15522
|
async function writeRawConfig(raw) {
|
|
15098
|
-
await
|
|
15523
|
+
await fsp8.writeFile(
|
|
15099
15524
|
paths.config(),
|
|
15100
15525
|
JSON.stringify(raw, null, 2) + "\n",
|
|
15101
15526
|
{ encoding: "utf8", mode: 384 }
|