@hydra-acp/cli 0.1.59 → 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 -235
- 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 [];
|
|
@@ -9518,20 +9798,6 @@ function formatUsage(usage) {
|
|
|
9518
9798
|
}
|
|
9519
9799
|
return parts.length === 0 ? null : parts.join(" \xB7 ");
|
|
9520
9800
|
}
|
|
9521
|
-
function formatElapsed(ms) {
|
|
9522
|
-
const totalSec = Math.floor(ms / 1e3);
|
|
9523
|
-
if (totalSec < 60) {
|
|
9524
|
-
return `${totalSec}s`;
|
|
9525
|
-
}
|
|
9526
|
-
const min = Math.floor(totalSec / 60);
|
|
9527
|
-
const sec = totalSec % 60;
|
|
9528
|
-
if (min < 60) {
|
|
9529
|
-
return sec === 0 ? `${min}m` : `${min}m ${sec}s`;
|
|
9530
|
-
}
|
|
9531
|
-
const hr = Math.floor(min / 60);
|
|
9532
|
-
const remMin = min % 60;
|
|
9533
|
-
return remMin === 0 ? `${hr}h` : `${hr}h ${remMin}m`;
|
|
9534
|
-
}
|
|
9535
9801
|
function formatTokens(n) {
|
|
9536
9802
|
if (n >= 1e6) {
|
|
9537
9803
|
return `${(n / 1e6).toFixed(1)}M`;
|
|
@@ -9750,7 +10016,7 @@ function mapCsiUToKeyName(code, mod) {
|
|
|
9750
10016
|
}
|
|
9751
10017
|
return null;
|
|
9752
10018
|
}
|
|
9753
|
-
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;
|
|
9754
10020
|
var init_screen = __esm({
|
|
9755
10021
|
"src/tui/screen.ts"() {
|
|
9756
10022
|
"use strict";
|
|
@@ -9758,6 +10024,7 @@ var init_screen = __esm({
|
|
|
9758
10024
|
init_paths();
|
|
9759
10025
|
init_session();
|
|
9760
10026
|
init_attachments();
|
|
10027
|
+
init_format();
|
|
9761
10028
|
init_sync();
|
|
9762
10029
|
SESSIONBAR_ROWS = 1;
|
|
9763
10030
|
BANNER_ROWS = 1;
|
|
@@ -9765,6 +10032,7 @@ var init_screen = __esm({
|
|
|
9765
10032
|
MAX_PROMPT_ROWS = 8;
|
|
9766
10033
|
MAX_QUEUED_ROWS = 5;
|
|
9767
10034
|
MAX_PERMISSION_ROWS = 12;
|
|
10035
|
+
MAX_OPTIONS_ROWS = 12;
|
|
9768
10036
|
MAX_HELP_ROWS = 30;
|
|
9769
10037
|
MAX_COMPLETION_ROWS = 6;
|
|
9770
10038
|
MAX_CHIP_ROWS = 4;
|
|
@@ -9839,6 +10107,7 @@ var init_screen = __esm({
|
|
|
9839
10107
|
lastFrameW = 0;
|
|
9840
10108
|
lastFrameH = 0;
|
|
9841
10109
|
permissionPrompt = null;
|
|
10110
|
+
optionsPrompt = null;
|
|
9842
10111
|
confirmPrompt = null;
|
|
9843
10112
|
helpPrompt = null;
|
|
9844
10113
|
completions = [];
|
|
@@ -10008,6 +10277,7 @@ var init_screen = __esm({
|
|
|
10008
10277
|
this.writeProgressIndicator(0);
|
|
10009
10278
|
this.started = false;
|
|
10010
10279
|
if (!opts.keepFullscreen) {
|
|
10280
|
+
emergencyTerminalReset();
|
|
10011
10281
|
this.term.fullscreen(false);
|
|
10012
10282
|
this.term("\n");
|
|
10013
10283
|
}
|
|
@@ -10181,6 +10451,11 @@ uncaught: ${err.stack ?? err.message}
|
|
|
10181
10451
|
}
|
|
10182
10452
|
if (text.includes("\n")) {
|
|
10183
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
|
+
}
|
|
10184
10459
|
for (let i = 0; i < parts.length; i++) {
|
|
10185
10460
|
if (parts[i].length > 0) {
|
|
10186
10461
|
this.handleRawStdin(Buffer.from(parts[i], "binary"));
|
|
@@ -10708,6 +10983,41 @@ uncaught: ${err.stack ?? err.message}
|
|
|
10708
10983
|
clearKey(key) {
|
|
10709
10984
|
this.keyedBlocks.delete(key);
|
|
10710
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
|
+
}
|
|
10711
11021
|
// Mark `key` as the sticky-bottom block. While set, whenever new content
|
|
10712
11022
|
// lands after the block's lines (appendLines / appendStreaming / a new
|
|
10713
11023
|
// upserted block) the screen floats this block back to the end so it
|
|
@@ -10826,6 +11136,15 @@ uncaught: ${err.stack ?? err.message}
|
|
|
10826
11136
|
this.permissionPrompt = spec ? { ...spec } : null;
|
|
10827
11137
|
this.repaint();
|
|
10828
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
|
+
}
|
|
10829
11148
|
// Two-line confirmation modal that takes over the prompt area. Pass
|
|
10830
11149
|
// null to dismiss. Currently unused — kept as a generic primitive for
|
|
10831
11150
|
// any future modal that needs a question + hint footer.
|
|
@@ -11316,7 +11635,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11316
11635
|
this.drawSeparator(h - SESSIONBAR_ROWS);
|
|
11317
11636
|
this.drawSessionbar();
|
|
11318
11637
|
this.placeCursor();
|
|
11319
|
-
if (this.permissionPrompt || this.confirmPrompt || this.helpPrompt) {
|
|
11638
|
+
if (this.permissionPrompt || this.optionsPrompt || this.confirmPrompt || this.helpPrompt) {
|
|
11320
11639
|
this.term.hideCursor(false);
|
|
11321
11640
|
}
|
|
11322
11641
|
this.lastPromptRows = promptRows;
|
|
@@ -11420,7 +11739,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11420
11739
|
this.repaint();
|
|
11421
11740
|
}
|
|
11422
11741
|
completionRows() {
|
|
11423
|
-
if (this.permissionPrompt || this.confirmPrompt || this.helpPrompt) {
|
|
11742
|
+
if (this.permissionPrompt || this.optionsPrompt || this.confirmPrompt || this.helpPrompt) {
|
|
11424
11743
|
return 0;
|
|
11425
11744
|
}
|
|
11426
11745
|
return Math.min(MAX_COMPLETION_ROWS, this.completions.length);
|
|
@@ -11560,6 +11879,10 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11560
11879
|
this.drawPermissionPrompt();
|
|
11561
11880
|
return;
|
|
11562
11881
|
}
|
|
11882
|
+
if (this.optionsPrompt) {
|
|
11883
|
+
this.drawOptionsPrompt();
|
|
11884
|
+
return;
|
|
11885
|
+
}
|
|
11563
11886
|
if (this.confirmPrompt) {
|
|
11564
11887
|
this.drawConfirmPrompt();
|
|
11565
11888
|
return;
|
|
@@ -11782,6 +12105,14 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11782
12105
|
this.term.moveTo(2, Math.min(optionRow, lastUsableRow));
|
|
11783
12106
|
return;
|
|
11784
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
|
+
}
|
|
11785
12116
|
if (this.confirmPrompt) {
|
|
11786
12117
|
const top2 = this.term.height - CONFIRM_PROMPT_ROWS - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS + 1;
|
|
11787
12118
|
this.term.moveTo(2, top2);
|
|
@@ -11820,6 +12151,9 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11820
12151
|
if (this.permissionPrompt) {
|
|
11821
12152
|
return this.permissionRows();
|
|
11822
12153
|
}
|
|
12154
|
+
if (this.optionsPrompt) {
|
|
12155
|
+
return this.optionsRows();
|
|
12156
|
+
}
|
|
11823
12157
|
if (this.confirmPrompt) {
|
|
11824
12158
|
return CONFIRM_PROMPT_ROWS;
|
|
11825
12159
|
}
|
|
@@ -11844,6 +12178,64 @@ uncaught: ${err.stack ?? err.message}
|
|
|
11844
12178
|
4 + this.permissionPrompt.options.length
|
|
11845
12179
|
);
|
|
11846
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
|
+
}
|
|
11847
12239
|
// Walk this.lines from the tail, accumulating wrapped rows via the
|
|
11848
12240
|
// wrap cache, until we have at least `needed` rows or run out. Returns
|
|
11849
12241
|
// the collected rows in original (top-down) order plus an `exhausted`
|
|
@@ -12018,7 +12410,7 @@ function drawBox(term, opts) {
|
|
|
12018
12410
|
const termW = readTermWidth(term);
|
|
12019
12411
|
const termH = readTermHeight(term);
|
|
12020
12412
|
const desiredContentW = opts.contentWidth ?? MAX_BOX_WIDTH;
|
|
12021
|
-
const maxContentW = Math.max(10,
|
|
12413
|
+
const maxContentW = Math.max(10, termW - 4);
|
|
12022
12414
|
const contentW = Math.min(desiredContentW, maxContentW);
|
|
12023
12415
|
const w = contentW + 2;
|
|
12024
12416
|
const contentH = Math.max(1, Math.min(opts.contentHeight, termH - 4));
|
|
@@ -12468,8 +12860,12 @@ async function pickSession(term, opts) {
|
|
|
12468
12860
|
return base;
|
|
12469
12861
|
};
|
|
12470
12862
|
let visible = applyPrefsFilters(allSessions);
|
|
12863
|
+
const formatOpts = {
|
|
12864
|
+
columns: opts.config.tui.sessionColumns ?? DEFAULT_COLUMNS,
|
|
12865
|
+
cwdMaxWidth: opts.config.tui.cwdColumnMaxWidth
|
|
12866
|
+
};
|
|
12471
12867
|
let rows = visible.map((s) => toRow(s, Date.now()));
|
|
12472
|
-
let widths = computeWidths(rows);
|
|
12868
|
+
let widths = computeWidths(rows, formatOpts);
|
|
12473
12869
|
let total = 1 + visible.length;
|
|
12474
12870
|
let selectedIdx = 0;
|
|
12475
12871
|
let scrollOffset = 0;
|
|
@@ -12496,7 +12892,11 @@ async function pickSession(term, opts) {
|
|
|
12496
12892
|
let findInFlight = false;
|
|
12497
12893
|
let renameBuffer = "";
|
|
12498
12894
|
let transientStatus = null;
|
|
12895
|
+
let currentSessionGone = false;
|
|
12499
12896
|
const composer = new InputDispatcher({ history: [] });
|
|
12897
|
+
if (opts.initialPrompt) {
|
|
12898
|
+
composer.setBuffer(opts.initialPrompt);
|
|
12899
|
+
}
|
|
12500
12900
|
const composerHistoryCap = opts.config.tui.promptHistoryMaxEntries;
|
|
12501
12901
|
loadHistory(paths.globalTuiHistoryFile()).then((entries) => {
|
|
12502
12902
|
const capped = entries.length > composerHistoryCap ? entries.slice(entries.length - composerHistoryCap) : entries;
|
|
@@ -12521,7 +12921,6 @@ async function pickSession(term, opts) {
|
|
|
12521
12921
|
let findBoxWindowStart = 0;
|
|
12522
12922
|
let findBoxCursorVisualRow = 0;
|
|
12523
12923
|
let findBoxCursorVisualCol = 0;
|
|
12524
|
-
const cwdMaxWidth = opts.config.tui.cwdColumnMaxWidth;
|
|
12525
12924
|
const computeLayout = () => {
|
|
12526
12925
|
termHeight = readTermHeight2(term);
|
|
12527
12926
|
termWidth = readTermWidth2(term);
|
|
@@ -12543,16 +12942,16 @@ async function pickSession(term, opts) {
|
|
|
12543
12942
|
const reserved = 6 + composerRows;
|
|
12544
12943
|
const maxViewportRows = Math.max(3, termHeight - reserved);
|
|
12545
12944
|
viewportSize = Math.min(visible.length, maxViewportRows);
|
|
12546
|
-
headerLine = formatRow(HEADER, widths, rowMaxWidth,
|
|
12945
|
+
headerLine = formatRow(HEADER, widths, rowMaxWidth, formatOpts).padEnd(
|
|
12547
12946
|
rowMaxWidth
|
|
12548
12947
|
);
|
|
12549
12948
|
sessionLines = rows.map(
|
|
12550
|
-
(r) => formatRow(r, widths, rowMaxWidth,
|
|
12949
|
+
(r) => formatRow(r, widths, rowMaxWidth, formatOpts).padEnd(rowMaxWidth)
|
|
12551
12950
|
);
|
|
12552
12951
|
};
|
|
12553
12952
|
const rebuildRows = () => {
|
|
12554
12953
|
rows = visible.map((s) => toRow(s, Date.now()));
|
|
12555
|
-
widths = computeWidths(rows);
|
|
12954
|
+
widths = computeWidths(rows, formatOpts);
|
|
12556
12955
|
total = 1 + visible.length;
|
|
12557
12956
|
computeLayout();
|
|
12558
12957
|
};
|
|
@@ -13295,9 +13694,19 @@ async function pickSession(term, opts) {
|
|
|
13295
13694
|
term.moveTo(1, indicatorRow() + 1);
|
|
13296
13695
|
term("\n");
|
|
13297
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
|
+
};
|
|
13298
13707
|
const renderFingerprint = () => {
|
|
13299
13708
|
const cells = rows.map(
|
|
13300
|
-
(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}`
|
|
13301
13710
|
).join("\n");
|
|
13302
13711
|
return `${selectedIdx}:${scrollOffset}:${transientStatus ?? ""}
|
|
13303
13712
|
${cells}`;
|
|
@@ -13387,6 +13796,9 @@ ${cells}`;
|
|
|
13387
13796
|
}
|
|
13388
13797
|
mode = "normal";
|
|
13389
13798
|
pendingAction = null;
|
|
13799
|
+
if (session.sessionId === opts.currentSessionId) {
|
|
13800
|
+
currentSessionGone = true;
|
|
13801
|
+
}
|
|
13390
13802
|
await refresh(kind === "kill" ? session.sessionId : void 0);
|
|
13391
13803
|
} catch (err) {
|
|
13392
13804
|
mode = "normal";
|
|
@@ -13733,8 +14145,7 @@ ${cells}`;
|
|
|
13733
14145
|
}
|
|
13734
14146
|
if (selectedIdx === 0 && !searchActive) {
|
|
13735
14147
|
if (name === "ESCAPE") {
|
|
13736
|
-
|
|
13737
|
-
resolve8({ kind: "abort" });
|
|
14148
|
+
tryAbort();
|
|
13738
14149
|
return;
|
|
13739
14150
|
}
|
|
13740
14151
|
if (name === "ENTER" || name === "KP_ENTER") {
|
|
@@ -13787,8 +14198,7 @@ ${cells}`;
|
|
|
13787
14198
|
const after = composer.state();
|
|
13788
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;
|
|
13789
14200
|
if (effects.some((e) => e.type === "exit")) {
|
|
13790
|
-
|
|
13791
|
-
resolve8({ kind: "abort" });
|
|
14201
|
+
tryAbort();
|
|
13792
14202
|
return;
|
|
13793
14203
|
}
|
|
13794
14204
|
if (unchanged) {
|
|
@@ -13861,8 +14271,7 @@ ${cells}`;
|
|
|
13861
14271
|
return;
|
|
13862
14272
|
}
|
|
13863
14273
|
if (name === "q" || name === "Q") {
|
|
13864
|
-
|
|
13865
|
-
resolve8({ kind: "abort" });
|
|
14274
|
+
tryAbort();
|
|
13866
14275
|
return;
|
|
13867
14276
|
}
|
|
13868
14277
|
if (name === "o" || name === "O") {
|
|
@@ -14038,8 +14447,7 @@ ${cells}`;
|
|
|
14038
14447
|
case "ESCAPE":
|
|
14039
14448
|
case "CTRL_C":
|
|
14040
14449
|
case "CTRL_D":
|
|
14041
|
-
|
|
14042
|
-
resolve8({ kind: "abort" });
|
|
14450
|
+
tryAbort();
|
|
14043
14451
|
return;
|
|
14044
14452
|
}
|
|
14045
14453
|
};
|
|
@@ -14088,6 +14496,9 @@ function sortSessions(sessions, cwd) {
|
|
|
14088
14496
|
return 0;
|
|
14089
14497
|
}
|
|
14090
14498
|
const base = s.cwd === cwd ? 2 : 1;
|
|
14499
|
+
if (s.awaitingInput) {
|
|
14500
|
+
return base + 4;
|
|
14501
|
+
}
|
|
14091
14502
|
return s.busy ? base + 2 : base;
|
|
14092
14503
|
};
|
|
14093
14504
|
return [...sessions].sort((a, b) => {
|
|
@@ -14404,28 +14815,218 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
14404
14815
|
}
|
|
14405
14816
|
return;
|
|
14406
14817
|
}
|
|
14407
|
-
if (name === "CTRL_U") {
|
|
14408
|
-
buffer = "";
|
|
14409
|
-
errorLine = null;
|
|
14410
|
-
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();
|
|
14411
15007
|
return;
|
|
14412
15008
|
}
|
|
14413
|
-
if (name === "
|
|
14414
|
-
|
|
14415
|
-
const lastSep = Math.max(
|
|
14416
|
-
trimmedRight.lastIndexOf("/"),
|
|
14417
|
-
trimmedRight.lastIndexOf(" ")
|
|
14418
|
-
);
|
|
14419
|
-
buffer = lastSep >= 0 ? trimmedRight.slice(0, lastSep + 1) : "";
|
|
14420
|
-
errorLine = null;
|
|
14421
|
-
repaintInput();
|
|
15009
|
+
if (name === "DOWN" || name === "TAB") {
|
|
15010
|
+
moveDown();
|
|
14422
15011
|
return;
|
|
14423
15012
|
}
|
|
14424
15013
|
if (data?.isCharacter) {
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
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
|
+
}
|
|
14429
15030
|
}
|
|
14430
15031
|
};
|
|
14431
15032
|
term.grabInput({});
|
|
@@ -14433,7 +15034,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
14433
15034
|
term.on("resize", onResize);
|
|
14434
15035
|
});
|
|
14435
15036
|
}
|
|
14436
|
-
function
|
|
15037
|
+
function truncate4(s, max) {
|
|
14437
15038
|
if (max <= 1) {
|
|
14438
15039
|
return "";
|
|
14439
15040
|
}
|
|
@@ -14442,23 +15043,20 @@ function truncate3(s, max) {
|
|
|
14442
15043
|
}
|
|
14443
15044
|
return s.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
14444
15045
|
}
|
|
14445
|
-
function
|
|
14446
|
-
if (
|
|
14447
|
-
return
|
|
14448
|
-
}
|
|
14449
|
-
if (s.length <= max) {
|
|
14450
|
-
return s;
|
|
15046
|
+
function padRight2(s, w) {
|
|
15047
|
+
if (s.length >= w) {
|
|
15048
|
+
return s.slice(0, w);
|
|
14451
15049
|
}
|
|
14452
|
-
return
|
|
15050
|
+
return s + " ".repeat(w - s.length);
|
|
14453
15051
|
}
|
|
14454
|
-
var
|
|
14455
|
-
|
|
15052
|
+
var PREFERRED_DEFAULT, MAX_VISIBLE_ROWS, PREFERRED_CONTENT_WIDTH;
|
|
15053
|
+
var init_agent_prompt = __esm({
|
|
15054
|
+
"src/tui/agent-prompt.ts"() {
|
|
14456
15055
|
"use strict";
|
|
14457
|
-
init_paths();
|
|
14458
|
-
init_session();
|
|
14459
|
-
init_cwd();
|
|
14460
|
-
init_completion();
|
|
14461
15056
|
init_prompt_utils();
|
|
15057
|
+
PREFERRED_DEFAULT = "opencode";
|
|
15058
|
+
MAX_VISIBLE_ROWS = 20;
|
|
15059
|
+
PREFERRED_CONTENT_WIDTH = 88;
|
|
14462
15060
|
}
|
|
14463
15061
|
});
|
|
14464
15062
|
|
|
@@ -14796,7 +15394,12 @@ async function runTuiApp(opts) {
|
|
|
14796
15394
|
const term = termkit.terminal;
|
|
14797
15395
|
const exitHint = {};
|
|
14798
15396
|
const viewPrefs = {
|
|
14799
|
-
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
|
|
14800
15403
|
};
|
|
14801
15404
|
const pickerPrefs = createPickerPrefs();
|
|
14802
15405
|
let altScreenEngaged = false;
|
|
@@ -14974,7 +15577,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14974
15577
|
"current_mode_update",
|
|
14975
15578
|
"available_commands_update",
|
|
14976
15579
|
"available_modes_update",
|
|
14977
|
-
"usage_update"
|
|
15580
|
+
"usage_update",
|
|
15581
|
+
"config_option_update"
|
|
14978
15582
|
]);
|
|
14979
15583
|
const handleSessionUpdate = (params) => {
|
|
14980
15584
|
const { update } = params ?? {};
|
|
@@ -15319,6 +15923,10 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15319
15923
|
historyPolicy: "full",
|
|
15320
15924
|
clientInfo: { name: "hydra-acp-tui", version: HYDRA_VERSION },
|
|
15321
15925
|
...opts.readonly === true ? { readonly: true } : {},
|
|
15926
|
+
...opts.drip === true ? {
|
|
15927
|
+
replayMode: "drip",
|
|
15928
|
+
...opts.dripSpeed !== void 0 ? { dripSpeed: opts.dripSpeed } : {}
|
|
15929
|
+
} : {},
|
|
15322
15930
|
// Forward the user-chosen cwd via a full resume hint. An empty
|
|
15323
15931
|
// upstreamSessionId routes through doResurrectFromImport
|
|
15324
15932
|
// (first-launch imports); a real one takes the normal session/load
|
|
@@ -15410,7 +16018,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15410
16018
|
dispatcher,
|
|
15411
16019
|
repaintThrottleMs: config.tui.repaintThrottleMs,
|
|
15412
16020
|
maxScrollbackLines: config.tui.maxScrollbackLines,
|
|
15413
|
-
mouse:
|
|
16021
|
+
mouse: viewPrefs.mouseEnabled,
|
|
15414
16022
|
progressIndicator: config.tui.progressIndicator,
|
|
15415
16023
|
readonly: opts.readonly === true,
|
|
15416
16024
|
onKey: (events) => {
|
|
@@ -15421,6 +16029,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15421
16029
|
if (tryHandleHelpKey(ev)) {
|
|
15422
16030
|
continue;
|
|
15423
16031
|
}
|
|
16032
|
+
if (tryHandleOptionsKey(ev)) {
|
|
16033
|
+
continue;
|
|
16034
|
+
}
|
|
15424
16035
|
if (tryHandleScrollbackSearchKey(ev)) {
|
|
15425
16036
|
continue;
|
|
15426
16037
|
}
|
|
@@ -15664,7 +16275,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15664
16275
|
const buildHelpEntries = () => {
|
|
15665
16276
|
const enqueueDesc = "enqueue prompt (sends now, or queues during a turn)";
|
|
15666
16277
|
const amendDesc = "amend the in-flight turn (cancel + replace)";
|
|
15667
|
-
const head =
|
|
16278
|
+
const head = viewPrefs.defaultEnterAction === "amend" ? [
|
|
15668
16279
|
["Enter", amendDesc],
|
|
15669
16280
|
["Ctrl+Enter / Shift+Enter / ^S", enqueueDesc]
|
|
15670
16281
|
] : [
|
|
@@ -15695,6 +16306,190 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15695
16306
|
screen.setHelpPrompt(null);
|
|
15696
16307
|
return true;
|
|
15697
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
|
+
};
|
|
15698
16493
|
const teardown = () => {
|
|
15699
16494
|
teardownStarted = true;
|
|
15700
16495
|
process.off("SIGINT", sigintHandler);
|
|
@@ -15893,14 +16688,14 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15893
16688
|
const handleEffect = (effect) => {
|
|
15894
16689
|
switch (effect.type) {
|
|
15895
16690
|
case "send":
|
|
15896
|
-
if (
|
|
16691
|
+
if (viewPrefs.defaultEnterAction === "amend") {
|
|
15897
16692
|
amendPrompt(effect.text, effect.attachments, effect.displayText);
|
|
15898
16693
|
} else {
|
|
15899
16694
|
enqueuePrompt(effect.text, effect.attachments, effect.displayText);
|
|
15900
16695
|
}
|
|
15901
16696
|
return;
|
|
15902
16697
|
case "amend":
|
|
15903
|
-
if (
|
|
16698
|
+
if (viewPrefs.defaultEnterAction === "amend") {
|
|
15904
16699
|
enqueuePrompt(effect.text, effect.attachments, effect.displayText);
|
|
15905
16700
|
} else {
|
|
15906
16701
|
amendPrompt(effect.text, effect.attachments, effect.displayText);
|
|
@@ -15993,9 +16788,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15993
16788
|
case "next-live-session":
|
|
15994
16789
|
void cycleLiveSession();
|
|
15995
16790
|
return;
|
|
15996
|
-
case "toggle-
|
|
15997
|
-
|
|
15998
|
-
renderToolsBlock();
|
|
16791
|
+
case "toggle-options":
|
|
16792
|
+
toggleOptionsModal();
|
|
15999
16793
|
return;
|
|
16000
16794
|
case "toggle-thoughts":
|
|
16001
16795
|
viewPrefs.showThoughts = !viewPrefs.showThoughts;
|
|
@@ -16007,6 +16801,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16007
16801
|
case "toggle-mouse": {
|
|
16008
16802
|
const next = !screen.isMouseEnabled();
|
|
16009
16803
|
screen.setMouseEnabled(next);
|
|
16804
|
+
viewPrefs.mouseEnabled = next;
|
|
16010
16805
|
screen.notify(
|
|
16011
16806
|
next ? "mouse capture on \u2014 wheel scrolls; shift+drag to select text" : "mouse capture off \u2014 click-drag selects text; PgUp/PgDn scrolls"
|
|
16012
16807
|
);
|
|
@@ -16277,9 +17072,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16277
17072
|
toolsBlockStartedAt = null;
|
|
16278
17073
|
toolsBlockEndedAt = null;
|
|
16279
17074
|
toolsBlockStopReason = null;
|
|
16280
|
-
toolsExpanded = false;
|
|
16281
17075
|
lastEditMarkPath = null;
|
|
16282
17076
|
turnHasShownProse = false;
|
|
17077
|
+
renderedEditDiffs.clear();
|
|
16283
17078
|
screen.clearScrollback();
|
|
16284
17079
|
return true;
|
|
16285
17080
|
case "/demo-plan": {
|
|
@@ -16460,16 +17255,27 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16460
17255
|
}
|
|
16461
17256
|
};
|
|
16462
17257
|
const toolStates = /* @__PURE__ */ new Map();
|
|
17258
|
+
const renderedEditDiffs = /* @__PURE__ */ new Map();
|
|
16463
17259
|
const exitPlanStates = /* @__PURE__ */ new Map();
|
|
16464
17260
|
const toolCallOrder = [];
|
|
16465
|
-
let toolsExpanded = false;
|
|
16466
17261
|
let toolsBlockStartedAt = null;
|
|
16467
17262
|
let toolsBlockEndedAt = null;
|
|
16468
17263
|
let toolsBlockStopReason = null;
|
|
16469
17264
|
let lastPlanEvent = null;
|
|
16470
17265
|
const TOOLS_COLLAPSED_LIMIT = config.tui.maxToolItems;
|
|
16471
17266
|
const PLAN_VISIBLE_LIMIT2 = config.tui.maxPlanItems;
|
|
16472
|
-
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
|
+
};
|
|
16473
17279
|
let agentBuffer = "";
|
|
16474
17280
|
let agentKey = null;
|
|
16475
17281
|
let agentSeq = 0;
|
|
@@ -16537,7 +17343,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16537
17343
|
}
|
|
16538
17344
|
const total = toolCallOrder.length;
|
|
16539
17345
|
const capped = TOOLS_COLLAPSED_LIMIT > 0;
|
|
16540
|
-
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));
|
|
16541
17347
|
const hidden = total - visibleIds.length;
|
|
16542
17348
|
const inProgress = toolsBlockEndedAt === null;
|
|
16543
17349
|
const end = toolsBlockEndedAt ?? Date.now();
|
|
@@ -16556,12 +17362,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16556
17362
|
const noun = total === 1 ? "tool" : "tools";
|
|
16557
17363
|
const timing = stoppedReason !== null ? stoppedLabel : inProgress ? formatElapsed(elapsed) : `took ${formatElapsed(elapsed)}`;
|
|
16558
17364
|
const parts = [`${total} ${noun}`, timing];
|
|
16559
|
-
if (inProgress && capped) {
|
|
16560
|
-
|
|
16561
|
-
parts.push(`${hidden} hidden \u2014 ^O expand`);
|
|
16562
|
-
} else if (toolsExpanded && total > TOOLS_COLLAPSED_LIMIT) {
|
|
16563
|
-
parts.push("^O collapse");
|
|
16564
|
-
}
|
|
17365
|
+
if (inProgress && capped && hidden > 0) {
|
|
17366
|
+
parts.push(`${hidden} hidden`);
|
|
16565
17367
|
}
|
|
16566
17368
|
summary = parts.join(" \xB7 ");
|
|
16567
17369
|
}
|
|
@@ -16580,7 +17382,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16580
17382
|
for (const id of visibleIds) {
|
|
16581
17383
|
const state = toolStates.get(id);
|
|
16582
17384
|
if (state) {
|
|
16583
|
-
lines.push(...formatToolLine2(state));
|
|
17385
|
+
lines.push(...formatToolLine2(state, end));
|
|
16584
17386
|
}
|
|
16585
17387
|
}
|
|
16586
17388
|
screen.upsertLines("tools", lines);
|
|
@@ -16597,7 +17399,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16597
17399
|
const state = existing ?? {
|
|
16598
17400
|
initialTitle: title ?? "tool",
|
|
16599
17401
|
latestTitle: title ?? "tool",
|
|
16600
|
-
status: status ?? "pending"
|
|
17402
|
+
status: status ?? "pending",
|
|
17403
|
+
startedAt: Date.now()
|
|
16601
17404
|
};
|
|
16602
17405
|
if (existing && title !== void 0) {
|
|
16603
17406
|
state.latestTitle = title;
|
|
@@ -16608,6 +17411,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16608
17411
|
if (!existing) {
|
|
16609
17412
|
state.status = status ?? "pending";
|
|
16610
17413
|
}
|
|
17414
|
+
if (state.endedAt === void 0 && isTerminalToolStatus(state.status)) {
|
|
17415
|
+
state.endedAt = Date.now();
|
|
17416
|
+
}
|
|
16611
17417
|
if (errorText !== void 0) {
|
|
16612
17418
|
state.errorText = errorText;
|
|
16613
17419
|
}
|
|
@@ -16627,35 +17433,60 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16627
17433
|
let lastEditMarkPath = null;
|
|
16628
17434
|
let turnHasShownProse = false;
|
|
16629
17435
|
const maybeRenderEditDiff = (toolCallId) => {
|
|
16630
|
-
const
|
|
16631
|
-
|
|
16632
|
-
|
|
16633
|
-
|
|
16634
|
-
|
|
16635
|
-
if (!state?.editDiff || state.status !== "completed") {
|
|
16636
|
-
return;
|
|
16637
|
-
}
|
|
16638
|
-
if (mode === "diff") {
|
|
16639
|
-
const lines2 = formatEditDiffBlock(state.editDiff, "diff");
|
|
16640
|
-
if (lines2.length > 0) {
|
|
16641
|
-
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;
|
|
16642
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);
|
|
16643
17468
|
return;
|
|
16644
17469
|
}
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
const diff = state.editDiff;
|
|
16649
|
-
if (diff.path && diff.path === lastEditMarkPath) {
|
|
16650
|
-
return;
|
|
16651
|
-
}
|
|
16652
|
-
const lines = formatEditDiffBlock(diff, "edit");
|
|
16653
|
-
if (lines.length === 0) {
|
|
16654
|
-
return;
|
|
17470
|
+
const diff = toolStates.get(toolCallId)?.editDiff;
|
|
17471
|
+
if (diff) {
|
|
17472
|
+
renderedEditDiffs.set(toolCallId, diff);
|
|
16655
17473
|
}
|
|
16656
|
-
screen.upsertLines(
|
|
16657
|
-
|
|
16658
|
-
|
|
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
|
+
}
|
|
16659
17490
|
}
|
|
16660
17491
|
};
|
|
16661
17492
|
applyRenderEvent = (event) => {
|
|
@@ -16727,7 +17558,6 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16727
17558
|
toolStates.clear();
|
|
16728
17559
|
exitPlanStates.clear();
|
|
16729
17560
|
toolCallOrder.length = 0;
|
|
16730
|
-
toolsExpanded = false;
|
|
16731
17561
|
toolsBlockEndedAt = null;
|
|
16732
17562
|
lastEditMarkPath = null;
|
|
16733
17563
|
turnHasShownProse = false;
|
|
@@ -16789,7 +17619,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16789
17619
|
closeAgentText();
|
|
16790
17620
|
closeThought();
|
|
16791
17621
|
lastPlanEvent = event;
|
|
16792
|
-
const lines = formatEvent(event,
|
|
17622
|
+
const lines = formatEvent(event, planFormatOptions());
|
|
16793
17623
|
if (lines.length > 0) {
|
|
16794
17624
|
screen.upsertLines("plan", [{ body: "" }, ...lines]);
|
|
16795
17625
|
}
|
|
@@ -16838,7 +17668,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16838
17668
|
stopped: true,
|
|
16839
17669
|
amended: event.amended === true
|
|
16840
17670
|
},
|
|
16841
|
-
|
|
17671
|
+
planFormatOptions()
|
|
16842
17672
|
);
|
|
16843
17673
|
if (lines.length > 0) {
|
|
16844
17674
|
screen.upsertLines("plan", [{ body: "" }, ...lines]);
|
|
@@ -16868,7 +17698,6 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16868
17698
|
toolsBlockStartedAt = null;
|
|
16869
17699
|
toolsBlockEndedAt = null;
|
|
16870
17700
|
toolsBlockStopReason = null;
|
|
16871
|
-
toolsExpanded = false;
|
|
16872
17701
|
upstreamInterruptedSeen = false;
|
|
16873
17702
|
lastEditMarkPath = null;
|
|
16874
17703
|
turnHasShownProse = false;
|
|
@@ -16956,7 +17785,6 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16956
17785
|
toolsBlockStartedAt = null;
|
|
16957
17786
|
toolsBlockEndedAt = null;
|
|
16958
17787
|
toolsBlockStopReason = null;
|
|
16959
|
-
toolsExpanded = false;
|
|
16960
17788
|
lastEditMarkPath = null;
|
|
16961
17789
|
turnHasShownProse = false;
|
|
16962
17790
|
};
|
|
@@ -17114,6 +17942,10 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17114
17942
|
return ctx;
|
|
17115
17943
|
}
|
|
17116
17944
|
if (opts.forceNew) {
|
|
17945
|
+
const agentStep = await ensureAgentForNew(term, target, opts);
|
|
17946
|
+
if (agentStep !== "ok") {
|
|
17947
|
+
return null;
|
|
17948
|
+
}
|
|
17117
17949
|
return newCtx(opts, cwd, config);
|
|
17118
17950
|
}
|
|
17119
17951
|
if (opts.resume) {
|
|
@@ -17137,7 +17969,8 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17137
17969
|
sessions,
|
|
17138
17970
|
config,
|
|
17139
17971
|
target,
|
|
17140
|
-
prefs: pickerPrefs
|
|
17972
|
+
prefs: pickerPrefs,
|
|
17973
|
+
...opts.initialPrompt !== void 0 ? { initialPrompt: opts.initialPrompt } : {}
|
|
17141
17974
|
});
|
|
17142
17975
|
if (choice.kind === "abort") {
|
|
17143
17976
|
return null;
|
|
@@ -17146,6 +17979,13 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17146
17979
|
if (choice.prompt !== void 0) {
|
|
17147
17980
|
opts.initialPrompt = choice.prompt;
|
|
17148
17981
|
}
|
|
17982
|
+
const agentStep = await ensureAgentForNew(term, target, opts);
|
|
17983
|
+
if (agentStep === "cancel") {
|
|
17984
|
+
return null;
|
|
17985
|
+
}
|
|
17986
|
+
if (agentStep === "back") {
|
|
17987
|
+
continue;
|
|
17988
|
+
}
|
|
17149
17989
|
return newCtx(opts, cwd, config);
|
|
17150
17990
|
}
|
|
17151
17991
|
if (choice.kind === "fork") {
|
|
@@ -17305,6 +18145,38 @@ function newCtx(opts, cwd, config) {
|
|
|
17305
18145
|
cwd
|
|
17306
18146
|
};
|
|
17307
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
|
+
}
|
|
17308
18180
|
function debugLogUpdate(update, event) {
|
|
17309
18181
|
writeDebugLine({
|
|
17310
18182
|
src: "session/update",
|
|
@@ -17450,6 +18322,7 @@ var init_app = __esm({
|
|
|
17450
18322
|
init_picker();
|
|
17451
18323
|
init_import_cwd_prompt();
|
|
17452
18324
|
init_import_action_prompt();
|
|
18325
|
+
init_agent_prompt();
|
|
17453
18326
|
init_screen();
|
|
17454
18327
|
init_input();
|
|
17455
18328
|
init_attachments();
|
|
@@ -17475,7 +18348,7 @@ var init_app = __esm({
|
|
|
17475
18348
|
["Alt+N / Alt+Tab", "next live session"],
|
|
17476
18349
|
["^T", "show / hide thoughts"],
|
|
17477
18350
|
["^V", "paste image from clipboard"],
|
|
17478
|
-
["^O", "
|
|
18351
|
+
["^O", "session options (tools \xB7 plan \xB7 thoughts \xB7 diffs \xB7 mouse \xB7 enter)"],
|
|
17479
18352
|
null,
|
|
17480
18353
|
["^R", "history reverse search (^S walks forward once engaged)"],
|
|
17481
18354
|
["PgUp / PgDn", "scroll scrollback"],
|
|
@@ -17516,6 +18389,7 @@ var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
17516
18389
|
"detach",
|
|
17517
18390
|
"diff",
|
|
17518
18391
|
"disabled",
|
|
18392
|
+
"drip",
|
|
17519
18393
|
"fold",
|
|
17520
18394
|
"follow",
|
|
17521
18395
|
"force",
|
|
@@ -17538,8 +18412,10 @@ var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
17538
18412
|
var KNOWN_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
17539
18413
|
"agent",
|
|
17540
18414
|
"args",
|
|
18415
|
+
"columns",
|
|
17541
18416
|
"command",
|
|
17542
18417
|
"cwd",
|
|
18418
|
+
"drip-speed",
|
|
17543
18419
|
"env",
|
|
17544
18420
|
"host",
|
|
17545
18421
|
"model",
|
|
@@ -20001,7 +20877,14 @@ var SessionManager = class {
|
|
|
20001
20877
|
histories: this.histories,
|
|
20002
20878
|
synopsisAgent: this.synopsisAgent,
|
|
20003
20879
|
synopsisModel: this.synopsisModel,
|
|
20004
|
-
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
|
+
},
|
|
20005
20888
|
persistSynopsis: (id, synopsis, through) => this.persistSynopsis(id, synopsis, through),
|
|
20006
20889
|
logger: this.logger,
|
|
20007
20890
|
npmRegistry: this.npmRegistry
|
|
@@ -20354,27 +21237,27 @@ var SessionManager = class {
|
|
|
20354
21237
|
return false;
|
|
20355
21238
|
}
|
|
20356
21239
|
}
|
|
20357
|
-
// When the last client detaches from a session that
|
|
20358
|
-
//
|
|
20359
|
-
//
|
|
20360
|
-
//
|
|
20361
|
-
//
|
|
20362
|
-
//
|
|
20363
|
-
//
|
|
20364
|
-
//
|
|
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.
|
|
20365
21255
|
async reapIfOrphanedNonInteractive(sessionId) {
|
|
20366
21256
|
const session = this.sessions.get(sessionId);
|
|
20367
21257
|
if (!session || session.attachedCount > 0) {
|
|
20368
21258
|
return;
|
|
20369
21259
|
}
|
|
20370
|
-
|
|
20371
|
-
{
|
|
20372
|
-
interactive: session.interactive,
|
|
20373
|
-
...session.originatingClient ? { originatingClient: session.originatingClient } : {}
|
|
20374
|
-
},
|
|
20375
|
-
true
|
|
20376
|
-
);
|
|
20377
|
-
if (interactive !== false) {
|
|
21260
|
+
if (session.interactive === true) {
|
|
20378
21261
|
return;
|
|
20379
21262
|
}
|
|
20380
21263
|
this.logger?.info(
|
|
@@ -20872,7 +21755,8 @@ var SessionManager = class {
|
|
|
20872
21755
|
updatedAt: used,
|
|
20873
21756
|
attachedClients: session.attachedCount,
|
|
20874
21757
|
status: "live",
|
|
20875
|
-
busy: session.turnStartedAt !== void 0
|
|
21758
|
+
busy: session.turnStartedAt !== void 0,
|
|
21759
|
+
awaitingInput: session.awaitingInput
|
|
20876
21760
|
});
|
|
20877
21761
|
}
|
|
20878
21762
|
const records = await this.store.list().catch(() => []);
|
|
@@ -20910,7 +21794,8 @@ var SessionManager = class {
|
|
|
20910
21794
|
updatedAt: used,
|
|
20911
21795
|
attachedClients: 0,
|
|
20912
21796
|
status: "cold",
|
|
20913
|
-
busy: false
|
|
21797
|
+
busy: false,
|
|
21798
|
+
awaitingInput: false
|
|
20914
21799
|
});
|
|
20915
21800
|
}
|
|
20916
21801
|
entries.sort((a, b) => a.updatedAt < b.updatedAt ? 1 : -1);
|
|
@@ -24814,10 +25699,11 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
24814
25699
|
params.clientInfo,
|
|
24815
25700
|
params.clientId
|
|
24816
25701
|
);
|
|
25702
|
+
const drip = params.replayMode === "drip";
|
|
24817
25703
|
const { entries: replay, appliedPolicy } = await session.attach(
|
|
24818
25704
|
client,
|
|
24819
25705
|
params.historyPolicy,
|
|
24820
|
-
{ afterMessageId: params.afterMessageId }
|
|
25706
|
+
{ afterMessageId: params.afterMessageId, raw: drip }
|
|
24821
25707
|
);
|
|
24822
25708
|
state.attached.set(session.sessionId, {
|
|
24823
25709
|
sessionId: session.sessionId,
|
|
@@ -24825,10 +25711,35 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
24825
25711
|
readonly
|
|
24826
25712
|
});
|
|
24827
25713
|
app.log.info(
|
|
24828
|
-
`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" : ""}`
|
|
24829
25715
|
);
|
|
24830
|
-
|
|
24831
|
-
|
|
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
|
+
}
|
|
24832
25743
|
}
|
|
24833
25744
|
session.replayPendingPermissions(client);
|
|
24834
25745
|
const modesPayload = buildModesPayload(session);
|
|
@@ -26689,12 +27600,15 @@ async function runSessionsList(opts = {}) {
|
|
|
26689
27600
|
}
|
|
26690
27601
|
const now = Date.now();
|
|
26691
27602
|
const rows = visible.map((s) => toRow(s, now));
|
|
26692
|
-
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);
|
|
26693
27608
|
const maxWidth = process.stdout.isTTY ? process.stdout.columns : void 0;
|
|
26694
|
-
|
|
26695
|
-
process.stdout.write(formatRow(HEADER, widths, maxWidth, cwdMax) + "\n");
|
|
27609
|
+
process.stdout.write(formatRow(HEADER, widths, maxWidth, formatOpts) + "\n");
|
|
26696
27610
|
for (const r of rows) {
|
|
26697
|
-
process.stdout.write(formatRow(r, widths, maxWidth,
|
|
27611
|
+
process.stdout.write(formatRow(r, widths, maxWidth, formatOpts) + "\n");
|
|
26698
27612
|
}
|
|
26699
27613
|
if (truncated > 0) {
|
|
26700
27614
|
process.stdout.write(
|
|
@@ -26958,10 +27872,14 @@ function printBundleInfo(raw, cwdColumnMaxWidth) {
|
|
|
26958
27872
|
}
|
|
26959
27873
|
const summary = bundleToSummary(parsed);
|
|
26960
27874
|
const row = toRow(summary);
|
|
26961
|
-
const
|
|
27875
|
+
const formatOpts = {
|
|
27876
|
+
columns: ALL_COLUMNS,
|
|
27877
|
+
cwdMaxWidth: cwdColumnMaxWidth
|
|
27878
|
+
};
|
|
27879
|
+
const widths = computeWidths([row], formatOpts);
|
|
26962
27880
|
const maxWidth = process.stdout.isTTY ? process.stdout.columns : void 0;
|
|
26963
|
-
process.stdout.write(formatRow(HEADER, widths, maxWidth,
|
|
26964
|
-
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");
|
|
26965
27883
|
const originUpstream = parsed.session.upstreamSessionId ?? "-";
|
|
26966
27884
|
process.stdout.write(
|
|
26967
27885
|
`
|
|
@@ -27537,6 +28455,7 @@ function aggregate(bundle, status) {
|
|
|
27537
28455
|
const durationMs = Number.isFinite(createdMs) && Number.isFinite(updatedMs) ? updatedMs - createdMs : null;
|
|
27538
28456
|
return {
|
|
27539
28457
|
sessionId: r.sessionId,
|
|
28458
|
+
...r.upstreamSessionId !== void 0 ? { upstreamSessionId: r.upstreamSessionId } : {},
|
|
27540
28459
|
...r.title !== void 0 ? { title: r.title } : {},
|
|
27541
28460
|
cwd: r.cwd,
|
|
27542
28461
|
agentId: r.agentId,
|
|
@@ -27564,6 +28483,9 @@ function formatSummary(d, verbose) {
|
|
|
27564
28483
|
const lines = [];
|
|
27565
28484
|
const pad = (label) => label.padEnd(14);
|
|
27566
28485
|
lines.push(`${pad("Session:")}${d.sessionId}`);
|
|
28486
|
+
if (d.upstreamSessionId) {
|
|
28487
|
+
lines.push(`${pad("Upstream:")}${d.upstreamSessionId}`);
|
|
28488
|
+
}
|
|
27567
28489
|
if (d.title) {
|
|
27568
28490
|
lines.push(`${pad("Title:")}${d.title}`);
|
|
27569
28491
|
}
|
|
@@ -27688,6 +28610,9 @@ function formatDuration(ms) {
|
|
|
27688
28610
|
return parts.join(" ");
|
|
27689
28611
|
}
|
|
27690
28612
|
|
|
28613
|
+
// src/cli.ts
|
|
28614
|
+
init_session_row();
|
|
28615
|
+
|
|
27691
28616
|
// src/cli/commands/extensions.ts
|
|
27692
28617
|
init_config();
|
|
27693
28618
|
init_service_token();
|
|
@@ -28704,6 +29629,32 @@ function formatAge(ms) {
|
|
|
28704
29629
|
const day = Math.floor(hour / 24);
|
|
28705
29630
|
return `${day} day${day === 1 ? "" : "s"}`;
|
|
28706
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
|
+
}
|
|
28707
29658
|
async function runAgentsInstall(agentId) {
|
|
28708
29659
|
if (!agentId) {
|
|
28709
29660
|
process.stderr.write("Usage: hydra-acp agent install <agent-id>\n");
|
|
@@ -28875,16 +29826,7 @@ async function runAgentsSet(agentId, modelId) {
|
|
|
28875
29826
|
process.exit(1);
|
|
28876
29827
|
return;
|
|
28877
29828
|
}
|
|
28878
|
-
|
|
28879
|
-
if (modelId === void 0) {
|
|
28880
|
-
raw.defaultAgent = agentId;
|
|
28881
|
-
await writeRawConfig3(raw);
|
|
28882
|
-
} else {
|
|
28883
|
-
const models = raw.defaultModels && typeof raw.defaultModels === "object" ? raw.defaultModels : {};
|
|
28884
|
-
models[agentId] = modelId;
|
|
28885
|
-
raw.defaultModels = models;
|
|
28886
|
-
await writeRawConfig3(raw);
|
|
28887
|
-
}
|
|
29829
|
+
await setDefaultAgent(agentId, modelId);
|
|
28888
29830
|
const disk = readAgentDefaults(await readRawConfig3());
|
|
28889
29831
|
if (modelId !== void 0 && agentId !== disk.agent) {
|
|
28890
29832
|
process.stdout.write(
|
|
@@ -28936,13 +29878,6 @@ async function readRawConfig3() {
|
|
|
28936
29878
|
const raw = await fsp12.readFile(paths.config(), "utf8");
|
|
28937
29879
|
return JSON.parse(raw);
|
|
28938
29880
|
}
|
|
28939
|
-
async function writeRawConfig3(raw) {
|
|
28940
|
-
await fsp12.writeFile(
|
|
28941
|
-
paths.config(),
|
|
28942
|
-
JSON.stringify(raw, null, 2) + "\n",
|
|
28943
|
-
{ encoding: "utf8", mode: 384 }
|
|
28944
|
-
);
|
|
28945
|
-
}
|
|
28946
29881
|
async function runAgentsRefresh() {
|
|
28947
29882
|
const config = await loadConfig();
|
|
28948
29883
|
const serviceToken = await loadServiceToken();
|
|
@@ -30458,6 +31393,9 @@ async function main() {
|
|
|
30458
31393
|
if (streamBufferBytes !== void 0) {
|
|
30459
31394
|
catOpts.streamBufferBytes = streamBufferBytes;
|
|
30460
31395
|
}
|
|
31396
|
+
if (agentIdFromFlag !== void 0) {
|
|
31397
|
+
await assertKnownAgent(agentIdFromFlag);
|
|
31398
|
+
}
|
|
30461
31399
|
suppressUpdateNotice = true;
|
|
30462
31400
|
await runCat(catOpts);
|
|
30463
31401
|
return;
|
|
@@ -30498,11 +31436,24 @@ async function main() {
|
|
|
30498
31436
|
case "sessions": {
|
|
30499
31437
|
const sub = positional[1];
|
|
30500
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
|
+
}
|
|
30501
31451
|
await runSessionsList({
|
|
30502
31452
|
all: flags.all === true,
|
|
30503
31453
|
json: flags.json === true,
|
|
30504
31454
|
host: typeof flags.host === "string" ? flags.host : void 0,
|
|
30505
|
-
includeNonInteractive: flags["include-non-interactive"] === true
|
|
31455
|
+
includeNonInteractive: flags["include-non-interactive"] === true,
|
|
31456
|
+
columns
|
|
30506
31457
|
});
|
|
30507
31458
|
return;
|
|
30508
31459
|
}
|
|
@@ -30737,6 +31688,9 @@ async function dispatchTui(flags, base) {
|
|
|
30737
31688
|
);
|
|
30738
31689
|
process.exit(2);
|
|
30739
31690
|
}
|
|
31691
|
+
if (base.agentId !== void 0) {
|
|
31692
|
+
await assertKnownAgent(base.agentId);
|
|
31693
|
+
}
|
|
30740
31694
|
setHydraProcessTitle(buildTitleFromArgv(process.argv.slice(2)));
|
|
30741
31695
|
const { runTui } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
30742
31696
|
const tuiOpts = { resume, forceNew, readonly };
|
|
@@ -30761,6 +31715,19 @@ async function dispatchTui(flags, base) {
|
|
|
30761
31715
|
if (base.dangerouslySkipPermissions === true) {
|
|
30762
31716
|
tuiOpts.dangerouslySkipPermissions = true;
|
|
30763
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
|
+
}
|
|
30764
31731
|
await runTui(tuiOpts);
|
|
30765
31732
|
}
|
|
30766
31733
|
function parseNumericFlag(flags, name) {
|
|
@@ -30875,10 +31842,11 @@ function printHelp() {
|
|
|
30875
31842
|
" hydra-acp daemon start [--foreground] Start daemon (detached by default; --foreground to attach)",
|
|
30876
31843
|
" hydra-acp daemon stop|restart",
|
|
30877
31844
|
" hydra-acp daemon logs [-f] [-n N] Tail or follow the daemon log",
|
|
30878
|
-
" 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>]",
|
|
30879
31846
|
" List sessions (live + 20 most-recent cold; --all lifts the cold cap AND surfaces non-interactive sessions; --json emits JSON for scripts).",
|
|
30880
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.",
|
|
30881
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.",
|
|
30882
31850
|
" hydra-acp session info <id> [--verbose] [--json] [--diff] [--fold] [--no-color] [--no-pager]",
|
|
30883
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).",
|
|
30884
31852
|
" hydra-acp session diff <id> [--json] [--no-color] [--no-pager] [--fold]",
|