@hydra-acp/cli 0.1.58 → 0.1.60
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 +1203 -240
- package/dist/index.d.ts +23 -6
- package/dist/index.js +177 -33
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -303,6 +303,34 @@ async function loadConfig() {
|
|
|
303
303
|
await migrateLegacyAuthToken();
|
|
304
304
|
return HydraConfig.parse(await readConfigFile());
|
|
305
305
|
}
|
|
306
|
+
async function updateRawConfig(mutate) {
|
|
307
|
+
await migrateLegacyAuthToken();
|
|
308
|
+
const raw = await readConfigFile();
|
|
309
|
+
mutate(raw);
|
|
310
|
+
HydraConfig.parse(raw);
|
|
311
|
+
await writeJsonAtomic(paths.config(), raw, { mode: 384 });
|
|
312
|
+
}
|
|
313
|
+
async function setTuiConfigValue(key, value) {
|
|
314
|
+
await updateRawConfig((raw) => {
|
|
315
|
+
const tui = raw.tui && typeof raw.tui === "object" && !Array.isArray(raw.tui) ? raw.tui : {};
|
|
316
|
+
tui[key] = value;
|
|
317
|
+
raw.tui = tui;
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
async function setDefaultAgent(agentId, modelId) {
|
|
321
|
+
await updateRawConfig((raw) => {
|
|
322
|
+
raw.defaultAgent = agentId;
|
|
323
|
+
if (modelId !== void 0) {
|
|
324
|
+
const models = raw.defaultModels && typeof raw.defaultModels === "object" ? raw.defaultModels : {};
|
|
325
|
+
models[agentId] = modelId;
|
|
326
|
+
raw.defaultModels = models;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
async function hasConfiguredDefaultAgent() {
|
|
331
|
+
const raw = await readConfigFile();
|
|
332
|
+
return typeof raw.defaultAgent === "string" && raw.defaultAgent.length > 0;
|
|
333
|
+
}
|
|
306
334
|
function expandHome(p) {
|
|
307
335
|
if (p === "~" || p === "$HOME") {
|
|
308
336
|
return homedir2();
|
|
@@ -385,7 +413,7 @@ var init_config = __esm({
|
|
|
385
413
|
// Width cap on the cwd column in the `sessions list` output and the
|
|
386
414
|
// TUI picker. Set higher if you keep deeply-nested working directories
|
|
387
415
|
// and want them visible; the elastic title column shrinks to make room.
|
|
388
|
-
cwdColumnMaxWidth: z.number().int().positive().default(
|
|
416
|
+
cwdColumnMaxWidth: z.number().int().positive().default(32),
|
|
389
417
|
// When true (default), emit OSC 9;4 progress-bar control codes so the
|
|
390
418
|
// host terminal can show an indeterminate busy indicator (taskbar pulse
|
|
391
419
|
// on Windows Terminal, dock badge on KDE/Konsole, etc.) while a turn is
|
|
@@ -436,7 +464,28 @@ var init_config = __esm({
|
|
|
436
464
|
// The diff payload is extracted from the ACP wire (content[]
|
|
437
465
|
// type:"diff" entries, falling back to rawInput shapes), so any agent
|
|
438
466
|
// that emits one of those shapes gets the treatment.
|
|
439
|
-
showFileUpdates: z.enum(["none", "edit", "diff"]).default("edit")
|
|
467
|
+
showFileUpdates: z.enum(["none", "edit", "diff"]).default("edit"),
|
|
468
|
+
// Columns shown in the `sessions list` output and the TUI picker, in
|
|
469
|
+
// the given order — so this controls both which columns appear and
|
|
470
|
+
// their left-to-right order. Valid names: session, upstream, host,
|
|
471
|
+
// state, agent, model, age, cwd, title, cost. Omit to use the built-in
|
|
472
|
+
// default (session, state, agent, age, cwd, title, cost — UPSTREAM,
|
|
473
|
+
// HOST, and MODEL hidden). The CLI's `--columns` flag overrides this
|
|
474
|
+
// per-invocation. Duplicate or unknown names are rejected.
|
|
475
|
+
sessionColumns: z.array(
|
|
476
|
+
z.enum([
|
|
477
|
+
"session",
|
|
478
|
+
"upstream",
|
|
479
|
+
"host",
|
|
480
|
+
"state",
|
|
481
|
+
"agent",
|
|
482
|
+
"model",
|
|
483
|
+
"age",
|
|
484
|
+
"cwd",
|
|
485
|
+
"title",
|
|
486
|
+
"cost"
|
|
487
|
+
])
|
|
488
|
+
).nonempty().optional()
|
|
440
489
|
});
|
|
441
490
|
ExtensionName = z.string().min(1).regex(/^[A-Za-z0-9._-]+$/, "extension name must be filename-safe");
|
|
442
491
|
ExtensionBody = z.object({
|
|
@@ -457,7 +506,7 @@ var init_config = __esm({
|
|
|
457
506
|
HydraConfig = z.object({
|
|
458
507
|
daemon: DaemonConfig.default({}),
|
|
459
508
|
registry: RegistryConfig.default({ url: REGISTRY_URL_DEFAULT, ttlHours: 24 }),
|
|
460
|
-
defaultAgent: z.string().default("
|
|
509
|
+
defaultAgent: z.string().default("opencode"),
|
|
461
510
|
// Optional per-agent default model id. When a brand-new agent process
|
|
462
511
|
// is spawned (session/new path), hydra issues session/set_model with
|
|
463
512
|
// the matching entry so the user lands on their preferred model from
|
|
@@ -505,7 +554,7 @@ var init_config = __esm({
|
|
|
505
554
|
maxScrollbackLines: 1e4,
|
|
506
555
|
mouse: false,
|
|
507
556
|
logMaxBytes: 5 * 1024 * 1024,
|
|
508
|
-
cwdColumnMaxWidth:
|
|
557
|
+
cwdColumnMaxWidth: 32,
|
|
509
558
|
progressIndicator: true,
|
|
510
559
|
defaultEnterAction: "amend",
|
|
511
560
|
showThoughts: true,
|
|
@@ -1122,7 +1171,8 @@ function sessionListEntryToWire(entry) {
|
|
|
1122
1171
|
const hydraMeta = {
|
|
1123
1172
|
attachedClients: entry.attachedClients,
|
|
1124
1173
|
status: entry.status,
|
|
1125
|
-
busy: entry.busy
|
|
1174
|
+
busy: entry.busy,
|
|
1175
|
+
awaitingInput: entry.awaitingInput
|
|
1126
1176
|
};
|
|
1127
1177
|
if (entry.agentId !== void 0) {
|
|
1128
1178
|
hydraMeta.agentId = entry.agentId;
|
|
@@ -1239,6 +1289,17 @@ var init_types = __esm({
|
|
|
1239
1289
|
// to a cold session does not resurrect or spawn an agent — just
|
|
1240
1290
|
// streams history from disk. Used by the TUI's view-only mode.
|
|
1241
1291
|
readonly: z3.boolean().optional(),
|
|
1292
|
+
// Debug-only replay pacing. When "drip", the daemon skips chunk
|
|
1293
|
+
// coalescing and re-emits each recorded session/update individually,
|
|
1294
|
+
// spacing them by their original recordedAt deltas (scaled by
|
|
1295
|
+
// dripSpeed, with a per-gap cap) so a session's streaming render can
|
|
1296
|
+
// be reproduced at its real granularity for flicker investigation.
|
|
1297
|
+
// Omitted/"instant" preserves the normal coalesced, as-fast-as-possible
|
|
1298
|
+
// replay.
|
|
1299
|
+
replayMode: z3.enum(["instant", "drip"]).optional(),
|
|
1300
|
+
// Multiplier applied to original inter-entry gaps in drip mode. >1
|
|
1301
|
+
// compresses time (faster), <1 stretches it. Defaults to 1.
|
|
1302
|
+
dripSpeed: z3.number().positive().optional(),
|
|
1242
1303
|
_meta: z3.record(z3.unknown()).optional()
|
|
1243
1304
|
});
|
|
1244
1305
|
HYDRA_META_KEY = "hydra-acp";
|
|
@@ -1294,6 +1355,11 @@ var init_types = __esm({
|
|
|
1294
1355
|
// Always false for cold sessions. Lets pickers render a busy dot
|
|
1295
1356
|
// without having to attach.
|
|
1296
1357
|
busy: z3.boolean().default(false),
|
|
1358
|
+
// True when the agent is blocked on the user (an outstanding
|
|
1359
|
+
// session/request_permission, which also covers agent-posed
|
|
1360
|
+
// questions). Always false for cold sessions. Lets pickers render a
|
|
1361
|
+
// distinct "waiting on you" glyph instead of the busy dot.
|
|
1362
|
+
awaitingInput: z3.boolean().default(false),
|
|
1297
1363
|
_meta: z3.record(z3.unknown()).optional()
|
|
1298
1364
|
});
|
|
1299
1365
|
SessionListEntryWire = z3.object({
|
|
@@ -3064,6 +3130,14 @@ var init_session = __esm({
|
|
|
3064
3130
|
get turnStartedAt() {
|
|
3065
3131
|
return this.promptStartedAt;
|
|
3066
3132
|
}
|
|
3133
|
+
// True when the agent is blocked on the user: an outstanding
|
|
3134
|
+
// session/request_permission (which also carries agent-posed
|
|
3135
|
+
// questions — there's no separate "ask the user" request in ACP).
|
|
3136
|
+
// Lets pickers show a "waiting on you" glyph distinct from the
|
|
3137
|
+
// "actively working" one without having to attach.
|
|
3138
|
+
get awaitingInput() {
|
|
3139
|
+
return this.inFlightPermissions.size > 0;
|
|
3140
|
+
}
|
|
3067
3141
|
// Read the persisted history from disk. Returns [] if no history
|
|
3068
3142
|
// file exists (fresh session, never prompted). Used by attach() and
|
|
3069
3143
|
// the HTTP /history endpoint.
|
|
@@ -3120,23 +3194,24 @@ var init_session = __esm({
|
|
|
3120
3194
|
return this.loadReplay(historyPolicy, opts);
|
|
3121
3195
|
}
|
|
3122
3196
|
async loadReplay(historyPolicy, opts) {
|
|
3197
|
+
const maybeCoalesce = (entries) => opts.raw ? entries : coalesceReplay(entries);
|
|
3123
3198
|
const raw = await this.getHistorySnapshot();
|
|
3124
3199
|
const state = this.buildStateSnapshotReplay();
|
|
3125
3200
|
if (historyPolicy === "after_message") {
|
|
3126
3201
|
const cutoff = opts.afterMessageId ? findMessageIdIndex(raw, opts.afterMessageId) : -1;
|
|
3127
3202
|
if (cutoff < 0) {
|
|
3128
3203
|
return {
|
|
3129
|
-
entries: [...state, ...
|
|
3204
|
+
entries: [...state, ...maybeCoalesce(raw)],
|
|
3130
3205
|
appliedPolicy: "full"
|
|
3131
3206
|
};
|
|
3132
3207
|
}
|
|
3133
3208
|
return {
|
|
3134
|
-
entries: [...state, ...
|
|
3209
|
+
entries: [...state, ...maybeCoalesce(raw.slice(cutoff + 1))],
|
|
3135
3210
|
appliedPolicy: "after_message"
|
|
3136
3211
|
};
|
|
3137
3212
|
}
|
|
3138
3213
|
return {
|
|
3139
|
-
entries: [...state, ...
|
|
3214
|
+
entries: [...state, ...maybeCoalesce(raw)],
|
|
3140
3215
|
appliedPolicy: "full"
|
|
3141
3216
|
};
|
|
3142
3217
|
}
|
|
@@ -4403,6 +4478,7 @@ var init_session = __esm({
|
|
|
4403
4478
|
const out = [
|
|
4404
4479
|
{ name: "hydra", description: "Hydra session command (kill, restart, title, agent <agent>)" },
|
|
4405
4480
|
{ name: "model", description: "Switch model; omit arg to list available models" },
|
|
4481
|
+
{ name: "mode", description: "Switch mode; omit arg to list available modes" },
|
|
4406
4482
|
{ name: "sessions", description: "List all sessions" },
|
|
4407
4483
|
{ name: "help", description: "Show available commands" }
|
|
4408
4484
|
];
|
|
@@ -4673,6 +4749,60 @@ ${body}
|
|
|
4673
4749
|
});
|
|
4674
4750
|
return { stopReason: "end_turn" };
|
|
4675
4751
|
}
|
|
4752
|
+
// /mode — the text-command twin of session/set_mode, so clients that
|
|
4753
|
+
// can only send prompts (e.g. Slack) can switch modes. With no arg it
|
|
4754
|
+
// lists advertised modes; with an arg it forwards session/set_mode to
|
|
4755
|
+
// the agent and then applies the change locally. The forward +
|
|
4756
|
+
// applyModeChange pair MUST stay identical to the daemon's
|
|
4757
|
+
// session/set_mode handler (see acp-ws.ts) — applyModeChange owns the
|
|
4758
|
+
// persistence + synthetic current_mode_update broadcast for both paths.
|
|
4759
|
+
async handleModeCommand(text) {
|
|
4760
|
+
const arg = text.slice("/mode".length).trim();
|
|
4761
|
+
if (arg === "") {
|
|
4762
|
+
const modes2 = this.agentAdvertisedModes;
|
|
4763
|
+
const current = this.currentMode;
|
|
4764
|
+
let body;
|
|
4765
|
+
if (modes2.length === 0) {
|
|
4766
|
+
body = current ? `Current mode: ${current}` : "_(no modes advertised yet)_";
|
|
4767
|
+
} else {
|
|
4768
|
+
const inList = current ? modes2.some((m) => m.id === current) : true;
|
|
4769
|
+
const lines = modes2.map((m) => {
|
|
4770
|
+
const marker = m.id === current ? "\u25B6 " : " ";
|
|
4771
|
+
const desc = m.name && m.name !== m.id ? ` ${m.name}` : "";
|
|
4772
|
+
return `${marker}${m.id}${desc}`;
|
|
4773
|
+
});
|
|
4774
|
+
if (!inList && current) {
|
|
4775
|
+
lines.unshift(`\u25B6 ${current}`);
|
|
4776
|
+
}
|
|
4777
|
+
body = lines.join("\n");
|
|
4778
|
+
}
|
|
4779
|
+
this.recordAndBroadcast("session/update", {
|
|
4780
|
+
sessionId: this.upstreamSessionId,
|
|
4781
|
+
update: {
|
|
4782
|
+
sessionUpdate: "agent_message_chunk",
|
|
4783
|
+
content: { type: "text", text: `
|
|
4784
|
+
${body}
|
|
4785
|
+
` },
|
|
4786
|
+
_meta: { "hydra-acp": { synthetic: true } }
|
|
4787
|
+
}
|
|
4788
|
+
});
|
|
4789
|
+
return { stopReason: "end_turn" };
|
|
4790
|
+
}
|
|
4791
|
+
const modes = this.agentAdvertisedModes;
|
|
4792
|
+
if (modes.length > 0 && !modes.some((m) => m.id === arg)) {
|
|
4793
|
+
const known = modes.map((m) => m.id).join(", ");
|
|
4794
|
+
throw withCode(
|
|
4795
|
+
new Error(`unknown mode: ${arg} (known: ${known})`),
|
|
4796
|
+
JsonRpcErrorCodes.InvalidParams
|
|
4797
|
+
);
|
|
4798
|
+
}
|
|
4799
|
+
await this.forwardRequest("session/set_mode", {
|
|
4800
|
+
sessionId: this.sessionId,
|
|
4801
|
+
modeId: arg
|
|
4802
|
+
});
|
|
4803
|
+
this.applyModeChange(arg);
|
|
4804
|
+
return { stopReason: "end_turn" };
|
|
4805
|
+
}
|
|
4676
4806
|
// /hydra title. With an arg, sets the title directly (synchronous,
|
|
4677
4807
|
// broadcasts session_info_update). Without an arg, asks the manager
|
|
4678
4808
|
// to schedule a background synopsis via the ephemeral-agent path —
|
|
@@ -5498,12 +5628,14 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
5498
5628
|
}
|
|
5499
5629
|
this.broadcastPromptReceived(entry);
|
|
5500
5630
|
const promptText = extractPromptText(entry.prompt).trim();
|
|
5501
|
-
if (promptText === "/model" || promptText.startsWith("/model ") || promptText === "/sessions" || promptText === "/help") {
|
|
5631
|
+
if (promptText === "/model" || promptText.startsWith("/model ") || promptText === "/mode" || promptText.startsWith("/mode ") || promptText === "/sessions" || promptText === "/help") {
|
|
5502
5632
|
let result;
|
|
5503
5633
|
if (promptText === "/sessions") {
|
|
5504
5634
|
result = await this.handleSessionsCommand();
|
|
5505
5635
|
} else if (promptText === "/help") {
|
|
5506
5636
|
result = await this.handleHelpCommand();
|
|
5637
|
+
} else if (promptText === "/mode" || promptText.startsWith("/mode ")) {
|
|
5638
|
+
result = await this.handleModeCommand(promptText);
|
|
5507
5639
|
} else {
|
|
5508
5640
|
result = await this.handleModelCommand(promptText);
|
|
5509
5641
|
}
|
|
@@ -5561,7 +5693,12 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
5561
5693
|
"current_mode_update",
|
|
5562
5694
|
"available_commands_update",
|
|
5563
5695
|
"available_modes_update",
|
|
5564
|
-
"usage_update"
|
|
5696
|
+
"usage_update",
|
|
5697
|
+
// opencode's non-spec carrier for model/mode/config state. Its canonical
|
|
5698
|
+
// form is harvested into meta.json and re-synthesized on attach, so
|
|
5699
|
+
// recording it would just give never-prompted sessions a non-empty
|
|
5700
|
+
// history and make them look interactive in the picker.
|
|
5701
|
+
"config_option_update"
|
|
5565
5702
|
]);
|
|
5566
5703
|
}
|
|
5567
5704
|
});
|
|
@@ -6338,10 +6475,28 @@ async function listSessions(target, opts = {}, fetchImpl = fetch) {
|
|
|
6338
6475
|
forkedFromSessionId: s.forkedFromSessionId,
|
|
6339
6476
|
forkedFromMessageId: s.forkedFromMessageId,
|
|
6340
6477
|
busy: s.busy,
|
|
6478
|
+
awaitingInput: s.awaitingInput,
|
|
6341
6479
|
originatingClient: s.originatingClient,
|
|
6342
6480
|
interactive: s.interactive
|
|
6343
6481
|
}));
|
|
6344
6482
|
}
|
|
6483
|
+
async function listAgents(target, fetchImpl = fetch) {
|
|
6484
|
+
const response = await fetchImpl(`${target.baseUrl}/v1/agents`, {
|
|
6485
|
+
headers: { Authorization: `Bearer ${target.token}` }
|
|
6486
|
+
});
|
|
6487
|
+
if (!response.ok) {
|
|
6488
|
+
throw new Error(`daemon returned HTTP ${response.status}`);
|
|
6489
|
+
}
|
|
6490
|
+
const body = await response.json();
|
|
6491
|
+
if (!Array.isArray(body.agents)) {
|
|
6492
|
+
return [];
|
|
6493
|
+
}
|
|
6494
|
+
return body.agents.map((a) => ({
|
|
6495
|
+
id: a.id,
|
|
6496
|
+
name: a.name,
|
|
6497
|
+
description: a.description
|
|
6498
|
+
}));
|
|
6499
|
+
}
|
|
6345
6500
|
async function forkSession(target, id, opts = {}, fetchImpl = fetch) {
|
|
6346
6501
|
const response = await fetchImpl(
|
|
6347
6502
|
`${target.baseUrl}/v1/sessions/${id}/fork`,
|
|
@@ -6469,30 +6624,24 @@ function formatAgentWithModel(agentId, model) {
|
|
|
6469
6624
|
}
|
|
6470
6625
|
return `${agent}${AGENT_MODEL_SEP}${short}`;
|
|
6471
6626
|
}
|
|
6472
|
-
function formatAgentCell(agentId
|
|
6473
|
-
|
|
6627
|
+
function formatAgentCell(agentId) {
|
|
6628
|
+
return agentId ?? "?";
|
|
6629
|
+
}
|
|
6630
|
+
function formatCostCell(usage) {
|
|
6474
6631
|
if (!usage || typeof usage.costAmount !== "number") {
|
|
6475
|
-
return
|
|
6632
|
+
return "";
|
|
6476
6633
|
}
|
|
6477
|
-
const
|
|
6478
|
-
if (
|
|
6479
|
-
return
|
|
6634
|
+
const { costAmount, costCurrency } = usage;
|
|
6635
|
+
if (costCurrency === void 0 || costCurrency === "USD") {
|
|
6636
|
+
return `$${Math.round(costAmount)}`;
|
|
6480
6637
|
}
|
|
6481
|
-
return
|
|
6638
|
+
return formatCost(costAmount, costCurrency);
|
|
6482
6639
|
}
|
|
6483
6640
|
function formatCost(amount, currency) {
|
|
6484
6641
|
const sign = currency === "USD" || currency === void 0 ? "$" : "";
|
|
6485
6642
|
const decimals = 2;
|
|
6486
6643
|
return `${sign}${amount.toFixed(decimals)}${currency && currency !== "USD" ? ` ${currency}` : ""}`;
|
|
6487
6644
|
}
|
|
6488
|
-
function formatCostCompact(amount, currency) {
|
|
6489
|
-
const whole = Math.round(amount);
|
|
6490
|
-
if (whole === 0) {
|
|
6491
|
-
return null;
|
|
6492
|
-
}
|
|
6493
|
-
const sign = currency === "USD" || currency === void 0 ? "$" : "";
|
|
6494
|
-
return `${sign}${whole}${currency && currency !== "USD" ? ` ${currency}` : ""}`;
|
|
6495
|
-
}
|
|
6496
6645
|
var AGENT_MODEL_SEP;
|
|
6497
6646
|
var init_agent_display = __esm({
|
|
6498
6647
|
"src/core/agent-display.ts"() {
|
|
@@ -6502,15 +6651,39 @@ var init_agent_display = __esm({
|
|
|
6502
6651
|
});
|
|
6503
6652
|
|
|
6504
6653
|
// src/cli/session-row.ts
|
|
6654
|
+
function parseColumns(raw) {
|
|
6655
|
+
const parts = raw.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
6656
|
+
if (parts.length === 0) {
|
|
6657
|
+
throw new Error("--columns: no column names given");
|
|
6658
|
+
}
|
|
6659
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6660
|
+
const out = [];
|
|
6661
|
+
for (const name of parts) {
|
|
6662
|
+
if (!ALL_COLUMNS.includes(name)) {
|
|
6663
|
+
throw new Error(
|
|
6664
|
+
`--columns: unknown column "${name}" (valid: ${ALL_COLUMNS.join(", ")})`
|
|
6665
|
+
);
|
|
6666
|
+
}
|
|
6667
|
+
if (seen.has(name)) {
|
|
6668
|
+
throw new Error(`--columns: duplicate column "${name}"`);
|
|
6669
|
+
}
|
|
6670
|
+
seen.add(name);
|
|
6671
|
+
out.push(name);
|
|
6672
|
+
}
|
|
6673
|
+
return out;
|
|
6674
|
+
}
|
|
6505
6675
|
function toRow(s, now = Date.now()) {
|
|
6506
6676
|
return {
|
|
6507
6677
|
session: stripHydraSessionPrefix(s.sessionId),
|
|
6508
6678
|
upstream: formatUpstreamCell(s.upstreamSessionId, s.importedFromMachine),
|
|
6509
|
-
|
|
6510
|
-
|
|
6679
|
+
host: s.importedFromMachine ?? "-",
|
|
6680
|
+
state: formatState(s.status, s.busy, s.awaitingInput),
|
|
6681
|
+
agent: formatAgentCell(s.agentId),
|
|
6682
|
+
model: shortenModel(s.currentModel) ?? "-",
|
|
6511
6683
|
age: formatRelativeAge(s.updatedAt, now),
|
|
6512
6684
|
title: s.title ?? "-",
|
|
6513
|
-
cwd: shortenHomePath(s.cwd)
|
|
6685
|
+
cwd: shortenHomePath(s.cwd),
|
|
6686
|
+
cost: formatCostCell(s.currentUsage)
|
|
6514
6687
|
};
|
|
6515
6688
|
}
|
|
6516
6689
|
function formatUpstreamCell(upstreamSessionId, importedFromMachine) {
|
|
@@ -6522,22 +6695,33 @@ function formatUpstreamCell(upstreamSessionId, importedFromMachine) {
|
|
|
6522
6695
|
}
|
|
6523
6696
|
return "-";
|
|
6524
6697
|
}
|
|
6525
|
-
function formatState(status, busy) {
|
|
6698
|
+
function formatState(status, busy, awaitingInput) {
|
|
6526
6699
|
if (status === "cold") {
|
|
6527
6700
|
return "COLD";
|
|
6528
6701
|
}
|
|
6702
|
+
if (awaitingInput) {
|
|
6703
|
+
return "LIVE\u25E6";
|
|
6704
|
+
}
|
|
6529
6705
|
return busy ? "LIVE\u2022" : "LIVE";
|
|
6530
6706
|
}
|
|
6531
|
-
function computeWidths(rows) {
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6707
|
+
function computeWidths(rows, opts = {}) {
|
|
6708
|
+
const columns = opts.columns ?? DEFAULT_COLUMNS;
|
|
6709
|
+
const w = {
|
|
6710
|
+
session: 0,
|
|
6711
|
+
upstream: 0,
|
|
6712
|
+
host: 0,
|
|
6713
|
+
state: 0,
|
|
6714
|
+
agent: 0,
|
|
6715
|
+
model: 0,
|
|
6716
|
+
age: 0,
|
|
6717
|
+
cwd: 0,
|
|
6718
|
+
title: 0,
|
|
6719
|
+
cost: 0
|
|
6540
6720
|
};
|
|
6721
|
+
for (const col of columns) {
|
|
6722
|
+
w[col] = maxLen(HEADER[col], rows.map((r) => r[col]));
|
|
6723
|
+
}
|
|
6724
|
+
return w;
|
|
6541
6725
|
}
|
|
6542
6726
|
function formatRelativeAge(iso, now) {
|
|
6543
6727
|
if (!iso) {
|
|
@@ -6584,27 +6768,56 @@ function maxLen(headerCell, values) {
|
|
|
6584
6768
|
}
|
|
6585
6769
|
return max;
|
|
6586
6770
|
}
|
|
6587
|
-
function formatRow(r, w, maxWidth,
|
|
6588
|
-
const
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
r.state.padEnd(w.state),
|
|
6592
|
-
r.agent.padEnd(w.agent),
|
|
6593
|
-
r.age.padStart(w.age)
|
|
6594
|
-
].join(SEP);
|
|
6771
|
+
function formatRow(r, w, maxWidth, opts = {}) {
|
|
6772
|
+
const columns = opts.columns ?? DEFAULT_COLUMNS;
|
|
6773
|
+
const cwdMaxWidth = opts.cwdMaxWidth ?? DEFAULT_CWD_MAX_WIDTH;
|
|
6774
|
+
const naturalCell = (col) => col === "age" ? r[col].padStart(w[col]) : r[col].padEnd(w[col]);
|
|
6595
6775
|
if (maxWidth === void 0) {
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6776
|
+
const cells2 = columns.map(
|
|
6777
|
+
(col, i) => i === columns.length - 1 ? r[col] : naturalCell(col)
|
|
6778
|
+
);
|
|
6779
|
+
return cells2.join(SEP);
|
|
6780
|
+
}
|
|
6781
|
+
const elasticIdx = columns.map((col, i) => ({ col, i })).filter(({ col }) => ELASTIC_COLUMNS.has(col));
|
|
6782
|
+
const lastElastic = elasticIdx.length > 0 ? elasticIdx[elasticIdx.length - 1].i : -1;
|
|
6783
|
+
if (lastElastic === -1) {
|
|
6784
|
+
const out2 = columns.map(naturalCell).join(SEP);
|
|
6785
|
+
return out2.length > maxWidth ? out2.slice(0, maxWidth) : out2;
|
|
6786
|
+
}
|
|
6787
|
+
const fixedWidth = columns.filter((col) => !ELASTIC_COLUMNS.has(col)).reduce((sum, col) => sum + w[col], 0);
|
|
6788
|
+
const sepCount = Math.max(0, columns.length - 1);
|
|
6789
|
+
let budget = maxWidth - fixedWidth - sepCount * SEP.length;
|
|
6790
|
+
if (budget < 0) {
|
|
6791
|
+
budget = 0;
|
|
6792
|
+
}
|
|
6793
|
+
const elasticAlloc = /* @__PURE__ */ new Map();
|
|
6794
|
+
let remaining = budget;
|
|
6795
|
+
for (const { col, i } of elasticIdx) {
|
|
6796
|
+
if (i === lastElastic) {
|
|
6797
|
+
continue;
|
|
6798
|
+
}
|
|
6799
|
+
const natural = col === "cwd" ? Math.min(w[col], cwdMaxWidth) : w[col];
|
|
6800
|
+
const alloc = Math.min(natural, Math.max(0, remaining - 1));
|
|
6801
|
+
elasticAlloc.set(i, alloc);
|
|
6802
|
+
remaining = Math.max(0, remaining - alloc);
|
|
6601
6803
|
}
|
|
6602
|
-
|
|
6603
|
-
const
|
|
6604
|
-
const
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6804
|
+
elasticAlloc.set(lastElastic, Math.max(0, remaining));
|
|
6805
|
+
const lastCol = columns.length - 1;
|
|
6806
|
+
const renderElastic = (col, width, isLast) => {
|
|
6807
|
+
if (col === "cwd") {
|
|
6808
|
+
return truncateMiddle(r[col], width).padEnd(width);
|
|
6809
|
+
}
|
|
6810
|
+
const cell = truncateRight(r[col], width);
|
|
6811
|
+
return isLast ? cell : cell.padEnd(width);
|
|
6812
|
+
};
|
|
6813
|
+
const cells = columns.map((col, i) => {
|
|
6814
|
+
if (ELASTIC_COLUMNS.has(col)) {
|
|
6815
|
+
return renderElastic(col, elasticAlloc.get(i) ?? 0, i === lastCol);
|
|
6816
|
+
}
|
|
6817
|
+
return naturalCell(col);
|
|
6818
|
+
});
|
|
6819
|
+
const out = cells.join(SEP);
|
|
6820
|
+
return out.length > maxWidth ? out.slice(0, maxWidth) : out;
|
|
6608
6821
|
}
|
|
6609
6822
|
function truncateRight(s, max) {
|
|
6610
6823
|
if (max <= 0) {
|
|
@@ -6632,24 +6845,49 @@ function truncateMiddle(s, max) {
|
|
|
6632
6845
|
const tail = max - 1 - head;
|
|
6633
6846
|
return s.slice(0, head) + "\u2026" + s.slice(s.length - tail);
|
|
6634
6847
|
}
|
|
6635
|
-
var HEADER, SEP, DEFAULT_CWD_MAX_WIDTH;
|
|
6848
|
+
var ALL_COLUMNS, DEFAULT_COLUMNS, ELASTIC_COLUMNS, HEADER, SEP, DEFAULT_CWD_MAX_WIDTH;
|
|
6636
6849
|
var init_session_row = __esm({
|
|
6637
6850
|
"src/cli/session-row.ts"() {
|
|
6638
6851
|
"use strict";
|
|
6639
6852
|
init_agent_display();
|
|
6640
6853
|
init_paths();
|
|
6641
6854
|
init_session();
|
|
6855
|
+
ALL_COLUMNS = [
|
|
6856
|
+
"session",
|
|
6857
|
+
"upstream",
|
|
6858
|
+
"host",
|
|
6859
|
+
"state",
|
|
6860
|
+
"agent",
|
|
6861
|
+
"model",
|
|
6862
|
+
"age",
|
|
6863
|
+
"cwd",
|
|
6864
|
+
"title",
|
|
6865
|
+
"cost"
|
|
6866
|
+
];
|
|
6867
|
+
DEFAULT_COLUMNS = [
|
|
6868
|
+
"session",
|
|
6869
|
+
"state",
|
|
6870
|
+
"agent",
|
|
6871
|
+
"age",
|
|
6872
|
+
"cwd",
|
|
6873
|
+
"title",
|
|
6874
|
+
"cost"
|
|
6875
|
+
];
|
|
6876
|
+
ELASTIC_COLUMNS = /* @__PURE__ */ new Set(["cwd", "title"]);
|
|
6642
6877
|
HEADER = {
|
|
6643
6878
|
session: "SESSION",
|
|
6644
6879
|
upstream: "UPSTREAM",
|
|
6880
|
+
host: "HOST",
|
|
6645
6881
|
state: "STATE",
|
|
6646
6882
|
agent: "AGENT",
|
|
6883
|
+
model: "MODEL",
|
|
6647
6884
|
age: "AGE",
|
|
6648
6885
|
title: "TITLE",
|
|
6649
|
-
cwd: "CWD"
|
|
6886
|
+
cwd: "CWD",
|
|
6887
|
+
cost: "COST"
|
|
6650
6888
|
};
|
|
6651
6889
|
SEP = " ";
|
|
6652
|
-
DEFAULT_CWD_MAX_WIDTH =
|
|
6890
|
+
DEFAULT_CWD_MAX_WIDTH = 32;
|
|
6653
6891
|
}
|
|
6654
6892
|
});
|
|
6655
6893
|
|
|
@@ -7202,7 +7440,21 @@ function formatBlock(text, prefix, bodyStyle, prefixStyle, sentBy, fillRow) {
|
|
|
7202
7440
|
}
|
|
7203
7441
|
return out;
|
|
7204
7442
|
}
|
|
7205
|
-
function
|
|
7443
|
+
function formatElapsed(ms) {
|
|
7444
|
+
const totalSec = Math.floor(ms / 1e3);
|
|
7445
|
+
if (totalSec < 60) {
|
|
7446
|
+
return `${totalSec}s`;
|
|
7447
|
+
}
|
|
7448
|
+
const min = Math.floor(totalSec / 60);
|
|
7449
|
+
const sec = totalSec % 60;
|
|
7450
|
+
if (min < 60) {
|
|
7451
|
+
return sec === 0 ? `${min}m` : `${min}m ${sec}s`;
|
|
7452
|
+
}
|
|
7453
|
+
const hr = Math.floor(min / 60);
|
|
7454
|
+
const remMin = min % 60;
|
|
7455
|
+
return remMin === 0 ? `${hr}h` : `${hr}h ${remMin}m`;
|
|
7456
|
+
}
|
|
7457
|
+
function formatToolLine2(state, now = Date.now()) {
|
|
7206
7458
|
const initial = state.initialTitle;
|
|
7207
7459
|
const latest = state.latestTitle;
|
|
7208
7460
|
const initialLc = initial.toLowerCase();
|
|
@@ -7215,6 +7467,10 @@ function formatToolLine2(state) {
|
|
|
7215
7467
|
} else {
|
|
7216
7468
|
title = `${initial} \xB7 ${latest}`;
|
|
7217
7469
|
}
|
|
7470
|
+
if (state.startedAt !== void 0) {
|
|
7471
|
+
const end = state.endedAt ?? now;
|
|
7472
|
+
title = `${title} \xB7 ${formatElapsed(end - state.startedAt)}`;
|
|
7473
|
+
}
|
|
7218
7474
|
const lines = [
|
|
7219
7475
|
{
|
|
7220
7476
|
prefix: ` ${toolStatusIcon(state.status)} `,
|
|
@@ -7234,22 +7490,32 @@ function formatToolLine2(state) {
|
|
|
7234
7490
|
}
|
|
7235
7491
|
function formatEditDiffBlock(diff, mode) {
|
|
7236
7492
|
const lines = [];
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
});
|
|
7243
|
-
}
|
|
7493
|
+
const header = (open2) => ({
|
|
7494
|
+
prefix: " ",
|
|
7495
|
+
body: `${open2 ? "\u25BE" : "\u25B8"} Edited ${sanitizeSingleLine(shortenHomePath(diff.path))}`,
|
|
7496
|
+
bodyStyle: "dim"
|
|
7497
|
+
});
|
|
7244
7498
|
if (mode === "edit") {
|
|
7499
|
+
if (diff.path) {
|
|
7500
|
+
lines.push(header(false));
|
|
7501
|
+
}
|
|
7245
7502
|
return lines;
|
|
7246
7503
|
}
|
|
7247
|
-
const body = buildUnifiedDiff(diff);
|
|
7504
|
+
const body = buildUnifiedDiff(diff, { maxLines: Infinity });
|
|
7248
7505
|
if (body.length === 0) {
|
|
7506
|
+
if (diff.path) {
|
|
7507
|
+
lines.push(header(false));
|
|
7508
|
+
}
|
|
7249
7509
|
return lines;
|
|
7250
7510
|
}
|
|
7511
|
+
if (diff.path) {
|
|
7512
|
+
lines.push(header(true));
|
|
7513
|
+
}
|
|
7251
7514
|
const fenced = "```diff\n" + body + "\n```";
|
|
7252
7515
|
lines.push(...parseAgentMarkdown(fenced));
|
|
7516
|
+
if (lines.length > 0) {
|
|
7517
|
+
lines.unshift({ body: "" });
|
|
7518
|
+
}
|
|
7253
7519
|
return lines;
|
|
7254
7520
|
}
|
|
7255
7521
|
function buildUnifiedDiff(diff, opts = {}) {
|
|
@@ -7324,6 +7590,20 @@ function diffLines(a, b) {
|
|
|
7324
7590
|
}
|
|
7325
7591
|
return out;
|
|
7326
7592
|
}
|
|
7593
|
+
function isTerminalToolStatus(status) {
|
|
7594
|
+
switch (status) {
|
|
7595
|
+
case "completed":
|
|
7596
|
+
case "succeeded":
|
|
7597
|
+
case "ok":
|
|
7598
|
+
case "failed":
|
|
7599
|
+
case "error":
|
|
7600
|
+
case "rejected":
|
|
7601
|
+
case "cancelled":
|
|
7602
|
+
return true;
|
|
7603
|
+
default:
|
|
7604
|
+
return false;
|
|
7605
|
+
}
|
|
7606
|
+
}
|
|
7327
7607
|
function toolStatusIcon(status) {
|
|
7328
7608
|
switch (status) {
|
|
7329
7609
|
case "completed":
|
|
@@ -8243,7 +8523,7 @@ var init_input = __esm({
|
|
|
8243
8523
|
case "ctrl-n":
|
|
8244
8524
|
return this.handleDown();
|
|
8245
8525
|
case "ctrl-o":
|
|
8246
|
-
return [{ type: "toggle-
|
|
8526
|
+
return [{ type: "toggle-options" }];
|
|
8247
8527
|
case "backspace":
|
|
8248
8528
|
this.backspace();
|
|
8249
8529
|
return [];
|
|
@@ -8256,11 +8536,6 @@ var init_input = __esm({
|
|
|
8256
8536
|
if (this.bufferIsEmpty()) {
|
|
8257
8537
|
return [{ type: "exit" }];
|
|
8258
8538
|
}
|
|
8259
|
-
const lastLine = this.buffer[this.buffer.length - 1] ?? "";
|
|
8260
|
-
const atEndOfBuffer = this.row === this.buffer.length - 1 && this.col >= lastLine.length;
|
|
8261
|
-
if (atEndOfBuffer) {
|
|
8262
|
-
return [{ type: "exit" }];
|
|
8263
|
-
}
|
|
8264
8539
|
this.deleteForward();
|
|
8265
8540
|
return [];
|
|
8266
8541
|
}
|
|
@@ -9523,20 +9798,6 @@ function formatUsage(usage) {
|
|
|
9523
9798
|
}
|
|
9524
9799
|
return parts.length === 0 ? null : parts.join(" \xB7 ");
|
|
9525
9800
|
}
|
|
9526
|
-
function formatElapsed(ms) {
|
|
9527
|
-
const totalSec = Math.floor(ms / 1e3);
|
|
9528
|
-
if (totalSec < 60) {
|
|
9529
|
-
return `${totalSec}s`;
|
|
9530
|
-
}
|
|
9531
|
-
const min = Math.floor(totalSec / 60);
|
|
9532
|
-
const sec = totalSec % 60;
|
|
9533
|
-
if (min < 60) {
|
|
9534
|
-
return sec === 0 ? `${min}m` : `${min}m ${sec}s`;
|
|
9535
|
-
}
|
|
9536
|
-
const hr = Math.floor(min / 60);
|
|
9537
|
-
const remMin = min % 60;
|
|
9538
|
-
return remMin === 0 ? `${hr}h` : `${hr}h ${remMin}m`;
|
|
9539
|
-
}
|
|
9540
9801
|
function formatTokens(n) {
|
|
9541
9802
|
if (n >= 1e6) {
|
|
9542
9803
|
return `${(n / 1e6).toFixed(1)}M`;
|
|
@@ -9755,7 +10016,7 @@ function mapCsiUToKeyName(code, mod) {
|
|
|
9755
10016
|
}
|
|
9756
10017
|
return null;
|
|
9757
10018
|
}
|
|
9758
|
-
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;
|
|
10019
|
+
var SESSIONBAR_ROWS, BANNER_ROWS, SEPARATOR_ROWS, MAX_PROMPT_ROWS, MAX_QUEUED_ROWS, MAX_PERMISSION_ROWS, MAX_OPTIONS_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;
|
|
9759
10020
|
var init_screen = __esm({
|
|
9760
10021
|
"src/tui/screen.ts"() {
|
|
9761
10022
|
"use strict";
|
|
@@ -9763,6 +10024,7 @@ var init_screen = __esm({
|
|
|
9763
10024
|
init_paths();
|
|
9764
10025
|
init_session();
|
|
9765
10026
|
init_attachments();
|
|
10027
|
+
init_format();
|
|
9766
10028
|
init_sync();
|
|
9767
10029
|
SESSIONBAR_ROWS = 1;
|
|
9768
10030
|
BANNER_ROWS = 1;
|
|
@@ -9770,6 +10032,7 @@ var init_screen = __esm({
|
|
|
9770
10032
|
MAX_PROMPT_ROWS = 8;
|
|
9771
10033
|
MAX_QUEUED_ROWS = 5;
|
|
9772
10034
|
MAX_PERMISSION_ROWS = 12;
|
|
10035
|
+
MAX_OPTIONS_ROWS = 12;
|
|
9773
10036
|
MAX_HELP_ROWS = 30;
|
|
9774
10037
|
MAX_COMPLETION_ROWS = 6;
|
|
9775
10038
|
MAX_CHIP_ROWS = 4;
|
|
@@ -9844,6 +10107,7 @@ var init_screen = __esm({
|
|
|
9844
10107
|
lastFrameW = 0;
|
|
9845
10108
|
lastFrameH = 0;
|
|
9846
10109
|
permissionPrompt = null;
|
|
10110
|
+
optionsPrompt = null;
|
|
9847
10111
|
confirmPrompt = null;
|
|
9848
10112
|
helpPrompt = null;
|
|
9849
10113
|
completions = [];
|
|
@@ -10013,6 +10277,7 @@ var init_screen = __esm({
|
|
|
10013
10277
|
this.writeProgressIndicator(0);
|
|
10014
10278
|
this.started = false;
|
|
10015
10279
|
if (!opts.keepFullscreen) {
|
|
10280
|
+
emergencyTerminalReset();
|
|
10016
10281
|
this.term.fullscreen(false);
|
|
10017
10282
|
this.term("\n");
|
|
10018
10283
|
}
|
|
@@ -10186,6 +10451,11 @@ uncaught: ${err.stack ?? err.message}
|
|
|
10186
10451
|
}
|
|
10187
10452
|
if (text.includes("\n")) {
|
|
10188
10453
|
const parts = text.split("\n");
|
|
10454
|
+
const nonEmpty = parts.filter((p) => p.length > 0).length;
|
|
10455
|
+
if (nonEmpty > 1) {
|
|
10456
|
+
this.onKey([{ type: "paste", text: text.replace(/\r/g, "") }]);
|
|
10457
|
+
return;
|
|
10458
|
+
}
|
|
10189
10459
|
for (let i = 0; i < parts.length; i++) {
|
|
10190
10460
|
if (parts[i].length > 0) {
|
|
10191
10461
|
this.handleRawStdin(Buffer.from(parts[i], "binary"));
|
|
@@ -10713,6 +10983,41 @@ uncaught: ${err.stack ?? err.message}
|
|
|
10713
10983
|
clearKey(key) {
|
|
10714
10984
|
this.keyedBlocks.delete(key);
|
|
10715
10985
|
}
|
|
10986
|
+
// Whether a keyed block currently exists in scrollback.
|
|
10987
|
+
hasKey(key) {
|
|
10988
|
+
return this.keyedBlocks.has(key);
|
|
10989
|
+
}
|
|
10990
|
+
// Splice a keyed block's lines out of scrollback entirely (unlike
|
|
10991
|
+
// clearKey, which only forgets the key but leaves the lines painted).
|
|
10992
|
+
// Later blocks' start indices shift up by the removed count so they
|
|
10993
|
+
// stay aligned with the lines array. No-op if the key is unknown.
|
|
10994
|
+
removeKey(key) {
|
|
10995
|
+
const block = this.keyedBlocks.get(key);
|
|
10996
|
+
if (!block) {
|
|
10997
|
+
return;
|
|
10998
|
+
}
|
|
10999
|
+
const end = block.start + block.count;
|
|
11000
|
+
const touchesEnd = end >= this.lines.length;
|
|
11001
|
+
const removedRows = this.wrappedRowsOfMany(
|
|
11002
|
+
this.lines.slice(block.start, end)
|
|
11003
|
+
);
|
|
11004
|
+
const removed = this.lines.splice(block.start, block.count);
|
|
11005
|
+
for (const line of removed) {
|
|
11006
|
+
this.forgetLine(line);
|
|
11007
|
+
}
|
|
11008
|
+
this.keyedBlocks.delete(key);
|
|
11009
|
+
for (const [k, range] of this.keyedBlocks) {
|
|
11010
|
+
if (k !== key && range.start > block.start) {
|
|
11011
|
+
range.start -= block.count;
|
|
11012
|
+
}
|
|
11013
|
+
}
|
|
11014
|
+
if (touchesEnd) {
|
|
11015
|
+
this.streamingActive = false;
|
|
11016
|
+
}
|
|
11017
|
+
this.adjustScrollForRowChange(-removedRows);
|
|
11018
|
+
this.moveStickyToEnd();
|
|
11019
|
+
this.scheduleRepaint();
|
|
11020
|
+
}
|
|
10716
11021
|
// Mark `key` as the sticky-bottom block. While set, whenever new content
|
|
10717
11022
|
// lands after the block's lines (appendLines / appendStreaming / a new
|
|
10718
11023
|
// upserted block) the screen floats this block back to the end so it
|
|
@@ -10831,6 +11136,15 @@ uncaught: ${err.stack ?? err.message}
|
|
|
10831
11136
|
this.permissionPrompt = spec ? { ...spec } : null;
|
|
10832
11137
|
this.repaint();
|
|
10833
11138
|
}
|
|
11139
|
+
// Interactive session-options modal (^O). Takes over the prompt area
|
|
11140
|
+
// like the permission modal. Pass null to dismiss.
|
|
11141
|
+
setOptionsPrompt(spec) {
|
|
11142
|
+
this.optionsPrompt = spec ? { ...spec, options: spec.options.map((o) => ({ ...o })) } : null;
|
|
11143
|
+
this.repaint();
|
|
11144
|
+
}
|
|
11145
|
+
isOptionsPromptActive() {
|
|
11146
|
+
return this.optionsPrompt !== null;
|
|
11147
|
+
}
|
|
10834
11148
|
// Two-line confirmation modal that takes over the prompt area. Pass
|
|
10835
11149
|
// null to dismiss. Currently unused — kept as a generic primitive for
|
|
10836
11150
|
// any future modal that needs a question + hint footer.
|
|
@@ -11321,7 +11635,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11321
11635
|
this.drawSeparator(h - SESSIONBAR_ROWS);
|
|
11322
11636
|
this.drawSessionbar();
|
|
11323
11637
|
this.placeCursor();
|
|
11324
|
-
if (this.permissionPrompt || this.confirmPrompt || this.helpPrompt) {
|
|
11638
|
+
if (this.permissionPrompt || this.optionsPrompt || this.confirmPrompt || this.helpPrompt) {
|
|
11325
11639
|
this.term.hideCursor(false);
|
|
11326
11640
|
}
|
|
11327
11641
|
this.lastPromptRows = promptRows;
|
|
@@ -11425,7 +11739,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11425
11739
|
this.repaint();
|
|
11426
11740
|
}
|
|
11427
11741
|
completionRows() {
|
|
11428
|
-
if (this.permissionPrompt || this.confirmPrompt || this.helpPrompt) {
|
|
11742
|
+
if (this.permissionPrompt || this.optionsPrompt || this.confirmPrompt || this.helpPrompt) {
|
|
11429
11743
|
return 0;
|
|
11430
11744
|
}
|
|
11431
11745
|
return Math.min(MAX_COMPLETION_ROWS, this.completions.length);
|
|
@@ -11565,6 +11879,10 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11565
11879
|
this.drawPermissionPrompt();
|
|
11566
11880
|
return;
|
|
11567
11881
|
}
|
|
11882
|
+
if (this.optionsPrompt) {
|
|
11883
|
+
this.drawOptionsPrompt();
|
|
11884
|
+
return;
|
|
11885
|
+
}
|
|
11568
11886
|
if (this.confirmPrompt) {
|
|
11569
11887
|
this.drawConfirmPrompt();
|
|
11570
11888
|
return;
|
|
@@ -11787,6 +12105,14 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11787
12105
|
this.term.moveTo(2, Math.min(optionRow, lastUsableRow));
|
|
11788
12106
|
return;
|
|
11789
12107
|
}
|
|
12108
|
+
if (this.optionsPrompt) {
|
|
12109
|
+
const rows = this.optionsRows();
|
|
12110
|
+
const top2 = this.term.height - rows - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
12111
|
+
const optionRow = top2 + 1 + this.optionsPrompt.selectedIndex;
|
|
12112
|
+
const lastUsableRow = this.term.height - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS;
|
|
12113
|
+
this.term.moveTo(2, Math.min(optionRow, lastUsableRow));
|
|
12114
|
+
return;
|
|
12115
|
+
}
|
|
11790
12116
|
if (this.confirmPrompt) {
|
|
11791
12117
|
const top2 = this.term.height - CONFIRM_PROMPT_ROWS - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
11792
12118
|
this.term.moveTo(2, top2);
|
|
@@ -11825,6 +12151,9 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11825
12151
|
if (this.permissionPrompt) {
|
|
11826
12152
|
return this.permissionRows();
|
|
11827
12153
|
}
|
|
12154
|
+
if (this.optionsPrompt) {
|
|
12155
|
+
return this.optionsRows();
|
|
12156
|
+
}
|
|
11828
12157
|
if (this.confirmPrompt) {
|
|
11829
12158
|
return CONFIRM_PROMPT_ROWS;
|
|
11830
12159
|
}
|
|
@@ -11849,6 +12178,64 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11849
12178
|
4 + this.permissionPrompt.options.length
|
|
11850
12179
|
);
|
|
11851
12180
|
}
|
|
12181
|
+
optionsRows() {
|
|
12182
|
+
if (!this.optionsPrompt) {
|
|
12183
|
+
return 0;
|
|
12184
|
+
}
|
|
12185
|
+
return Math.min(MAX_OPTIONS_ROWS, 2 + this.optionsPrompt.options.length);
|
|
12186
|
+
}
|
|
12187
|
+
drawOptionsPrompt() {
|
|
12188
|
+
const spec = this.optionsPrompt;
|
|
12189
|
+
if (!spec) {
|
|
12190
|
+
return;
|
|
12191
|
+
}
|
|
12192
|
+
const w = this.term.width;
|
|
12193
|
+
const rows = this.optionsRows();
|
|
12194
|
+
const top = this.term.height - rows - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
12195
|
+
let row = top;
|
|
12196
|
+
const writeRow = (sig, paint) => {
|
|
12197
|
+
if (row >= top + rows) {
|
|
12198
|
+
return;
|
|
12199
|
+
}
|
|
12200
|
+
this.paintRow(row, sig, paint);
|
|
12201
|
+
row += 1;
|
|
12202
|
+
};
|
|
12203
|
+
writeRow(`opts|t|${w}|${spec.title}`, () => {
|
|
12204
|
+
this.term.brightYellow(` \u2699 ${truncate(spec.title, w - 5)}`);
|
|
12205
|
+
});
|
|
12206
|
+
const labelWidth = Math.max(
|
|
12207
|
+
...spec.options.map((o) => o.label.length),
|
|
12208
|
+
0
|
|
12209
|
+
);
|
|
12210
|
+
for (let i = 0; i < spec.options.length; i++) {
|
|
12211
|
+
if (row >= top + rows - 1) {
|
|
12212
|
+
break;
|
|
12213
|
+
}
|
|
12214
|
+
const opt = spec.options[i];
|
|
12215
|
+
if (!opt) {
|
|
12216
|
+
continue;
|
|
12217
|
+
}
|
|
12218
|
+
const isSel = i === spec.selectedIndex;
|
|
12219
|
+
const marker = isSel ? "\u276F" : " ";
|
|
12220
|
+
const prefix = ` ${marker} ${i + 1}. `;
|
|
12221
|
+
const paddedLabel = opt.label.padEnd(labelWidth);
|
|
12222
|
+
const room = w - prefix.length - 3;
|
|
12223
|
+
const body = `${prefix}${truncate(`${paddedLabel} ${opt.value}`, room)}`;
|
|
12224
|
+
writeRow(
|
|
12225
|
+
`opts|o|${w}|${i}|${isSel ? "1" : "0"}|${opt.value}|${opt.label}`,
|
|
12226
|
+
() => {
|
|
12227
|
+
if (isSel) {
|
|
12228
|
+
this.term.brightYellow(body);
|
|
12229
|
+
} else {
|
|
12230
|
+
this.term.dim(body);
|
|
12231
|
+
}
|
|
12232
|
+
}
|
|
12233
|
+
);
|
|
12234
|
+
}
|
|
12235
|
+
writeRow(`opts|hint|${w}`, () => {
|
|
12236
|
+
this.term.dim(" \u2191/\u2193 choose \xB7 Enter this session \xB7 s save default \xB7 Esc close");
|
|
12237
|
+
});
|
|
12238
|
+
}
|
|
11852
12239
|
// Walk this.lines from the tail, accumulating wrapped rows via the
|
|
11853
12240
|
// wrap cache, until we have at least `needed` rows or run out. Returns
|
|
11854
12241
|
// the collected rows in original (top-down) order plus an `exhausted`
|
|
@@ -12023,7 +12410,7 @@ function drawBox(term, opts) {
|
|
|
12023
12410
|
const termW = readTermWidth(term);
|
|
12024
12411
|
const termH = readTermHeight(term);
|
|
12025
12412
|
const desiredContentW = opts.contentWidth ?? MAX_BOX_WIDTH;
|
|
12026
|
-
const maxContentW = Math.max(10,
|
|
12413
|
+
const maxContentW = Math.max(10, termW - 4);
|
|
12027
12414
|
const contentW = Math.min(desiredContentW, maxContentW);
|
|
12028
12415
|
const w = contentW + 2;
|
|
12029
12416
|
const contentH = Math.max(1, Math.min(opts.contentHeight, termH - 4));
|
|
@@ -12473,8 +12860,12 @@ async function pickSession(term, opts) {
|
|
|
12473
12860
|
return base;
|
|
12474
12861
|
};
|
|
12475
12862
|
let visible = applyPrefsFilters(allSessions);
|
|
12863
|
+
const formatOpts = {
|
|
12864
|
+
columns: opts.config.tui.sessionColumns ?? DEFAULT_COLUMNS,
|
|
12865
|
+
cwdMaxWidth: opts.config.tui.cwdColumnMaxWidth
|
|
12866
|
+
};
|
|
12476
12867
|
let rows = visible.map((s) => toRow(s, Date.now()));
|
|
12477
|
-
let widths = computeWidths(rows);
|
|
12868
|
+
let widths = computeWidths(rows, formatOpts);
|
|
12478
12869
|
let total = 1 + visible.length;
|
|
12479
12870
|
let selectedIdx = 0;
|
|
12480
12871
|
let scrollOffset = 0;
|
|
@@ -12501,7 +12892,11 @@ async function pickSession(term, opts) {
|
|
|
12501
12892
|
let findInFlight = false;
|
|
12502
12893
|
let renameBuffer = "";
|
|
12503
12894
|
let transientStatus = null;
|
|
12895
|
+
let currentSessionGone = false;
|
|
12504
12896
|
const composer = new InputDispatcher({ history: [] });
|
|
12897
|
+
if (opts.initialPrompt) {
|
|
12898
|
+
composer.setBuffer(opts.initialPrompt);
|
|
12899
|
+
}
|
|
12505
12900
|
const composerHistoryCap = opts.config.tui.promptHistoryMaxEntries;
|
|
12506
12901
|
loadHistory(paths.globalTuiHistoryFile()).then((entries) => {
|
|
12507
12902
|
const capped = entries.length > composerHistoryCap ? entries.slice(entries.length - composerHistoryCap) : entries;
|
|
@@ -12526,7 +12921,6 @@ async function pickSession(term, opts) {
|
|
|
12526
12921
|
let findBoxWindowStart = 0;
|
|
12527
12922
|
let findBoxCursorVisualRow = 0;
|
|
12528
12923
|
let findBoxCursorVisualCol = 0;
|
|
12529
|
-
const cwdMaxWidth = opts.config.tui.cwdColumnMaxWidth;
|
|
12530
12924
|
const computeLayout = () => {
|
|
12531
12925
|
termHeight = readTermHeight2(term);
|
|
12532
12926
|
termWidth = readTermWidth2(term);
|
|
@@ -12548,16 +12942,16 @@ async function pickSession(term, opts) {
|
|
|
12548
12942
|
const reserved = 6 + composerRows;
|
|
12549
12943
|
const maxViewportRows = Math.max(3, termHeight - reserved);
|
|
12550
12944
|
viewportSize = Math.min(visible.length, maxViewportRows);
|
|
12551
|
-
headerLine = formatRow(HEADER, widths, rowMaxWidth,
|
|
12945
|
+
headerLine = formatRow(HEADER, widths, rowMaxWidth, formatOpts).padEnd(
|
|
12552
12946
|
rowMaxWidth
|
|
12553
12947
|
);
|
|
12554
12948
|
sessionLines = rows.map(
|
|
12555
|
-
(r) => formatRow(r, widths, rowMaxWidth,
|
|
12949
|
+
(r) => formatRow(r, widths, rowMaxWidth, formatOpts).padEnd(rowMaxWidth)
|
|
12556
12950
|
);
|
|
12557
12951
|
};
|
|
12558
12952
|
const rebuildRows = () => {
|
|
12559
12953
|
rows = visible.map((s) => toRow(s, Date.now()));
|
|
12560
|
-
widths = computeWidths(rows);
|
|
12954
|
+
widths = computeWidths(rows, formatOpts);
|
|
12561
12955
|
total = 1 + visible.length;
|
|
12562
12956
|
computeLayout();
|
|
12563
12957
|
};
|
|
@@ -13300,9 +13694,19 @@ async function pickSession(term, opts) {
|
|
|
13300
13694
|
term.moveTo(1, indicatorRow() + 1);
|
|
13301
13695
|
term("\n");
|
|
13302
13696
|
};
|
|
13697
|
+
const tryAbort = () => {
|
|
13698
|
+
if (currentSessionGone) {
|
|
13699
|
+
transientStatus = "current session ended \u2014 pick a session or start a new one";
|
|
13700
|
+
paintIndicator();
|
|
13701
|
+
return false;
|
|
13702
|
+
}
|
|
13703
|
+
cleanup();
|
|
13704
|
+
resolve8({ kind: "abort" });
|
|
13705
|
+
return true;
|
|
13706
|
+
};
|
|
13303
13707
|
const renderFingerprint = () => {
|
|
13304
13708
|
const cells = rows.map(
|
|
13305
|
-
(r) => `${r.session}|${r.upstream}|${r.state}|${r.agent}|${r.age}|${r.title}|${r.cwd}`
|
|
13709
|
+
(r) => `${r.session}|${r.upstream}|${r.host}|${r.state}|${r.agent}|${r.model}|${r.age}|${r.title}|${r.cwd}|${r.cost}`
|
|
13306
13710
|
).join("\n");
|
|
13307
13711
|
return `${selectedIdx}:${scrollOffset}:${transientStatus ?? ""}
|
|
13308
13712
|
${cells}`;
|
|
@@ -13392,6 +13796,9 @@ ${cells}`;
|
|
|
13392
13796
|
}
|
|
13393
13797
|
mode = "normal";
|
|
13394
13798
|
pendingAction = null;
|
|
13799
|
+
if (session.sessionId === opts.currentSessionId) {
|
|
13800
|
+
currentSessionGone = true;
|
|
13801
|
+
}
|
|
13395
13802
|
await refresh(kind === "kill" ? session.sessionId : void 0);
|
|
13396
13803
|
} catch (err) {
|
|
13397
13804
|
mode = "normal";
|
|
@@ -13738,8 +14145,7 @@ ${cells}`;
|
|
|
13738
14145
|
}
|
|
13739
14146
|
if (selectedIdx === 0 && !searchActive) {
|
|
13740
14147
|
if (name === "ESCAPE") {
|
|
13741
|
-
|
|
13742
|
-
resolve8({ kind: "abort" });
|
|
14148
|
+
tryAbort();
|
|
13743
14149
|
return;
|
|
13744
14150
|
}
|
|
13745
14151
|
if (name === "ENTER" || name === "KP_ENTER") {
|
|
@@ -13792,8 +14198,7 @@ ${cells}`;
|
|
|
13792
14198
|
const after = composer.state();
|
|
13793
14199
|
const unchanged = before.buffer.length === after.buffer.length && before.buffer.every((line, i) => line === after.buffer[i]) && before.row === after.row && before.col === after.col;
|
|
13794
14200
|
if (effects.some((e) => e.type === "exit")) {
|
|
13795
|
-
|
|
13796
|
-
resolve8({ kind: "abort" });
|
|
14201
|
+
tryAbort();
|
|
13797
14202
|
return;
|
|
13798
14203
|
}
|
|
13799
14204
|
if (unchanged) {
|
|
@@ -13866,8 +14271,7 @@ ${cells}`;
|
|
|
13866
14271
|
return;
|
|
13867
14272
|
}
|
|
13868
14273
|
if (name === "q" || name === "Q") {
|
|
13869
|
-
|
|
13870
|
-
resolve8({ kind: "abort" });
|
|
14274
|
+
tryAbort();
|
|
13871
14275
|
return;
|
|
13872
14276
|
}
|
|
13873
14277
|
if (name === "o" || name === "O") {
|
|
@@ -14043,8 +14447,7 @@ ${cells}`;
|
|
|
14043
14447
|
case "ESCAPE":
|
|
14044
14448
|
case "CTRL_C":
|
|
14045
14449
|
case "CTRL_D":
|
|
14046
|
-
|
|
14047
|
-
resolve8({ kind: "abort" });
|
|
14450
|
+
tryAbort();
|
|
14048
14451
|
return;
|
|
14049
14452
|
}
|
|
14050
14453
|
};
|
|
@@ -14093,6 +14496,9 @@ function sortSessions(sessions, cwd) {
|
|
|
14093
14496
|
return 0;
|
|
14094
14497
|
}
|
|
14095
14498
|
const base = s.cwd === cwd ? 2 : 1;
|
|
14499
|
+
if (s.awaitingInput) {
|
|
14500
|
+
return base + 4;
|
|
14501
|
+
}
|
|
14096
14502
|
return s.busy ? base + 2 : base;
|
|
14097
14503
|
};
|
|
14098
14504
|
return [...sessions].sort((a, b) => {
|
|
@@ -14409,28 +14815,218 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
14409
14815
|
}
|
|
14410
14816
|
return;
|
|
14411
14817
|
}
|
|
14412
|
-
if (name === "CTRL_U") {
|
|
14413
|
-
buffer = "";
|
|
14414
|
-
errorLine = null;
|
|
14415
|
-
repaintInput();
|
|
14818
|
+
if (name === "CTRL_U") {
|
|
14819
|
+
buffer = "";
|
|
14820
|
+
errorLine = null;
|
|
14821
|
+
repaintInput();
|
|
14822
|
+
return;
|
|
14823
|
+
}
|
|
14824
|
+
if (name === "CTRL_W") {
|
|
14825
|
+
const trimmedRight = buffer.replace(/[/\s]+$/, "");
|
|
14826
|
+
const lastSep = Math.max(
|
|
14827
|
+
trimmedRight.lastIndexOf("/"),
|
|
14828
|
+
trimmedRight.lastIndexOf(" ")
|
|
14829
|
+
);
|
|
14830
|
+
buffer = lastSep >= 0 ? trimmedRight.slice(0, lastSep + 1) : "";
|
|
14831
|
+
errorLine = null;
|
|
14832
|
+
repaintInput();
|
|
14833
|
+
return;
|
|
14834
|
+
}
|
|
14835
|
+
if (data?.isCharacter) {
|
|
14836
|
+
buffer += name;
|
|
14837
|
+
errorLine = null;
|
|
14838
|
+
repaintInput();
|
|
14839
|
+
return;
|
|
14840
|
+
}
|
|
14841
|
+
};
|
|
14842
|
+
term.grabInput({});
|
|
14843
|
+
term.on("key", onKey);
|
|
14844
|
+
term.on("resize", onResize);
|
|
14845
|
+
});
|
|
14846
|
+
}
|
|
14847
|
+
function truncate3(s, max) {
|
|
14848
|
+
if (max <= 1) {
|
|
14849
|
+
return "";
|
|
14850
|
+
}
|
|
14851
|
+
if (s.length <= max) {
|
|
14852
|
+
return s;
|
|
14853
|
+
}
|
|
14854
|
+
return s.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
14855
|
+
}
|
|
14856
|
+
function truncateLeft(s, max) {
|
|
14857
|
+
if (max <= 1) {
|
|
14858
|
+
return "";
|
|
14859
|
+
}
|
|
14860
|
+
if (s.length <= max) {
|
|
14861
|
+
return s;
|
|
14862
|
+
}
|
|
14863
|
+
return "\u2026" + s.slice(s.length - (max - 1));
|
|
14864
|
+
}
|
|
14865
|
+
var init_import_cwd_prompt = __esm({
|
|
14866
|
+
"src/tui/import-cwd-prompt.ts"() {
|
|
14867
|
+
"use strict";
|
|
14868
|
+
init_paths();
|
|
14869
|
+
init_session();
|
|
14870
|
+
init_cwd();
|
|
14871
|
+
init_completion();
|
|
14872
|
+
init_prompt_utils();
|
|
14873
|
+
}
|
|
14874
|
+
});
|
|
14875
|
+
|
|
14876
|
+
// src/tui/agent-prompt.ts
|
|
14877
|
+
function initialIndex(agents) {
|
|
14878
|
+
const idx = agents.findIndex((a) => a.id === PREFERRED_DEFAULT);
|
|
14879
|
+
return idx === -1 ? 0 : idx;
|
|
14880
|
+
}
|
|
14881
|
+
async function promptForAgent(term, agents) {
|
|
14882
|
+
resetTerminalModes();
|
|
14883
|
+
let selected = initialIndex(agents);
|
|
14884
|
+
let windowStart = 0;
|
|
14885
|
+
const visibleRows = () => {
|
|
14886
|
+
const termBudget = readTermHeight(term) - 8;
|
|
14887
|
+
return Math.max(1, Math.min(MAX_VISIBLE_ROWS, agents.length, termBudget));
|
|
14888
|
+
};
|
|
14889
|
+
const reclamp = () => {
|
|
14890
|
+
const rows = visibleRows();
|
|
14891
|
+
if (selected < windowStart) {
|
|
14892
|
+
windowStart = selected;
|
|
14893
|
+
} else if (selected >= windowStart + rows) {
|
|
14894
|
+
windowStart = selected - rows + 1;
|
|
14895
|
+
}
|
|
14896
|
+
const maxStart = Math.max(0, agents.length - rows);
|
|
14897
|
+
if (windowStart > maxStart) {
|
|
14898
|
+
windowStart = maxStart;
|
|
14899
|
+
}
|
|
14900
|
+
if (windowStart < 0) {
|
|
14901
|
+
windowStart = 0;
|
|
14902
|
+
}
|
|
14903
|
+
};
|
|
14904
|
+
const render = () => {
|
|
14905
|
+
reclamp();
|
|
14906
|
+
const rows = visibleRows();
|
|
14907
|
+
const contentHeight = rows + 4;
|
|
14908
|
+
const contentWidth = Math.min(
|
|
14909
|
+
PREFERRED_CONTENT_WIDTH,
|
|
14910
|
+
Math.max(40, readTermWidth(term) - 8)
|
|
14911
|
+
);
|
|
14912
|
+
const layout = drawBox(term, {
|
|
14913
|
+
contentHeight,
|
|
14914
|
+
contentWidth,
|
|
14915
|
+
title: "Select agent"
|
|
14916
|
+
});
|
|
14917
|
+
const innerW = layout.contentW;
|
|
14918
|
+
let row = 0;
|
|
14919
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
14920
|
+
term.noFormat(" Which agent should this session use?");
|
|
14921
|
+
row += 2;
|
|
14922
|
+
const end = Math.min(agents.length, windowStart + rows);
|
|
14923
|
+
for (let i = windowStart; i < end; i++) {
|
|
14924
|
+
const agent = agents[i];
|
|
14925
|
+
if (!agent) {
|
|
14926
|
+
continue;
|
|
14927
|
+
}
|
|
14928
|
+
const pointer = i === selected ? "\u276F" : " ";
|
|
14929
|
+
const desc = agent.description ?? agent.name;
|
|
14930
|
+
const idPart = ` ${pointer} ${agent.id}`;
|
|
14931
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
14932
|
+
if (i === selected) {
|
|
14933
|
+
const line = `${idPart} ${desc}`;
|
|
14934
|
+
term.brightWhite.bgBlue.noFormat(padRight2(truncate4(line, innerW), innerW));
|
|
14935
|
+
} else {
|
|
14936
|
+
term.noFormat(idPart);
|
|
14937
|
+
const room = innerW - idPart.length - 2;
|
|
14938
|
+
if (room > 1) {
|
|
14939
|
+
term.dim.noFormat(` ${truncate4(desc, room)}`);
|
|
14940
|
+
}
|
|
14941
|
+
}
|
|
14942
|
+
row++;
|
|
14943
|
+
}
|
|
14944
|
+
row++;
|
|
14945
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
14946
|
+
const more = agents.length > rows ? ` (${selected + 1}/${agents.length})` : "";
|
|
14947
|
+
term.dim.noFormat(
|
|
14948
|
+
` \u2191/\u2193 navigate \xB7 Enter this session \xB7 s set default \xB7 Esc back${more}`
|
|
14949
|
+
);
|
|
14950
|
+
return layout;
|
|
14951
|
+
};
|
|
14952
|
+
render();
|
|
14953
|
+
term.hideCursor();
|
|
14954
|
+
return await new Promise((resolve8) => {
|
|
14955
|
+
let resolved = false;
|
|
14956
|
+
const cleanup = () => {
|
|
14957
|
+
if (resolved) {
|
|
14958
|
+
return;
|
|
14959
|
+
}
|
|
14960
|
+
resolved = true;
|
|
14961
|
+
term.off("key", onKey);
|
|
14962
|
+
term.off("resize", onResize);
|
|
14963
|
+
term.grabInput(false);
|
|
14964
|
+
term.hideCursor(false);
|
|
14965
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
14966
|
+
};
|
|
14967
|
+
const finish = (value) => {
|
|
14968
|
+
cleanup();
|
|
14969
|
+
resolve8(value);
|
|
14970
|
+
};
|
|
14971
|
+
const onResize = () => {
|
|
14972
|
+
if (resolved) {
|
|
14973
|
+
return;
|
|
14974
|
+
}
|
|
14975
|
+
render();
|
|
14976
|
+
};
|
|
14977
|
+
const moveDown = () => {
|
|
14978
|
+
if (selected < agents.length - 1) {
|
|
14979
|
+
selected++;
|
|
14980
|
+
render();
|
|
14981
|
+
}
|
|
14982
|
+
};
|
|
14983
|
+
const moveUp = () => {
|
|
14984
|
+
if (selected > 0) {
|
|
14985
|
+
selected--;
|
|
14986
|
+
render();
|
|
14987
|
+
}
|
|
14988
|
+
};
|
|
14989
|
+
const onKey = (name, _m, data) => {
|
|
14990
|
+
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
14991
|
+
finish({ kind: "cancel" });
|
|
14992
|
+
return;
|
|
14993
|
+
}
|
|
14994
|
+
if (name === "ESCAPE") {
|
|
14995
|
+
finish({ kind: "back" });
|
|
14996
|
+
return;
|
|
14997
|
+
}
|
|
14998
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
14999
|
+
const agent = agents[selected];
|
|
15000
|
+
if (agent) {
|
|
15001
|
+
finish({ kind: "select", agentId: agent.id, persist: false });
|
|
15002
|
+
}
|
|
15003
|
+
return;
|
|
15004
|
+
}
|
|
15005
|
+
if (name === "UP" || name === "SHIFT_TAB") {
|
|
15006
|
+
moveUp();
|
|
14416
15007
|
return;
|
|
14417
15008
|
}
|
|
14418
|
-
if (name === "
|
|
14419
|
-
|
|
14420
|
-
const lastSep = Math.max(
|
|
14421
|
-
trimmedRight.lastIndexOf("/"),
|
|
14422
|
-
trimmedRight.lastIndexOf(" ")
|
|
14423
|
-
);
|
|
14424
|
-
buffer = lastSep >= 0 ? trimmedRight.slice(0, lastSep + 1) : "";
|
|
14425
|
-
errorLine = null;
|
|
14426
|
-
repaintInput();
|
|
15009
|
+
if (name === "DOWN" || name === "TAB") {
|
|
15010
|
+
moveDown();
|
|
14427
15011
|
return;
|
|
14428
15012
|
}
|
|
14429
15013
|
if (data?.isCharacter) {
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
15014
|
+
const lower = name.toLowerCase();
|
|
15015
|
+
if (lower === "s") {
|
|
15016
|
+
const agent = agents[selected];
|
|
15017
|
+
if (agent) {
|
|
15018
|
+
finish({ kind: "select", agentId: agent.id, persist: true });
|
|
15019
|
+
}
|
|
15020
|
+
return;
|
|
15021
|
+
}
|
|
15022
|
+
if (lower === "j") {
|
|
15023
|
+
moveDown();
|
|
15024
|
+
return;
|
|
15025
|
+
}
|
|
15026
|
+
if (lower === "k") {
|
|
15027
|
+
moveUp();
|
|
15028
|
+
return;
|
|
15029
|
+
}
|
|
14434
15030
|
}
|
|
14435
15031
|
};
|
|
14436
15032
|
term.grabInput({});
|
|
@@ -14438,7 +15034,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
14438
15034
|
term.on("resize", onResize);
|
|
14439
15035
|
});
|
|
14440
15036
|
}
|
|
14441
|
-
function
|
|
15037
|
+
function truncate4(s, max) {
|
|
14442
15038
|
if (max <= 1) {
|
|
14443
15039
|
return "";
|
|
14444
15040
|
}
|
|
@@ -14447,23 +15043,20 @@ function truncate3(s, max) {
|
|
|
14447
15043
|
}
|
|
14448
15044
|
return s.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
14449
15045
|
}
|
|
14450
|
-
function
|
|
14451
|
-
if (
|
|
14452
|
-
return
|
|
14453
|
-
}
|
|
14454
|
-
if (s.length <= max) {
|
|
14455
|
-
return s;
|
|
15046
|
+
function padRight2(s, w) {
|
|
15047
|
+
if (s.length >= w) {
|
|
15048
|
+
return s.slice(0, w);
|
|
14456
15049
|
}
|
|
14457
|
-
return
|
|
15050
|
+
return s + " ".repeat(w - s.length);
|
|
14458
15051
|
}
|
|
14459
|
-
var
|
|
14460
|
-
|
|
15052
|
+
var PREFERRED_DEFAULT, MAX_VISIBLE_ROWS, PREFERRED_CONTENT_WIDTH;
|
|
15053
|
+
var init_agent_prompt = __esm({
|
|
15054
|
+
"src/tui/agent-prompt.ts"() {
|
|
14461
15055
|
"use strict";
|
|
14462
|
-
init_paths();
|
|
14463
|
-
init_session();
|
|
14464
|
-
init_cwd();
|
|
14465
|
-
init_completion();
|
|
14466
15056
|
init_prompt_utils();
|
|
15057
|
+
PREFERRED_DEFAULT = "opencode";
|
|
15058
|
+
MAX_VISIBLE_ROWS = 20;
|
|
15059
|
+
PREFERRED_CONTENT_WIDTH = 88;
|
|
14467
15060
|
}
|
|
14468
15061
|
});
|
|
14469
15062
|
|
|
@@ -14801,7 +15394,12 @@ async function runTuiApp(opts) {
|
|
|
14801
15394
|
const term = termkit.terminal;
|
|
14802
15395
|
const exitHint = {};
|
|
14803
15396
|
const viewPrefs = {
|
|
14804
|
-
showThoughts: config.tui.showThoughts
|
|
15397
|
+
showThoughts: config.tui.showThoughts,
|
|
15398
|
+
toolsExpanded: false,
|
|
15399
|
+
planExpanded: false,
|
|
15400
|
+
showFileUpdates: config.tui.showFileUpdates,
|
|
15401
|
+
mouseEnabled: config.tui.mouse,
|
|
15402
|
+
defaultEnterAction: config.tui.defaultEnterAction
|
|
14805
15403
|
};
|
|
14806
15404
|
const pickerPrefs = createPickerPrefs();
|
|
14807
15405
|
let altScreenEngaged = false;
|
|
@@ -14979,7 +15577,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14979
15577
|
"current_mode_update",
|
|
14980
15578
|
"available_commands_update",
|
|
14981
15579
|
"available_modes_update",
|
|
14982
|
-
"usage_update"
|
|
15580
|
+
"usage_update",
|
|
15581
|
+
"config_option_update"
|
|
14983
15582
|
]);
|
|
14984
15583
|
const handleSessionUpdate = (params) => {
|
|
14985
15584
|
const { update } = params ?? {};
|
|
@@ -15324,6 +15923,10 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15324
15923
|
historyPolicy: "full",
|
|
15325
15924
|
clientInfo: { name: "hydra-acp-tui", version: HYDRA_VERSION },
|
|
15326
15925
|
...opts.readonly === true ? { readonly: true } : {},
|
|
15926
|
+
...opts.drip === true ? {
|
|
15927
|
+
replayMode: "drip",
|
|
15928
|
+
...opts.dripSpeed !== void 0 ? { dripSpeed: opts.dripSpeed } : {}
|
|
15929
|
+
} : {},
|
|
15327
15930
|
// Forward the user-chosen cwd via a full resume hint. An empty
|
|
15328
15931
|
// upstreamSessionId routes through doResurrectFromImport
|
|
15329
15932
|
// (first-launch imports); a real one takes the normal session/load
|
|
@@ -15415,7 +16018,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15415
16018
|
dispatcher,
|
|
15416
16019
|
repaintThrottleMs: config.tui.repaintThrottleMs,
|
|
15417
16020
|
maxScrollbackLines: config.tui.maxScrollbackLines,
|
|
15418
|
-
mouse:
|
|
16021
|
+
mouse: viewPrefs.mouseEnabled,
|
|
15419
16022
|
progressIndicator: config.tui.progressIndicator,
|
|
15420
16023
|
readonly: opts.readonly === true,
|
|
15421
16024
|
onKey: (events) => {
|
|
@@ -15426,6 +16029,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15426
16029
|
if (tryHandleHelpKey(ev)) {
|
|
15427
16030
|
continue;
|
|
15428
16031
|
}
|
|
16032
|
+
if (tryHandleOptionsKey(ev)) {
|
|
16033
|
+
continue;
|
|
16034
|
+
}
|
|
15429
16035
|
if (tryHandleScrollbackSearchKey(ev)) {
|
|
15430
16036
|
continue;
|
|
15431
16037
|
}
|
|
@@ -15669,7 +16275,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15669
16275
|
const buildHelpEntries = () => {
|
|
15670
16276
|
const enqueueDesc = "enqueue prompt (sends now, or queues during a turn)";
|
|
15671
16277
|
const amendDesc = "amend the in-flight turn (cancel + replace)";
|
|
15672
|
-
const head =
|
|
16278
|
+
const head = viewPrefs.defaultEnterAction === "amend" ? [
|
|
15673
16279
|
["Enter", amendDesc],
|
|
15674
16280
|
["Ctrl+Enter / Shift+Enter / ^S", enqueueDesc]
|
|
15675
16281
|
] : [
|
|
@@ -15700,6 +16306,190 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15700
16306
|
screen.setHelpPrompt(null);
|
|
15701
16307
|
return true;
|
|
15702
16308
|
};
|
|
16309
|
+
const OPTION_IDS = [
|
|
16310
|
+
"tools",
|
|
16311
|
+
"plan",
|
|
16312
|
+
"thoughts",
|
|
16313
|
+
"diffs",
|
|
16314
|
+
"mouse",
|
|
16315
|
+
"enter"
|
|
16316
|
+
];
|
|
16317
|
+
let optionsSelectedIndex = 0;
|
|
16318
|
+
const optionValue = (id) => {
|
|
16319
|
+
switch (id) {
|
|
16320
|
+
case "tools":
|
|
16321
|
+
return viewPrefs.toolsExpanded ? "expanded" : "collapsed";
|
|
16322
|
+
case "plan":
|
|
16323
|
+
return viewPrefs.planExpanded ? "expanded" : "collapsed";
|
|
16324
|
+
case "thoughts":
|
|
16325
|
+
return viewPrefs.showThoughts ? "shown" : "hidden";
|
|
16326
|
+
case "diffs":
|
|
16327
|
+
return viewPrefs.showFileUpdates;
|
|
16328
|
+
case "mouse":
|
|
16329
|
+
return viewPrefs.mouseEnabled ? "on" : "off";
|
|
16330
|
+
case "enter":
|
|
16331
|
+
return viewPrefs.defaultEnterAction;
|
|
16332
|
+
}
|
|
16333
|
+
};
|
|
16334
|
+
const optionLabel = (id) => {
|
|
16335
|
+
switch (id) {
|
|
16336
|
+
case "tools":
|
|
16337
|
+
return "Tools";
|
|
16338
|
+
case "plan":
|
|
16339
|
+
return "Plan";
|
|
16340
|
+
case "thoughts":
|
|
16341
|
+
return "Thoughts";
|
|
16342
|
+
case "diffs":
|
|
16343
|
+
return "File updates";
|
|
16344
|
+
case "mouse":
|
|
16345
|
+
return "Mouse capture";
|
|
16346
|
+
case "enter":
|
|
16347
|
+
return "Enter key";
|
|
16348
|
+
}
|
|
16349
|
+
};
|
|
16350
|
+
const buildOptionsSpec = () => ({
|
|
16351
|
+
title: "Session options",
|
|
16352
|
+
options: OPTION_IDS.map((id) => ({
|
|
16353
|
+
label: optionLabel(id),
|
|
16354
|
+
value: optionValue(id)
|
|
16355
|
+
})),
|
|
16356
|
+
selectedIndex: optionsSelectedIndex
|
|
16357
|
+
});
|
|
16358
|
+
const refreshOptionsPrompt = () => {
|
|
16359
|
+
if (!screen.isOptionsPromptActive()) {
|
|
16360
|
+
return;
|
|
16361
|
+
}
|
|
16362
|
+
screen.setOptionsPrompt(buildOptionsSpec());
|
|
16363
|
+
};
|
|
16364
|
+
const toggleOptionsModal = () => {
|
|
16365
|
+
if (screen.isOptionsPromptActive()) {
|
|
16366
|
+
screen.setOptionsPrompt(null);
|
|
16367
|
+
return;
|
|
16368
|
+
}
|
|
16369
|
+
optionsSelectedIndex = 0;
|
|
16370
|
+
screen.setOptionsPrompt(buildOptionsSpec());
|
|
16371
|
+
};
|
|
16372
|
+
const applyOptionToggle = (id) => {
|
|
16373
|
+
switch (id) {
|
|
16374
|
+
case "tools":
|
|
16375
|
+
viewPrefs.toolsExpanded = !viewPrefs.toolsExpanded;
|
|
16376
|
+
renderToolsBlock();
|
|
16377
|
+
break;
|
|
16378
|
+
case "plan":
|
|
16379
|
+
viewPrefs.planExpanded = !viewPrefs.planExpanded;
|
|
16380
|
+
rerenderPlan();
|
|
16381
|
+
break;
|
|
16382
|
+
case "thoughts":
|
|
16383
|
+
viewPrefs.showThoughts = !viewPrefs.showThoughts;
|
|
16384
|
+
screen.setHideThoughts(!viewPrefs.showThoughts);
|
|
16385
|
+
break;
|
|
16386
|
+
case "diffs":
|
|
16387
|
+
viewPrefs.showFileUpdates = viewPrefs.showFileUpdates === "diff" ? "edit" : "diff";
|
|
16388
|
+
reRenderAllEditDiffs();
|
|
16389
|
+
break;
|
|
16390
|
+
case "mouse": {
|
|
16391
|
+
const next = !screen.isMouseEnabled();
|
|
16392
|
+
screen.setMouseEnabled(next);
|
|
16393
|
+
viewPrefs.mouseEnabled = next;
|
|
16394
|
+
break;
|
|
16395
|
+
}
|
|
16396
|
+
case "enter":
|
|
16397
|
+
viewPrefs.defaultEnterAction = viewPrefs.defaultEnterAction === "amend" ? "enqueue" : "amend";
|
|
16398
|
+
break;
|
|
16399
|
+
}
|
|
16400
|
+
refreshOptionsPrompt();
|
|
16401
|
+
};
|
|
16402
|
+
const saveOption = (id) => {
|
|
16403
|
+
void (async () => {
|
|
16404
|
+
try {
|
|
16405
|
+
switch (id) {
|
|
16406
|
+
case "tools":
|
|
16407
|
+
case "plan":
|
|
16408
|
+
screen.notify(`${optionLabel(id)} is session-only \u2014 not saved`);
|
|
16409
|
+
return;
|
|
16410
|
+
case "thoughts":
|
|
16411
|
+
await setTuiConfigValue("showThoughts", viewPrefs.showThoughts);
|
|
16412
|
+
break;
|
|
16413
|
+
case "diffs":
|
|
16414
|
+
await setTuiConfigValue(
|
|
16415
|
+
"showFileUpdates",
|
|
16416
|
+
viewPrefs.showFileUpdates
|
|
16417
|
+
);
|
|
16418
|
+
break;
|
|
16419
|
+
case "mouse":
|
|
16420
|
+
await setTuiConfigValue("mouse", viewPrefs.mouseEnabled);
|
|
16421
|
+
break;
|
|
16422
|
+
case "enter":
|
|
16423
|
+
await setTuiConfigValue(
|
|
16424
|
+
"defaultEnterAction",
|
|
16425
|
+
viewPrefs.defaultEnterAction
|
|
16426
|
+
);
|
|
16427
|
+
break;
|
|
16428
|
+
}
|
|
16429
|
+
screen.notify(`saved default: ${optionLabel(id)} ${optionValue(id)}`);
|
|
16430
|
+
} catch (err) {
|
|
16431
|
+
screen.notify(
|
|
16432
|
+
`save failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16433
|
+
);
|
|
16434
|
+
}
|
|
16435
|
+
})();
|
|
16436
|
+
};
|
|
16437
|
+
const tryHandleOptionsKey = (ev) => {
|
|
16438
|
+
if (!screen.isOptionsPromptActive()) {
|
|
16439
|
+
return false;
|
|
16440
|
+
}
|
|
16441
|
+
if (ev.type === "key") {
|
|
16442
|
+
switch (ev.name) {
|
|
16443
|
+
case "up":
|
|
16444
|
+
optionsSelectedIndex = Math.max(0, optionsSelectedIndex - 1);
|
|
16445
|
+
refreshOptionsPrompt();
|
|
16446
|
+
return true;
|
|
16447
|
+
case "down":
|
|
16448
|
+
optionsSelectedIndex = Math.min(
|
|
16449
|
+
OPTION_IDS.length - 1,
|
|
16450
|
+
optionsSelectedIndex + 1
|
|
16451
|
+
);
|
|
16452
|
+
refreshOptionsPrompt();
|
|
16453
|
+
return true;
|
|
16454
|
+
case "enter": {
|
|
16455
|
+
const id = OPTION_IDS[optionsSelectedIndex];
|
|
16456
|
+
if (id) {
|
|
16457
|
+
applyOptionToggle(id);
|
|
16458
|
+
}
|
|
16459
|
+
return true;
|
|
16460
|
+
}
|
|
16461
|
+
case "ctrl-o":
|
|
16462
|
+
case "escape":
|
|
16463
|
+
case "ctrl-c":
|
|
16464
|
+
screen.setOptionsPrompt(null);
|
|
16465
|
+
return true;
|
|
16466
|
+
case "ctrl-d":
|
|
16467
|
+
screen.setOptionsPrompt(null);
|
|
16468
|
+
return false;
|
|
16469
|
+
default:
|
|
16470
|
+
return true;
|
|
16471
|
+
}
|
|
16472
|
+
}
|
|
16473
|
+
if (ev.type === "char") {
|
|
16474
|
+
if (/^[1-9]$/.test(ev.ch)) {
|
|
16475
|
+
const idx = parseInt(ev.ch, 10) - 1;
|
|
16476
|
+
const id = OPTION_IDS[idx];
|
|
16477
|
+
if (id) {
|
|
16478
|
+
optionsSelectedIndex = idx;
|
|
16479
|
+
applyOptionToggle(id);
|
|
16480
|
+
}
|
|
16481
|
+
return true;
|
|
16482
|
+
}
|
|
16483
|
+
if (ev.ch === "s" || ev.ch === "S") {
|
|
16484
|
+
const id = OPTION_IDS[optionsSelectedIndex];
|
|
16485
|
+
if (id) {
|
|
16486
|
+
saveOption(id);
|
|
16487
|
+
}
|
|
16488
|
+
return true;
|
|
16489
|
+
}
|
|
16490
|
+
}
|
|
16491
|
+
return true;
|
|
16492
|
+
};
|
|
15703
16493
|
const teardown = () => {
|
|
15704
16494
|
teardownStarted = true;
|
|
15705
16495
|
process.off("SIGINT", sigintHandler);
|
|
@@ -15898,14 +16688,14 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15898
16688
|
const handleEffect = (effect) => {
|
|
15899
16689
|
switch (effect.type) {
|
|
15900
16690
|
case "send":
|
|
15901
|
-
if (
|
|
16691
|
+
if (viewPrefs.defaultEnterAction === "amend") {
|
|
15902
16692
|
amendPrompt(effect.text, effect.attachments, effect.displayText);
|
|
15903
16693
|
} else {
|
|
15904
16694
|
enqueuePrompt(effect.text, effect.attachments, effect.displayText);
|
|
15905
16695
|
}
|
|
15906
16696
|
return;
|
|
15907
16697
|
case "amend":
|
|
15908
|
-
if (
|
|
16698
|
+
if (viewPrefs.defaultEnterAction === "amend") {
|
|
15909
16699
|
enqueuePrompt(effect.text, effect.attachments, effect.displayText);
|
|
15910
16700
|
} else {
|
|
15911
16701
|
amendPrompt(effect.text, effect.attachments, effect.displayText);
|
|
@@ -15998,9 +16788,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15998
16788
|
case "next-live-session":
|
|
15999
16789
|
void cycleLiveSession();
|
|
16000
16790
|
return;
|
|
16001
|
-
case "toggle-
|
|
16002
|
-
|
|
16003
|
-
renderToolsBlock();
|
|
16791
|
+
case "toggle-options":
|
|
16792
|
+
toggleOptionsModal();
|
|
16004
16793
|
return;
|
|
16005
16794
|
case "toggle-thoughts":
|
|
16006
16795
|
viewPrefs.showThoughts = !viewPrefs.showThoughts;
|
|
@@ -16012,6 +16801,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16012
16801
|
case "toggle-mouse": {
|
|
16013
16802
|
const next = !screen.isMouseEnabled();
|
|
16014
16803
|
screen.setMouseEnabled(next);
|
|
16804
|
+
viewPrefs.mouseEnabled = next;
|
|
16015
16805
|
screen.notify(
|
|
16016
16806
|
next ? "mouse capture on \u2014 wheel scrolls; shift+drag to select text" : "mouse capture off \u2014 click-drag selects text; PgUp/PgDn scrolls"
|
|
16017
16807
|
);
|
|
@@ -16282,9 +17072,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16282
17072
|
toolsBlockStartedAt = null;
|
|
16283
17073
|
toolsBlockEndedAt = null;
|
|
16284
17074
|
toolsBlockStopReason = null;
|
|
16285
|
-
toolsExpanded = false;
|
|
16286
17075
|
lastEditMarkPath = null;
|
|
16287
17076
|
turnHasShownProse = false;
|
|
17077
|
+
renderedEditDiffs.clear();
|
|
16288
17078
|
screen.clearScrollback();
|
|
16289
17079
|
return true;
|
|
16290
17080
|
case "/demo-plan": {
|
|
@@ -16465,16 +17255,27 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16465
17255
|
}
|
|
16466
17256
|
};
|
|
16467
17257
|
const toolStates = /* @__PURE__ */ new Map();
|
|
17258
|
+
const renderedEditDiffs = /* @__PURE__ */ new Map();
|
|
16468
17259
|
const exitPlanStates = /* @__PURE__ */ new Map();
|
|
16469
17260
|
const toolCallOrder = [];
|
|
16470
|
-
let toolsExpanded = false;
|
|
16471
17261
|
let toolsBlockStartedAt = null;
|
|
16472
17262
|
let toolsBlockEndedAt = null;
|
|
16473
17263
|
let toolsBlockStopReason = null;
|
|
16474
17264
|
let lastPlanEvent = null;
|
|
16475
17265
|
const TOOLS_COLLAPSED_LIMIT = config.tui.maxToolItems;
|
|
16476
17266
|
const PLAN_VISIBLE_LIMIT2 = config.tui.maxPlanItems;
|
|
16477
|
-
const
|
|
17267
|
+
const planFormatOptions = () => ({
|
|
17268
|
+
maxPlanItems: viewPrefs.planExpanded ? Infinity : PLAN_VISIBLE_LIMIT2
|
|
17269
|
+
});
|
|
17270
|
+
const rerenderPlan = () => {
|
|
17271
|
+
if (lastPlanEvent === null) {
|
|
17272
|
+
return;
|
|
17273
|
+
}
|
|
17274
|
+
const lines = formatEvent(lastPlanEvent, planFormatOptions());
|
|
17275
|
+
if (lines.length > 0) {
|
|
17276
|
+
screen.upsertLines("plan", [{ body: "" }, ...lines]);
|
|
17277
|
+
}
|
|
17278
|
+
};
|
|
16478
17279
|
let agentBuffer = "";
|
|
16479
17280
|
let agentKey = null;
|
|
16480
17281
|
let agentSeq = 0;
|
|
@@ -16542,7 +17343,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16542
17343
|
}
|
|
16543
17344
|
const total = toolCallOrder.length;
|
|
16544
17345
|
const capped = TOOLS_COLLAPSED_LIMIT > 0;
|
|
16545
|
-
const visibleIds = !capped || toolsExpanded ? toolCallOrder : toolCallOrder.slice(Math.max(0, total - TOOLS_COLLAPSED_LIMIT));
|
|
17346
|
+
const visibleIds = !capped || viewPrefs.toolsExpanded ? toolCallOrder : toolCallOrder.slice(Math.max(0, total - TOOLS_COLLAPSED_LIMIT));
|
|
16546
17347
|
const hidden = total - visibleIds.length;
|
|
16547
17348
|
const inProgress = toolsBlockEndedAt === null;
|
|
16548
17349
|
const end = toolsBlockEndedAt ?? Date.now();
|
|
@@ -16561,12 +17362,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16561
17362
|
const noun = total === 1 ? "tool" : "tools";
|
|
16562
17363
|
const timing = stoppedReason !== null ? stoppedLabel : inProgress ? formatElapsed(elapsed) : `took ${formatElapsed(elapsed)}`;
|
|
16563
17364
|
const parts = [`${total} ${noun}`, timing];
|
|
16564
|
-
if (inProgress && capped) {
|
|
16565
|
-
|
|
16566
|
-
parts.push(`${hidden} hidden \u2014 ^O expand`);
|
|
16567
|
-
} else if (toolsExpanded && total > TOOLS_COLLAPSED_LIMIT) {
|
|
16568
|
-
parts.push("^O collapse");
|
|
16569
|
-
}
|
|
17365
|
+
if (inProgress && capped && hidden > 0) {
|
|
17366
|
+
parts.push(`${hidden} hidden`);
|
|
16570
17367
|
}
|
|
16571
17368
|
summary = parts.join(" \xB7 ");
|
|
16572
17369
|
}
|
|
@@ -16585,7 +17382,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16585
17382
|
for (const id of visibleIds) {
|
|
16586
17383
|
const state = toolStates.get(id);
|
|
16587
17384
|
if (state) {
|
|
16588
|
-
lines.push(...formatToolLine2(state));
|
|
17385
|
+
lines.push(...formatToolLine2(state, end));
|
|
16589
17386
|
}
|
|
16590
17387
|
}
|
|
16591
17388
|
screen.upsertLines("tools", lines);
|
|
@@ -16602,7 +17399,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16602
17399
|
const state = existing ?? {
|
|
16603
17400
|
initialTitle: title ?? "tool",
|
|
16604
17401
|
latestTitle: title ?? "tool",
|
|
16605
|
-
status: status ?? "pending"
|
|
17402
|
+
status: status ?? "pending",
|
|
17403
|
+
startedAt: Date.now()
|
|
16606
17404
|
};
|
|
16607
17405
|
if (existing && title !== void 0) {
|
|
16608
17406
|
state.latestTitle = title;
|
|
@@ -16613,6 +17411,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16613
17411
|
if (!existing) {
|
|
16614
17412
|
state.status = status ?? "pending";
|
|
16615
17413
|
}
|
|
17414
|
+
if (state.endedAt === void 0 && isTerminalToolStatus(state.status)) {
|
|
17415
|
+
state.endedAt = Date.now();
|
|
17416
|
+
}
|
|
16616
17417
|
if (errorText !== void 0) {
|
|
16617
17418
|
state.errorText = errorText;
|
|
16618
17419
|
}
|
|
@@ -16632,35 +17433,60 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16632
17433
|
let lastEditMarkPath = null;
|
|
16633
17434
|
let turnHasShownProse = false;
|
|
16634
17435
|
const maybeRenderEditDiff = (toolCallId) => {
|
|
16635
|
-
const
|
|
16636
|
-
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
if (!state?.editDiff || state.status !== "completed") {
|
|
16641
|
-
return;
|
|
16642
|
-
}
|
|
16643
|
-
if (mode === "diff") {
|
|
16644
|
-
const lines2 = formatEditDiffBlock(state.editDiff, "diff");
|
|
16645
|
-
if (lines2.length > 0) {
|
|
16646
|
-
screen.upsertLines(`editdiff:${toolCallId}`, lines2);
|
|
17436
|
+
const key = `editdiff:${toolCallId}`;
|
|
17437
|
+
const lines = (() => {
|
|
17438
|
+
const mode = viewPrefs.showFileUpdates;
|
|
17439
|
+
if (mode === "none") {
|
|
17440
|
+
return null;
|
|
16647
17441
|
}
|
|
17442
|
+
const state = toolStates.get(toolCallId);
|
|
17443
|
+
if (!state?.editDiff || state.status !== "completed") {
|
|
17444
|
+
return null;
|
|
17445
|
+
}
|
|
17446
|
+
if (mode === "diff") {
|
|
17447
|
+
const out2 = formatEditDiffBlock(state.editDiff, "diff");
|
|
17448
|
+
return out2.length > 0 ? out2 : null;
|
|
17449
|
+
}
|
|
17450
|
+
if (!turnHasShownProse) {
|
|
17451
|
+
return null;
|
|
17452
|
+
}
|
|
17453
|
+
const diff2 = state.editDiff;
|
|
17454
|
+
if (diff2.path && diff2.path === lastEditMarkPath && !screen.hasKey(key)) {
|
|
17455
|
+
return null;
|
|
17456
|
+
}
|
|
17457
|
+
const out = formatEditDiffBlock(diff2, "edit");
|
|
17458
|
+
if (out.length === 0) {
|
|
17459
|
+
return null;
|
|
17460
|
+
}
|
|
17461
|
+
if (diff2.path) {
|
|
17462
|
+
lastEditMarkPath = diff2.path;
|
|
17463
|
+
}
|
|
17464
|
+
return out;
|
|
17465
|
+
})();
|
|
17466
|
+
if (lines === null) {
|
|
17467
|
+
screen.removeKey(key);
|
|
16648
17468
|
return;
|
|
16649
17469
|
}
|
|
16650
|
-
|
|
16651
|
-
|
|
16652
|
-
|
|
16653
|
-
const diff = state.editDiff;
|
|
16654
|
-
if (diff.path && diff.path === lastEditMarkPath) {
|
|
16655
|
-
return;
|
|
16656
|
-
}
|
|
16657
|
-
const lines = formatEditDiffBlock(diff, "edit");
|
|
16658
|
-
if (lines.length === 0) {
|
|
16659
|
-
return;
|
|
17470
|
+
const diff = toolStates.get(toolCallId)?.editDiff;
|
|
17471
|
+
if (diff) {
|
|
17472
|
+
renderedEditDiffs.set(toolCallId, diff);
|
|
16660
17473
|
}
|
|
16661
|
-
screen.upsertLines(
|
|
16662
|
-
|
|
16663
|
-
|
|
17474
|
+
screen.upsertLines(key, lines);
|
|
17475
|
+
};
|
|
17476
|
+
const reRenderAllEditDiffs = () => {
|
|
17477
|
+
const mode = viewPrefs.showFileUpdates;
|
|
17478
|
+
for (const [toolCallId, diff] of renderedEditDiffs) {
|
|
17479
|
+
const key = `editdiff:${toolCallId}`;
|
|
17480
|
+
if (mode === "none") {
|
|
17481
|
+
screen.removeKey(key);
|
|
17482
|
+
continue;
|
|
17483
|
+
}
|
|
17484
|
+
const out = formatEditDiffBlock(diff, mode);
|
|
17485
|
+
if (out.length === 0) {
|
|
17486
|
+
screen.removeKey(key);
|
|
17487
|
+
} else {
|
|
17488
|
+
screen.upsertLines(key, out);
|
|
17489
|
+
}
|
|
16664
17490
|
}
|
|
16665
17491
|
};
|
|
16666
17492
|
applyRenderEvent = (event) => {
|
|
@@ -16732,7 +17558,6 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16732
17558
|
toolStates.clear();
|
|
16733
17559
|
exitPlanStates.clear();
|
|
16734
17560
|
toolCallOrder.length = 0;
|
|
16735
|
-
toolsExpanded = false;
|
|
16736
17561
|
toolsBlockEndedAt = null;
|
|
16737
17562
|
lastEditMarkPath = null;
|
|
16738
17563
|
turnHasShownProse = false;
|
|
@@ -16794,7 +17619,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16794
17619
|
closeAgentText();
|
|
16795
17620
|
closeThought();
|
|
16796
17621
|
lastPlanEvent = event;
|
|
16797
|
-
const lines = formatEvent(event,
|
|
17622
|
+
const lines = formatEvent(event, planFormatOptions());
|
|
16798
17623
|
if (lines.length > 0) {
|
|
16799
17624
|
screen.upsertLines("plan", [{ body: "" }, ...lines]);
|
|
16800
17625
|
}
|
|
@@ -16843,7 +17668,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16843
17668
|
stopped: true,
|
|
16844
17669
|
amended: event.amended === true
|
|
16845
17670
|
},
|
|
16846
|
-
|
|
17671
|
+
planFormatOptions()
|
|
16847
17672
|
);
|
|
16848
17673
|
if (lines.length > 0) {
|
|
16849
17674
|
screen.upsertLines("plan", [{ body: "" }, ...lines]);
|
|
@@ -16873,7 +17698,6 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16873
17698
|
toolsBlockStartedAt = null;
|
|
16874
17699
|
toolsBlockEndedAt = null;
|
|
16875
17700
|
toolsBlockStopReason = null;
|
|
16876
|
-
toolsExpanded = false;
|
|
16877
17701
|
upstreamInterruptedSeen = false;
|
|
16878
17702
|
lastEditMarkPath = null;
|
|
16879
17703
|
turnHasShownProse = false;
|
|
@@ -16961,7 +17785,6 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16961
17785
|
toolsBlockStartedAt = null;
|
|
16962
17786
|
toolsBlockEndedAt = null;
|
|
16963
17787
|
toolsBlockStopReason = null;
|
|
16964
|
-
toolsExpanded = false;
|
|
16965
17788
|
lastEditMarkPath = null;
|
|
16966
17789
|
turnHasShownProse = false;
|
|
16967
17790
|
};
|
|
@@ -17119,6 +17942,10 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17119
17942
|
return ctx;
|
|
17120
17943
|
}
|
|
17121
17944
|
if (opts.forceNew) {
|
|
17945
|
+
const agentStep = await ensureAgentForNew(term, target, opts);
|
|
17946
|
+
if (agentStep !== "ok") {
|
|
17947
|
+
return null;
|
|
17948
|
+
}
|
|
17122
17949
|
return newCtx(opts, cwd, config);
|
|
17123
17950
|
}
|
|
17124
17951
|
if (opts.resume) {
|
|
@@ -17142,7 +17969,8 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17142
17969
|
sessions,
|
|
17143
17970
|
config,
|
|
17144
17971
|
target,
|
|
17145
|
-
prefs: pickerPrefs
|
|
17972
|
+
prefs: pickerPrefs,
|
|
17973
|
+
...opts.initialPrompt !== void 0 ? { initialPrompt: opts.initialPrompt } : {}
|
|
17146
17974
|
});
|
|
17147
17975
|
if (choice.kind === "abort") {
|
|
17148
17976
|
return null;
|
|
@@ -17151,6 +17979,13 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17151
17979
|
if (choice.prompt !== void 0) {
|
|
17152
17980
|
opts.initialPrompt = choice.prompt;
|
|
17153
17981
|
}
|
|
17982
|
+
const agentStep = await ensureAgentForNew(term, target, opts);
|
|
17983
|
+
if (agentStep === "cancel") {
|
|
17984
|
+
return null;
|
|
17985
|
+
}
|
|
17986
|
+
if (agentStep === "back") {
|
|
17987
|
+
continue;
|
|
17988
|
+
}
|
|
17154
17989
|
return newCtx(opts, cwd, config);
|
|
17155
17990
|
}
|
|
17156
17991
|
if (choice.kind === "fork") {
|
|
@@ -17310,6 +18145,38 @@ function newCtx(opts, cwd, config) {
|
|
|
17310
18145
|
cwd
|
|
17311
18146
|
};
|
|
17312
18147
|
}
|
|
18148
|
+
async function ensureAgentForNew(term, target, opts) {
|
|
18149
|
+
if (opts.agentId) {
|
|
18150
|
+
return "ok";
|
|
18151
|
+
}
|
|
18152
|
+
if (await hasConfiguredDefaultAgent()) {
|
|
18153
|
+
return "ok";
|
|
18154
|
+
}
|
|
18155
|
+
let agents;
|
|
18156
|
+
try {
|
|
18157
|
+
agents = await listAgents(target);
|
|
18158
|
+
} catch {
|
|
18159
|
+
return "ok";
|
|
18160
|
+
}
|
|
18161
|
+
if (agents.length === 0) {
|
|
18162
|
+
return "ok";
|
|
18163
|
+
}
|
|
18164
|
+
const result = await promptForAgent(term, agents);
|
|
18165
|
+
if (result.kind === "cancel") {
|
|
18166
|
+
return "cancel";
|
|
18167
|
+
}
|
|
18168
|
+
if (result.kind === "back") {
|
|
18169
|
+
return "back";
|
|
18170
|
+
}
|
|
18171
|
+
opts.agentId = result.agentId;
|
|
18172
|
+
if (result.persist) {
|
|
18173
|
+
try {
|
|
18174
|
+
await setDefaultAgent(result.agentId);
|
|
18175
|
+
} catch {
|
|
18176
|
+
}
|
|
18177
|
+
}
|
|
18178
|
+
return "ok";
|
|
18179
|
+
}
|
|
17313
18180
|
function debugLogUpdate(update, event) {
|
|
17314
18181
|
writeDebugLine({
|
|
17315
18182
|
src: "session/update",
|
|
@@ -17455,6 +18322,7 @@ var init_app = __esm({
|
|
|
17455
18322
|
init_picker();
|
|
17456
18323
|
init_import_cwd_prompt();
|
|
17457
18324
|
init_import_action_prompt();
|
|
18325
|
+
init_agent_prompt();
|
|
17458
18326
|
init_screen();
|
|
17459
18327
|
init_input();
|
|
17460
18328
|
init_attachments();
|
|
@@ -17480,7 +18348,7 @@ var init_app = __esm({
|
|
|
17480
18348
|
["Alt+N / Alt+Tab", "next live session"],
|
|
17481
18349
|
["^T", "show / hide thoughts"],
|
|
17482
18350
|
["^V", "paste image from clipboard"],
|
|
17483
|
-
["^O", "
|
|
18351
|
+
["^O", "session options (tools \xB7 plan \xB7 thoughts \xB7 diffs \xB7 mouse \xB7 enter)"],
|
|
17484
18352
|
null,
|
|
17485
18353
|
["^R", "history reverse search (^S walks forward once engaged)"],
|
|
17486
18354
|
["PgUp / PgDn", "scroll scrollback"],
|
|
@@ -17521,6 +18389,7 @@ var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
17521
18389
|
"detach",
|
|
17522
18390
|
"diff",
|
|
17523
18391
|
"disabled",
|
|
18392
|
+
"drip",
|
|
17524
18393
|
"fold",
|
|
17525
18394
|
"follow",
|
|
17526
18395
|
"force",
|
|
@@ -17543,8 +18412,10 @@ var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
17543
18412
|
var KNOWN_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
17544
18413
|
"agent",
|
|
17545
18414
|
"args",
|
|
18415
|
+
"columns",
|
|
17546
18416
|
"command",
|
|
17547
18417
|
"cwd",
|
|
18418
|
+
"drip-speed",
|
|
17548
18419
|
"env",
|
|
17549
18420
|
"host",
|
|
17550
18421
|
"model",
|
|
@@ -20006,7 +20877,14 @@ var SessionManager = class {
|
|
|
20006
20877
|
histories: this.histories,
|
|
20007
20878
|
synopsisAgent: this.synopsisAgent,
|
|
20008
20879
|
synopsisModel: this.synopsisModel,
|
|
20009
|
-
persistTitle: (id, title) =>
|
|
20880
|
+
persistTitle: async (id, title) => {
|
|
20881
|
+
const live = this.get(id);
|
|
20882
|
+
if (live) {
|
|
20883
|
+
await live.retitle(title);
|
|
20884
|
+
return;
|
|
20885
|
+
}
|
|
20886
|
+
await this.persistTitle(id, title);
|
|
20887
|
+
},
|
|
20010
20888
|
persistSynopsis: (id, synopsis, through) => this.persistSynopsis(id, synopsis, through),
|
|
20011
20889
|
logger: this.logger,
|
|
20012
20890
|
npmRegistry: this.npmRegistry
|
|
@@ -20359,27 +21237,27 @@ var SessionManager = class {
|
|
|
20359
21237
|
return false;
|
|
20360
21238
|
}
|
|
20361
21239
|
}
|
|
20362
|
-
// When the last client detaches from a session that
|
|
20363
|
-
//
|
|
20364
|
-
//
|
|
20365
|
-
//
|
|
20366
|
-
//
|
|
20367
|
-
//
|
|
20368
|
-
//
|
|
20369
|
-
//
|
|
21240
|
+
// When the last client detaches from a session that was never promoted
|
|
21241
|
+
// to interactive, close it so its agent process doesn't linger until the
|
|
21242
|
+
// (default 1h) idle timeout fires. This covers both `hydra cat` runs
|
|
21243
|
+
// (born interactive:undefined with originatingClient hydra-acp-cat, every
|
|
21244
|
+
// prompt ancillary) and any other client that opened a session but never
|
|
21245
|
+
// sent a real, non-ancillary prompt. Promotion to interactive is
|
|
21246
|
+
// synchronous on the first real prompt (Session.prompt sets _interactive
|
|
21247
|
+
// = true before enqueuing), so a session that ever saw a genuine turn
|
|
21248
|
+
// resolves to true here and is left running. The cold record is kept, so
|
|
21249
|
+
// re-attaching resurrects via the reseed path.
|
|
21250
|
+
//
|
|
21251
|
+
// Note: this only fires from the explicit session/detach handler — raw WS
|
|
21252
|
+
// close deliberately does NOT reap (see acp-ws.ts), so an abrupt
|
|
21253
|
+
// disconnect of a never-prompted session falls through to the idle
|
|
21254
|
+
// timeout rather than being torn down.
|
|
20370
21255
|
async reapIfOrphanedNonInteractive(sessionId) {
|
|
20371
21256
|
const session = this.sessions.get(sessionId);
|
|
20372
21257
|
if (!session || session.attachedCount > 0) {
|
|
20373
21258
|
return;
|
|
20374
21259
|
}
|
|
20375
|
-
|
|
20376
|
-
{
|
|
20377
|
-
interactive: session.interactive,
|
|
20378
|
-
...session.originatingClient ? { originatingClient: session.originatingClient } : {}
|
|
20379
|
-
},
|
|
20380
|
-
true
|
|
20381
|
-
);
|
|
20382
|
-
if (interactive !== false) {
|
|
21260
|
+
if (session.interactive === true) {
|
|
20383
21261
|
return;
|
|
20384
21262
|
}
|
|
20385
21263
|
this.logger?.info(
|
|
@@ -20877,7 +21755,8 @@ var SessionManager = class {
|
|
|
20877
21755
|
updatedAt: used,
|
|
20878
21756
|
attachedClients: session.attachedCount,
|
|
20879
21757
|
status: "live",
|
|
20880
|
-
busy: session.turnStartedAt !== void 0
|
|
21758
|
+
busy: session.turnStartedAt !== void 0,
|
|
21759
|
+
awaitingInput: session.awaitingInput
|
|
20881
21760
|
});
|
|
20882
21761
|
}
|
|
20883
21762
|
const records = await this.store.list().catch(() => []);
|
|
@@ -20915,7 +21794,8 @@ var SessionManager = class {
|
|
|
20915
21794
|
updatedAt: used,
|
|
20916
21795
|
attachedClients: 0,
|
|
20917
21796
|
status: "cold",
|
|
20918
|
-
busy: false
|
|
21797
|
+
busy: false,
|
|
21798
|
+
awaitingInput: false
|
|
20919
21799
|
});
|
|
20920
21800
|
}
|
|
20921
21801
|
entries.sort((a, b) => a.updatedAt < b.updatedAt ? 1 : -1);
|
|
@@ -24819,10 +25699,11 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
24819
25699
|
params.clientInfo,
|
|
24820
25700
|
params.clientId
|
|
24821
25701
|
);
|
|
25702
|
+
const drip = params.replayMode === "drip";
|
|
24822
25703
|
const { entries: replay, appliedPolicy } = await session.attach(
|
|
24823
25704
|
client,
|
|
24824
25705
|
params.historyPolicy,
|
|
24825
|
-
{ afterMessageId: params.afterMessageId }
|
|
25706
|
+
{ afterMessageId: params.afterMessageId, raw: drip }
|
|
24826
25707
|
);
|
|
24827
25708
|
state.attached.set(session.sessionId, {
|
|
24828
25709
|
sessionId: session.sessionId,
|
|
@@ -24830,10 +25711,35 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
24830
25711
|
readonly
|
|
24831
25712
|
});
|
|
24832
25713
|
app.log.info(
|
|
24833
|
-
`session/attach OK sessionId=${session.sessionId} clientId=${client.clientId} attachedCount=${state.attached.size} requestedPolicy=${params.historyPolicy} appliedPolicy=${appliedPolicy} replayed=${replay.length} readonly=${readonly}`
|
|
25714
|
+
`session/attach OK sessionId=${session.sessionId} clientId=${client.clientId} attachedCount=${state.attached.size} requestedPolicy=${params.historyPolicy} appliedPolicy=${appliedPolicy} replayed=${replay.length} readonly=${readonly}${drip ? " replayMode=drip" : ""}`
|
|
24834
25715
|
);
|
|
24835
|
-
|
|
24836
|
-
|
|
25716
|
+
if (drip) {
|
|
25717
|
+
const speed = params.dripSpeed && params.dripSpeed > 0 ? params.dripSpeed : 1;
|
|
25718
|
+
const MAX_GAP_MS = 750;
|
|
25719
|
+
void (async () => {
|
|
25720
|
+
let prev = null;
|
|
25721
|
+
for (const note of replay) {
|
|
25722
|
+
const at = typeof note.recordedAt === "number" ? note.recordedAt : null;
|
|
25723
|
+
if (prev !== null && at !== null) {
|
|
25724
|
+
const gap = Math.min(MAX_GAP_MS, Math.max(0, (at - prev) / speed));
|
|
25725
|
+
if (gap > 0) {
|
|
25726
|
+
await new Promise((r) => setTimeout(r, gap));
|
|
25727
|
+
}
|
|
25728
|
+
}
|
|
25729
|
+
if (at !== null) {
|
|
25730
|
+
prev = at;
|
|
25731
|
+
}
|
|
25732
|
+
try {
|
|
25733
|
+
await connection.notify(note.method, note.params);
|
|
25734
|
+
} catch {
|
|
25735
|
+
return;
|
|
25736
|
+
}
|
|
25737
|
+
}
|
|
25738
|
+
})();
|
|
25739
|
+
} else {
|
|
25740
|
+
for (const note of replay) {
|
|
25741
|
+
await connection.notify(note.method, note.params);
|
|
25742
|
+
}
|
|
24837
25743
|
}
|
|
24838
25744
|
session.replayPendingPermissions(client);
|
|
24839
25745
|
const modesPayload = buildModesPayload(session);
|
|
@@ -26694,12 +27600,15 @@ async function runSessionsList(opts = {}) {
|
|
|
26694
27600
|
}
|
|
26695
27601
|
const now = Date.now();
|
|
26696
27602
|
const rows = visible.map((s) => toRow(s, now));
|
|
26697
|
-
const
|
|
27603
|
+
const formatOpts = {
|
|
27604
|
+
columns: opts.columns ?? config.tui.sessionColumns ?? DEFAULT_COLUMNS,
|
|
27605
|
+
cwdMaxWidth: config.tui.cwdColumnMaxWidth
|
|
27606
|
+
};
|
|
27607
|
+
const widths = computeWidths(rows, formatOpts);
|
|
26698
27608
|
const maxWidth = process.stdout.isTTY ? process.stdout.columns : void 0;
|
|
26699
|
-
|
|
26700
|
-
process.stdout.write(formatRow(HEADER, widths, maxWidth, cwdMax) + "\n");
|
|
27609
|
+
process.stdout.write(formatRow(HEADER, widths, maxWidth, formatOpts) + "\n");
|
|
26701
27610
|
for (const r of rows) {
|
|
26702
|
-
process.stdout.write(formatRow(r, widths, maxWidth,
|
|
27611
|
+
process.stdout.write(formatRow(r, widths, maxWidth, formatOpts) + "\n");
|
|
26703
27612
|
}
|
|
26704
27613
|
if (truncated > 0) {
|
|
26705
27614
|
process.stdout.write(
|
|
@@ -26963,10 +27872,14 @@ function printBundleInfo(raw, cwdColumnMaxWidth) {
|
|
|
26963
27872
|
}
|
|
26964
27873
|
const summary = bundleToSummary(parsed);
|
|
26965
27874
|
const row = toRow(summary);
|
|
26966
|
-
const
|
|
27875
|
+
const formatOpts = {
|
|
27876
|
+
columns: ALL_COLUMNS,
|
|
27877
|
+
cwdMaxWidth: cwdColumnMaxWidth
|
|
27878
|
+
};
|
|
27879
|
+
const widths = computeWidths([row], formatOpts);
|
|
26967
27880
|
const maxWidth = process.stdout.isTTY ? process.stdout.columns : void 0;
|
|
26968
|
-
process.stdout.write(formatRow(HEADER, widths, maxWidth,
|
|
26969
|
-
process.stdout.write(formatRow(row, widths, maxWidth,
|
|
27881
|
+
process.stdout.write(formatRow(HEADER, widths, maxWidth, formatOpts) + "\n");
|
|
27882
|
+
process.stdout.write(formatRow(row, widths, maxWidth, formatOpts) + "\n");
|
|
26970
27883
|
const originUpstream = parsed.session.upstreamSessionId ?? "-";
|
|
26971
27884
|
process.stdout.write(
|
|
26972
27885
|
`
|
|
@@ -27542,6 +28455,7 @@ function aggregate(bundle, status) {
|
|
|
27542
28455
|
const durationMs = Number.isFinite(createdMs) && Number.isFinite(updatedMs) ? updatedMs - createdMs : null;
|
|
27543
28456
|
return {
|
|
27544
28457
|
sessionId: r.sessionId,
|
|
28458
|
+
...r.upstreamSessionId !== void 0 ? { upstreamSessionId: r.upstreamSessionId } : {},
|
|
27545
28459
|
...r.title !== void 0 ? { title: r.title } : {},
|
|
27546
28460
|
cwd: r.cwd,
|
|
27547
28461
|
agentId: r.agentId,
|
|
@@ -27569,6 +28483,9 @@ function formatSummary(d, verbose) {
|
|
|
27569
28483
|
const lines = [];
|
|
27570
28484
|
const pad = (label) => label.padEnd(14);
|
|
27571
28485
|
lines.push(`${pad("Session:")}${d.sessionId}`);
|
|
28486
|
+
if (d.upstreamSessionId) {
|
|
28487
|
+
lines.push(`${pad("Upstream:")}${d.upstreamSessionId}`);
|
|
28488
|
+
}
|
|
27572
28489
|
if (d.title) {
|
|
27573
28490
|
lines.push(`${pad("Title:")}${d.title}`);
|
|
27574
28491
|
}
|
|
@@ -27693,6 +28610,9 @@ function formatDuration(ms) {
|
|
|
27693
28610
|
return parts.join(" ");
|
|
27694
28611
|
}
|
|
27695
28612
|
|
|
28613
|
+
// src/cli.ts
|
|
28614
|
+
init_session_row();
|
|
28615
|
+
|
|
27696
28616
|
// src/cli/commands/extensions.ts
|
|
27697
28617
|
init_config();
|
|
27698
28618
|
init_service_token();
|
|
@@ -28709,6 +29629,32 @@ function formatAge(ms) {
|
|
|
28709
29629
|
const day = Math.floor(hour / 24);
|
|
28710
29630
|
return `${day} day${day === 1 ? "" : "s"}`;
|
|
28711
29631
|
}
|
|
29632
|
+
async function assertKnownAgent(agentId) {
|
|
29633
|
+
const config = await loadConfig();
|
|
29634
|
+
const serviceToken = await loadServiceToken();
|
|
29635
|
+
const baseUrl = httpBase(config.daemon.host, config.daemon.port, !!config.daemon.tls);
|
|
29636
|
+
let known;
|
|
29637
|
+
try {
|
|
29638
|
+
const r = await fetch(`${baseUrl}/v1/agents`, {
|
|
29639
|
+
headers: { Authorization: `Bearer ${serviceToken}` }
|
|
29640
|
+
});
|
|
29641
|
+
if (!r.ok) {
|
|
29642
|
+
return;
|
|
29643
|
+
}
|
|
29644
|
+
const body = await r.json();
|
|
29645
|
+
known = body.agents.map((a) => a.id);
|
|
29646
|
+
} catch {
|
|
29647
|
+
return;
|
|
29648
|
+
}
|
|
29649
|
+
if (known.includes(agentId)) {
|
|
29650
|
+
return;
|
|
29651
|
+
}
|
|
29652
|
+
process.stderr.write(
|
|
29653
|
+
`hydra-acp: unknown agent '${agentId}'. Run 'hydra-acp agent list' to see available agents.
|
|
29654
|
+
`
|
|
29655
|
+
);
|
|
29656
|
+
process.exit(2);
|
|
29657
|
+
}
|
|
28712
29658
|
async function runAgentsInstall(agentId) {
|
|
28713
29659
|
if (!agentId) {
|
|
28714
29660
|
process.stderr.write("Usage: hydra-acp agent install <agent-id>\n");
|
|
@@ -28880,16 +29826,7 @@ async function runAgentsSet(agentId, modelId) {
|
|
|
28880
29826
|
process.exit(1);
|
|
28881
29827
|
return;
|
|
28882
29828
|
}
|
|
28883
|
-
|
|
28884
|
-
if (modelId === void 0) {
|
|
28885
|
-
raw.defaultAgent = agentId;
|
|
28886
|
-
await writeRawConfig3(raw);
|
|
28887
|
-
} else {
|
|
28888
|
-
const models = raw.defaultModels && typeof raw.defaultModels === "object" ? raw.defaultModels : {};
|
|
28889
|
-
models[agentId] = modelId;
|
|
28890
|
-
raw.defaultModels = models;
|
|
28891
|
-
await writeRawConfig3(raw);
|
|
28892
|
-
}
|
|
29829
|
+
await setDefaultAgent(agentId, modelId);
|
|
28893
29830
|
const disk = readAgentDefaults(await readRawConfig3());
|
|
28894
29831
|
if (modelId !== void 0 && agentId !== disk.agent) {
|
|
28895
29832
|
process.stdout.write(
|
|
@@ -28941,13 +29878,6 @@ async function readRawConfig3() {
|
|
|
28941
29878
|
const raw = await fsp12.readFile(paths.config(), "utf8");
|
|
28942
29879
|
return JSON.parse(raw);
|
|
28943
29880
|
}
|
|
28944
|
-
async function writeRawConfig3(raw) {
|
|
28945
|
-
await fsp12.writeFile(
|
|
28946
|
-
paths.config(),
|
|
28947
|
-
JSON.stringify(raw, null, 2) + "\n",
|
|
28948
|
-
{ encoding: "utf8", mode: 384 }
|
|
28949
|
-
);
|
|
28950
|
-
}
|
|
28951
29881
|
async function runAgentsRefresh() {
|
|
28952
29882
|
const config = await loadConfig();
|
|
28953
29883
|
const serviceToken = await loadServiceToken();
|
|
@@ -30463,6 +31393,9 @@ async function main() {
|
|
|
30463
31393
|
if (streamBufferBytes !== void 0) {
|
|
30464
31394
|
catOpts.streamBufferBytes = streamBufferBytes;
|
|
30465
31395
|
}
|
|
31396
|
+
if (agentIdFromFlag !== void 0) {
|
|
31397
|
+
await assertKnownAgent(agentIdFromFlag);
|
|
31398
|
+
}
|
|
30466
31399
|
suppressUpdateNotice = true;
|
|
30467
31400
|
await runCat(catOpts);
|
|
30468
31401
|
return;
|
|
@@ -30503,11 +31436,24 @@ async function main() {
|
|
|
30503
31436
|
case "sessions": {
|
|
30504
31437
|
const sub = positional[1];
|
|
30505
31438
|
if (sub === void 0 || sub === "list") {
|
|
31439
|
+
const columnsRaw = resolveOption(flags, "columns");
|
|
31440
|
+
let columns;
|
|
31441
|
+
if (columnsRaw !== void 0) {
|
|
31442
|
+
try {
|
|
31443
|
+
columns = parseColumns(columnsRaw);
|
|
31444
|
+
} catch (err) {
|
|
31445
|
+
process.stderr.write(`${err.message}
|
|
31446
|
+
`);
|
|
31447
|
+
process.exit(2);
|
|
31448
|
+
return;
|
|
31449
|
+
}
|
|
31450
|
+
}
|
|
30506
31451
|
await runSessionsList({
|
|
30507
31452
|
all: flags.all === true,
|
|
30508
31453
|
json: flags.json === true,
|
|
30509
31454
|
host: typeof flags.host === "string" ? flags.host : void 0,
|
|
30510
|
-
includeNonInteractive: flags["include-non-interactive"] === true
|
|
31455
|
+
includeNonInteractive: flags["include-non-interactive"] === true,
|
|
31456
|
+
columns
|
|
30511
31457
|
});
|
|
30512
31458
|
return;
|
|
30513
31459
|
}
|
|
@@ -30742,6 +31688,9 @@ async function dispatchTui(flags, base) {
|
|
|
30742
31688
|
);
|
|
30743
31689
|
process.exit(2);
|
|
30744
31690
|
}
|
|
31691
|
+
if (base.agentId !== void 0) {
|
|
31692
|
+
await assertKnownAgent(base.agentId);
|
|
31693
|
+
}
|
|
30745
31694
|
setHydraProcessTitle(buildTitleFromArgv(process.argv.slice(2)));
|
|
30746
31695
|
const { runTui } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
30747
31696
|
const tuiOpts = { resume, forceNew, readonly };
|
|
@@ -30766,6 +31715,19 @@ async function dispatchTui(flags, base) {
|
|
|
30766
31715
|
if (base.dangerouslySkipPermissions === true) {
|
|
30767
31716
|
tuiOpts.dangerouslySkipPermissions = true;
|
|
30768
31717
|
}
|
|
31718
|
+
if (flags.drip === true) {
|
|
31719
|
+
if (base.sessionId === void 0) {
|
|
31720
|
+
process.stderr.write(
|
|
31721
|
+
"hydra-acp: --drip requires a session id. Pass --session <id-or-url> --drip.\n"
|
|
31722
|
+
);
|
|
31723
|
+
process.exit(2);
|
|
31724
|
+
}
|
|
31725
|
+
tuiOpts.drip = true;
|
|
31726
|
+
const dripSpeed = parseNumericFlag(flags, "drip-speed");
|
|
31727
|
+
if (dripSpeed !== void 0 && dripSpeed > 0) {
|
|
31728
|
+
tuiOpts.dripSpeed = dripSpeed;
|
|
31729
|
+
}
|
|
31730
|
+
}
|
|
30769
31731
|
await runTui(tuiOpts);
|
|
30770
31732
|
}
|
|
30771
31733
|
function parseNumericFlag(flags, name) {
|
|
@@ -30880,10 +31842,11 @@ function printHelp() {
|
|
|
30880
31842
|
" hydra-acp daemon start [--foreground] Start daemon (detached by default; --foreground to attach)",
|
|
30881
31843
|
" hydra-acp daemon stop|restart",
|
|
30882
31844
|
" hydra-acp daemon logs [-f] [-n N] Tail or follow the daemon log",
|
|
30883
|
-
" hydra-acp session [list] [--all] [--json] [--host=<host>] [--include-non-interactive]",
|
|
31845
|
+
" hydra-acp session [list] [--all] [--json] [--host=<host>] [--include-non-interactive] [--columns=<list>]",
|
|
30884
31846
|
" List sessions (live + 20 most-recent cold; --all lifts the cold cap AND surfaces non-interactive sessions; --json emits JSON for scripts).",
|
|
30885
31847
|
" --host filters by origin machine: 'local' (default) shows only sessions created here, 'all' shows everything, or pass a hostname (e.g. machine-b) to show only imports from that peer.",
|
|
30886
31848
|
" --include-non-interactive surfaces ancillary (e.g. `hydra cat`) or never-prompted sessions while keeping the cold cap (a narrower --all).",
|
|
31849
|
+
" --columns picks which columns show and their order, e.g. --columns=session,state,title,cost (valid: session,upstream,host,state,agent,model,age,cwd,title,cost). UPSTREAM/HOST/MODEL hidden by default; COST is the trailing column; overrides config.tui.sessionColumns.",
|
|
30887
31850
|
" hydra-acp session info <id> [--verbose] [--json] [--diff] [--fold] [--no-color] [--no-pager]",
|
|
30888
31851
|
" Aggregate one session: turn count, tool histogram, files touched, cost/duration, synopsis. --diff appends the session diff under the summary and pages the whole thing on a TTY (inherits --fold).",
|
|
30889
31852
|
" hydra-acp session diff <id> [--json] [--no-color] [--no-pager] [--fold]",
|