@hydra-acp/cli 0.1.6 → 0.1.7
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/README.md +2 -0
- package/dist/cli.js +493 -160
- package/dist/index.d.ts +9 -1
- package/dist/index.js +127 -34
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -32,6 +32,9 @@ var init_paths = __esm({
|
|
|
32
32
|
paths = {
|
|
33
33
|
home: hydraHome,
|
|
34
34
|
config: () => path.join(hydraHome(), "config.json"),
|
|
35
|
+
// Auth token lives in its own file so config.json can be version-
|
|
36
|
+
// controlled without leaking the secret. Raw string contents, mode 0600.
|
|
37
|
+
authToken: () => path.join(hydraHome(), "auth-token"),
|
|
35
38
|
pidFile: () => path.join(hydraHome(), "daemon.pid"),
|
|
36
39
|
logFile: () => path.join(hydraHome(), "daemon.log"),
|
|
37
40
|
currentLogFile: () => path.join(hydraHome(), "current.log"),
|
|
@@ -68,61 +71,95 @@ function extensionList(config) {
|
|
|
68
71
|
...body
|
|
69
72
|
}));
|
|
70
73
|
}
|
|
71
|
-
async function
|
|
72
|
-
const configPath = paths.config();
|
|
74
|
+
async function readConfigFile() {
|
|
73
75
|
let raw;
|
|
74
76
|
try {
|
|
75
|
-
raw = await fs.readFile(
|
|
77
|
+
raw = await fs.readFile(paths.config(), "utf8");
|
|
76
78
|
} catch (err) {
|
|
77
79
|
const e = err;
|
|
78
80
|
if (e.code === "ENOENT") {
|
|
79
|
-
|
|
80
|
-
`No config found at ${configPath}. Run \`hydra-acp init\` to create one.`
|
|
81
|
-
);
|
|
81
|
+
return {};
|
|
82
82
|
}
|
|
83
83
|
throw err;
|
|
84
84
|
}
|
|
85
|
-
|
|
86
|
-
return HydraConfig.parse(parsed);
|
|
85
|
+
return JSON.parse(raw);
|
|
87
86
|
}
|
|
88
|
-
async function
|
|
87
|
+
async function loadAuthToken() {
|
|
88
|
+
let tokenFile;
|
|
89
89
|
try {
|
|
90
|
-
await fs.
|
|
90
|
+
const text = await fs.readFile(paths.authToken(), "utf8");
|
|
91
|
+
const trimmed = text.trim();
|
|
92
|
+
if (trimmed.length > 0) {
|
|
93
|
+
tokenFile = trimmed;
|
|
94
|
+
}
|
|
91
95
|
} catch (err) {
|
|
92
96
|
const e = err;
|
|
93
97
|
if (e.code !== "ENOENT") {
|
|
94
98
|
throw err;
|
|
95
99
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
}
|
|
101
|
+
const raw = await readConfigFile();
|
|
102
|
+
const daemon = raw.daemon;
|
|
103
|
+
const legacy = daemon && typeof daemon.authToken === "string" ? daemon.authToken : void 0;
|
|
104
|
+
if (tokenFile && legacy) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Auth token present in both ${paths.authToken()} and ${paths.config()} (daemon.authToken). Remove daemon.authToken from config.json to resolve.`
|
|
100
107
|
);
|
|
101
|
-
return config;
|
|
102
108
|
}
|
|
103
|
-
|
|
109
|
+
if (tokenFile) {
|
|
110
|
+
return tokenFile;
|
|
111
|
+
}
|
|
112
|
+
if (legacy) {
|
|
113
|
+
await migrateLegacyAuthToken(raw, daemon, legacy);
|
|
114
|
+
return legacy;
|
|
115
|
+
}
|
|
116
|
+
return void 0;
|
|
104
117
|
}
|
|
105
|
-
async function
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
async function migrateLegacyAuthToken(raw, daemon, token) {
|
|
119
|
+
await writeAuthToken(token);
|
|
120
|
+
delete daemon.authToken;
|
|
121
|
+
if (Object.keys(daemon).length === 0) {
|
|
122
|
+
delete raw.daemon;
|
|
123
|
+
}
|
|
124
|
+
await fs.writeFile(paths.config(), JSON.stringify(raw, null, 2) + "\n", {
|
|
110
125
|
encoding: "utf8",
|
|
111
126
|
mode: 384
|
|
112
127
|
});
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
await fs.writeFile(path7, JSON.stringify(raw, null, 2) + "\n", {
|
|
128
|
+
process.stderr.write(
|
|
129
|
+
`hydra-acp: migrated auth token from ${paths.config()} to ${paths.authToken()}.
|
|
130
|
+
`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
async function writeAuthToken(token) {
|
|
134
|
+
await fs.mkdir(paths.home(), { recursive: true });
|
|
135
|
+
await fs.writeFile(paths.authToken(), token + "\n", {
|
|
122
136
|
encoding: "utf8",
|
|
123
137
|
mode: 384
|
|
124
138
|
});
|
|
125
139
|
}
|
|
140
|
+
async function loadConfig() {
|
|
141
|
+
const token = await loadAuthToken();
|
|
142
|
+
if (!token) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`No auth token found at ${paths.authToken()}. Run \`hydra-acp init\` to create one.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const raw = await readConfigFile();
|
|
148
|
+
const daemon = raw.daemon ??= {};
|
|
149
|
+
daemon.authToken = token;
|
|
150
|
+
return HydraConfig.parse(raw);
|
|
151
|
+
}
|
|
152
|
+
async function ensureConfig() {
|
|
153
|
+
if (!await loadAuthToken()) {
|
|
154
|
+
const token = generateAuthToken();
|
|
155
|
+
await writeAuthToken(token);
|
|
156
|
+
process.stderr.write(
|
|
157
|
+
`hydra-acp: initialized ${paths.authToken()} with a fresh auth token.
|
|
158
|
+
`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return loadConfig();
|
|
162
|
+
}
|
|
126
163
|
function generateAuthToken() {
|
|
127
164
|
const bytes = new Uint8Array(32);
|
|
128
165
|
crypto.getRandomValues(bytes);
|
|
@@ -177,7 +214,13 @@ var init_config = __esm({
|
|
|
177
214
|
// Cap on logical lines retained in the in-memory scrollback render
|
|
178
215
|
// buffer. Oldest lines are dropped on overflow. The on-disk session
|
|
179
216
|
// history is unaffected; this only bounds the TUI's local view buffer.
|
|
180
|
-
maxScrollbackLines: z.number().int().positive().default(1e4)
|
|
217
|
+
maxScrollbackLines: z.number().int().positive().default(1e4),
|
|
218
|
+
// When true (default), the TUI captures mouse events so the wheel can
|
|
219
|
+
// drive scrollback. The cost: terminals route clicks to the app, so
|
|
220
|
+
// text selection requires shift+drag to bypass mouse reporting. Set
|
|
221
|
+
// false to disable capture — wheel scrollback stops working, but
|
|
222
|
+
// plain click-drag selects text via the terminal emulator.
|
|
223
|
+
mouse: z.boolean().default(true)
|
|
181
224
|
});
|
|
182
225
|
ExtensionName = z.string().min(1).regex(/^[A-Za-z0-9._-]+$/, "extension name must be filename-safe");
|
|
183
226
|
ExtensionBody = z.object({
|
|
@@ -210,7 +253,11 @@ var init_config = __esm({
|
|
|
210
253
|
// recency and truncated to this count. `--all` overrides in the CLI.
|
|
211
254
|
sessionListColdLimit: z.number().int().nonnegative().default(20),
|
|
212
255
|
extensions: z.record(ExtensionName, ExtensionBody).default({}),
|
|
213
|
-
tui: TuiConfig.default({
|
|
256
|
+
tui: TuiConfig.default({
|
|
257
|
+
repaintThrottleMs: 1e3,
|
|
258
|
+
maxScrollbackLines: 1e4,
|
|
259
|
+
mouse: true
|
|
260
|
+
})
|
|
214
261
|
});
|
|
215
262
|
}
|
|
216
263
|
});
|
|
@@ -395,7 +442,7 @@ var init_connection = __esm({
|
|
|
395
442
|
"src/acp/connection.ts"() {
|
|
396
443
|
"use strict";
|
|
397
444
|
init_types();
|
|
398
|
-
JsonRpcConnection = class {
|
|
445
|
+
JsonRpcConnection = class _JsonRpcConnection {
|
|
399
446
|
constructor(stream) {
|
|
400
447
|
this.stream = stream;
|
|
401
448
|
this.stream.onMessage((m) => this.handleIncoming(m));
|
|
@@ -405,6 +452,16 @@ var init_connection = __esm({
|
|
|
405
452
|
requestHandlers = /* @__PURE__ */ new Map();
|
|
406
453
|
defaultRequestHandler;
|
|
407
454
|
notificationHandlers = /* @__PURE__ */ new Map();
|
|
455
|
+
// Notifications received before a handler was registered. Some agents
|
|
456
|
+
// (e.g. claude-acp) advertise their command list in the same chunk as
|
|
457
|
+
// the `session/new` response, which is processed before the consumer
|
|
458
|
+
// can attach its `session/update` handler. Without this buffer those
|
|
459
|
+
// notifications would be silently dropped, so e.g. `/model` would
|
|
460
|
+
// never appear in the TUI's slash-completion palette. Capped per
|
|
461
|
+
// method to keep the buffer from growing unboundedly when nothing
|
|
462
|
+
// ever subscribes.
|
|
463
|
+
bufferedNotifications = /* @__PURE__ */ new Map();
|
|
464
|
+
static MAX_BUFFERED_PER_METHOD = 64;
|
|
408
465
|
pending = /* @__PURE__ */ new Map();
|
|
409
466
|
closed = false;
|
|
410
467
|
closeHandlers = [];
|
|
@@ -416,6 +473,17 @@ var init_connection = __esm({
|
|
|
416
473
|
}
|
|
417
474
|
onNotification(method, handler) {
|
|
418
475
|
this.notificationHandlers.set(method, handler);
|
|
476
|
+
const queued = this.bufferedNotifications.get(method);
|
|
477
|
+
if (!queued) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
this.bufferedNotifications.delete(method);
|
|
481
|
+
for (const note of queued) {
|
|
482
|
+
try {
|
|
483
|
+
handler(note.params, note.method);
|
|
484
|
+
} catch {
|
|
485
|
+
}
|
|
486
|
+
}
|
|
419
487
|
}
|
|
420
488
|
onClose(handler) {
|
|
421
489
|
this.closeHandlers.push(handler);
|
|
@@ -501,6 +569,16 @@ var init_connection = __esm({
|
|
|
501
569
|
const handler = this.notificationHandlers.get(note.method);
|
|
502
570
|
if (handler) {
|
|
503
571
|
handler(note.params, note.method);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
let queued = this.bufferedNotifications.get(note.method);
|
|
575
|
+
if (!queued) {
|
|
576
|
+
queued = [];
|
|
577
|
+
this.bufferedNotifications.set(note.method, queued);
|
|
578
|
+
}
|
|
579
|
+
queued.push(note);
|
|
580
|
+
if (queued.length > _JsonRpcConnection.MAX_BUFFERED_PER_METHOD) {
|
|
581
|
+
queued.shift();
|
|
504
582
|
}
|
|
505
583
|
}
|
|
506
584
|
handleResponse(res) {
|
|
@@ -557,12 +635,12 @@ var init_hydra_commands = __esm({
|
|
|
557
635
|
HYDRA_COMMANDS = [
|
|
558
636
|
{
|
|
559
637
|
verb: "title",
|
|
560
|
-
name: "
|
|
638
|
+
name: "hydra title",
|
|
561
639
|
description: "Regenerate the session title via the agent (or set manually with an arg)"
|
|
562
640
|
},
|
|
563
641
|
{
|
|
564
642
|
verb: "agent",
|
|
565
|
-
name: "
|
|
643
|
+
name: "hydra agent",
|
|
566
644
|
argsHint: "<agent>",
|
|
567
645
|
description: "Swap the agent backing this session, preserving context"
|
|
568
646
|
}
|
|
@@ -3437,6 +3515,7 @@ var init_screen = __esm({
|
|
|
3437
3515
|
streamingActive = false;
|
|
3438
3516
|
lastPromptRows = 0;
|
|
3439
3517
|
queuedTexts = [];
|
|
3518
|
+
lastQueueEditingIndex = -1;
|
|
3440
3519
|
repaintPaused = 0;
|
|
3441
3520
|
repaintPending = false;
|
|
3442
3521
|
lastRepaintAt = 0;
|
|
@@ -3493,12 +3572,14 @@ var init_screen = __esm({
|
|
|
3493
3572
|
pasteActive = false;
|
|
3494
3573
|
pasteBuffer = "";
|
|
3495
3574
|
rawStdinHandler;
|
|
3575
|
+
mouseEnabled;
|
|
3496
3576
|
constructor(opts) {
|
|
3497
3577
|
this.term = opts.term;
|
|
3498
3578
|
this.dispatcher = opts.dispatcher;
|
|
3499
3579
|
this.onKey = opts.onKey;
|
|
3500
3580
|
this.contentRepaintThrottleMs = opts.repaintThrottleMs ?? DEFAULT_CONTENT_REPAINT_THROTTLE_MS;
|
|
3501
3581
|
this.maxScrollbackLines = opts.maxScrollbackLines ?? DEFAULT_MAX_SCROLLBACK_LINES;
|
|
3582
|
+
this.mouseEnabled = opts.mouse ?? true;
|
|
3502
3583
|
this.resizeHandler = () => this.repaint();
|
|
3503
3584
|
this.keyHandler = (name, _matches, data) => this.handleKey(name, data);
|
|
3504
3585
|
this.mouseHandler = (name) => this.handleMouse(name);
|
|
@@ -3515,10 +3596,16 @@ var init_screen = __esm({
|
|
|
3515
3596
|
this.lastFrameH = 0;
|
|
3516
3597
|
this.lastWindowTitle = null;
|
|
3517
3598
|
process.stdout.write("\x1B[?7l");
|
|
3518
|
-
this.
|
|
3599
|
+
if (this.mouseEnabled) {
|
|
3600
|
+
this.term.grabInput({ mouse: "button" });
|
|
3601
|
+
} else {
|
|
3602
|
+
this.term.grabInput(true);
|
|
3603
|
+
}
|
|
3519
3604
|
this.term.hideCursor(false);
|
|
3520
3605
|
this.term.on("key", this.keyHandler);
|
|
3521
|
-
|
|
3606
|
+
if (this.mouseEnabled) {
|
|
3607
|
+
this.term.on("mouse", this.mouseHandler);
|
|
3608
|
+
}
|
|
3522
3609
|
this.term.on("resize", this.resizeHandler);
|
|
3523
3610
|
this.installBracketedPaste();
|
|
3524
3611
|
this.repaint();
|
|
@@ -3530,7 +3617,9 @@ var init_screen = __esm({
|
|
|
3530
3617
|
this.started = false;
|
|
3531
3618
|
this.uninstallBracketedPaste();
|
|
3532
3619
|
this.term.off("key", this.keyHandler);
|
|
3533
|
-
|
|
3620
|
+
if (this.mouseEnabled) {
|
|
3621
|
+
this.term.off("mouse", this.mouseHandler);
|
|
3622
|
+
}
|
|
3534
3623
|
this.term.off("resize", this.resizeHandler);
|
|
3535
3624
|
this.term.grabInput(false);
|
|
3536
3625
|
this.term.hideCursor(false);
|
|
@@ -3880,6 +3969,7 @@ var init_screen = __esm({
|
|
|
3880
3969
|
}
|
|
3881
3970
|
setQueuedPrompts(texts) {
|
|
3882
3971
|
this.queuedTexts = [...texts];
|
|
3972
|
+
this.lastQueueEditingIndex = this.dispatcher.state().queueIndex;
|
|
3883
3973
|
this.repaint();
|
|
3884
3974
|
}
|
|
3885
3975
|
// While a permission prompt is active, the prompt area is replaced with
|
|
@@ -3932,12 +4022,19 @@ var init_screen = __esm({
|
|
|
3932
4022
|
// row count changed (alt+enter added a line, backspace joined two), the
|
|
3933
4023
|
// separator and scrollback bottom shift, so we need a full repaint;
|
|
3934
4024
|
// otherwise an in-place prompt redraw is enough. (Queued-zone changes
|
|
3935
|
-
// already trigger a full repaint via setQueuedPrompts.)
|
|
4025
|
+
// already trigger a full repaint via setQueuedPrompts.) Queue-edit
|
|
4026
|
+
// navigation may also change which queued row is marked, so check
|
|
4027
|
+
// for that and redraw just that zone in-place.
|
|
3936
4028
|
refreshPrompt() {
|
|
3937
4029
|
if (this.promptRows() !== this.lastPromptRows) {
|
|
3938
4030
|
this.repaint();
|
|
3939
4031
|
return;
|
|
3940
4032
|
}
|
|
4033
|
+
const editingIndex = this.dispatcher.state().queueIndex;
|
|
4034
|
+
if (editingIndex !== this.lastQueueEditingIndex) {
|
|
4035
|
+
this.lastQueueEditingIndex = editingIndex;
|
|
4036
|
+
this.drawQueuedZone();
|
|
4037
|
+
}
|
|
3941
4038
|
this.drawPrompt();
|
|
3942
4039
|
this.placeCursor();
|
|
3943
4040
|
}
|
|
@@ -3988,6 +4085,14 @@ var init_screen = __esm({
|
|
|
3988
4085
|
this.scrollOffset = 0;
|
|
3989
4086
|
this.repaint();
|
|
3990
4087
|
}
|
|
4088
|
+
scrollToTop() {
|
|
4089
|
+
const max = this.maxScrollOffset();
|
|
4090
|
+
if (this.scrollOffset === max) {
|
|
4091
|
+
return;
|
|
4092
|
+
}
|
|
4093
|
+
this.scrollOffset = max;
|
|
4094
|
+
this.repaint();
|
|
4095
|
+
}
|
|
3991
4096
|
scrollPageSize() {
|
|
3992
4097
|
return Math.max(1, this.scrollbackVisibleRows() - 2);
|
|
3993
4098
|
}
|
|
@@ -4215,19 +4320,26 @@ var init_screen = __esm({
|
|
|
4215
4320
|
const separatorRow = this.term.height - promptRows - BANNER_ROWS;
|
|
4216
4321
|
const queuedBottom = separatorRow - 1;
|
|
4217
4322
|
const queuedTop = queuedBottom - rows + 1;
|
|
4323
|
+
const editingIndex = this.dispatcher.state().queueIndex;
|
|
4218
4324
|
for (let i = 0; i < rows; i++) {
|
|
4219
4325
|
const row = queuedTop + i;
|
|
4220
4326
|
const text = this.queuedTexts[i];
|
|
4221
4327
|
const isLast = i === rows - 1 && this.queuedTexts.length > MAX_QUEUED_ROWS;
|
|
4222
4328
|
const overflow = this.queuedTexts.length - MAX_QUEUED_ROWS;
|
|
4223
4329
|
const summary = text === void 0 ? "" : isLast ? `+ ${overflow + 1} more queued` : truncate(firstLine2(text), w - 4);
|
|
4224
|
-
const
|
|
4330
|
+
const editing = !isLast && i === editingIndex;
|
|
4331
|
+
const sig = text === void 0 ? `queued|${w}|empty` : `queued|${w}|${editing ? "edit" : isLast ? "ovf" : "row"}|${summary}`;
|
|
4225
4332
|
this.paintRow(row, sig, () => {
|
|
4226
4333
|
if (text === void 0) {
|
|
4227
4334
|
return;
|
|
4228
4335
|
}
|
|
4229
|
-
const
|
|
4230
|
-
const padded =
|
|
4336
|
+
const rest = `\u23F3 ${summary}`;
|
|
4337
|
+
const padded = rest + " ".repeat(Math.max(0, w - 1 - rest.length));
|
|
4338
|
+
if (editing) {
|
|
4339
|
+
this.term.bgBlue.brightYellow("\u25B8");
|
|
4340
|
+
} else {
|
|
4341
|
+
this.term.bgBlue(" ");
|
|
4342
|
+
}
|
|
4231
4343
|
this.term.bgBlue.brightWhite.noFormat(padded);
|
|
4232
4344
|
});
|
|
4233
4345
|
}
|
|
@@ -4533,8 +4645,17 @@ var init_input = __esm({
|
|
|
4533
4645
|
col = 0;
|
|
4534
4646
|
planMode = false;
|
|
4535
4647
|
historyIndex = -1;
|
|
4648
|
+
// Queue editing: when the user walks Up past row 0 with queued prompts
|
|
4649
|
+
// present, the most-recently-queued item lands in the buffer and
|
|
4650
|
+
// queueIndex tracks which slot of `queue` is being edited. Enter submits
|
|
4651
|
+
// the edit (queue-edit) or, on an empty buffer, drops the slot
|
|
4652
|
+
// (queue-remove). -1 means not editing a queue slot.
|
|
4653
|
+
queueIndex = -1;
|
|
4536
4654
|
savedDraft = null;
|
|
4537
4655
|
history = [];
|
|
4656
|
+
// Waiting queue snapshot (excludes the in-flight head). Newest item lives
|
|
4657
|
+
// at the end so Up walks the array right-to-left.
|
|
4658
|
+
queue = [];
|
|
4538
4659
|
turnRunning = false;
|
|
4539
4660
|
// Single-slot kill ring. The most recent killed text (^U, ^K, ^W) lands
|
|
4540
4661
|
// here so ^Y can yank it back. Standard readline keeps a stack; we
|
|
@@ -4550,7 +4671,8 @@ var init_input = __esm({
|
|
|
4550
4671
|
row: this.row,
|
|
4551
4672
|
col: this.col,
|
|
4552
4673
|
planMode: this.planMode,
|
|
4553
|
-
historyIndex: this.historyIndex
|
|
4674
|
+
historyIndex: this.historyIndex,
|
|
4675
|
+
queueIndex: this.queueIndex
|
|
4554
4676
|
};
|
|
4555
4677
|
}
|
|
4556
4678
|
setTurnRunning(running) {
|
|
@@ -4561,6 +4683,16 @@ var init_input = __esm({
|
|
|
4561
4683
|
this.historyIndex = -1;
|
|
4562
4684
|
this.savedDraft = null;
|
|
4563
4685
|
}
|
|
4686
|
+
// Snapshot of the waiting queue (head excluded). Called by the app after
|
|
4687
|
+
// every queue mutation so Up/Down can walk a fresh view. queueIndex is
|
|
4688
|
+
// only invalidated when it falls outside the new bounds — staying in
|
|
4689
|
+
// bounds preserves the user's edit if the queue grew or stayed put.
|
|
4690
|
+
setQueue(queue) {
|
|
4691
|
+
this.queue = [...queue];
|
|
4692
|
+
if (this.queueIndex >= this.queue.length) {
|
|
4693
|
+
this.queueIndex = -1;
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4564
4696
|
// Replace the contents of the first row, leaving subsequent rows alone.
|
|
4565
4697
|
// Used by slash-command completion: the partial /foo gets swapped for the
|
|
4566
4698
|
// matched command name. Cursor moves to the end of the replacement.
|
|
@@ -4570,6 +4702,15 @@ var init_input = __esm({
|
|
|
4570
4702
|
this.col = text.length;
|
|
4571
4703
|
}
|
|
4572
4704
|
}
|
|
4705
|
+
// Public seed for the buffer (used for Escape pre-fill). Treated like a
|
|
4706
|
+
// fresh draft: nav state and any saved draft are cleared, cursor lands
|
|
4707
|
+
// at the end so the user can edit immediately.
|
|
4708
|
+
setBuffer(text) {
|
|
4709
|
+
this.loadEntry(text);
|
|
4710
|
+
this.historyIndex = -1;
|
|
4711
|
+
this.queueIndex = -1;
|
|
4712
|
+
this.savedDraft = null;
|
|
4713
|
+
}
|
|
4573
4714
|
feed(event) {
|
|
4574
4715
|
if (event.type === "char") {
|
|
4575
4716
|
this.insertChar(event.ch);
|
|
@@ -4607,14 +4748,16 @@ var init_input = __esm({
|
|
|
4607
4748
|
case "right":
|
|
4608
4749
|
this.moveRight();
|
|
4609
4750
|
return [];
|
|
4610
|
-
case "home":
|
|
4611
4751
|
case "ctrl-a":
|
|
4612
4752
|
this.col = 0;
|
|
4613
4753
|
return [];
|
|
4614
|
-
case "end":
|
|
4615
4754
|
case "ctrl-e":
|
|
4616
4755
|
this.col = this.currentLine().length;
|
|
4617
4756
|
return [];
|
|
4757
|
+
case "home":
|
|
4758
|
+
return this.handleHome();
|
|
4759
|
+
case "end":
|
|
4760
|
+
return this.handleEnd();
|
|
4618
4761
|
case "ctrl-b":
|
|
4619
4762
|
this.moveLeft();
|
|
4620
4763
|
return [];
|
|
@@ -4637,7 +4780,11 @@ var init_input = __esm({
|
|
|
4637
4780
|
case "ctrl-c":
|
|
4638
4781
|
return this.handleCtrlC();
|
|
4639
4782
|
case "ctrl-d":
|
|
4640
|
-
|
|
4783
|
+
if (this.bufferIsEmpty()) {
|
|
4784
|
+
return [{ type: "exit" }];
|
|
4785
|
+
}
|
|
4786
|
+
this.deleteForward();
|
|
4787
|
+
return [];
|
|
4641
4788
|
case "ctrl-l":
|
|
4642
4789
|
return [{ type: "redraw" }];
|
|
4643
4790
|
case "ctrl-p":
|
|
@@ -4652,6 +4799,9 @@ var init_input = __esm({
|
|
|
4652
4799
|
this.yank();
|
|
4653
4800
|
return [];
|
|
4654
4801
|
case "escape":
|
|
4802
|
+
if (this.turnRunning) {
|
|
4803
|
+
return [{ type: "cancel", prefill: true }];
|
|
4804
|
+
}
|
|
4655
4805
|
return [];
|
|
4656
4806
|
}
|
|
4657
4807
|
}
|
|
@@ -4672,6 +4822,7 @@ var init_input = __esm({
|
|
|
4672
4822
|
this.row = 0;
|
|
4673
4823
|
this.col = 0;
|
|
4674
4824
|
this.historyIndex = -1;
|
|
4825
|
+
this.queueIndex = -1;
|
|
4675
4826
|
this.savedDraft = null;
|
|
4676
4827
|
}
|
|
4677
4828
|
insertChar(ch) {
|
|
@@ -4805,50 +4956,92 @@ var init_input = __esm({
|
|
|
4805
4956
|
this.col = 0;
|
|
4806
4957
|
}
|
|
4807
4958
|
}
|
|
4808
|
-
// Up
|
|
4809
|
-
//
|
|
4959
|
+
// Up walks the navigation stack from newest to oldest: pending queue
|
|
4960
|
+
// items first (so the user can edit something they just enqueued),
|
|
4961
|
+
// then prompt history. Cursor movement within a multi-line buffer
|
|
4962
|
+
// takes priority when not already navigating.
|
|
4810
4963
|
handleUp() {
|
|
4811
4964
|
if (this.row > 0) {
|
|
4812
4965
|
this.row -= 1;
|
|
4813
4966
|
this.col = Math.min(this.col, this.currentLine().length);
|
|
4814
4967
|
return [];
|
|
4815
4968
|
}
|
|
4816
|
-
if (this.
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4969
|
+
if (this.queueIndex === -1 && this.historyIndex === -1) {
|
|
4970
|
+
if (this.queue.length === 0 && this.history.length === 0) {
|
|
4971
|
+
return [];
|
|
4972
|
+
}
|
|
4820
4973
|
this.savedDraft = {
|
|
4821
4974
|
buffer: [...this.buffer],
|
|
4822
4975
|
row: this.row,
|
|
4823
4976
|
col: this.col
|
|
4824
4977
|
};
|
|
4978
|
+
if (this.queue.length > 0) {
|
|
4979
|
+
this.queueIndex = this.queue.length - 1;
|
|
4980
|
+
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
4981
|
+
} else {
|
|
4982
|
+
this.historyIndex = this.history.length - 1;
|
|
4983
|
+
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
4984
|
+
}
|
|
4985
|
+
return [];
|
|
4986
|
+
}
|
|
4987
|
+
if (this.queueIndex >= 0) {
|
|
4988
|
+
if (this.queueIndex > 0) {
|
|
4989
|
+
this.queueIndex -= 1;
|
|
4990
|
+
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
4991
|
+
return [];
|
|
4992
|
+
}
|
|
4993
|
+
if (this.history.length === 0) {
|
|
4994
|
+
return [];
|
|
4995
|
+
}
|
|
4996
|
+
this.queueIndex = -1;
|
|
4825
4997
|
this.historyIndex = this.history.length - 1;
|
|
4826
|
-
|
|
4827
|
-
this.historyIndex -= 1;
|
|
4828
|
-
} else {
|
|
4998
|
+
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
4829
4999
|
return [];
|
|
4830
5000
|
}
|
|
4831
|
-
|
|
5001
|
+
if (this.historyIndex > 0) {
|
|
5002
|
+
this.historyIndex -= 1;
|
|
5003
|
+
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
5004
|
+
}
|
|
4832
5005
|
return [];
|
|
4833
5006
|
}
|
|
4834
|
-
// Down
|
|
4835
|
-
//
|
|
4836
|
-
//
|
|
5007
|
+
// Down reverses the Up walk: history (older → newer), then queue
|
|
5008
|
+
// (oldest → newest), then restore the original draft. Within a
|
|
5009
|
+
// multi-line buffer, plain cursor movement still wins when no
|
|
5010
|
+
// navigation is in progress.
|
|
4837
5011
|
handleDown() {
|
|
4838
|
-
if (this.row < this.buffer.length - 1 && this.historyIndex === -1) {
|
|
5012
|
+
if (this.row < this.buffer.length - 1 && this.historyIndex === -1 && this.queueIndex === -1) {
|
|
4839
5013
|
this.row += 1;
|
|
4840
5014
|
this.col = Math.min(this.col, this.currentLine().length);
|
|
4841
5015
|
return [];
|
|
4842
5016
|
}
|
|
4843
|
-
if (this.historyIndex
|
|
5017
|
+
if (this.historyIndex >= 0) {
|
|
5018
|
+
if (this.historyIndex < this.history.length - 1) {
|
|
5019
|
+
this.historyIndex += 1;
|
|
5020
|
+
this.loadEntry(this.history[this.historyIndex] ?? "");
|
|
5021
|
+
return [];
|
|
5022
|
+
}
|
|
5023
|
+
this.historyIndex = -1;
|
|
5024
|
+
if (this.queue.length > 0) {
|
|
5025
|
+
this.queueIndex = 0;
|
|
5026
|
+
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
5027
|
+
return [];
|
|
5028
|
+
}
|
|
5029
|
+
this.restoreDraft();
|
|
4844
5030
|
return [];
|
|
4845
5031
|
}
|
|
4846
|
-
if (this.
|
|
4847
|
-
this.
|
|
4848
|
-
|
|
5032
|
+
if (this.queueIndex >= 0) {
|
|
5033
|
+
if (this.queueIndex < this.queue.length - 1) {
|
|
5034
|
+
this.queueIndex += 1;
|
|
5035
|
+
this.loadEntry(this.queue[this.queueIndex] ?? "");
|
|
5036
|
+
return [];
|
|
5037
|
+
}
|
|
5038
|
+
this.queueIndex = -1;
|
|
5039
|
+
this.restoreDraft();
|
|
4849
5040
|
return [];
|
|
4850
5041
|
}
|
|
4851
|
-
|
|
5042
|
+
return [];
|
|
5043
|
+
}
|
|
5044
|
+
restoreDraft() {
|
|
4852
5045
|
if (this.savedDraft) {
|
|
4853
5046
|
this.buffer = [...this.savedDraft.buffer];
|
|
4854
5047
|
this.row = this.savedDraft.row;
|
|
@@ -4857,11 +5050,9 @@ var init_input = __esm({
|
|
|
4857
5050
|
} else {
|
|
4858
5051
|
this.clearBuffer();
|
|
4859
5052
|
}
|
|
4860
|
-
return [];
|
|
4861
5053
|
}
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
this.buffer = entry.split("\n");
|
|
5054
|
+
loadEntry(text) {
|
|
5055
|
+
this.buffer = text.split("\n");
|
|
4865
5056
|
if (this.buffer.length === 0) {
|
|
4866
5057
|
this.buffer = [""];
|
|
4867
5058
|
}
|
|
@@ -4870,6 +5061,14 @@ var init_input = __esm({
|
|
|
4870
5061
|
}
|
|
4871
5062
|
send() {
|
|
4872
5063
|
const text = this.bufferText();
|
|
5064
|
+
if (this.queueIndex >= 0 && this.queueIndex < this.queue.length) {
|
|
5065
|
+
const index = this.queueIndex;
|
|
5066
|
+
this.clearBuffer();
|
|
5067
|
+
if (text.trim().length === 0) {
|
|
5068
|
+
return [{ type: "queue-remove", index }];
|
|
5069
|
+
}
|
|
5070
|
+
return [{ type: "queue-edit", index, text }];
|
|
5071
|
+
}
|
|
4873
5072
|
if (text.trim().length === 0) {
|
|
4874
5073
|
return [];
|
|
4875
5074
|
}
|
|
@@ -4877,25 +5076,105 @@ var init_input = __esm({
|
|
|
4877
5076
|
this.clearBuffer();
|
|
4878
5077
|
return [{ type: "send", text, planMode }];
|
|
4879
5078
|
}
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
5079
|
+
// Home: jump to the very start of the prompt buffer. If we're already
|
|
5080
|
+
// there, fall through to scrolling the scrollback to its top.
|
|
5081
|
+
handleHome() {
|
|
5082
|
+
if (this.row !== 0 || this.col !== 0) {
|
|
5083
|
+
this.row = 0;
|
|
5084
|
+
this.col = 0;
|
|
5085
|
+
return [];
|
|
5086
|
+
}
|
|
5087
|
+
return [{ type: "scroll-to-top" }];
|
|
5088
|
+
}
|
|
5089
|
+
// End: jump to the end of the last line of the prompt buffer. Already
|
|
5090
|
+
// there → scroll the scrollback to the bottom (newest).
|
|
5091
|
+
handleEnd() {
|
|
5092
|
+
const lastRow = this.buffer.length - 1;
|
|
5093
|
+
const lastCol = (this.buffer[lastRow] ?? "").length;
|
|
5094
|
+
if (this.row !== lastRow || this.col !== lastCol) {
|
|
5095
|
+
this.row = lastRow;
|
|
5096
|
+
this.col = lastCol;
|
|
5097
|
+
return [];
|
|
4883
5098
|
}
|
|
5099
|
+
return [{ type: "scroll-to-bottom" }];
|
|
5100
|
+
}
|
|
5101
|
+
handleCtrlC() {
|
|
4884
5102
|
if (!this.bufferIsEmpty()) {
|
|
4885
|
-
this.
|
|
5103
|
+
this.buffer = [""];
|
|
5104
|
+
this.row = 0;
|
|
5105
|
+
this.col = 0;
|
|
5106
|
+
if (this.queueIndex === -1) {
|
|
5107
|
+
this.historyIndex = -1;
|
|
5108
|
+
this.savedDraft = null;
|
|
5109
|
+
}
|
|
4886
5110
|
return [];
|
|
4887
5111
|
}
|
|
5112
|
+
if (this.queueIndex >= 0) {
|
|
5113
|
+
this.queueIndex = -1;
|
|
5114
|
+
this.restoreDraft();
|
|
5115
|
+
return [];
|
|
5116
|
+
}
|
|
5117
|
+
if (this.turnRunning) {
|
|
5118
|
+
return [{ type: "cancel" }];
|
|
5119
|
+
}
|
|
4888
5120
|
return [{ type: "exit" }];
|
|
4889
5121
|
}
|
|
4890
5122
|
};
|
|
4891
5123
|
}
|
|
4892
5124
|
});
|
|
4893
5125
|
|
|
5126
|
+
// src/tui/completion.ts
|
|
5127
|
+
function longestCommonPrefix(names) {
|
|
5128
|
+
if (names.length === 0) {
|
|
5129
|
+
return "";
|
|
5130
|
+
}
|
|
5131
|
+
let prefix = names[0] ?? "";
|
|
5132
|
+
for (let i = 1; i < names.length; i++) {
|
|
5133
|
+
const n = names[i] ?? "";
|
|
5134
|
+
let j = 0;
|
|
5135
|
+
while (j < prefix.length && j < n.length && prefix[j] === n[j]) {
|
|
5136
|
+
j += 1;
|
|
5137
|
+
}
|
|
5138
|
+
prefix = prefix.slice(0, j);
|
|
5139
|
+
if (prefix.length === 0) {
|
|
5140
|
+
break;
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
return prefix;
|
|
5144
|
+
}
|
|
5145
|
+
function computeTabCompletion(args) {
|
|
5146
|
+
const { matches, firstLine: firstLine3 } = args;
|
|
5147
|
+
if (matches.length === 0) {
|
|
5148
|
+
return null;
|
|
5149
|
+
}
|
|
5150
|
+
const space = firstLine3.indexOf(" ");
|
|
5151
|
+
const typedPrefix = space === -1 ? firstLine3 : firstLine3.slice(0, space);
|
|
5152
|
+
const tail = space === -1 ? "" : firstLine3.slice(space);
|
|
5153
|
+
if (matches.length === 1) {
|
|
5154
|
+
const name = matches[0] ?? "";
|
|
5155
|
+
const suffix = tail.startsWith(" ") ? "" : " ";
|
|
5156
|
+
return name + suffix + tail;
|
|
5157
|
+
}
|
|
5158
|
+
const commonPrefix = longestCommonPrefix(matches);
|
|
5159
|
+
if (commonPrefix.length <= typedPrefix.length) {
|
|
5160
|
+
return null;
|
|
5161
|
+
}
|
|
5162
|
+
return commonPrefix + tail;
|
|
5163
|
+
}
|
|
5164
|
+
var init_completion = __esm({
|
|
5165
|
+
"src/tui/completion.ts"() {
|
|
5166
|
+
"use strict";
|
|
5167
|
+
}
|
|
5168
|
+
});
|
|
5169
|
+
|
|
4894
5170
|
// src/tui/render-update.ts
|
|
4895
5171
|
import stripAnsi from "strip-ansi";
|
|
4896
5172
|
function sanitizeWireText(text) {
|
|
4897
5173
|
return stripAnsi(text).replace(STRIP_CONTROLS, "");
|
|
4898
5174
|
}
|
|
5175
|
+
function sanitizeSingleLine(text) {
|
|
5176
|
+
return sanitizeWireText(text).replace(/[\n\t]+/g, " ").replace(/ +/g, " ").trim();
|
|
5177
|
+
}
|
|
4899
5178
|
function mapUpdate(update) {
|
|
4900
5179
|
if (!update || typeof update !== "object") {
|
|
4901
5180
|
return null;
|
|
@@ -4939,7 +5218,7 @@ function mapUpdate(update) {
|
|
|
4939
5218
|
}
|
|
4940
5219
|
function mapSessionInfo(u) {
|
|
4941
5220
|
const rawTitle = readString(u, "title");
|
|
4942
|
-
const title = rawTitle !== void 0 ?
|
|
5221
|
+
const title = rawTitle !== void 0 ? sanitizeSingleLine(rawTitle) : void 0;
|
|
4943
5222
|
const meta = u._meta;
|
|
4944
5223
|
let agentId;
|
|
4945
5224
|
if (meta && typeof meta === "object" && !Array.isArray(meta)) {
|
|
@@ -4963,10 +5242,9 @@ function mapSessionInfo(u) {
|
|
|
4963
5242
|
}
|
|
4964
5243
|
return event;
|
|
4965
5244
|
}
|
|
4966
|
-
function
|
|
4967
|
-
const list = u.availableCommands ?? u.commands;
|
|
5245
|
+
function normalizeAdvertisedCommands(list) {
|
|
4968
5246
|
if (!Array.isArray(list)) {
|
|
4969
|
-
return
|
|
5247
|
+
return [];
|
|
4970
5248
|
}
|
|
4971
5249
|
const out = [];
|
|
4972
5250
|
for (const raw of list) {
|
|
@@ -4978,13 +5256,20 @@ function mapAvailableCommands(u) {
|
|
|
4978
5256
|
continue;
|
|
4979
5257
|
}
|
|
4980
5258
|
const rawName = c.name.startsWith("/") ? c.name : `/${c.name}`;
|
|
4981
|
-
const cmd = { name:
|
|
5259
|
+
const cmd = { name: sanitizeSingleLine(rawName) };
|
|
4982
5260
|
if (typeof c.description === "string") {
|
|
4983
|
-
cmd.description =
|
|
5261
|
+
cmd.description = sanitizeSingleLine(c.description);
|
|
4984
5262
|
}
|
|
4985
5263
|
out.push(cmd);
|
|
4986
5264
|
}
|
|
4987
|
-
return
|
|
5265
|
+
return out;
|
|
5266
|
+
}
|
|
5267
|
+
function mapAvailableCommands(u) {
|
|
5268
|
+
const list = u.availableCommands ?? u.commands;
|
|
5269
|
+
if (!Array.isArray(list)) {
|
|
5270
|
+
return null;
|
|
5271
|
+
}
|
|
5272
|
+
return { kind: "available-commands", commands: normalizeAdvertisedCommands(list) };
|
|
4988
5273
|
}
|
|
4989
5274
|
function mapUsage(u) {
|
|
4990
5275
|
const event = { kind: "usage-update" };
|
|
@@ -5046,7 +5331,7 @@ function mapToolCall(u) {
|
|
|
5046
5331
|
return null;
|
|
5047
5332
|
}
|
|
5048
5333
|
const rawTitle = readString(u, "title") ?? readString(u, "name") ?? readString(u, "label") ?? "tool call";
|
|
5049
|
-
const title =
|
|
5334
|
+
const title = sanitizeSingleLine(rawTitle);
|
|
5050
5335
|
const status = readString(u, "status");
|
|
5051
5336
|
const rawKind = readString(u, "kind");
|
|
5052
5337
|
const event = { kind: "tool-call", toolCallId, title };
|
|
@@ -5064,7 +5349,7 @@ function mapToolCallUpdate(u) {
|
|
|
5064
5349
|
return null;
|
|
5065
5350
|
}
|
|
5066
5351
|
const rawTitle = readString(u, "title");
|
|
5067
|
-
const title = rawTitle !== void 0 ?
|
|
5352
|
+
const title = rawTitle !== void 0 ? sanitizeSingleLine(rawTitle) : void 0;
|
|
5068
5353
|
const status = readString(u, "status");
|
|
5069
5354
|
const meaningful = title !== void 0 || status === "completed" || status === "failed" || status === "rejected" || status === "cancelled";
|
|
5070
5355
|
if (!meaningful) {
|
|
@@ -5090,7 +5375,7 @@ function mapPlan(u) {
|
|
|
5090
5375
|
continue;
|
|
5091
5376
|
}
|
|
5092
5377
|
const e = raw;
|
|
5093
|
-
const content = typeof e.content === "string" ?
|
|
5378
|
+
const content = typeof e.content === "string" ? sanitizeSingleLine(e.content) : void 0;
|
|
5094
5379
|
if (!content) {
|
|
5095
5380
|
continue;
|
|
5096
5381
|
}
|
|
@@ -5110,14 +5395,14 @@ function mapMode(u) {
|
|
|
5110
5395
|
if (!mode) {
|
|
5111
5396
|
return null;
|
|
5112
5397
|
}
|
|
5113
|
-
return { kind: "mode-changed", mode:
|
|
5398
|
+
return { kind: "mode-changed", mode: sanitizeSingleLine(mode) };
|
|
5114
5399
|
}
|
|
5115
5400
|
function mapModel(u) {
|
|
5116
5401
|
const model = readString(u, "currentModel") ?? readString(u, "model");
|
|
5117
5402
|
if (!model) {
|
|
5118
5403
|
return null;
|
|
5119
5404
|
}
|
|
5120
|
-
return { kind: "model-changed", model:
|
|
5405
|
+
return { kind: "model-changed", model: sanitizeSingleLine(model) };
|
|
5121
5406
|
}
|
|
5122
5407
|
function mapTurnComplete(u) {
|
|
5123
5408
|
const stopReason = readString(u, "stopReason");
|
|
@@ -5606,7 +5891,8 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
5606
5891
|
const { update } = params ?? {};
|
|
5607
5892
|
const event = mapUpdate(update);
|
|
5608
5893
|
debugLogUpdate(update, event);
|
|
5609
|
-
|
|
5894
|
+
const rawTag = update?.sessionUpdate;
|
|
5895
|
+
if (rawTag === "prompt_received") {
|
|
5610
5896
|
adjustPendingTurns(1);
|
|
5611
5897
|
} else if (event?.kind === "turn-complete") {
|
|
5612
5898
|
adjustPendingTurns(-1);
|
|
@@ -5677,11 +5963,11 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
5677
5963
|
const rawOptions = Array.isArray(p.options) ? p.options : [];
|
|
5678
5964
|
const options = rawOptions.map((o) => ({
|
|
5679
5965
|
optionId: o.optionId,
|
|
5680
|
-
name:
|
|
5966
|
+
name: sanitizeSingleLine(o.name ?? ""),
|
|
5681
5967
|
...o.kind !== void 0 ? { kind: o.kind } : {}
|
|
5682
5968
|
}));
|
|
5683
5969
|
const rawTitle = p.toolCall?.title ?? p.toolCall?.name ?? "tool";
|
|
5684
|
-
const title =
|
|
5970
|
+
const title = sanitizeSingleLine(rawTitle);
|
|
5685
5971
|
const toolCallId = p.toolCall?.toolCallId;
|
|
5686
5972
|
if (options.length === 0) {
|
|
5687
5973
|
screen.appendLines([
|
|
@@ -5759,9 +6045,7 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
5759
6045
|
initialMode = hydraMeta.currentMode;
|
|
5760
6046
|
initialTurnStartedAt = hydraMeta.turnStartedAt;
|
|
5761
6047
|
if (hydraMeta.availableCommands) {
|
|
5762
|
-
initialCommands = hydraMeta.availableCommands
|
|
5763
|
-
(c) => c.description !== void 0 ? { name: c.name, description: c.description } : { name: c.name }
|
|
5764
|
-
);
|
|
6048
|
+
initialCommands = normalizeAdvertisedCommands(hydraMeta.availableCommands);
|
|
5765
6049
|
}
|
|
5766
6050
|
} else {
|
|
5767
6051
|
const attached = await conn.request("session/attach", {
|
|
@@ -5786,9 +6070,7 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
5786
6070
|
initialMode = hydraMeta.currentMode;
|
|
5787
6071
|
initialTurnStartedAt = hydraMeta.turnStartedAt;
|
|
5788
6072
|
if (hydraMeta.availableCommands) {
|
|
5789
|
-
initialCommands = hydraMeta.availableCommands
|
|
5790
|
-
(c) => c.description !== void 0 ? { name: c.name, description: c.description } : { name: c.name }
|
|
5791
|
-
);
|
|
6073
|
+
initialCommands = normalizeAdvertisedCommands(hydraMeta.availableCommands);
|
|
5792
6074
|
}
|
|
5793
6075
|
}
|
|
5794
6076
|
const historyFile = paths.tuiHistoryFile(resolvedSessionId);
|
|
@@ -5799,11 +6081,13 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
5799
6081
|
dispatcher.setTurnRunning(true);
|
|
5800
6082
|
}
|
|
5801
6083
|
let turnInFlight = null;
|
|
6084
|
+
let pendingPrefill = null;
|
|
5802
6085
|
const screen = new Screen({
|
|
5803
6086
|
term,
|
|
5804
6087
|
dispatcher,
|
|
5805
6088
|
repaintThrottleMs: config.tui.repaintThrottleMs,
|
|
5806
6089
|
maxScrollbackLines: config.tui.maxScrollbackLines,
|
|
6090
|
+
mouse: config.tui.mouse,
|
|
5807
6091
|
onKey: (events) => {
|
|
5808
6092
|
for (const ev of events) {
|
|
5809
6093
|
if (pendingPermission && tryHandlePermissionKey(ev)) {
|
|
@@ -5830,6 +6114,7 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
5830
6114
|
{ name: "/quit", description: "Exit the TUI" },
|
|
5831
6115
|
{ name: "/clear", description: "Clear scrollback" },
|
|
5832
6116
|
{ name: "/sessions", description: "List sessions" },
|
|
6117
|
+
{ name: "/model", description: "Switch model: /model <model-id>" },
|
|
5833
6118
|
{ name: "/demo-plan", description: "Inject synthetic plan events (UI test)" },
|
|
5834
6119
|
{ name: "/demo-tool", description: "Inject a synthetic tool-call sequence (UI test)" }
|
|
5835
6120
|
];
|
|
@@ -5864,48 +6149,24 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
5864
6149
|
screen.setCompletions(currentCompletions());
|
|
5865
6150
|
};
|
|
5866
6151
|
const tryHandleCompletionKey = (ev) => {
|
|
5867
|
-
if (ev.type !== "key") {
|
|
6152
|
+
if (ev.type !== "key" || ev.name !== "tab") {
|
|
5868
6153
|
return false;
|
|
5869
6154
|
}
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
const typedPrefix = space === -1 ? firstLine3 : firstLine3.slice(0, space);
|
|
5881
|
-
const tail = space === -1 ? "" : firstLine3.slice(space);
|
|
5882
|
-
let next = commonPrefix;
|
|
5883
|
-
if (commonPrefix.length <= typedPrefix.length || matches.length === 1) {
|
|
5884
|
-
next = first.name + (tail.startsWith(" ") ? "" : " ");
|
|
5885
|
-
}
|
|
5886
|
-
dispatcher.replaceFirstLine(next + tail);
|
|
6155
|
+
const matches = currentCompletions();
|
|
6156
|
+
if (matches.length === 0) {
|
|
6157
|
+
return false;
|
|
6158
|
+
}
|
|
6159
|
+
const firstLine3 = dispatcher.state().buffer[0] ?? "";
|
|
6160
|
+
const next = computeTabCompletion({
|
|
6161
|
+
matches: matches.map((m) => m.name),
|
|
6162
|
+
firstLine: firstLine3
|
|
6163
|
+
});
|
|
6164
|
+
if (next === null) {
|
|
5887
6165
|
return true;
|
|
5888
6166
|
}
|
|
5889
|
-
|
|
6167
|
+
dispatcher.replaceFirstLine(next);
|
|
6168
|
+
return true;
|
|
5890
6169
|
};
|
|
5891
|
-
function longestCommonPrefix(names) {
|
|
5892
|
-
if (names.length === 0) {
|
|
5893
|
-
return "";
|
|
5894
|
-
}
|
|
5895
|
-
let prefix = names[0] ?? "";
|
|
5896
|
-
for (let i = 1; i < names.length; i++) {
|
|
5897
|
-
const n = names[i] ?? "";
|
|
5898
|
-
let j = 0;
|
|
5899
|
-
while (j < prefix.length && j < n.length && prefix[j] === n[j]) {
|
|
5900
|
-
j += 1;
|
|
5901
|
-
}
|
|
5902
|
-
prefix = prefix.slice(0, j);
|
|
5903
|
-
if (prefix.length === 0) {
|
|
5904
|
-
break;
|
|
5905
|
-
}
|
|
5906
|
-
}
|
|
5907
|
-
return prefix;
|
|
5908
|
-
}
|
|
5909
6170
|
const tryHandlePermissionKey = (ev) => {
|
|
5910
6171
|
if (!pendingPermission) {
|
|
5911
6172
|
return false;
|
|
@@ -6112,22 +6373,45 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
6112
6373
|
}
|
|
6113
6374
|
resume(nextOpts);
|
|
6114
6375
|
};
|
|
6376
|
+
const queueHeadOffset = () => workerActive ? 1 : 0;
|
|
6115
6377
|
const handleEffect = (effect) => {
|
|
6116
6378
|
switch (effect.type) {
|
|
6117
6379
|
case "send":
|
|
6118
6380
|
enqueuePrompt(effect.text, effect.planMode);
|
|
6119
6381
|
return;
|
|
6120
|
-
case "
|
|
6382
|
+
case "queue-edit": {
|
|
6383
|
+
const realIdx = effect.index + queueHeadOffset();
|
|
6384
|
+
const existing = promptQueue[realIdx];
|
|
6385
|
+
if (existing) {
|
|
6386
|
+
promptQueue[realIdx] = { text: effect.text, planMode: existing.planMode };
|
|
6387
|
+
refreshQueueDisplay();
|
|
6388
|
+
}
|
|
6389
|
+
return;
|
|
6390
|
+
}
|
|
6391
|
+
case "queue-remove": {
|
|
6392
|
+
const realIdx = effect.index + queueHeadOffset();
|
|
6393
|
+
if (realIdx >= 0 && realIdx < promptQueue.length) {
|
|
6394
|
+
promptQueue.splice(realIdx, 1);
|
|
6395
|
+
refreshQueueDisplay();
|
|
6396
|
+
}
|
|
6397
|
+
return;
|
|
6398
|
+
}
|
|
6399
|
+
case "cancel": {
|
|
6400
|
+
if (effect.prefill && turnInFlight) {
|
|
6401
|
+
const headOffset = workerActive ? 1 : 0;
|
|
6402
|
+
const waitingEmpty = promptQueue.length <= headOffset;
|
|
6403
|
+
const bufferEmpty = dispatcher.state().buffer.every((line) => line === "");
|
|
6404
|
+
if (waitingEmpty && bufferEmpty) {
|
|
6405
|
+
pendingPrefill = turnInFlight.text;
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6121
6408
|
if (turnInFlight) {
|
|
6122
6409
|
turnInFlight.cancel();
|
|
6123
6410
|
} else if (pendingTurns > 0) {
|
|
6124
6411
|
cancelRemoteTurn();
|
|
6125
6412
|
}
|
|
6126
|
-
if (promptQueue.length > (workerActive ? 1 : 0)) {
|
|
6127
|
-
promptQueue.length = workerActive ? 1 : 0;
|
|
6128
|
-
refreshQueueDisplay();
|
|
6129
|
-
}
|
|
6130
6413
|
return;
|
|
6414
|
+
}
|
|
6131
6415
|
case "exit":
|
|
6132
6416
|
void requestExit();
|
|
6133
6417
|
return;
|
|
@@ -6140,6 +6424,12 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
6140
6424
|
case "redraw":
|
|
6141
6425
|
screen.fullRedraw();
|
|
6142
6426
|
return;
|
|
6427
|
+
case "scroll-to-top":
|
|
6428
|
+
screen.scrollToTop();
|
|
6429
|
+
return;
|
|
6430
|
+
case "scroll-to-bottom":
|
|
6431
|
+
screen.scrollToBottom();
|
|
6432
|
+
return;
|
|
6143
6433
|
case "switch-session":
|
|
6144
6434
|
void switchSession();
|
|
6145
6435
|
return;
|
|
@@ -6155,6 +6445,7 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
6155
6445
|
const waiting = promptQueue.slice(workerActive ? 1 : 0);
|
|
6156
6446
|
screen.setQueuedPrompts(waiting.map((p) => p.text));
|
|
6157
6447
|
screen.setBanner({ queued: waiting.length });
|
|
6448
|
+
dispatcher.setQueue(waiting.map((p) => p.text));
|
|
6158
6449
|
};
|
|
6159
6450
|
const enqueuePrompt = (text, planMode) => {
|
|
6160
6451
|
screen.scrollToBottom();
|
|
@@ -6283,6 +6574,40 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
6283
6574
|
}
|
|
6284
6575
|
]);
|
|
6285
6576
|
return true;
|
|
6577
|
+
case "/model": {
|
|
6578
|
+
const arg = space === -1 ? "" : trimmed.slice(space + 1).trim();
|
|
6579
|
+
if (arg === "") {
|
|
6580
|
+
screen.appendLines([
|
|
6581
|
+
{
|
|
6582
|
+
prefix: " ",
|
|
6583
|
+
body: "Usage: /model <model-id>",
|
|
6584
|
+
bodyStyle: "info"
|
|
6585
|
+
}
|
|
6586
|
+
]);
|
|
6587
|
+
return true;
|
|
6588
|
+
}
|
|
6589
|
+
conn.request("session/set_model", {
|
|
6590
|
+
sessionId: resolvedSessionId,
|
|
6591
|
+
modelId: arg
|
|
6592
|
+
}).then(() => {
|
|
6593
|
+
screen.appendLines([
|
|
6594
|
+
{
|
|
6595
|
+
prefix: " ",
|
|
6596
|
+
body: `model set to ${arg}`,
|
|
6597
|
+
bodyStyle: "system"
|
|
6598
|
+
}
|
|
6599
|
+
]);
|
|
6600
|
+
}).catch((err) => {
|
|
6601
|
+
screen.appendLines([
|
|
6602
|
+
{
|
|
6603
|
+
prefix: " ",
|
|
6604
|
+
body: `set_model failed: ${err.message}`,
|
|
6605
|
+
bodyStyle: "tool-status-fail"
|
|
6606
|
+
}
|
|
6607
|
+
]);
|
|
6608
|
+
});
|
|
6609
|
+
return true;
|
|
6610
|
+
}
|
|
6286
6611
|
default:
|
|
6287
6612
|
return false;
|
|
6288
6613
|
}
|
|
@@ -6302,6 +6627,15 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
6302
6627
|
} finally {
|
|
6303
6628
|
workerActive = false;
|
|
6304
6629
|
refreshQueueDisplay();
|
|
6630
|
+
if (pendingPrefill !== null) {
|
|
6631
|
+
const text = pendingPrefill;
|
|
6632
|
+
pendingPrefill = null;
|
|
6633
|
+
const bufferEmpty = dispatcher.state().buffer.every((line) => line === "");
|
|
6634
|
+
if (bufferEmpty) {
|
|
6635
|
+
dispatcher.setBuffer(text);
|
|
6636
|
+
screen.refreshPrompt();
|
|
6637
|
+
}
|
|
6638
|
+
}
|
|
6305
6639
|
}
|
|
6306
6640
|
};
|
|
6307
6641
|
const processPrompt = async (text, planMode) => {
|
|
@@ -6311,6 +6645,7 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
6311
6645
|
appendRender({ kind: "user-text", text });
|
|
6312
6646
|
let cancelled = false;
|
|
6313
6647
|
turnInFlight = {
|
|
6648
|
+
text,
|
|
6314
6649
|
cancel: () => {
|
|
6315
6650
|
if (cancelled) {
|
|
6316
6651
|
return;
|
|
@@ -6407,12 +6742,13 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
6407
6742
|
}
|
|
6408
6743
|
summary = parts.join(" \xB7 ");
|
|
6409
6744
|
}
|
|
6745
|
+
const pureThinking = total === 0 && inProgress;
|
|
6410
6746
|
const lines = [
|
|
6411
6747
|
{
|
|
6412
6748
|
prefix: "\u2692 ",
|
|
6413
|
-
prefixStyle: "tool",
|
|
6749
|
+
prefixStyle: pureThinking ? "tool-status-running" : "tool",
|
|
6414
6750
|
body: summary,
|
|
6415
|
-
bodyStyle: "dim"
|
|
6751
|
+
bodyStyle: pureThinking ? "tool-status-running" : "dim"
|
|
6416
6752
|
}
|
|
6417
6753
|
];
|
|
6418
6754
|
for (const id of visibleIds) {
|
|
@@ -6588,6 +6924,8 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
6588
6924
|
}, 1e3);
|
|
6589
6925
|
}
|
|
6590
6926
|
startToolsBlock();
|
|
6927
|
+
} else if (initialTurnStartedAt === void 0 && pendingTurns > 0) {
|
|
6928
|
+
adjustPendingTurns(-pendingTurns);
|
|
6591
6929
|
}
|
|
6592
6930
|
const resetInFlightUiState = () => {
|
|
6593
6931
|
if (pendingPermission) {
|
|
@@ -6789,6 +7127,7 @@ var init_app = __esm({
|
|
|
6789
7127
|
init_picker();
|
|
6790
7128
|
init_screen();
|
|
6791
7129
|
init_input();
|
|
7130
|
+
init_completion();
|
|
6792
7131
|
init_render_update();
|
|
6793
7132
|
init_format();
|
|
6794
7133
|
PLAN_PREFIX_TEXT = "Plan mode is on. Outline what you would do without making any changes. Do not edit files, run shell commands, or otherwise execute side effects; produce a plan only.";
|
|
@@ -6875,35 +7214,28 @@ init_config();
|
|
|
6875
7214
|
import * as fs2 from "fs/promises";
|
|
6876
7215
|
async function runInit(flags) {
|
|
6877
7216
|
await fs2.mkdir(paths.home(), { recursive: true });
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
existing = void 0;
|
|
6883
|
-
}
|
|
6884
|
-
if (!existing) {
|
|
6885
|
-
const config = await writeMinimalInitConfig();
|
|
7217
|
+
const existingToken = await loadAuthToken();
|
|
7218
|
+
if (!existingToken) {
|
|
7219
|
+
const token = generateAuthToken();
|
|
7220
|
+
await writeAuthToken(token);
|
|
6886
7221
|
process.stdout.write(
|
|
6887
|
-
`Initialized ${paths.
|
|
6888
|
-
Auth token: ${
|
|
7222
|
+
`Initialized ${paths.authToken()}
|
|
7223
|
+
Auth token: ${token}
|
|
6889
7224
|
`
|
|
6890
7225
|
);
|
|
6891
7226
|
return;
|
|
6892
7227
|
}
|
|
6893
7228
|
if (flagBool(flags, "rotate-token")) {
|
|
6894
7229
|
const newToken = generateAuthToken();
|
|
6895
|
-
await
|
|
6896
|
-
const daemon = raw.daemon ??= {};
|
|
6897
|
-
daemon.authToken = newToken;
|
|
6898
|
-
});
|
|
7230
|
+
await writeAuthToken(newToken);
|
|
6899
7231
|
process.stdout.write(
|
|
6900
|
-
`Rotated token in ${paths.
|
|
7232
|
+
`Rotated token in ${paths.authToken()}
|
|
6901
7233
|
New token: ${newToken}
|
|
6902
7234
|
`
|
|
6903
7235
|
);
|
|
6904
7236
|
return;
|
|
6905
7237
|
}
|
|
6906
|
-
process.stdout.write(`
|
|
7238
|
+
process.stdout.write(`Auth token already exists at ${paths.authToken()}.
|
|
6907
7239
|
`);
|
|
6908
7240
|
process.stdout.write("Pass --rotate-token to generate a new auth token.\n");
|
|
6909
7241
|
}
|
|
@@ -7294,13 +7626,13 @@ function npxPackageBasename(agent) {
|
|
|
7294
7626
|
const atIdx = afterSlash.lastIndexOf("@");
|
|
7295
7627
|
return atIdx <= 0 ? afterSlash : afterSlash.slice(0, atIdx);
|
|
7296
7628
|
}
|
|
7297
|
-
async function planSpawn(agent,
|
|
7629
|
+
async function planSpawn(agent, callerArgs = []) {
|
|
7298
7630
|
if (agent.distribution.npx) {
|
|
7299
7631
|
const npx = agent.distribution.npx;
|
|
7300
|
-
const
|
|
7632
|
+
const tail = callerArgs.length > 0 ? callerArgs : npx.args ?? [];
|
|
7301
7633
|
return {
|
|
7302
7634
|
command: "npx",
|
|
7303
|
-
args,
|
|
7635
|
+
args: ["-y", npx.package, ...tail],
|
|
7304
7636
|
env: npx.env ?? {}
|
|
7305
7637
|
};
|
|
7306
7638
|
}
|
|
@@ -7316,18 +7648,19 @@ async function planSpawn(agent, extraArgs = []) {
|
|
|
7316
7648
|
version: agent.version ?? "current",
|
|
7317
7649
|
target
|
|
7318
7650
|
});
|
|
7651
|
+
const tail = callerArgs.length > 0 ? callerArgs : target.args ?? [];
|
|
7319
7652
|
return {
|
|
7320
7653
|
command: cmdPath,
|
|
7321
|
-
args:
|
|
7654
|
+
args: tail,
|
|
7322
7655
|
env: target.env ?? {}
|
|
7323
7656
|
};
|
|
7324
7657
|
}
|
|
7325
7658
|
if (agent.distribution.uvx) {
|
|
7326
7659
|
const uvx = agent.distribution.uvx;
|
|
7327
|
-
const
|
|
7660
|
+
const tail = callerArgs.length > 0 ? callerArgs : uvx.args ?? [];
|
|
7328
7661
|
return {
|
|
7329
7662
|
command: "uvx",
|
|
7330
|
-
args,
|
|
7663
|
+
args: [uvx.package, ...tail],
|
|
7331
7664
|
env: uvx.env ?? {}
|
|
7332
7665
|
};
|
|
7333
7666
|
}
|