@hydra-acp/cli 0.1.47 → 0.1.49
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 +2111 -565
- package/dist/index.d.ts +45 -13
- package/dist/index.js +749 -56
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1397,7 +1397,10 @@ var init_connection = __esm({
|
|
|
1397
1397
|
// every entry would be re-appended to history.jsonl, doubling the log
|
|
1398
1398
|
// each time the session was woken up.
|
|
1399
1399
|
drainBuffered(method) {
|
|
1400
|
+
const buf = this.bufferedNotifications.get(method);
|
|
1401
|
+
const count = buf?.length ?? 0;
|
|
1400
1402
|
this.bufferedNotifications.delete(method);
|
|
1403
|
+
return count;
|
|
1401
1404
|
}
|
|
1402
1405
|
onClose(handler) {
|
|
1403
1406
|
this.closeHandlers.push(handler);
|
|
@@ -2265,6 +2268,8 @@ var init_session = __esm({
|
|
|
2265
2268
|
listSessions;
|
|
2266
2269
|
logger;
|
|
2267
2270
|
transformChain;
|
|
2271
|
+
extensionCommands;
|
|
2272
|
+
extensionCommandsUnsub;
|
|
2268
2273
|
// Outstanding "processing" claims: token → claim waiting for respondsTo discharge.
|
|
2269
2274
|
pendingClaims = /* @__PURE__ */ new Map();
|
|
2270
2275
|
agentChangeHandlers = [];
|
|
@@ -2360,6 +2365,14 @@ var init_session = __esm({
|
|
|
2360
2365
|
this.listSessions = init.listSessions;
|
|
2361
2366
|
this.logger = init.logger;
|
|
2362
2367
|
this.transformChain = init.transformChain ?? [];
|
|
2368
|
+
this.extensionCommands = init.extensionCommands;
|
|
2369
|
+
if (this.extensionCommands) {
|
|
2370
|
+
this.extensionCommandsUnsub = this.extensionCommands.onChange(() => {
|
|
2371
|
+
if (!this.closed) {
|
|
2372
|
+
this.broadcastMergedCommands();
|
|
2373
|
+
}
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2363
2376
|
if (init.firstPromptSeeded) {
|
|
2364
2377
|
this.firstPromptSeeded = true;
|
|
2365
2378
|
}
|
|
@@ -2374,18 +2387,11 @@ var init_session = __esm({
|
|
|
2374
2387
|
this.notifyChain("session.opened", {});
|
|
2375
2388
|
}
|
|
2376
2389
|
broadcastMergedCommands() {
|
|
2377
|
-
const merged = [
|
|
2378
|
-
...hydraCommandsAsAdvertised(),
|
|
2379
|
-
{ name: "model <model-id>", description: "Switch model; omit arg to list available models" },
|
|
2380
|
-
{ name: "sessions", description: "List all sessions" },
|
|
2381
|
-
{ name: "help", description: "Show available commands" },
|
|
2382
|
-
...this.agentAdvertisedCommands
|
|
2383
|
-
];
|
|
2384
2390
|
this.recordAndBroadcast("session/update", {
|
|
2385
2391
|
sessionId: this.upstreamSessionId,
|
|
2386
2392
|
update: {
|
|
2387
2393
|
sessionUpdate: "available_commands_update",
|
|
2388
|
-
availableCommands:
|
|
2394
|
+
availableCommands: this.mergedAvailableCommands()
|
|
2389
2395
|
}
|
|
2390
2396
|
});
|
|
2391
2397
|
}
|
|
@@ -3548,6 +3554,9 @@ var init_session = __esm({
|
|
|
3548
3554
|
if (!trimmed || trimmed === this.currentModel) {
|
|
3549
3555
|
return true;
|
|
3550
3556
|
}
|
|
3557
|
+
this.logger?.info(
|
|
3558
|
+
`live current_model_update: sessionId=${this.sessionId} ${JSON.stringify(this.currentModel)} \u2192 ${JSON.stringify(trimmed)}`
|
|
3559
|
+
);
|
|
3551
3560
|
this.currentModel = trimmed;
|
|
3552
3561
|
for (const handler of this.modelHandlers) {
|
|
3553
3562
|
try {
|
|
@@ -3593,6 +3602,9 @@ var init_session = __esm({
|
|
|
3593
3602
|
if (typeof cv === "string") {
|
|
3594
3603
|
const trimmed = cv.trim();
|
|
3595
3604
|
if (trimmed && trimmed !== this.currentModel) {
|
|
3605
|
+
this.logger?.info(
|
|
3606
|
+
`live config_option_update(model): sessionId=${this.sessionId} ${JSON.stringify(this.currentModel)} \u2192 ${JSON.stringify(trimmed)}`
|
|
3607
|
+
);
|
|
3596
3608
|
this.currentModel = trimmed;
|
|
3597
3609
|
for (const handler of this.modelHandlers) {
|
|
3598
3610
|
try {
|
|
@@ -3761,6 +3773,9 @@ var init_session = __esm({
|
|
|
3761
3773
|
this.broadcastAvailableModes();
|
|
3762
3774
|
}
|
|
3763
3775
|
setAgentAdvertisedModels(models) {
|
|
3776
|
+
this.logger?.info(
|
|
3777
|
+
`setAgentAdvertisedModels: sessionId=${this.sessionId} currentModel=${JSON.stringify(this.currentModel)} newList=[${models.map((m) => m.modelId).join(",")}]`
|
|
3778
|
+
);
|
|
3764
3779
|
if (sameAdvertisedModels(this.agentAdvertisedModels, models)) {
|
|
3765
3780
|
this.broadcastAvailableModels();
|
|
3766
3781
|
return;
|
|
@@ -3792,6 +3807,38 @@ var init_session = __esm({
|
|
|
3792
3807
|
onModeChange(handler) {
|
|
3793
3808
|
this.modeHandlers.push(handler);
|
|
3794
3809
|
}
|
|
3810
|
+
// Apply a model change initiated by a client request (session/set_model)
|
|
3811
|
+
// when the agent doesn't emit a current_model_update notification, or
|
|
3812
|
+
// emits a non-spec shape (e.g. config_option_update). Fires modelHandlers
|
|
3813
|
+
// (persistence) and broadcasts a synthetic current_model_update so all
|
|
3814
|
+
// attached clients — including the originator — repaint immediately.
|
|
3815
|
+
applyModelChange(modelId) {
|
|
3816
|
+
const trimmed = modelId.trim();
|
|
3817
|
+
if (!trimmed || trimmed === this.currentModel) {
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
this.logger?.info(
|
|
3821
|
+
`applyModelChange: sessionId=${this.sessionId} ${JSON.stringify(this.currentModel)} \u2192 ${JSON.stringify(trimmed)}`
|
|
3822
|
+
);
|
|
3823
|
+
this.currentModel = trimmed;
|
|
3824
|
+
for (const handler of this.modelHandlers) {
|
|
3825
|
+
try {
|
|
3826
|
+
handler(trimmed);
|
|
3827
|
+
} catch {
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
const update = {
|
|
3831
|
+
sessionUpdate: "current_model_update",
|
|
3832
|
+
currentModel: trimmed
|
|
3833
|
+
};
|
|
3834
|
+
if (this.agentAdvertisedModels.length > 0) {
|
|
3835
|
+
update.availableModels = [...this.agentAdvertisedModels];
|
|
3836
|
+
}
|
|
3837
|
+
this.recordAndBroadcast("session/update", {
|
|
3838
|
+
sessionId: this.upstreamSessionId,
|
|
3839
|
+
update
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3795
3842
|
// Apply a mode change initiated by a client request (session/set_mode)
|
|
3796
3843
|
// when the agent doesn't emit a current_mode_update notification on its
|
|
3797
3844
|
// own. Fires modeHandlers so the persistence hook and any other listeners
|
|
@@ -3815,11 +3862,31 @@ var init_session = __esm({
|
|
|
3815
3862
|
onUsageChange(handler) {
|
|
3816
3863
|
this.usageHandlers.push(handler);
|
|
3817
3864
|
}
|
|
3818
|
-
// Returns a freshly merged command list (hydra ∪ agent) for
|
|
3819
|
-
// that need a snapshot — notably acp-ws.ts's buildResponseMeta
|
|
3820
|
-
// assembling the attach response.
|
|
3865
|
+
// Returns a freshly merged command list (hydra ∪ extension ∪ agent) for
|
|
3866
|
+
// callers that need a snapshot — notably acp-ws.ts's buildResponseMeta
|
|
3867
|
+
// when assembling the attach response. Order: built-in hydra verbs,
|
|
3868
|
+
// top-level daemon verbs (/model, /sessions, /help), extension-registered
|
|
3869
|
+
// entries, then whatever the agent advertised.
|
|
3821
3870
|
mergedAvailableCommands() {
|
|
3822
|
-
|
|
3871
|
+
const out = [
|
|
3872
|
+
...hydraCommandsAsAdvertised(),
|
|
3873
|
+
{ name: "model <model-id>", description: "Switch model; omit arg to list available models" },
|
|
3874
|
+
{ name: "sessions", description: "List all sessions" },
|
|
3875
|
+
{ name: "help", description: "Show available commands" }
|
|
3876
|
+
];
|
|
3877
|
+
if (this.extensionCommands) {
|
|
3878
|
+
for (const { name, command } of this.extensionCommands.list()) {
|
|
3879
|
+
const head = `hydra ${name} ${command.verb}`;
|
|
3880
|
+
const display = command.argsHint ? `${head} ${command.argsHint}` : head;
|
|
3881
|
+
const entry = { name: display };
|
|
3882
|
+
if (command.description) {
|
|
3883
|
+
entry.description = command.description;
|
|
3884
|
+
}
|
|
3885
|
+
out.push(entry);
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
out.push(...this.agentAdvertisedCommands);
|
|
3889
|
+
return out;
|
|
3823
3890
|
}
|
|
3824
3891
|
// The agent's own advertised commands (not merged with hydra verbs).
|
|
3825
3892
|
// Used by SessionManager to persist into meta.json so cold resurrect
|
|
@@ -3871,39 +3938,118 @@ var init_session = __esm({
|
|
|
3871
3938
|
// caller's promise resolves like a normal turn. To add a verb: append
|
|
3872
3939
|
// an entry to HYDRA_COMMANDS (drives validation + client advertising)
|
|
3873
3940
|
// and a dispatch case in the switch below.
|
|
3941
|
+
//
|
|
3942
|
+
// Extensions/transformers can also bind verbs via the
|
|
3943
|
+
// ExtensionCommandRegistry: "/hydra <process-name> <verb> [args]" routes
|
|
3944
|
+
// to that process's WS connection. Built-in hydra verbs win on name
|
|
3945
|
+
// collision so an extension can never shadow them.
|
|
3874
3946
|
async handleSlashCommand(text) {
|
|
3875
3947
|
const rest = text.slice("/hydra".length).trim();
|
|
3876
3948
|
const match = rest.match(/^(\S+)(?:\s+([\s\S]*))?$/);
|
|
3877
|
-
const
|
|
3878
|
-
const
|
|
3879
|
-
if (
|
|
3949
|
+
const first = match?.[1] ?? "";
|
|
3950
|
+
const remainder = (match?.[2] ?? "").trim();
|
|
3951
|
+
if (first === "") {
|
|
3880
3952
|
return { stopReason: "end_turn" };
|
|
3881
3953
|
}
|
|
3882
|
-
if (
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3954
|
+
if (HYDRA_COMMANDS.some((c) => c.verb === first)) {
|
|
3955
|
+
switch (first) {
|
|
3956
|
+
case "title":
|
|
3957
|
+
return this.runTitleCommand(remainder);
|
|
3958
|
+
case "agent":
|
|
3959
|
+
return this.runAgentCommand(remainder);
|
|
3960
|
+
case "kill":
|
|
3961
|
+
return this.runKillCommand();
|
|
3962
|
+
case "restart":
|
|
3963
|
+
return this.runRestartCommand();
|
|
3964
|
+
default: {
|
|
3965
|
+
const err2 = new Error(
|
|
3966
|
+
`no dispatcher for /hydra verb ${first}`
|
|
3967
|
+
);
|
|
3968
|
+
err2.code = JsonRpcErrorCodes.InternalError;
|
|
3969
|
+
throw err2;
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3889
3972
|
}
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
`no dispatcher for /hydra verb ${verb}`
|
|
3902
|
-
);
|
|
3903
|
-
err.code = JsonRpcErrorCodes.InternalError;
|
|
3904
|
-
throw err;
|
|
3973
|
+
if (this.extensionCommands?.has(first)) {
|
|
3974
|
+
return this.runExtensionCommand(first, remainder);
|
|
3975
|
+
}
|
|
3976
|
+
const known = HYDRA_COMMANDS.map((c) => c.verb);
|
|
3977
|
+
if (this.extensionCommands) {
|
|
3978
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3979
|
+
for (const { name } of this.extensionCommands.list()) {
|
|
3980
|
+
if (!seen.has(name)) {
|
|
3981
|
+
known.push(name);
|
|
3982
|
+
seen.add(name);
|
|
3983
|
+
}
|
|
3905
3984
|
}
|
|
3906
3985
|
}
|
|
3986
|
+
const err = new Error(
|
|
3987
|
+
`unknown /hydra verb: ${first} (known: ${known.join(", ")})`
|
|
3988
|
+
);
|
|
3989
|
+
err.code = JsonRpcErrorCodes.InvalidParams;
|
|
3990
|
+
throw err;
|
|
3991
|
+
}
|
|
3992
|
+
// "/hydra <name> <verb> [args]" — name matches a registered extension
|
|
3993
|
+
// or transformer. We split the remainder into verb + args, validate the
|
|
3994
|
+
// verb against what the process advertised, and forward as a
|
|
3995
|
+
// hydra-acp/extension_command request on the process's WS connection.
|
|
3996
|
+
// The reply's text (if any) is broadcast as a synthetic
|
|
3997
|
+
// agent_message_chunk so it appears in the conversation alongside the
|
|
3998
|
+
// user's invocation.
|
|
3999
|
+
runExtensionCommand(name, remainder) {
|
|
4000
|
+
return this.enqueuePrompt(async () => {
|
|
4001
|
+
const entry = this.extensionCommands?.get(name);
|
|
4002
|
+
if (!entry) {
|
|
4003
|
+
return this.emitExtensionReply(
|
|
4004
|
+
`extension "${name}" is no longer connected`
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
4007
|
+
const m = remainder.match(/^(\S+)(?:\s+([\s\S]*))?$/);
|
|
4008
|
+
const verb = m?.[1] ?? "";
|
|
4009
|
+
const args = (m?.[2] ?? "").trim();
|
|
4010
|
+
if (verb === "") {
|
|
4011
|
+
const verbs = entry.commands.map((c) => c.verb).join(", ");
|
|
4012
|
+
return this.emitExtensionReply(
|
|
4013
|
+
`/hydra ${name} requires a verb (known: ${verbs || "(none)"})`
|
|
4014
|
+
);
|
|
4015
|
+
}
|
|
4016
|
+
if (!entry.commands.some((c) => c.verb === verb)) {
|
|
4017
|
+
const verbs = entry.commands.map((c) => c.verb).join(", ");
|
|
4018
|
+
return this.emitExtensionReply(
|
|
4019
|
+
`unknown verb "${verb}" for ${name} (known: ${verbs || "(none)"})`
|
|
4020
|
+
);
|
|
4021
|
+
}
|
|
4022
|
+
let reply;
|
|
4023
|
+
try {
|
|
4024
|
+
reply = await entry.connection.request("hydra-acp/extension_command", {
|
|
4025
|
+
sessionId: this.sessionId,
|
|
4026
|
+
verb,
|
|
4027
|
+
args
|
|
4028
|
+
});
|
|
4029
|
+
} catch (err) {
|
|
4030
|
+
return this.emitExtensionReply(
|
|
4031
|
+
`${name} ${verb}: ${err.message}`
|
|
4032
|
+
);
|
|
4033
|
+
}
|
|
4034
|
+
const text = reply && typeof reply === "object" && typeof reply.text === "string" ? reply.text : "";
|
|
4035
|
+
if (text.length > 0) {
|
|
4036
|
+
return this.emitExtensionReply(text);
|
|
4037
|
+
}
|
|
4038
|
+
return { stopReason: "end_turn" };
|
|
4039
|
+
});
|
|
4040
|
+
}
|
|
4041
|
+
emitExtensionReply(text) {
|
|
4042
|
+
this.recordAndBroadcast("session/update", {
|
|
4043
|
+
sessionId: this.upstreamSessionId,
|
|
4044
|
+
update: {
|
|
4045
|
+
sessionUpdate: "agent_message_chunk",
|
|
4046
|
+
content: { type: "text", text: `
|
|
4047
|
+
${text}
|
|
4048
|
+
` },
|
|
4049
|
+
_meta: { "hydra-acp": { synthetic: true } }
|
|
4050
|
+
}
|
|
4051
|
+
});
|
|
4052
|
+
return { stopReason: "end_turn" };
|
|
3907
4053
|
}
|
|
3908
4054
|
async handleSessionsCommand() {
|
|
3909
4055
|
let text;
|
|
@@ -3966,11 +4112,15 @@ ${text}
|
|
|
3966
4112
|
if (models.length === 0) {
|
|
3967
4113
|
body = current ? `Current model: ${current}` : "_(no models advertised yet)_";
|
|
3968
4114
|
} else {
|
|
4115
|
+
const inList = current ? models.some((m) => m.modelId === current) : true;
|
|
3969
4116
|
const lines = models.map((m) => {
|
|
3970
4117
|
const marker = m.modelId === current ? " \u25C0" : "";
|
|
3971
4118
|
const desc = m.name && m.name !== m.modelId ? ` ${m.name}` : "";
|
|
3972
4119
|
return `${m.modelId}${marker}${desc}`;
|
|
3973
4120
|
});
|
|
4121
|
+
if (!inList && current) {
|
|
4122
|
+
lines.unshift(`${current} \u25C0`);
|
|
4123
|
+
}
|
|
3974
4124
|
body = lines.join("\n");
|
|
3975
4125
|
}
|
|
3976
4126
|
this.recordAndBroadcast("session/update", {
|
|
@@ -4398,6 +4548,10 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
4398
4548
|
}
|
|
4399
4549
|
this.closed = true;
|
|
4400
4550
|
this.cancelIdleTimer();
|
|
4551
|
+
if (this.extensionCommandsUnsub) {
|
|
4552
|
+
this.extensionCommandsUnsub();
|
|
4553
|
+
this.extensionCommandsUnsub = void 0;
|
|
4554
|
+
}
|
|
4401
4555
|
if (this.currentEntry?.kind === "user") {
|
|
4402
4556
|
this.broadcastTurnComplete(
|
|
4403
4557
|
this.currentEntry.clientId,
|
|
@@ -5598,6 +5752,20 @@ async function regenSessionTitle(target, id, fetchImpl = fetch) {
|
|
|
5598
5752
|
throw new Error(`daemon returned HTTP ${response.status}`);
|
|
5599
5753
|
}
|
|
5600
5754
|
}
|
|
5755
|
+
async function searchSessions(target, query, opts = {}, fetchImpl = fetch) {
|
|
5756
|
+
const url = new URL(`${target.baseUrl}/v1/sessions/search`);
|
|
5757
|
+
url.searchParams.set("q", query);
|
|
5758
|
+
if (opts.sessionIds && opts.sessionIds.length > 0) {
|
|
5759
|
+
url.searchParams.set("sessionIds", opts.sessionIds.join(","));
|
|
5760
|
+
}
|
|
5761
|
+
const response = await fetchImpl(url.toString(), {
|
|
5762
|
+
headers: { Authorization: `Bearer ${target.token}` }
|
|
5763
|
+
});
|
|
5764
|
+
if (!response.ok) {
|
|
5765
|
+
throw new Error(`daemon returned HTTP ${response.status}`);
|
|
5766
|
+
}
|
|
5767
|
+
return await response.json();
|
|
5768
|
+
}
|
|
5601
5769
|
async function deleteSession(target, id, fetchImpl = fetch) {
|
|
5602
5770
|
const response = await fetchImpl(`${target.baseUrl}/v1/sessions/${id}`, {
|
|
5603
5771
|
method: "DELETE",
|
|
@@ -7277,7 +7445,7 @@ function writeStyled(term, text, style) {
|
|
|
7277
7445
|
term(text);
|
|
7278
7446
|
return;
|
|
7279
7447
|
case "thought":
|
|
7280
|
-
term.brightBlack
|
|
7448
|
+
term.brightBlack(text);
|
|
7281
7449
|
return;
|
|
7282
7450
|
case "tool":
|
|
7283
7451
|
term.brightBlue.noFormat(text);
|
|
@@ -9982,8 +10150,8 @@ uncaught: ${err.stack ?? err.message}
|
|
|
9982
10150
|
}
|
|
9983
10151
|
});
|
|
9984
10152
|
|
|
9985
|
-
// src/tui/
|
|
9986
|
-
|
|
10153
|
+
// src/tui/prompt-utils.ts
|
|
10154
|
+
function resetTerminalModes() {
|
|
9987
10155
|
process.stdout.write("\x1B[<u");
|
|
9988
10156
|
process.stdout.write("\x1B[?2004l");
|
|
9989
10157
|
process.stdout.write("\x1B[>4;0m");
|
|
@@ -9993,55 +10161,503 @@ async function pickSession(term, opts) {
|
|
|
9993
10161
|
process.stdout.write("\x1B[?1006l");
|
|
9994
10162
|
process.stdout.write("\x1B[?1l");
|
|
9995
10163
|
process.stdout.write("\x1B>");
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10164
|
+
}
|
|
10165
|
+
function readTermWidth(term) {
|
|
10166
|
+
return term.width ?? 80;
|
|
10167
|
+
}
|
|
10168
|
+
function readTermHeight(term) {
|
|
10169
|
+
return term.height ?? 24;
|
|
10170
|
+
}
|
|
10171
|
+
function drawBox(term, opts) {
|
|
10172
|
+
const termW = readTermWidth(term);
|
|
10173
|
+
const termH = readTermHeight(term);
|
|
10174
|
+
const desiredContentW = opts.contentWidth ?? MAX_BOX_WIDTH;
|
|
10175
|
+
const maxContentW = Math.max(10, Math.min(MAX_BOX_WIDTH, termW - 4));
|
|
10176
|
+
const contentW = Math.min(desiredContentW, maxContentW);
|
|
10177
|
+
const w = contentW + 2;
|
|
10178
|
+
const contentH = Math.max(1, Math.min(opts.contentHeight, termH - 4));
|
|
10179
|
+
const h = contentH + 2;
|
|
10180
|
+
const x = Math.max(1, Math.floor((termW - w) / 2) + 1);
|
|
10181
|
+
const y = Math.max(1, Math.floor((termH - h) / 2) + 1);
|
|
10182
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
10183
|
+
const topInner = HORIZ.repeat(w - 2);
|
|
10184
|
+
const top = renderTitleStrip(topInner, opts.title);
|
|
10185
|
+
term.moveTo(x, y);
|
|
10186
|
+
term.dim.noFormat(TL);
|
|
10187
|
+
paintTopStrip(term, top);
|
|
10188
|
+
term.dim.noFormat(TR);
|
|
10189
|
+
for (let row = 1; row <= contentH; row++) {
|
|
10190
|
+
term.moveTo(x, y + row);
|
|
10191
|
+
term.dim.noFormat(VERT);
|
|
10192
|
+
term.moveTo(x + w - 1, y + row);
|
|
10193
|
+
term.dim.noFormat(VERT);
|
|
10194
|
+
}
|
|
10195
|
+
term.moveTo(x, y + h - 1);
|
|
10196
|
+
term.dim.noFormat(BL + HORIZ.repeat(w - 2) + BR);
|
|
10197
|
+
return {
|
|
10198
|
+
x,
|
|
10199
|
+
y,
|
|
10200
|
+
w,
|
|
10201
|
+
h,
|
|
10202
|
+
contentX: x + 1,
|
|
10203
|
+
contentY: y + 1,
|
|
10204
|
+
contentW,
|
|
10205
|
+
contentH
|
|
10010
10206
|
};
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
if (
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10207
|
+
}
|
|
10208
|
+
function renderTitleStrip(innerDashes, title) {
|
|
10209
|
+
if (!title) {
|
|
10210
|
+
return { dashes: innerDashes };
|
|
10211
|
+
}
|
|
10212
|
+
const chip = ` ${title} `;
|
|
10213
|
+
if (chip.length + 4 > innerDashes.length) {
|
|
10214
|
+
return { dashes: innerDashes };
|
|
10215
|
+
}
|
|
10216
|
+
const offset = 2;
|
|
10217
|
+
const dashes = innerDashes.slice(0, offset) + " ".repeat(chip.length) + innerDashes.slice(offset + chip.length);
|
|
10218
|
+
return { dashes, title: { offset, text: chip } };
|
|
10219
|
+
}
|
|
10220
|
+
function paintTopStrip(term, strip) {
|
|
10221
|
+
if (!strip.title) {
|
|
10222
|
+
term.dim.noFormat(strip.dashes);
|
|
10223
|
+
return;
|
|
10224
|
+
}
|
|
10225
|
+
term.dim.noFormat(strip.dashes.slice(0, strip.title.offset));
|
|
10226
|
+
term.brightCyan.noFormat(strip.title.text);
|
|
10227
|
+
term.dim.noFormat(strip.dashes.slice(strip.title.offset + strip.title.text.length));
|
|
10228
|
+
}
|
|
10229
|
+
var MAX_BOX_WIDTH, HORIZ, VERT, TL, TR, BL, BR;
|
|
10230
|
+
var init_prompt_utils = __esm({
|
|
10231
|
+
"src/tui/prompt-utils.ts"() {
|
|
10232
|
+
"use strict";
|
|
10233
|
+
MAX_BOX_WIDTH = 64;
|
|
10234
|
+
HORIZ = "\u2500";
|
|
10235
|
+
VERT = "\u2502";
|
|
10236
|
+
TL = "\u250C";
|
|
10237
|
+
TR = "\u2510";
|
|
10238
|
+
BL = "\u2514";
|
|
10239
|
+
BR = "\u2518";
|
|
10240
|
+
}
|
|
10241
|
+
});
|
|
10242
|
+
|
|
10243
|
+
// src/tui/import-action-prompt.ts
|
|
10244
|
+
function actionPromptStep(selected, key, choices = ACTION_CHOICES) {
|
|
10245
|
+
if (key.kind === "cancel") {
|
|
10246
|
+
return { kind: "cancel" };
|
|
10247
|
+
}
|
|
10248
|
+
if (key.kind === "back") {
|
|
10249
|
+
return { kind: "back" };
|
|
10250
|
+
}
|
|
10251
|
+
if (key.kind === "enter") {
|
|
10252
|
+
const choice = choices[selected];
|
|
10253
|
+
if (!choice) {
|
|
10254
|
+
return { kind: "back" };
|
|
10019
10255
|
}
|
|
10256
|
+
return { kind: "resolve", action: choice.key };
|
|
10020
10257
|
}
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10258
|
+
if (key.kind === "up") {
|
|
10259
|
+
return {
|
|
10260
|
+
kind: "continue",
|
|
10261
|
+
selected: Math.max(0, selected - 1)
|
|
10262
|
+
};
|
|
10263
|
+
}
|
|
10264
|
+
if (key.kind === "down") {
|
|
10265
|
+
return {
|
|
10266
|
+
kind: "continue",
|
|
10267
|
+
selected: Math.min(choices.length - 1, selected + 1)
|
|
10268
|
+
};
|
|
10269
|
+
}
|
|
10270
|
+
if (key.kind === "char") {
|
|
10271
|
+
const lower = key.ch.toLowerCase();
|
|
10272
|
+
if (lower === "n") {
|
|
10273
|
+
return {
|
|
10274
|
+
kind: "continue",
|
|
10275
|
+
selected: Math.min(choices.length - 1, selected + 1)
|
|
10276
|
+
};
|
|
10277
|
+
}
|
|
10278
|
+
if (lower === "p") {
|
|
10279
|
+
return {
|
|
10280
|
+
kind: "continue",
|
|
10281
|
+
selected: Math.max(0, selected - 1)
|
|
10282
|
+
};
|
|
10283
|
+
}
|
|
10284
|
+
const idx = choices.findIndex((c) => c.hotkey.toLowerCase() === lower);
|
|
10030
10285
|
if (idx >= 0) {
|
|
10031
|
-
|
|
10286
|
+
const choice = choices[idx];
|
|
10287
|
+
if (choice) {
|
|
10288
|
+
return { kind: "resolve", action: choice.key };
|
|
10289
|
+
}
|
|
10032
10290
|
}
|
|
10033
10291
|
}
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
const
|
|
10041
|
-
let
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10292
|
+
return { kind: "continue", selected };
|
|
10293
|
+
}
|
|
10294
|
+
async function promptForImportAction(term, session) {
|
|
10295
|
+
resetTerminalModes();
|
|
10296
|
+
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
10297
|
+
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
10298
|
+
const originalCwd = shortenHomePath(session.cwd);
|
|
10299
|
+
let selected = ACTION_CHOICES.findIndex((c) => c.key === "view");
|
|
10300
|
+
if (selected < 0) {
|
|
10301
|
+
selected = 0;
|
|
10302
|
+
}
|
|
10303
|
+
const render = () => {
|
|
10304
|
+
const choiceRows = ACTION_CHOICES.length * 2;
|
|
10305
|
+
const contentHeight = 7 + choiceRows + 2;
|
|
10306
|
+
const layout = drawBox(term, {
|
|
10307
|
+
contentHeight,
|
|
10308
|
+
title: "Imported session"
|
|
10309
|
+
});
|
|
10310
|
+
const innerW = layout.contentW;
|
|
10311
|
+
const headerRows = [
|
|
10312
|
+
{ label: "session: ", value: shortId2 },
|
|
10313
|
+
{ label: "from: ", value: fromMachine },
|
|
10314
|
+
{ label: "cwd: ", value: originalCwd }
|
|
10315
|
+
];
|
|
10316
|
+
let row = 0;
|
|
10317
|
+
for (const hr of headerRows) {
|
|
10318
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10319
|
+
term.dim.noFormat(` ${hr.label}`);
|
|
10320
|
+
term.noFormat(truncate2(hr.value, innerW - hr.label.length - 2));
|
|
10321
|
+
row++;
|
|
10322
|
+
}
|
|
10323
|
+
row++;
|
|
10324
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10325
|
+
term.noFormat(" What do you want to do?");
|
|
10326
|
+
row += 2;
|
|
10327
|
+
for (let i = 0; i < ACTION_CHOICES.length; i++) {
|
|
10328
|
+
const choice = ACTION_CHOICES[i];
|
|
10329
|
+
if (!choice) {
|
|
10330
|
+
continue;
|
|
10331
|
+
}
|
|
10332
|
+
const pointer = i === selected ? "\u276F" : " ";
|
|
10333
|
+
const label = ` ${pointer} ${choice.label}`;
|
|
10334
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10335
|
+
if (i === selected) {
|
|
10336
|
+
term.brightWhite.bgBlue.noFormat(padRight(label, innerW));
|
|
10337
|
+
} else {
|
|
10338
|
+
term.noFormat(label);
|
|
10339
|
+
}
|
|
10340
|
+
row++;
|
|
10341
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10342
|
+
term.dim.noFormat(` ${choice.description}`);
|
|
10343
|
+
row++;
|
|
10344
|
+
}
|
|
10345
|
+
row++;
|
|
10346
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10347
|
+
term.dim.noFormat(" \u2191/\u2193 navigate \xB7 Enter select \xB7 f/v jump \xB7 Esc back");
|
|
10348
|
+
return layout;
|
|
10349
|
+
};
|
|
10350
|
+
render();
|
|
10351
|
+
term.hideCursor();
|
|
10352
|
+
return await new Promise((resolve6) => {
|
|
10353
|
+
let resolved = false;
|
|
10354
|
+
const cleanup = () => {
|
|
10355
|
+
if (resolved) {
|
|
10356
|
+
return;
|
|
10357
|
+
}
|
|
10358
|
+
resolved = true;
|
|
10359
|
+
term.off("key", onKey);
|
|
10360
|
+
term.off("resize", onResize);
|
|
10361
|
+
term.grabInput(false);
|
|
10362
|
+
term.hideCursor(false);
|
|
10363
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
10364
|
+
};
|
|
10365
|
+
const finish = (value) => {
|
|
10366
|
+
cleanup();
|
|
10367
|
+
resolve6(value);
|
|
10368
|
+
};
|
|
10369
|
+
const onResize = () => {
|
|
10370
|
+
if (resolved) {
|
|
10371
|
+
return;
|
|
10372
|
+
}
|
|
10373
|
+
render();
|
|
10374
|
+
};
|
|
10375
|
+
const onKey = (name, _matches, data) => {
|
|
10376
|
+
const input = mapKey(name, data);
|
|
10377
|
+
if (!input) {
|
|
10378
|
+
return;
|
|
10379
|
+
}
|
|
10380
|
+
const step = actionPromptStep(selected, input);
|
|
10381
|
+
if (step.kind === "cancel") {
|
|
10382
|
+
finish("cancel");
|
|
10383
|
+
return;
|
|
10384
|
+
}
|
|
10385
|
+
if (step.kind === "back") {
|
|
10386
|
+
finish("back");
|
|
10387
|
+
return;
|
|
10388
|
+
}
|
|
10389
|
+
if (step.kind === "resolve") {
|
|
10390
|
+
finish(step.action);
|
|
10391
|
+
return;
|
|
10392
|
+
}
|
|
10393
|
+
if (step.selected !== selected) {
|
|
10394
|
+
selected = step.selected;
|
|
10395
|
+
render();
|
|
10396
|
+
}
|
|
10397
|
+
};
|
|
10398
|
+
term.grabInput({});
|
|
10399
|
+
term.on("key", onKey);
|
|
10400
|
+
term.on("resize", onResize);
|
|
10401
|
+
});
|
|
10402
|
+
}
|
|
10403
|
+
function mapKey(name, data) {
|
|
10404
|
+
if (name === "UP") {
|
|
10405
|
+
return { kind: "up" };
|
|
10406
|
+
}
|
|
10407
|
+
if (name === "DOWN") {
|
|
10408
|
+
return { kind: "down" };
|
|
10409
|
+
}
|
|
10410
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
10411
|
+
return { kind: "enter" };
|
|
10412
|
+
}
|
|
10413
|
+
if (name === "ESCAPE") {
|
|
10414
|
+
return { kind: "back" };
|
|
10415
|
+
}
|
|
10416
|
+
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
10417
|
+
return { kind: "cancel" };
|
|
10418
|
+
}
|
|
10419
|
+
if (data?.isCharacter) {
|
|
10420
|
+
return { kind: "char", ch: name };
|
|
10421
|
+
}
|
|
10422
|
+
return null;
|
|
10423
|
+
}
|
|
10424
|
+
async function promptForLaunchOrView(term, session, focus) {
|
|
10425
|
+
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
10426
|
+
const titleOrCwd = session.title ?? shortenHomePath(session.cwd);
|
|
10427
|
+
let selected = 1;
|
|
10428
|
+
const CHOICES = [
|
|
10429
|
+
{ label: "Launch", hotkey: "l", description: "start a new agent session" },
|
|
10430
|
+
{ label: "View transcript", hotkey: "v", description: "open read-only, no agent spawn" }
|
|
10431
|
+
];
|
|
10432
|
+
const render = () => {
|
|
10433
|
+
const layout = drawBox(term, { contentHeight: 11, title: "Open session" });
|
|
10434
|
+
const innerW = layout.contentW;
|
|
10435
|
+
let row = 0;
|
|
10436
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10437
|
+
term.dim.noFormat(" session: ");
|
|
10438
|
+
term.noFormat(truncate2(shortId2, innerW - 10));
|
|
10439
|
+
row++;
|
|
10440
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10441
|
+
term.noFormat(" " + truncate2(titleOrCwd, innerW - 2));
|
|
10442
|
+
row++;
|
|
10443
|
+
row++;
|
|
10444
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10445
|
+
term.noFormat(" What do you want to do?");
|
|
10446
|
+
row += 2;
|
|
10447
|
+
for (let i = 0; i < CHOICES.length; i++) {
|
|
10448
|
+
const choice = CHOICES[i];
|
|
10449
|
+
if (!choice) {
|
|
10450
|
+
continue;
|
|
10451
|
+
}
|
|
10452
|
+
const pointer = i === selected ? "\u276F" : " ";
|
|
10453
|
+
const label = ` ${pointer} ${choice.label}`;
|
|
10454
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10455
|
+
if (i === selected) {
|
|
10456
|
+
term.brightWhite.bgBlue.noFormat(padRight(label, innerW));
|
|
10457
|
+
} else {
|
|
10458
|
+
term.noFormat(label);
|
|
10459
|
+
}
|
|
10460
|
+
row++;
|
|
10461
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10462
|
+
term.dim.noFormat(` ${choice.description}`);
|
|
10463
|
+
row++;
|
|
10464
|
+
}
|
|
10465
|
+
row++;
|
|
10466
|
+
term.moveTo(layout.contentX, layout.contentY + row);
|
|
10467
|
+
term.dim.noFormat(" \u2191/\u2193 navigate \xB7 Enter select \xB7 l/v jump \xB7 Esc back");
|
|
10468
|
+
};
|
|
10469
|
+
render();
|
|
10470
|
+
term.hideCursor();
|
|
10471
|
+
return await new Promise((resolve6) => {
|
|
10472
|
+
let resolved = false;
|
|
10473
|
+
const cleanup = () => {
|
|
10474
|
+
resolved = true;
|
|
10475
|
+
};
|
|
10476
|
+
const finish = (value) => {
|
|
10477
|
+
cleanup();
|
|
10478
|
+
focus.pop();
|
|
10479
|
+
resolve6(value);
|
|
10480
|
+
};
|
|
10481
|
+
const onKey = (name, _m, data) => {
|
|
10482
|
+
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
10483
|
+
finish("cancel");
|
|
10484
|
+
return;
|
|
10485
|
+
}
|
|
10486
|
+
if (name === "ESCAPE") {
|
|
10487
|
+
finish("back");
|
|
10488
|
+
return;
|
|
10489
|
+
}
|
|
10490
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
10491
|
+
finish(selected === 0 ? "launch" : "view");
|
|
10492
|
+
return;
|
|
10493
|
+
}
|
|
10494
|
+
if (name === "UP" || name === "SHIFT_TAB") {
|
|
10495
|
+
if (selected > 0) {
|
|
10496
|
+
selected--;
|
|
10497
|
+
render();
|
|
10498
|
+
}
|
|
10499
|
+
return;
|
|
10500
|
+
}
|
|
10501
|
+
if (name === "DOWN" || name === "TAB") {
|
|
10502
|
+
if (selected < CHOICES.length - 1) {
|
|
10503
|
+
selected++;
|
|
10504
|
+
render();
|
|
10505
|
+
}
|
|
10506
|
+
return;
|
|
10507
|
+
}
|
|
10508
|
+
if (data?.isCharacter) {
|
|
10509
|
+
const lower = name.toLowerCase();
|
|
10510
|
+
if (lower === "l") {
|
|
10511
|
+
finish("launch");
|
|
10512
|
+
return;
|
|
10513
|
+
}
|
|
10514
|
+
if (lower === "v") {
|
|
10515
|
+
finish("view");
|
|
10516
|
+
return;
|
|
10517
|
+
}
|
|
10518
|
+
if (lower === "n") {
|
|
10519
|
+
if (selected < CHOICES.length - 1) {
|
|
10520
|
+
selected++;
|
|
10521
|
+
render();
|
|
10522
|
+
}
|
|
10523
|
+
return;
|
|
10524
|
+
}
|
|
10525
|
+
if (lower === "p") {
|
|
10526
|
+
if (selected > 0) {
|
|
10527
|
+
selected--;
|
|
10528
|
+
render();
|
|
10529
|
+
}
|
|
10530
|
+
return;
|
|
10531
|
+
}
|
|
10532
|
+
}
|
|
10533
|
+
};
|
|
10534
|
+
focus.push({
|
|
10535
|
+
onKey: (name, _m, data) => {
|
|
10536
|
+
if (!resolved) onKey(name, _m, data);
|
|
10537
|
+
},
|
|
10538
|
+
onResize: () => {
|
|
10539
|
+
if (!resolved) render();
|
|
10540
|
+
}
|
|
10541
|
+
});
|
|
10542
|
+
});
|
|
10543
|
+
}
|
|
10544
|
+
function truncate2(s, max) {
|
|
10545
|
+
if (max <= 1) {
|
|
10546
|
+
return "";
|
|
10547
|
+
}
|
|
10548
|
+
if (s.length <= max) {
|
|
10549
|
+
return s;
|
|
10550
|
+
}
|
|
10551
|
+
return s.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
10552
|
+
}
|
|
10553
|
+
function padRight(s, w) {
|
|
10554
|
+
if (s.length >= w) {
|
|
10555
|
+
return s.slice(0, w);
|
|
10556
|
+
}
|
|
10557
|
+
return s + " ".repeat(w - s.length);
|
|
10558
|
+
}
|
|
10559
|
+
var ACTION_CHOICES;
|
|
10560
|
+
var init_import_action_prompt = __esm({
|
|
10561
|
+
"src/tui/import-action-prompt.ts"() {
|
|
10562
|
+
"use strict";
|
|
10563
|
+
init_paths();
|
|
10564
|
+
init_session();
|
|
10565
|
+
init_prompt_utils();
|
|
10566
|
+
ACTION_CHOICES = [
|
|
10567
|
+
{
|
|
10568
|
+
key: "fork-local",
|
|
10569
|
+
label: "Fork locally",
|
|
10570
|
+
hotkey: "f",
|
|
10571
|
+
description: "spawn a local fork \u2014 original imported copy stays as-is"
|
|
10572
|
+
},
|
|
10573
|
+
{
|
|
10574
|
+
key: "view",
|
|
10575
|
+
label: "View transcript",
|
|
10576
|
+
hotkey: "v",
|
|
10577
|
+
description: "open read-only, no agent spawn"
|
|
10578
|
+
}
|
|
10579
|
+
];
|
|
10580
|
+
}
|
|
10581
|
+
});
|
|
10582
|
+
|
|
10583
|
+
// src/tui/picker.ts
|
|
10584
|
+
function createPickerPrefs() {
|
|
10585
|
+
return { filters: { cwdOnly: false, hostFilter: "__local" } };
|
|
10586
|
+
}
|
|
10587
|
+
async function pickSession(term, opts) {
|
|
10588
|
+
process.stdout.write("\x1B[<u");
|
|
10589
|
+
process.stdout.write("\x1B[?2004l");
|
|
10590
|
+
process.stdout.write("\x1B[>4;0m");
|
|
10591
|
+
process.stdout.write("\x1B[>5;0m");
|
|
10592
|
+
process.stdout.write("\x1B[?1000l");
|
|
10593
|
+
process.stdout.write("\x1B[?1002l");
|
|
10594
|
+
process.stdout.write("\x1B[?1006l");
|
|
10595
|
+
process.stdout.write("\x1B[?1l");
|
|
10596
|
+
process.stdout.write("\x1B>");
|
|
10597
|
+
const sortSessions = (sessions) => {
|
|
10598
|
+
const score = (s) => {
|
|
10599
|
+
if (s.status !== "live") {
|
|
10600
|
+
return 0;
|
|
10601
|
+
}
|
|
10602
|
+
return s.cwd === opts.cwd ? 2 : 1;
|
|
10603
|
+
};
|
|
10604
|
+
return [...sessions].sort((a, b) => {
|
|
10605
|
+
const tier = score(b) - score(a);
|
|
10606
|
+
if (tier !== 0) {
|
|
10607
|
+
return tier;
|
|
10608
|
+
}
|
|
10609
|
+
return b.updatedAt.slice(0, 16).localeCompare(a.updatedAt.slice(0, 16));
|
|
10610
|
+
});
|
|
10611
|
+
};
|
|
10612
|
+
const prefs = opts.prefs ?? createPickerPrefs();
|
|
10613
|
+
if (opts.prefs === void 0 && opts.currentSessionId !== void 0) {
|
|
10614
|
+
const current = opts.sessions.find(
|
|
10615
|
+
(s) => s.sessionId === opts.currentSessionId
|
|
10616
|
+
);
|
|
10617
|
+
if (current?.importedFromMachine) {
|
|
10618
|
+
prefs.filters.hostFilter = "__all";
|
|
10619
|
+
}
|
|
10620
|
+
}
|
|
10621
|
+
let allSessions = sortSessions(opts.sessions);
|
|
10622
|
+
const applyPrefsFilters = (sessions) => {
|
|
10623
|
+
let base = sessions;
|
|
10624
|
+
if (prefs.filters.cwdOnly) {
|
|
10625
|
+
base = base.filter((s) => s.cwd === opts.cwd);
|
|
10626
|
+
}
|
|
10627
|
+
base = filterByHost(base, prefs.filters.hostFilter);
|
|
10628
|
+
return base;
|
|
10629
|
+
};
|
|
10630
|
+
let visible = applyPrefsFilters(allSessions);
|
|
10631
|
+
let rows = visible.map((s) => toRow(s, Date.now()));
|
|
10632
|
+
let widths = computeWidths(rows);
|
|
10633
|
+
let total = 1 + visible.length;
|
|
10634
|
+
let selectedIdx = 0;
|
|
10635
|
+
let scrollOffset = 0;
|
|
10636
|
+
if (opts.currentSessionId !== void 0) {
|
|
10637
|
+
const idx = visible.findIndex((s) => s.sessionId === opts.currentSessionId);
|
|
10638
|
+
if (idx >= 0) {
|
|
10639
|
+
selectedIdx = idx + 1;
|
|
10640
|
+
}
|
|
10641
|
+
}
|
|
10642
|
+
let searchActive = false;
|
|
10643
|
+
let searchTerm = "";
|
|
10644
|
+
let mode = "normal";
|
|
10645
|
+
let pendingAction = null;
|
|
10646
|
+
let findSubMode = "input";
|
|
10647
|
+
let findComposer = new InputDispatcher({ history: [] });
|
|
10648
|
+
let findResults = [];
|
|
10649
|
+
let findTruncated = false;
|
|
10650
|
+
let findSelectedIdx = 0;
|
|
10651
|
+
let findSnippetIdx = 0;
|
|
10652
|
+
let findError = null;
|
|
10653
|
+
let findInFlight = false;
|
|
10654
|
+
let renameBuffer = "";
|
|
10655
|
+
let transientStatus = null;
|
|
10656
|
+
const composer = new InputDispatcher({ history: [] });
|
|
10657
|
+
let termHeight = readTermHeight2(term);
|
|
10658
|
+
let termWidth = readTermWidth2(term);
|
|
10659
|
+
let viewportSize = 0;
|
|
10660
|
+
let composerTitle = "";
|
|
10045
10661
|
let composerRoom = 0;
|
|
10046
10662
|
let composerVisualRows = [];
|
|
10047
10663
|
let composerRows = 1;
|
|
@@ -10051,10 +10667,16 @@ async function pickSession(term, opts) {
|
|
|
10051
10667
|
let headerLine = "";
|
|
10052
10668
|
let sessionLines = [];
|
|
10053
10669
|
let startRow = 1;
|
|
10670
|
+
let findRoom = 0;
|
|
10671
|
+
let findVisualRows = [];
|
|
10672
|
+
let findBoxRows = 1;
|
|
10673
|
+
let findBoxWindowStart = 0;
|
|
10674
|
+
let findBoxCursorVisualRow = 0;
|
|
10675
|
+
let findBoxCursorVisualCol = 0;
|
|
10054
10676
|
const cwdMaxWidth = opts.config.tui.cwdColumnMaxWidth;
|
|
10055
10677
|
const computeLayout = () => {
|
|
10056
|
-
termHeight =
|
|
10057
|
-
termWidth =
|
|
10678
|
+
termHeight = readTermHeight2(term);
|
|
10679
|
+
termWidth = readTermWidth2(term);
|
|
10058
10680
|
const rowMaxWidth = Math.max(10, termWidth - ROW_PREFIX_WIDTH);
|
|
10059
10681
|
composerRoom = Math.max(10, termWidth - BOX_HORIZONTAL_PAD);
|
|
10060
10682
|
const titleBudget = Math.max(10, termWidth - 8);
|
|
@@ -10087,11 +10709,7 @@ async function pickSession(term, opts) {
|
|
|
10087
10709
|
computeLayout();
|
|
10088
10710
|
};
|
|
10089
10711
|
const applyFilter = () => {
|
|
10090
|
-
|
|
10091
|
-
if (cwdOnly) {
|
|
10092
|
-
base = base.filter((s) => s.cwd === opts.cwd);
|
|
10093
|
-
}
|
|
10094
|
-
base = filterByHost(base, hostFilter);
|
|
10712
|
+
const base = applyPrefsFilters(allSessions);
|
|
10095
10713
|
if (searchActive && searchTerm.length > 0) {
|
|
10096
10714
|
visible = base.filter((s) => matchesSearch(s, searchTerm));
|
|
10097
10715
|
} else {
|
|
@@ -10109,6 +10727,19 @@ async function pickSession(term, opts) {
|
|
|
10109
10727
|
}
|
|
10110
10728
|
adjustScroll();
|
|
10111
10729
|
};
|
|
10730
|
+
const restoreCursorAfterFilter = (keepId) => {
|
|
10731
|
+
if (keepId !== void 0) {
|
|
10732
|
+
const idx = visible.findIndex((s) => s.sessionId === keepId);
|
|
10733
|
+
if (idx >= 0) {
|
|
10734
|
+
selectedIdx = idx + 1;
|
|
10735
|
+
adjustScroll();
|
|
10736
|
+
return;
|
|
10737
|
+
}
|
|
10738
|
+
}
|
|
10739
|
+
selectedIdx = visible.length > 0 ? 1 : 0;
|
|
10740
|
+
scrollOffset = 0;
|
|
10741
|
+
adjustScroll();
|
|
10742
|
+
};
|
|
10112
10743
|
const adjustScroll = () => {
|
|
10113
10744
|
if (selectedIdx === 0) {
|
|
10114
10745
|
return;
|
|
@@ -10177,12 +10808,12 @@ async function pickSession(term, opts) {
|
|
|
10177
10808
|
const above = scrollOffset;
|
|
10178
10809
|
const below = Math.max(0, visible.length - scrollOffset - viewportSize);
|
|
10179
10810
|
const parts = [];
|
|
10180
|
-
if (cwdOnly) {
|
|
10811
|
+
if (prefs.filters.cwdOnly) {
|
|
10181
10812
|
parts.push("cwd-only");
|
|
10182
10813
|
}
|
|
10183
|
-
if (hostFilter !== "__all") {
|
|
10814
|
+
if (prefs.filters.hostFilter !== "__all") {
|
|
10184
10815
|
parts.push(
|
|
10185
|
-
hostFilter === "__local" ? "host: local" : `host: ${hostFilter}`
|
|
10816
|
+
prefs.filters.hostFilter === "__local" ? "host: local" : `host: ${prefs.filters.hostFilter}`
|
|
10186
10817
|
);
|
|
10187
10818
|
}
|
|
10188
10819
|
if (above > 0) {
|
|
@@ -10234,59 +10865,370 @@ async function pickSession(term, opts) {
|
|
|
10234
10865
|
if (visualOffset < 0 || visualOffset >= composerRows) {
|
|
10235
10866
|
return;
|
|
10236
10867
|
}
|
|
10237
|
-
const col = 3 + composerCursorCol;
|
|
10238
|
-
term.moveTo(col, composerBodyRow(visualOffset));
|
|
10868
|
+
const col = 3 + composerCursorCol;
|
|
10869
|
+
term.moveTo(col, composerBodyRow(visualOffset));
|
|
10870
|
+
};
|
|
10871
|
+
const renderFromScratch = () => {
|
|
10872
|
+
withSync(() => {
|
|
10873
|
+
term.hideCursor();
|
|
10874
|
+
computeLayout();
|
|
10875
|
+
adjustScroll();
|
|
10876
|
+
startRow = 1;
|
|
10877
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
10878
|
+
paintComposerTopBorder();
|
|
10879
|
+
term("\n");
|
|
10880
|
+
for (let v = 0; v < composerRows; v++) {
|
|
10881
|
+
paintComposerBodyRow(composerWindowStart + v);
|
|
10882
|
+
term("\n");
|
|
10883
|
+
}
|
|
10884
|
+
paintComposerBottomBorder();
|
|
10885
|
+
term("\n\n");
|
|
10886
|
+
term.dim.noFormat(` ${headerLine}`)("\n");
|
|
10887
|
+
for (let v = 0; v < viewportSize; v++) {
|
|
10888
|
+
paintSessionRow(scrollOffset + v);
|
|
10889
|
+
term("\n");
|
|
10890
|
+
}
|
|
10891
|
+
paintIndicator();
|
|
10892
|
+
term("\n");
|
|
10893
|
+
if (selectedIdx === 0) {
|
|
10894
|
+
placeComposerCursor();
|
|
10895
|
+
term.hideCursor(false);
|
|
10896
|
+
}
|
|
10897
|
+
});
|
|
10898
|
+
};
|
|
10899
|
+
const renderHelp = () => {
|
|
10900
|
+
withSync(() => {
|
|
10901
|
+
term.hideCursor();
|
|
10902
|
+
term.moveTo(1, 1).eraseDisplayBelow();
|
|
10903
|
+
term.brightWhite.bold.noFormat(" Picker hotkeys")("\n\n");
|
|
10904
|
+
for (const entry of HELP_ENTRIES) {
|
|
10905
|
+
if (entry === null) {
|
|
10906
|
+
term("\n");
|
|
10907
|
+
continue;
|
|
10908
|
+
}
|
|
10909
|
+
const [keys, desc] = entry;
|
|
10910
|
+
term.brightCyan.noFormat(` ${keys.padEnd(HELP_KEYS_WIDTH)}`);
|
|
10911
|
+
term.noFormat(desc)("\n");
|
|
10912
|
+
}
|
|
10913
|
+
term("\n");
|
|
10914
|
+
term.dim.noFormat(" press any key to dismiss")("\n");
|
|
10915
|
+
});
|
|
10916
|
+
};
|
|
10917
|
+
const findResultsStartRow = () => findBoxRows + 4;
|
|
10918
|
+
const FIND_FOOTER_ROWS = 2;
|
|
10919
|
+
let findScrollOffset = 0;
|
|
10920
|
+
const findViewportSize = () => {
|
|
10921
|
+
termHeight = readTermHeight2(term);
|
|
10922
|
+
const avail = Math.max(2, termHeight - (findBoxRows + 3) - FIND_FOOTER_ROWS);
|
|
10923
|
+
return Math.max(1, Math.floor(avail / 2));
|
|
10924
|
+
};
|
|
10925
|
+
const adjustFindScroll = () => {
|
|
10926
|
+
const v = findViewportSize();
|
|
10927
|
+
if (findSelectedIdx < findScrollOffset) {
|
|
10928
|
+
findScrollOffset = findSelectedIdx;
|
|
10929
|
+
} else if (findSelectedIdx >= findScrollOffset + v) {
|
|
10930
|
+
findScrollOffset = findSelectedIdx - v + 1;
|
|
10931
|
+
}
|
|
10932
|
+
if (findScrollOffset + v > findResults.length) {
|
|
10933
|
+
findScrollOffset = Math.max(0, findResults.length - v);
|
|
10934
|
+
}
|
|
10935
|
+
if (findScrollOffset < 0) {
|
|
10936
|
+
findScrollOffset = 0;
|
|
10937
|
+
}
|
|
10938
|
+
};
|
|
10939
|
+
const paintFindBoxTopBorder = (focused) => {
|
|
10940
|
+
termWidth = readTermWidth2(term);
|
|
10941
|
+
const inner = Math.max(2, termWidth - 2);
|
|
10942
|
+
const title = "\u2500 Find sessions ";
|
|
10943
|
+
const dashes = "\u2500".repeat(Math.max(1, inner - title.length));
|
|
10944
|
+
if (focused) {
|
|
10945
|
+
term.brightBlue.noFormat(`\u256D${title}${dashes}\u256E`);
|
|
10946
|
+
} else {
|
|
10947
|
+
term.dim.noFormat(`\u256D${title}${dashes}\u256E`);
|
|
10948
|
+
}
|
|
10949
|
+
term.styleReset();
|
|
10950
|
+
};
|
|
10951
|
+
const computeFindBoxLayout = () => {
|
|
10952
|
+
termWidth = readTermWidth2(term);
|
|
10953
|
+
findRoom = Math.max(10, termWidth - BOX_HORIZONTAL_PAD);
|
|
10954
|
+
const state = findComposer.state();
|
|
10955
|
+
findVisualRows = computePromptVisualRows(state.buffer, findRoom);
|
|
10956
|
+
const layout = computePromptLayout(findVisualRows, state, FIND_BOX_MAX_ROWS);
|
|
10957
|
+
findBoxRows = layout.rendered;
|
|
10958
|
+
findBoxWindowStart = layout.windowStart;
|
|
10959
|
+
findBoxCursorVisualRow = layout.cursorVisualRow;
|
|
10960
|
+
findBoxCursorVisualCol = layout.cursorVisualCol;
|
|
10961
|
+
};
|
|
10962
|
+
const paintFindBoxBodyRow = (visualIdx, focused) => {
|
|
10963
|
+
termWidth = readTermWidth2(term);
|
|
10964
|
+
const inner = Math.max(2, termWidth - 2);
|
|
10965
|
+
const vr = findVisualRows[visualIdx];
|
|
10966
|
+
let slice = "";
|
|
10967
|
+
if (vr) {
|
|
10968
|
+
slice = (findComposer.state().buffer[vr.bufferIdx] ?? "").slice(
|
|
10969
|
+
vr.startCol,
|
|
10970
|
+
vr.endCol
|
|
10971
|
+
);
|
|
10972
|
+
}
|
|
10973
|
+
const padWidth = Math.max(0, inner - 1 - slice.length);
|
|
10974
|
+
const pad = " ".repeat(padWidth);
|
|
10975
|
+
if (focused) {
|
|
10976
|
+
term.brightBlue.noFormat("\u2502");
|
|
10977
|
+
term.noFormat(` ${slice}${pad}`);
|
|
10978
|
+
term.brightBlue.noFormat("\u2502");
|
|
10979
|
+
} else {
|
|
10980
|
+
term.dim.noFormat("\u2502");
|
|
10981
|
+
term.noFormat(` ${slice}${pad}`);
|
|
10982
|
+
term.dim.noFormat("\u2502");
|
|
10983
|
+
}
|
|
10984
|
+
term.styleReset();
|
|
10985
|
+
};
|
|
10986
|
+
const paintFindBoxBottomBorder = (focused) => {
|
|
10987
|
+
termWidth = readTermWidth2(term);
|
|
10988
|
+
const inner = Math.max(2, termWidth - 2);
|
|
10989
|
+
const dashes = "\u2500".repeat(inner);
|
|
10990
|
+
if (focused) {
|
|
10991
|
+
term.brightBlue.noFormat(`\u2570${dashes}\u256F`);
|
|
10992
|
+
} else {
|
|
10993
|
+
term.dim.noFormat(`\u2570${dashes}\u256F`);
|
|
10994
|
+
}
|
|
10995
|
+
term.styleReset();
|
|
10996
|
+
};
|
|
10997
|
+
const findBoxCursorCol = () => 3 + findBoxCursorVisualCol;
|
|
10998
|
+
const findBoxCursorScreenRow = () => 2 + (findBoxCursorVisualRow - findBoxWindowStart);
|
|
10999
|
+
const repaintFindBoxChrome = () => {
|
|
11000
|
+
const focused = findSubMode === "input";
|
|
11001
|
+
withSync(() => {
|
|
11002
|
+
if (focused) {
|
|
11003
|
+
term.hideCursor();
|
|
11004
|
+
}
|
|
11005
|
+
term.moveTo(1, 1);
|
|
11006
|
+
paintFindBoxTopBorder(focused);
|
|
11007
|
+
for (let v = 0; v < findBoxRows; v++) {
|
|
11008
|
+
term.moveTo(1, 2 + v);
|
|
11009
|
+
paintFindBoxBodyRow(findBoxWindowStart + v, focused);
|
|
11010
|
+
}
|
|
11011
|
+
term.moveTo(1, 2 + findBoxRows);
|
|
11012
|
+
paintFindBoxBottomBorder(focused);
|
|
11013
|
+
if (focused) {
|
|
11014
|
+
term.moveTo(findBoxCursorCol(), findBoxCursorScreenRow());
|
|
11015
|
+
term.hideCursor(false);
|
|
11016
|
+
}
|
|
11017
|
+
});
|
|
11018
|
+
};
|
|
11019
|
+
const repaintFindBoxBodyRows = () => {
|
|
11020
|
+
withSync(() => {
|
|
11021
|
+
term.hideCursor();
|
|
11022
|
+
for (let v = 0; v < findBoxRows; v++) {
|
|
11023
|
+
term.moveTo(1, 2 + v);
|
|
11024
|
+
paintFindBoxBodyRow(findBoxWindowStart + v, true);
|
|
11025
|
+
}
|
|
11026
|
+
term.moveTo(findBoxCursorCol(), findBoxCursorScreenRow());
|
|
11027
|
+
term.hideCursor(false);
|
|
11028
|
+
});
|
|
11029
|
+
};
|
|
11030
|
+
const SNIPPET_KIND_GLYPH = {
|
|
11031
|
+
user: "user",
|
|
11032
|
+
agent: "agent",
|
|
11033
|
+
thought: "thought",
|
|
11034
|
+
tool: "tool",
|
|
11035
|
+
"tool-input": "tool-input"
|
|
11036
|
+
};
|
|
11037
|
+
const findResultData = (idx, focused) => {
|
|
11038
|
+
const hit = findResults[idx];
|
|
11039
|
+
if (!hit) {
|
|
11040
|
+
return { rowBudget: 20, line1: "", line2: "", focusedRow: false };
|
|
11041
|
+
}
|
|
11042
|
+
const w = readTermWidth2(term);
|
|
11043
|
+
const rowBudget = Math.max(20, w - ROW_PREFIX_WIDTH);
|
|
11044
|
+
const shortId3 = stripHydraSessionPrefix(hit.sessionId);
|
|
11045
|
+
const title = hit.title ?? shortenHomePath(hit.cwd);
|
|
11046
|
+
const counterText = focused && hit.snippets.length > 1 ? ` [${findSnippetIdx + 1}/${hit.snippets.length}]` : focused && hit.totalMatches > hit.snippets.length ? ` [${hit.snippets.length} of ${hit.totalMatches}]` : "";
|
|
11047
|
+
const head = `${shortId3} ${hit.status === "live" ? "live" : "cold"}`;
|
|
11048
|
+
const titleBudget = Math.max(5, rowBudget - head.length - counterText.length - 2);
|
|
11049
|
+
const titleSlice = truncateMiddle(title, titleBudget);
|
|
11050
|
+
const line1 = `${head} ${titleSlice}${counterText}`.padEnd(rowBudget);
|
|
11051
|
+
const snippet = hit.snippets[focused ? findSnippetIdx : 0];
|
|
11052
|
+
const kind = snippet ? SNIPPET_KIND_GLYPH[snippet.kind] ?? snippet.kind : "";
|
|
11053
|
+
const prefix = snippet?.toolName ? `${kind} \xB7 ${snippet.toolName}` : kind;
|
|
11054
|
+
const snippetBudget = Math.max(10, rowBudget - prefix.length - 6);
|
|
11055
|
+
const text = snippet ? truncateMiddle(snippet.text, snippetBudget) : "";
|
|
11056
|
+
const line2 = snippet ? ` ${prefix} ${text}` : " (no snippet)";
|
|
11057
|
+
return { rowBudget, line1, line2: line2.padEnd(rowBudget + ROW_PREFIX_WIDTH), focusedRow: focused };
|
|
11058
|
+
};
|
|
11059
|
+
const paintFindResultA = (idx, focused) => {
|
|
11060
|
+
const { line1, focusedRow } = findResultData(idx, focused);
|
|
11061
|
+
if (focusedRow) {
|
|
11062
|
+
term.brightWhite.bgBlue.noFormat(`\u276F ${line1}`);
|
|
11063
|
+
} else {
|
|
11064
|
+
term.noFormat(` ${line1}`);
|
|
11065
|
+
}
|
|
11066
|
+
term.styleReset();
|
|
11067
|
+
};
|
|
11068
|
+
const paintFindResultB = (idx, focused) => {
|
|
11069
|
+
const { line2 } = findResultData(idx, focused);
|
|
11070
|
+
term.dim.noFormat(line2);
|
|
11071
|
+
term.styleReset();
|
|
11072
|
+
};
|
|
11073
|
+
const paintFindIndicator = () => {
|
|
11074
|
+
if (findInFlight) {
|
|
11075
|
+
term.dim.noFormat(" searching\u2026");
|
|
11076
|
+
term.styleReset();
|
|
11077
|
+
term.eraseLineAfter();
|
|
11078
|
+
} else if (findError !== null) {
|
|
11079
|
+
term.brightRed.noFormat(` ${findError}`);
|
|
11080
|
+
term.styleReset();
|
|
11081
|
+
term.eraseLineAfter();
|
|
11082
|
+
} else if (findSubMode === "input") {
|
|
11083
|
+
if (findResults.length > 0) {
|
|
11084
|
+
term.dim.noFormat(" Enter to search \xB7 \u2193 browse results \xB7 Esc cancel");
|
|
11085
|
+
} else {
|
|
11086
|
+
term.dim.noFormat(" Enter to search \xB7 Esc cancel");
|
|
11087
|
+
}
|
|
11088
|
+
term.styleReset();
|
|
11089
|
+
term.eraseLineAfter();
|
|
11090
|
+
} else {
|
|
11091
|
+
const sCount = findResults.length;
|
|
11092
|
+
const truncSuffix = findTruncated ? " \xB7 truncated" : "";
|
|
11093
|
+
const countPart = sCount > 0 ? ` ${sCount} ${sCount === 1 ? "session" : "sessions"} match${truncSuffix} \xB7 ` : " ";
|
|
11094
|
+
term.dim.noFormat(
|
|
11095
|
+
`${countPart}\u2191 edit query \xB7 Up/Down sessions \xB7 n/p snippets \xB7 Enter open \xB7 Esc back`
|
|
11096
|
+
);
|
|
11097
|
+
term.styleReset();
|
|
11098
|
+
term.eraseLineAfter();
|
|
11099
|
+
}
|
|
10239
11100
|
};
|
|
10240
|
-
const
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
}
|
|
11101
|
+
const renderFind = () => {
|
|
11102
|
+
computeFindBoxLayout();
|
|
11103
|
+
const focused = findSubMode === "input";
|
|
11104
|
+
const queryText = findComposer.state().buffer.join("\n");
|
|
10245
11105
|
withSync(() => {
|
|
10246
11106
|
term.hideCursor();
|
|
10247
|
-
computeLayout();
|
|
10248
|
-
adjustScroll();
|
|
10249
|
-
startRow = 1;
|
|
10250
11107
|
term.moveTo(1, 1).eraseDisplayBelow();
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
11108
|
+
paintFindBoxTopBorder(focused);
|
|
11109
|
+
for (let v = 0; v < findBoxRows; v++) {
|
|
11110
|
+
term.moveTo(1, 2 + v);
|
|
11111
|
+
paintFindBoxBodyRow(findBoxWindowStart + v, focused);
|
|
11112
|
+
}
|
|
11113
|
+
term.moveTo(1, 2 + findBoxRows);
|
|
11114
|
+
paintFindBoxBottomBorder(focused);
|
|
11115
|
+
const sCount = findResults.length;
|
|
11116
|
+
if (sCount === 0) {
|
|
11117
|
+
term.moveTo(1, findResultsStartRow());
|
|
11118
|
+
if (findInFlight) {
|
|
11119
|
+
} else if (findError === null && queryText.trim().length === 0) {
|
|
11120
|
+
term.dim.noFormat(" type a query in the box above, then press Enter");
|
|
11121
|
+
term.eraseLineAfter();
|
|
11122
|
+
} else if (findError === null) {
|
|
11123
|
+
term.dim.noFormat(" no matches");
|
|
11124
|
+
term.eraseLineAfter();
|
|
11125
|
+
}
|
|
11126
|
+
term.moveTo(1, findResultsStartRow() + 1);
|
|
11127
|
+
paintFindIndicator();
|
|
11128
|
+
} else {
|
|
11129
|
+
adjustFindScroll();
|
|
11130
|
+
const v = findViewportSize();
|
|
11131
|
+
const listFocused = findSubMode !== "input";
|
|
11132
|
+
for (let i = 0; i < v; i++) {
|
|
11133
|
+
const idx = findScrollOffset + i;
|
|
11134
|
+
term.moveTo(1, findResultsStartRow() + i * 2);
|
|
11135
|
+
if (idx < sCount) {
|
|
11136
|
+
paintFindResultA(idx, listFocused && idx === findSelectedIdx);
|
|
11137
|
+
} else {
|
|
11138
|
+
term.eraseLineAfter();
|
|
11139
|
+
}
|
|
11140
|
+
term.moveTo(1, findResultsStartRow() + i * 2 + 1);
|
|
11141
|
+
if (idx < sCount) {
|
|
11142
|
+
paintFindResultB(idx, listFocused && idx === findSelectedIdx);
|
|
11143
|
+
} else {
|
|
11144
|
+
term.eraseLineAfter();
|
|
11145
|
+
}
|
|
11146
|
+
}
|
|
11147
|
+
term.moveTo(1, findResultsStartRow() + v * 2);
|
|
11148
|
+
paintFindIndicator();
|
|
10263
11149
|
}
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
if (selectedIdx === 0) {
|
|
10267
|
-
placeComposerCursor();
|
|
11150
|
+
if (focused) {
|
|
11151
|
+
term.moveTo(findBoxCursorCol(), findBoxCursorScreenRow());
|
|
10268
11152
|
term.hideCursor(false);
|
|
10269
11153
|
}
|
|
10270
11154
|
});
|
|
10271
11155
|
};
|
|
10272
|
-
const
|
|
11156
|
+
const repaintFindResult = (idx, focused) => {
|
|
11157
|
+
const viewportIdx = idx - findScrollOffset;
|
|
11158
|
+
if (viewportIdx < 0 || viewportIdx >= findViewportSize()) {
|
|
11159
|
+
return;
|
|
11160
|
+
}
|
|
10273
11161
|
withSync(() => {
|
|
10274
|
-
term.
|
|
10275
|
-
|
|
10276
|
-
term.
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
11162
|
+
term.moveTo(1, findResultsStartRow() + viewportIdx * 2);
|
|
11163
|
+
paintFindResultA(idx, focused);
|
|
11164
|
+
term.moveTo(1, findResultsStartRow() + viewportIdx * 2 + 1);
|
|
11165
|
+
paintFindResultB(idx, focused);
|
|
11166
|
+
});
|
|
11167
|
+
};
|
|
11168
|
+
const repaintFindIndicatorRow = () => {
|
|
11169
|
+
withSync(() => {
|
|
11170
|
+
term.moveTo(1, findResultsStartRow() + findViewportSize() * 2);
|
|
11171
|
+
paintFindIndicator();
|
|
11172
|
+
});
|
|
11173
|
+
};
|
|
11174
|
+
const repaintFindViewport = () => {
|
|
11175
|
+
withSync(() => {
|
|
11176
|
+
const v = findViewportSize();
|
|
11177
|
+
const sCount = findResults.length;
|
|
11178
|
+
const listFocused = findSubMode !== "input";
|
|
11179
|
+
for (let i = 0; i < v; i++) {
|
|
11180
|
+
const idx = findScrollOffset + i;
|
|
11181
|
+
term.moveTo(1, findResultsStartRow() + i * 2);
|
|
11182
|
+
if (idx < sCount) {
|
|
11183
|
+
paintFindResultA(idx, listFocused && idx === findSelectedIdx);
|
|
11184
|
+
} else {
|
|
11185
|
+
term.eraseLineAfter();
|
|
11186
|
+
}
|
|
11187
|
+
term.moveTo(1, findResultsStartRow() + i * 2 + 1);
|
|
11188
|
+
if (idx < sCount) {
|
|
11189
|
+
paintFindResultB(idx, listFocused && idx === findSelectedIdx);
|
|
11190
|
+
} else {
|
|
11191
|
+
term.eraseLineAfter();
|
|
10281
11192
|
}
|
|
10282
|
-
const [keys, desc] = entry;
|
|
10283
|
-
term.brightCyan.noFormat(` ${keys.padEnd(HELP_KEYS_WIDTH)}`);
|
|
10284
|
-
term.noFormat(desc)("\n");
|
|
10285
11193
|
}
|
|
10286
|
-
term(
|
|
10287
|
-
|
|
11194
|
+
term.moveTo(1, findResultsStartRow() + v * 2);
|
|
11195
|
+
paintFindIndicator();
|
|
10288
11196
|
});
|
|
10289
11197
|
};
|
|
11198
|
+
const findQueryText = () => findComposer.state().buffer.join("\n");
|
|
11199
|
+
const runFind = async () => {
|
|
11200
|
+
const query = findQueryText().trim();
|
|
11201
|
+
if (query.length === 0) {
|
|
11202
|
+
return;
|
|
11203
|
+
}
|
|
11204
|
+
if (visible.length === 0) {
|
|
11205
|
+
findError = "no sessions in view to search";
|
|
11206
|
+
renderFind();
|
|
11207
|
+
return;
|
|
11208
|
+
}
|
|
11209
|
+
findInFlight = true;
|
|
11210
|
+
findError = null;
|
|
11211
|
+
renderFind();
|
|
11212
|
+
try {
|
|
11213
|
+
const out = await searchSessions(opts.target, query, {
|
|
11214
|
+
sessionIds: visible.map((s) => s.sessionId)
|
|
11215
|
+
});
|
|
11216
|
+
findResults = out.results;
|
|
11217
|
+
findTruncated = out.truncated;
|
|
11218
|
+
findSelectedIdx = 0;
|
|
11219
|
+
findSnippetIdx = 0;
|
|
11220
|
+
findScrollOffset = 0;
|
|
11221
|
+
findSubMode = out.results.length > 0 ? "results" : "input";
|
|
11222
|
+
computeFindBoxLayout();
|
|
11223
|
+
} catch (err) {
|
|
11224
|
+
findError = `search failed: ${err.message}`;
|
|
11225
|
+
} finally {
|
|
11226
|
+
findInFlight = false;
|
|
11227
|
+
renderFind();
|
|
11228
|
+
}
|
|
11229
|
+
};
|
|
11230
|
+
let exitFind = () => {
|
|
11231
|
+
};
|
|
10290
11232
|
const repaintComposerChrome = () => {
|
|
10291
11233
|
withSync(() => {
|
|
10292
11234
|
const showCursor = selectedIdx === 0;
|
|
@@ -10435,23 +11377,48 @@ async function pickSession(term, opts) {
|
|
|
10435
11377
|
let resolved = false;
|
|
10436
11378
|
let autoRefreshTimer = null;
|
|
10437
11379
|
let autoRefreshInFlight = false;
|
|
10438
|
-
const
|
|
10439
|
-
|
|
10440
|
-
|
|
11380
|
+
const focusStack = [];
|
|
11381
|
+
const pushLayer = (layer) => {
|
|
11382
|
+
focusStack.push(layer);
|
|
11383
|
+
};
|
|
11384
|
+
const popLayer = () => {
|
|
11385
|
+
focusStack.pop();
|
|
11386
|
+
if (!resolved) {
|
|
11387
|
+
focusStack[focusStack.length - 1]?.onResize();
|
|
10441
11388
|
}
|
|
10442
|
-
|
|
11389
|
+
};
|
|
11390
|
+
const focus = { push: pushLayer, pop: popLayer };
|
|
11391
|
+
exitFind = () => {
|
|
11392
|
+
findComposer = new InputDispatcher({ history: [] });
|
|
11393
|
+
findResults = [];
|
|
11394
|
+
findTruncated = false;
|
|
11395
|
+
findSelectedIdx = 0;
|
|
11396
|
+
findSnippetIdx = 0;
|
|
11397
|
+
findScrollOffset = 0;
|
|
11398
|
+
findError = null;
|
|
11399
|
+
findInFlight = false;
|
|
11400
|
+
findSubMode = "input";
|
|
11401
|
+
popLayer();
|
|
11402
|
+
};
|
|
11403
|
+
const dispatch = (name, _matches, data) => {
|
|
11404
|
+
focusStack[focusStack.length - 1]?.onKey(name, _matches, data);
|
|
11405
|
+
};
|
|
11406
|
+
const dispatchResize = () => {
|
|
11407
|
+
if (resolved) return;
|
|
11408
|
+
focusStack[focusStack.length - 1]?.onResize();
|
|
10443
11409
|
};
|
|
10444
11410
|
const cleanup = () => {
|
|
10445
11411
|
if (resolved) {
|
|
10446
11412
|
return;
|
|
10447
11413
|
}
|
|
10448
11414
|
resolved = true;
|
|
11415
|
+
focusStack.length = 0;
|
|
10449
11416
|
if (autoRefreshTimer) {
|
|
10450
11417
|
clearInterval(autoRefreshTimer);
|
|
10451
11418
|
autoRefreshTimer = null;
|
|
10452
11419
|
}
|
|
10453
|
-
term.off("key",
|
|
10454
|
-
term.off("resize",
|
|
11420
|
+
term.off("key", dispatch);
|
|
11421
|
+
term.off("resize", dispatchResize);
|
|
10455
11422
|
process.stdout.write("\x1B[?2004l");
|
|
10456
11423
|
const tClean = term;
|
|
10457
11424
|
if (tClean.stdin && tkStdinHandler) {
|
|
@@ -10607,18 +11574,231 @@ ${cells}`;
|
|
|
10607
11574
|
paintIndicator();
|
|
10608
11575
|
return true;
|
|
10609
11576
|
};
|
|
10610
|
-
const
|
|
10611
|
-
|
|
11577
|
+
const openHelpLayer = () => {
|
|
11578
|
+
renderHelp();
|
|
11579
|
+
pushLayer({
|
|
11580
|
+
onKey: (name) => {
|
|
11581
|
+
if (name === "CTRL_C") {
|
|
11582
|
+
cleanup();
|
|
11583
|
+
resolve6({ kind: "abort" });
|
|
11584
|
+
return;
|
|
11585
|
+
}
|
|
11586
|
+
popLayer();
|
|
11587
|
+
},
|
|
11588
|
+
onResize: () => renderHelp()
|
|
11589
|
+
});
|
|
11590
|
+
};
|
|
11591
|
+
const openFindLayer = () => {
|
|
11592
|
+
if (visible.length === 0) {
|
|
11593
|
+
transientStatus = "no sessions to search";
|
|
11594
|
+
paintIndicator();
|
|
10612
11595
|
return;
|
|
10613
11596
|
}
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
11597
|
+
findComposer = new InputDispatcher({ history: [] });
|
|
11598
|
+
findResults = [];
|
|
11599
|
+
findTruncated = false;
|
|
11600
|
+
findSelectedIdx = 0;
|
|
11601
|
+
findSnippetIdx = 0;
|
|
11602
|
+
findScrollOffset = 0;
|
|
11603
|
+
findError = null;
|
|
11604
|
+
findInFlight = false;
|
|
11605
|
+
findSubMode = "input";
|
|
11606
|
+
computeFindBoxLayout();
|
|
11607
|
+
renderFind();
|
|
11608
|
+
const findOnKey = (name, _matches, data) => {
|
|
11609
|
+
if (findSubMode === "input") {
|
|
11610
|
+
if (findInFlight) {
|
|
11611
|
+
return;
|
|
11612
|
+
}
|
|
11613
|
+
if (name === "ESCAPE" || name === "CTRL_C") {
|
|
11614
|
+
exitFind();
|
|
11615
|
+
return;
|
|
11616
|
+
}
|
|
11617
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
11618
|
+
if (findQueryText().trim().length === 0) {
|
|
11619
|
+
return;
|
|
11620
|
+
}
|
|
11621
|
+
void runFind();
|
|
11622
|
+
return;
|
|
11623
|
+
}
|
|
11624
|
+
if ((name === "DOWN" || name === "TAB" || name === "CTRL_N") && findResults.length > 0) {
|
|
11625
|
+
findSubMode = "results";
|
|
11626
|
+
findSelectedIdx = 0;
|
|
11627
|
+
findSnippetIdx = 0;
|
|
11628
|
+
withSync(() => {
|
|
11629
|
+
repaintFindBoxChrome();
|
|
11630
|
+
repaintFindResult(0, true);
|
|
11631
|
+
repaintFindIndicatorRow();
|
|
11632
|
+
term.hideCursor();
|
|
11633
|
+
});
|
|
11634
|
+
return;
|
|
11635
|
+
}
|
|
11636
|
+
const before = findComposer.state();
|
|
11637
|
+
let event = null;
|
|
11638
|
+
if (data?.isCharacter) {
|
|
11639
|
+
event = { type: "char", ch: name };
|
|
11640
|
+
} else {
|
|
11641
|
+
const mapped = mapKeyName(name);
|
|
11642
|
+
if (mapped !== null)
|
|
11643
|
+
event = { type: "key", name: mapped };
|
|
11644
|
+
}
|
|
11645
|
+
if (event === null) {
|
|
11646
|
+
term.moveTo(findBoxCursorCol(), findBoxCursorScreenRow());
|
|
11647
|
+
return;
|
|
11648
|
+
}
|
|
11649
|
+
findComposer.feed(event);
|
|
11650
|
+
const after = findComposer.state();
|
|
11651
|
+
const unchanged = before.buffer.length === after.buffer.length && before.buffer.every((l, i) => l === after.buffer[i]) && before.row === after.row && before.col === after.col;
|
|
11652
|
+
if (unchanged) {
|
|
11653
|
+
term.moveTo(findBoxCursorCol(), findBoxCursorScreenRow());
|
|
11654
|
+
return;
|
|
11655
|
+
}
|
|
11656
|
+
const prevRows = findBoxRows;
|
|
11657
|
+
computeFindBoxLayout();
|
|
11658
|
+
if (findBoxRows !== prevRows) {
|
|
11659
|
+
renderFind();
|
|
11660
|
+
} else {
|
|
11661
|
+
repaintFindBoxBodyRows();
|
|
11662
|
+
}
|
|
10618
11663
|
return;
|
|
10619
11664
|
}
|
|
10620
|
-
|
|
10621
|
-
|
|
11665
|
+
if (findSubMode === "results") {
|
|
11666
|
+
if (name === "ESCAPE" || name === "CTRL_C") {
|
|
11667
|
+
exitFind();
|
|
11668
|
+
return;
|
|
11669
|
+
}
|
|
11670
|
+
if (name === "CTRL_F") {
|
|
11671
|
+
findSubMode = "input";
|
|
11672
|
+
repaintFindViewport();
|
|
11673
|
+
repaintFindIndicatorRow();
|
|
11674
|
+
repaintFindBoxChrome();
|
|
11675
|
+
return;
|
|
11676
|
+
}
|
|
11677
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
11678
|
+
const hit = findResults[findSelectedIdx];
|
|
11679
|
+
if (!hit) {
|
|
11680
|
+
return;
|
|
11681
|
+
}
|
|
11682
|
+
const session = visible.find((s) => s.sessionId === hit.sessionId);
|
|
11683
|
+
const isImportedPassive = !!session?.importedFromMachine && !session.upstreamSessionId;
|
|
11684
|
+
if (isImportedPassive) {
|
|
11685
|
+
cleanup();
|
|
11686
|
+
const result = {
|
|
11687
|
+
kind: "attach",
|
|
11688
|
+
sessionId: hit.sessionId
|
|
11689
|
+
};
|
|
11690
|
+
if (session.agentId !== void 0) {
|
|
11691
|
+
result.agentId = session.agentId;
|
|
11692
|
+
}
|
|
11693
|
+
resolve6(result);
|
|
11694
|
+
return;
|
|
11695
|
+
}
|
|
11696
|
+
void (async () => {
|
|
11697
|
+
const action = await promptForLaunchOrView(term, {
|
|
11698
|
+
sessionId: hit.sessionId,
|
|
11699
|
+
title: hit.title,
|
|
11700
|
+
cwd: hit.cwd
|
|
11701
|
+
}, focus);
|
|
11702
|
+
if (action === "cancel") {
|
|
11703
|
+
cleanup();
|
|
11704
|
+
resolve6({ kind: "abort" });
|
|
11705
|
+
return;
|
|
11706
|
+
}
|
|
11707
|
+
if (action === "back") return;
|
|
11708
|
+
cleanup();
|
|
11709
|
+
const result = {
|
|
11710
|
+
kind: "attach",
|
|
11711
|
+
sessionId: hit.sessionId,
|
|
11712
|
+
readonly: action === "view"
|
|
11713
|
+
};
|
|
11714
|
+
if (session?.agentId !== void 0) {
|
|
11715
|
+
result.agentId = session.agentId;
|
|
11716
|
+
}
|
|
11717
|
+
resolve6(result);
|
|
11718
|
+
})();
|
|
11719
|
+
return;
|
|
11720
|
+
}
|
|
11721
|
+
if (data?.isCharacter && (name === "n" || name === "N")) {
|
|
11722
|
+
const hit = findResults[findSelectedIdx];
|
|
11723
|
+
if (!hit || hit.snippets.length <= 1) {
|
|
11724
|
+
return;
|
|
11725
|
+
}
|
|
11726
|
+
findSnippetIdx = (findSnippetIdx + 1) % hit.snippets.length;
|
|
11727
|
+
repaintFindResult(findSelectedIdx, true);
|
|
11728
|
+
return;
|
|
11729
|
+
}
|
|
11730
|
+
if (data?.isCharacter && (name === "p" || name === "P")) {
|
|
11731
|
+
const hit = findResults[findSelectedIdx];
|
|
11732
|
+
if (!hit || hit.snippets.length <= 1) {
|
|
11733
|
+
return;
|
|
11734
|
+
}
|
|
11735
|
+
findSnippetIdx = (findSnippetIdx - 1 + hit.snippets.length) % hit.snippets.length;
|
|
11736
|
+
repaintFindResult(findSelectedIdx, true);
|
|
11737
|
+
return;
|
|
11738
|
+
}
|
|
11739
|
+
const moveDeep = (delta) => {
|
|
11740
|
+
if (delta < 0 && findSelectedIdx === 0) {
|
|
11741
|
+
findSubMode = "input";
|
|
11742
|
+
withSync(() => {
|
|
11743
|
+
repaintFindResult(0, false);
|
|
11744
|
+
repaintFindIndicatorRow();
|
|
11745
|
+
repaintFindBoxChrome();
|
|
11746
|
+
});
|
|
11747
|
+
return;
|
|
11748
|
+
}
|
|
11749
|
+
const next = Math.min(
|
|
11750
|
+
findResults.length - 1,
|
|
11751
|
+
Math.max(0, findSelectedIdx + delta)
|
|
11752
|
+
);
|
|
11753
|
+
if (next === findSelectedIdx) {
|
|
11754
|
+
return;
|
|
11755
|
+
}
|
|
11756
|
+
const oldIdx = findSelectedIdx;
|
|
11757
|
+
const oldScroll = findScrollOffset;
|
|
11758
|
+
findSelectedIdx = next;
|
|
11759
|
+
findSnippetIdx = 0;
|
|
11760
|
+
adjustFindScroll();
|
|
11761
|
+
if (findScrollOffset !== oldScroll) {
|
|
11762
|
+
repaintFindViewport();
|
|
11763
|
+
} else {
|
|
11764
|
+
withSync(() => {
|
|
11765
|
+
repaintFindResult(oldIdx, false);
|
|
11766
|
+
repaintFindResult(findSelectedIdx, true);
|
|
11767
|
+
});
|
|
11768
|
+
repaintFindIndicatorRow();
|
|
11769
|
+
}
|
|
11770
|
+
};
|
|
11771
|
+
switch (name) {
|
|
11772
|
+
case "UP":
|
|
11773
|
+
case "SHIFT_TAB":
|
|
11774
|
+
case "CTRL_P":
|
|
11775
|
+
moveDeep(-1);
|
|
11776
|
+
return;
|
|
11777
|
+
case "DOWN":
|
|
11778
|
+
case "TAB":
|
|
11779
|
+
case "CTRL_N":
|
|
11780
|
+
moveDeep(1);
|
|
11781
|
+
return;
|
|
11782
|
+
case "PAGE_UP":
|
|
11783
|
+
moveDeep(-findViewportSize());
|
|
11784
|
+
return;
|
|
11785
|
+
case "PAGE_DOWN":
|
|
11786
|
+
moveDeep(findViewportSize());
|
|
11787
|
+
return;
|
|
11788
|
+
case "HOME":
|
|
11789
|
+
moveDeep(-findSelectedIdx);
|
|
11790
|
+
return;
|
|
11791
|
+
case "END":
|
|
11792
|
+
moveDeep(findResults.length);
|
|
11793
|
+
return;
|
|
11794
|
+
}
|
|
11795
|
+
return;
|
|
11796
|
+
}
|
|
11797
|
+
};
|
|
11798
|
+
pushLayer({ onKey: findOnKey, onResize: () => renderFind() });
|
|
11799
|
+
};
|
|
11800
|
+
const onKey = (name, _matches, data) => {
|
|
11801
|
+
if (mode === "busy") {
|
|
10622
11802
|
return;
|
|
10623
11803
|
}
|
|
10624
11804
|
if (mode === "rename") {
|
|
@@ -10682,6 +11862,10 @@ ${cells}`;
|
|
|
10682
11862
|
return;
|
|
10683
11863
|
}
|
|
10684
11864
|
clearTransient();
|
|
11865
|
+
if (name === "CTRL_F") {
|
|
11866
|
+
openFindLayer();
|
|
11867
|
+
return;
|
|
11868
|
+
}
|
|
10685
11869
|
if (selectedIdx === 0 && !searchActive) {
|
|
10686
11870
|
if (name === "ESCAPE" || name === "CTRL_C" || name === "CTRL_D") {
|
|
10687
11871
|
cleanup();
|
|
@@ -10753,8 +11937,7 @@ ${cells}`;
|
|
|
10753
11937
|
return;
|
|
10754
11938
|
}
|
|
10755
11939
|
if (!searchActive && data?.isCharacter && name === "?") {
|
|
10756
|
-
|
|
10757
|
-
renderHelp();
|
|
11940
|
+
openHelpLayer();
|
|
10758
11941
|
return;
|
|
10759
11942
|
}
|
|
10760
11943
|
if (searchActive) {
|
|
@@ -10812,29 +11995,20 @@ ${cells}`;
|
|
|
10812
11995
|
}
|
|
10813
11996
|
if (name === "o" || name === "O") {
|
|
10814
11997
|
const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
10815
|
-
cwdOnly = !cwdOnly;
|
|
11998
|
+
prefs.filters.cwdOnly = !prefs.filters.cwdOnly;
|
|
10816
11999
|
applyFilter();
|
|
10817
|
-
|
|
10818
|
-
const idx = visible.findIndex((s) => s.sessionId === keepId);
|
|
10819
|
-
if (idx >= 0) {
|
|
10820
|
-
selectedIdx = idx + 1;
|
|
10821
|
-
adjustScroll();
|
|
10822
|
-
}
|
|
10823
|
-
}
|
|
12000
|
+
restoreCursorAfterFilter(keepId);
|
|
10824
12001
|
renderFromScratch();
|
|
10825
12002
|
return;
|
|
10826
12003
|
}
|
|
10827
12004
|
if (name === "h" || name === "H") {
|
|
10828
12005
|
const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
10829
|
-
hostFilter = nextHostFilter(
|
|
12006
|
+
prefs.filters.hostFilter = nextHostFilter(
|
|
12007
|
+
prefs.filters.hostFilter,
|
|
12008
|
+
allSessions
|
|
12009
|
+
);
|
|
10830
12010
|
applyFilter();
|
|
10831
|
-
|
|
10832
|
-
const idx = visible.findIndex((s) => s.sessionId === keepId);
|
|
10833
|
-
if (idx >= 0) {
|
|
10834
|
-
selectedIdx = idx + 1;
|
|
10835
|
-
adjustScroll();
|
|
10836
|
-
}
|
|
10837
|
-
}
|
|
12011
|
+
restoreCursorAfterFilter(keepId);
|
|
10838
12012
|
renderFromScratch();
|
|
10839
12013
|
return;
|
|
10840
12014
|
}
|
|
@@ -10971,6 +12145,12 @@ ${cells}`;
|
|
|
10971
12145
|
return;
|
|
10972
12146
|
}
|
|
10973
12147
|
};
|
|
12148
|
+
pushLayer({
|
|
12149
|
+
onKey: (name, _matches, data) => onKey(name, _matches, data),
|
|
12150
|
+
onResize: () => {
|
|
12151
|
+
if (!resolved) renderFromScratch();
|
|
12152
|
+
}
|
|
12153
|
+
});
|
|
10974
12154
|
term.grabInput({});
|
|
10975
12155
|
const tSetup = term;
|
|
10976
12156
|
if (tSetup.stdin && typeof tSetup.onStdin === "function") {
|
|
@@ -10979,10 +12159,10 @@ ${cells}`;
|
|
|
10979
12159
|
tSetup.stdin.on("data", rawStdinHandler);
|
|
10980
12160
|
process.stdout.write("\x1B[?2004h");
|
|
10981
12161
|
}
|
|
10982
|
-
term.on("key",
|
|
10983
|
-
term.on("resize",
|
|
12162
|
+
term.on("key", dispatch);
|
|
12163
|
+
term.on("resize", dispatchResize);
|
|
10984
12164
|
autoRefreshTimer = setInterval(() => {
|
|
10985
|
-
if (resolved || mode !== "normal" || searchActive || autoRefreshInFlight) {
|
|
12165
|
+
if (resolved || focusStack.length > 1 || mode !== "normal" || searchActive || autoRefreshInFlight) {
|
|
10986
12166
|
return;
|
|
10987
12167
|
}
|
|
10988
12168
|
const currentId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
@@ -10993,10 +12173,10 @@ ${cells}`;
|
|
|
10993
12173
|
}, 3e3);
|
|
10994
12174
|
});
|
|
10995
12175
|
}
|
|
10996
|
-
function
|
|
12176
|
+
function readTermHeight2(term) {
|
|
10997
12177
|
return term.height ?? 24;
|
|
10998
12178
|
}
|
|
10999
|
-
function
|
|
12179
|
+
function readTermWidth2(term) {
|
|
11000
12180
|
return term.width ?? 80;
|
|
11001
12181
|
}
|
|
11002
12182
|
function formatComposerTitle(cwd, maxWidth) {
|
|
@@ -11051,7 +12231,7 @@ function matchesSearch(s, term) {
|
|
|
11051
12231
|
}
|
|
11052
12232
|
return false;
|
|
11053
12233
|
}
|
|
11054
|
-
var ROW_PREFIX_WIDTH, PICKER_COMPOSER_MAX_ROWS, BOX_HORIZONTAL_PAD, HELP_KEYS_WIDTH, HELP_ENTRIES;
|
|
12234
|
+
var ROW_PREFIX_WIDTH, PICKER_COMPOSER_MAX_ROWS, FIND_BOX_MAX_ROWS, BOX_HORIZONTAL_PAD, HELP_KEYS_WIDTH, HELP_ENTRIES;
|
|
11055
12235
|
var init_picker = __esm({
|
|
11056
12236
|
"src/tui/picker.ts"() {
|
|
11057
12237
|
"use strict";
|
|
@@ -11061,9 +12241,11 @@ var init_picker = __esm({
|
|
|
11061
12241
|
init_discovery();
|
|
11062
12242
|
init_input();
|
|
11063
12243
|
init_screen();
|
|
12244
|
+
init_import_action_prompt();
|
|
11064
12245
|
init_sync();
|
|
11065
12246
|
ROW_PREFIX_WIDTH = 2;
|
|
11066
12247
|
PICKER_COMPOSER_MAX_ROWS = 4;
|
|
12248
|
+
FIND_BOX_MAX_ROWS = 4;
|
|
11067
12249
|
BOX_HORIZONTAL_PAD = 4;
|
|
11068
12250
|
HELP_KEYS_WIDTH = 20;
|
|
11069
12251
|
HELP_ENTRIES = [
|
|
@@ -11076,7 +12258,8 @@ var init_picker = __esm({
|
|
|
11076
12258
|
["Enter", "open selected session"],
|
|
11077
12259
|
["v", "view-only (open transcript without spawning the agent)"],
|
|
11078
12260
|
null,
|
|
11079
|
-
["/", "search sessions"],
|
|
12261
|
+
["/", "search sessions (metadata)"],
|
|
12262
|
+
["^f", "find in session history (content + tool inputs)"],
|
|
11080
12263
|
["o", "toggle cwd-only filter"],
|
|
11081
12264
|
["h", "cycle host filter (local / <peer> / all)"],
|
|
11082
12265
|
["r", "refresh from daemon"],
|
|
@@ -11109,103 +12292,13 @@ async function validateLocalCwd(input) {
|
|
|
11109
12292
|
}
|
|
11110
12293
|
if (!stat5.isDirectory()) {
|
|
11111
12294
|
return { ok: false, reason: `${resolved} is not a directory` };
|
|
11112
|
-
}
|
|
11113
|
-
return { ok: true, path: resolved };
|
|
11114
|
-
}
|
|
11115
|
-
var init_cwd = __esm({
|
|
11116
|
-
"src/core/cwd.ts"() {
|
|
11117
|
-
"use strict";
|
|
11118
|
-
init_config();
|
|
11119
|
-
}
|
|
11120
|
-
});
|
|
11121
|
-
|
|
11122
|
-
// src/tui/prompt-utils.ts
|
|
11123
|
-
function resetTerminalModes() {
|
|
11124
|
-
process.stdout.write("\x1B[<u");
|
|
11125
|
-
process.stdout.write("\x1B[?2004l");
|
|
11126
|
-
process.stdout.write("\x1B[>4;0m");
|
|
11127
|
-
process.stdout.write("\x1B[>5;0m");
|
|
11128
|
-
process.stdout.write("\x1B[?1000l");
|
|
11129
|
-
process.stdout.write("\x1B[?1002l");
|
|
11130
|
-
process.stdout.write("\x1B[?1006l");
|
|
11131
|
-
process.stdout.write("\x1B[?1l");
|
|
11132
|
-
process.stdout.write("\x1B>");
|
|
11133
|
-
}
|
|
11134
|
-
function readTermWidth2(term) {
|
|
11135
|
-
return term.width ?? 80;
|
|
11136
|
-
}
|
|
11137
|
-
function readTermHeight2(term) {
|
|
11138
|
-
return term.height ?? 24;
|
|
11139
|
-
}
|
|
11140
|
-
function drawBox(term, opts) {
|
|
11141
|
-
const termW = readTermWidth2(term);
|
|
11142
|
-
const termH = readTermHeight2(term);
|
|
11143
|
-
const desiredContentW = opts.contentWidth ?? MAX_BOX_WIDTH;
|
|
11144
|
-
const maxContentW = Math.max(10, Math.min(MAX_BOX_WIDTH, termW - 4));
|
|
11145
|
-
const contentW = Math.min(desiredContentW, maxContentW);
|
|
11146
|
-
const w = contentW + 2;
|
|
11147
|
-
const contentH = Math.max(1, Math.min(opts.contentHeight, termH - 4));
|
|
11148
|
-
const h = contentH + 2;
|
|
11149
|
-
const x = Math.max(1, Math.floor((termW - w) / 2) + 1);
|
|
11150
|
-
const y = Math.max(1, Math.floor((termH - h) / 2) + 1);
|
|
11151
|
-
term.moveTo(1, 1).eraseDisplayBelow();
|
|
11152
|
-
const topInner = HORIZ.repeat(w - 2);
|
|
11153
|
-
const top = renderTitleStrip(topInner, opts.title);
|
|
11154
|
-
term.moveTo(x, y);
|
|
11155
|
-
term.dim.noFormat(TL);
|
|
11156
|
-
paintTopStrip(term, top);
|
|
11157
|
-
term.dim.noFormat(TR);
|
|
11158
|
-
for (let row = 1; row <= contentH; row++) {
|
|
11159
|
-
term.moveTo(x, y + row);
|
|
11160
|
-
term.dim.noFormat(VERT);
|
|
11161
|
-
term.moveTo(x + w - 1, y + row);
|
|
11162
|
-
term.dim.noFormat(VERT);
|
|
11163
|
-
}
|
|
11164
|
-
term.moveTo(x, y + h - 1);
|
|
11165
|
-
term.dim.noFormat(BL + HORIZ.repeat(w - 2) + BR);
|
|
11166
|
-
return {
|
|
11167
|
-
x,
|
|
11168
|
-
y,
|
|
11169
|
-
w,
|
|
11170
|
-
h,
|
|
11171
|
-
contentX: x + 1,
|
|
11172
|
-
contentY: y + 1,
|
|
11173
|
-
contentW,
|
|
11174
|
-
contentH
|
|
11175
|
-
};
|
|
11176
|
-
}
|
|
11177
|
-
function renderTitleStrip(innerDashes, title) {
|
|
11178
|
-
if (!title) {
|
|
11179
|
-
return { dashes: innerDashes };
|
|
11180
|
-
}
|
|
11181
|
-
const chip = ` ${title} `;
|
|
11182
|
-
if (chip.length + 4 > innerDashes.length) {
|
|
11183
|
-
return { dashes: innerDashes };
|
|
11184
|
-
}
|
|
11185
|
-
const offset = 2;
|
|
11186
|
-
const dashes = innerDashes.slice(0, offset) + " ".repeat(chip.length) + innerDashes.slice(offset + chip.length);
|
|
11187
|
-
return { dashes, title: { offset, text: chip } };
|
|
11188
|
-
}
|
|
11189
|
-
function paintTopStrip(term, strip) {
|
|
11190
|
-
if (!strip.title) {
|
|
11191
|
-
term.dim.noFormat(strip.dashes);
|
|
11192
|
-
return;
|
|
11193
|
-
}
|
|
11194
|
-
term.dim.noFormat(strip.dashes.slice(0, strip.title.offset));
|
|
11195
|
-
term.brightCyan.noFormat(strip.title.text);
|
|
11196
|
-
term.dim.noFormat(strip.dashes.slice(strip.title.offset + strip.title.text.length));
|
|
12295
|
+
}
|
|
12296
|
+
return { ok: true, path: resolved };
|
|
11197
12297
|
}
|
|
11198
|
-
var
|
|
11199
|
-
|
|
11200
|
-
"src/tui/prompt-utils.ts"() {
|
|
12298
|
+
var init_cwd = __esm({
|
|
12299
|
+
"src/core/cwd.ts"() {
|
|
11201
12300
|
"use strict";
|
|
11202
|
-
|
|
11203
|
-
HORIZ = "\u2500";
|
|
11204
|
-
VERT = "\u2502";
|
|
11205
|
-
TL = "\u250C";
|
|
11206
|
-
TR = "\u2510";
|
|
11207
|
-
BL = "\u2514";
|
|
11208
|
-
BR = "\u2518";
|
|
12301
|
+
init_config();
|
|
11209
12302
|
}
|
|
11210
12303
|
});
|
|
11211
12304
|
|
|
@@ -11225,7 +12318,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11225
12318
|
const contentHeight = 9;
|
|
11226
12319
|
layout = drawBox(term, {
|
|
11227
12320
|
contentHeight,
|
|
11228
|
-
title: "
|
|
12321
|
+
title: "Fork locally \u2014 choose cwd"
|
|
11229
12322
|
});
|
|
11230
12323
|
const innerW = layout.contentW;
|
|
11231
12324
|
const headerRows = [
|
|
@@ -11237,7 +12330,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11237
12330
|
for (const hr of headerRows) {
|
|
11238
12331
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11239
12332
|
term.dim.noFormat(` ${hr.label}`);
|
|
11240
|
-
term.noFormat(
|
|
12333
|
+
term.noFormat(truncate3(hr.value, innerW - hr.label.length - 2));
|
|
11241
12334
|
row++;
|
|
11242
12335
|
}
|
|
11243
12336
|
row++;
|
|
@@ -11248,7 +12341,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11248
12341
|
row += 2;
|
|
11249
12342
|
if (errorLine !== null) {
|
|
11250
12343
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11251
|
-
term.red.noFormat(` ${
|
|
12344
|
+
term.red.noFormat(` ${truncate3(errorLine, innerW - 2)}`);
|
|
11252
12345
|
} else {
|
|
11253
12346
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11254
12347
|
term.dim.noFormat(
|
|
@@ -11284,7 +12377,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11284
12377
|
term.dim.noFormat("\u2502");
|
|
11285
12378
|
term.moveTo(layout.contentX, layout.contentY + errRow);
|
|
11286
12379
|
if (errorLine !== null) {
|
|
11287
|
-
term.red.noFormat(` ${
|
|
12380
|
+
term.red.noFormat(` ${truncate3(errorLine, layout.contentW - 2)}`);
|
|
11288
12381
|
} else {
|
|
11289
12382
|
term.dim.noFormat(
|
|
11290
12383
|
" Enter accept \xB7 Esc back \xB7 ^U clear \xB7 ^W kill word"
|
|
@@ -11380,7 +12473,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11380
12473
|
term.on("resize", onResize);
|
|
11381
12474
|
});
|
|
11382
12475
|
}
|
|
11383
|
-
function
|
|
12476
|
+
function truncate3(s, max) {
|
|
11384
12477
|
if (max <= 1) {
|
|
11385
12478
|
return "";
|
|
11386
12479
|
}
|
|
@@ -11408,223 +12501,6 @@ var init_import_cwd_prompt = __esm({
|
|
|
11408
12501
|
}
|
|
11409
12502
|
});
|
|
11410
12503
|
|
|
11411
|
-
// src/tui/import-action-prompt.ts
|
|
11412
|
-
function actionPromptStep(selected, key, choices = ACTION_CHOICES) {
|
|
11413
|
-
if (key.kind === "cancel") {
|
|
11414
|
-
return { kind: "cancel" };
|
|
11415
|
-
}
|
|
11416
|
-
if (key.kind === "back") {
|
|
11417
|
-
return { kind: "back" };
|
|
11418
|
-
}
|
|
11419
|
-
if (key.kind === "enter") {
|
|
11420
|
-
const choice = choices[selected];
|
|
11421
|
-
if (!choice) {
|
|
11422
|
-
return { kind: "back" };
|
|
11423
|
-
}
|
|
11424
|
-
return { kind: "resolve", action: choice.key };
|
|
11425
|
-
}
|
|
11426
|
-
if (key.kind === "up") {
|
|
11427
|
-
return {
|
|
11428
|
-
kind: "continue",
|
|
11429
|
-
selected: Math.max(0, selected - 1)
|
|
11430
|
-
};
|
|
11431
|
-
}
|
|
11432
|
-
if (key.kind === "down") {
|
|
11433
|
-
return {
|
|
11434
|
-
kind: "continue",
|
|
11435
|
-
selected: Math.min(choices.length - 1, selected + 1)
|
|
11436
|
-
};
|
|
11437
|
-
}
|
|
11438
|
-
if (key.kind === "char") {
|
|
11439
|
-
const lower = key.ch.toLowerCase();
|
|
11440
|
-
if (lower === "n") {
|
|
11441
|
-
return {
|
|
11442
|
-
kind: "continue",
|
|
11443
|
-
selected: Math.min(choices.length - 1, selected + 1)
|
|
11444
|
-
};
|
|
11445
|
-
}
|
|
11446
|
-
if (lower === "p") {
|
|
11447
|
-
return {
|
|
11448
|
-
kind: "continue",
|
|
11449
|
-
selected: Math.max(0, selected - 1)
|
|
11450
|
-
};
|
|
11451
|
-
}
|
|
11452
|
-
const idx = choices.findIndex((c) => c.hotkey.toLowerCase() === lower);
|
|
11453
|
-
if (idx >= 0) {
|
|
11454
|
-
const choice = choices[idx];
|
|
11455
|
-
if (choice) {
|
|
11456
|
-
return { kind: "resolve", action: choice.key };
|
|
11457
|
-
}
|
|
11458
|
-
}
|
|
11459
|
-
}
|
|
11460
|
-
return { kind: "continue", selected };
|
|
11461
|
-
}
|
|
11462
|
-
async function promptForImportAction(term, session) {
|
|
11463
|
-
resetTerminalModes();
|
|
11464
|
-
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
11465
|
-
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
11466
|
-
const originalCwd = shortenHomePath(session.cwd);
|
|
11467
|
-
let selected = 0;
|
|
11468
|
-
const render = () => {
|
|
11469
|
-
const choiceRows = ACTION_CHOICES.length * 2;
|
|
11470
|
-
const contentHeight = 7 + choiceRows + 2;
|
|
11471
|
-
const layout = drawBox(term, {
|
|
11472
|
-
contentHeight,
|
|
11473
|
-
title: "Imported session"
|
|
11474
|
-
});
|
|
11475
|
-
const innerW = layout.contentW;
|
|
11476
|
-
const headerRows = [
|
|
11477
|
-
{ label: "session: ", value: shortId2 },
|
|
11478
|
-
{ label: "from: ", value: fromMachine },
|
|
11479
|
-
{ label: "cwd: ", value: originalCwd }
|
|
11480
|
-
];
|
|
11481
|
-
let row = 0;
|
|
11482
|
-
for (const hr of headerRows) {
|
|
11483
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11484
|
-
term.dim.noFormat(` ${hr.label}`);
|
|
11485
|
-
term.noFormat(truncate3(hr.value, innerW - hr.label.length - 2));
|
|
11486
|
-
row++;
|
|
11487
|
-
}
|
|
11488
|
-
row++;
|
|
11489
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11490
|
-
term.noFormat(" What do you want to do?");
|
|
11491
|
-
row += 2;
|
|
11492
|
-
for (let i = 0; i < ACTION_CHOICES.length; i++) {
|
|
11493
|
-
const choice = ACTION_CHOICES[i];
|
|
11494
|
-
if (!choice) {
|
|
11495
|
-
continue;
|
|
11496
|
-
}
|
|
11497
|
-
const pointer = i === selected ? "\u276F" : " ";
|
|
11498
|
-
const label = ` ${pointer} ${choice.label}`;
|
|
11499
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11500
|
-
if (i === selected) {
|
|
11501
|
-
term.brightWhite.bgBlue.noFormat(padRight(label, innerW));
|
|
11502
|
-
} else {
|
|
11503
|
-
term.noFormat(label);
|
|
11504
|
-
}
|
|
11505
|
-
row++;
|
|
11506
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11507
|
-
term.dim.noFormat(` ${choice.description}`);
|
|
11508
|
-
row++;
|
|
11509
|
-
}
|
|
11510
|
-
row++;
|
|
11511
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11512
|
-
term.dim.noFormat(" \u2191/\u2193 navigate \xB7 Enter select \xB7 r/v jump \xB7 Esc back");
|
|
11513
|
-
return layout;
|
|
11514
|
-
};
|
|
11515
|
-
render();
|
|
11516
|
-
term.hideCursor();
|
|
11517
|
-
return await new Promise((resolve6) => {
|
|
11518
|
-
let resolved = false;
|
|
11519
|
-
const cleanup = () => {
|
|
11520
|
-
if (resolved) {
|
|
11521
|
-
return;
|
|
11522
|
-
}
|
|
11523
|
-
resolved = true;
|
|
11524
|
-
term.off("key", onKey);
|
|
11525
|
-
term.off("resize", onResize);
|
|
11526
|
-
term.grabInput(false);
|
|
11527
|
-
term.hideCursor(false);
|
|
11528
|
-
term.moveTo(1, 1).eraseDisplayBelow();
|
|
11529
|
-
};
|
|
11530
|
-
const finish = (value) => {
|
|
11531
|
-
cleanup();
|
|
11532
|
-
resolve6(value);
|
|
11533
|
-
};
|
|
11534
|
-
const onResize = () => {
|
|
11535
|
-
if (resolved) {
|
|
11536
|
-
return;
|
|
11537
|
-
}
|
|
11538
|
-
render();
|
|
11539
|
-
};
|
|
11540
|
-
const onKey = (name, _matches, data) => {
|
|
11541
|
-
const input = mapKey(name, data);
|
|
11542
|
-
if (!input) {
|
|
11543
|
-
return;
|
|
11544
|
-
}
|
|
11545
|
-
const step = actionPromptStep(selected, input);
|
|
11546
|
-
if (step.kind === "cancel") {
|
|
11547
|
-
finish("cancel");
|
|
11548
|
-
return;
|
|
11549
|
-
}
|
|
11550
|
-
if (step.kind === "back") {
|
|
11551
|
-
finish("back");
|
|
11552
|
-
return;
|
|
11553
|
-
}
|
|
11554
|
-
if (step.kind === "resolve") {
|
|
11555
|
-
finish(step.action);
|
|
11556
|
-
return;
|
|
11557
|
-
}
|
|
11558
|
-
if (step.selected !== selected) {
|
|
11559
|
-
selected = step.selected;
|
|
11560
|
-
render();
|
|
11561
|
-
}
|
|
11562
|
-
};
|
|
11563
|
-
term.grabInput({});
|
|
11564
|
-
term.on("key", onKey);
|
|
11565
|
-
term.on("resize", onResize);
|
|
11566
|
-
});
|
|
11567
|
-
}
|
|
11568
|
-
function mapKey(name, data) {
|
|
11569
|
-
if (name === "UP") {
|
|
11570
|
-
return { kind: "up" };
|
|
11571
|
-
}
|
|
11572
|
-
if (name === "DOWN") {
|
|
11573
|
-
return { kind: "down" };
|
|
11574
|
-
}
|
|
11575
|
-
if (name === "ENTER" || name === "KP_ENTER") {
|
|
11576
|
-
return { kind: "enter" };
|
|
11577
|
-
}
|
|
11578
|
-
if (name === "ESCAPE") {
|
|
11579
|
-
return { kind: "back" };
|
|
11580
|
-
}
|
|
11581
|
-
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
11582
|
-
return { kind: "cancel" };
|
|
11583
|
-
}
|
|
11584
|
-
if (data?.isCharacter) {
|
|
11585
|
-
return { kind: "char", ch: name };
|
|
11586
|
-
}
|
|
11587
|
-
return null;
|
|
11588
|
-
}
|
|
11589
|
-
function truncate3(s, max) {
|
|
11590
|
-
if (max <= 1) {
|
|
11591
|
-
return "";
|
|
11592
|
-
}
|
|
11593
|
-
if (s.length <= max) {
|
|
11594
|
-
return s;
|
|
11595
|
-
}
|
|
11596
|
-
return s.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
11597
|
-
}
|
|
11598
|
-
function padRight(s, w) {
|
|
11599
|
-
if (s.length >= w) {
|
|
11600
|
-
return s.slice(0, w);
|
|
11601
|
-
}
|
|
11602
|
-
return s + " ".repeat(w - s.length);
|
|
11603
|
-
}
|
|
11604
|
-
var ACTION_CHOICES;
|
|
11605
|
-
var init_import_action_prompt = __esm({
|
|
11606
|
-
"src/tui/import-action-prompt.ts"() {
|
|
11607
|
-
"use strict";
|
|
11608
|
-
init_paths();
|
|
11609
|
-
init_session();
|
|
11610
|
-
init_prompt_utils();
|
|
11611
|
-
ACTION_CHOICES = [
|
|
11612
|
-
{
|
|
11613
|
-
key: "run-local",
|
|
11614
|
-
label: "Run locally",
|
|
11615
|
-
hotkey: "r",
|
|
11616
|
-
description: "spawn the agent on this machine with a local cwd"
|
|
11617
|
-
},
|
|
11618
|
-
{
|
|
11619
|
-
key: "view",
|
|
11620
|
-
label: "View transcript",
|
|
11621
|
-
hotkey: "v",
|
|
11622
|
-
description: "open read-only, no agent spawn"
|
|
11623
|
-
}
|
|
11624
|
-
];
|
|
11625
|
-
}
|
|
11626
|
-
});
|
|
11627
|
-
|
|
11628
12504
|
// src/tui/clipboard.ts
|
|
11629
12505
|
import { spawn as nodeSpawn } from "child_process";
|
|
11630
12506
|
import fs21 from "fs/promises";
|
|
@@ -12023,41 +12899,69 @@ function formatEvent(event) {
|
|
|
12023
12899
|
return [];
|
|
12024
12900
|
}
|
|
12025
12901
|
}
|
|
12026
|
-
function applyInlineMarkup(text) {
|
|
12902
|
+
function applyInlineMarkup(text, opts) {
|
|
12903
|
+
const codeOpen = opts?.codeOpen ?? "^C";
|
|
12904
|
+
const boldReset = opts?.boldReset ?? "^:";
|
|
12905
|
+
const codeReset = opts?.codeReset ?? "^:";
|
|
12027
12906
|
let s = text.replace(/\^/g, "^^");
|
|
12028
|
-
s = s.replace(/\*\*(.+?)\*\*/g,
|
|
12029
|
-
s = s.replace(/`([^`]+)`/g,
|
|
12907
|
+
s = s.replace(/\*\*(.+?)\*\*/g, `^+$1${boldReset}`);
|
|
12908
|
+
s = s.replace(/`([^`]+)`/g, `${codeOpen}$1${codeReset}`);
|
|
12030
12909
|
return s;
|
|
12031
12910
|
}
|
|
12032
|
-
function
|
|
12911
|
+
function parseMarkdown(text, opts) {
|
|
12912
|
+
const {
|
|
12913
|
+
proseStyle,
|
|
12914
|
+
highlightCode,
|
|
12915
|
+
prefixStyle,
|
|
12916
|
+
firstPrefix = " ",
|
|
12917
|
+
inlineOpts
|
|
12918
|
+
} = opts;
|
|
12033
12919
|
const out = [];
|
|
12034
12920
|
const lines = text.split("\n");
|
|
12035
12921
|
let inCode = false;
|
|
12036
12922
|
let codeLang = "";
|
|
12037
12923
|
let codeBuffer = [];
|
|
12924
|
+
let firstNonBlank = firstPrefix !== " ";
|
|
12925
|
+
const line = (body, bodyStyle, prefix = " ") => {
|
|
12926
|
+
const entry = { prefix, body, bodyStyle };
|
|
12927
|
+
if (prefixStyle !== void 0)
|
|
12928
|
+
entry.prefixStyle = prefixStyle;
|
|
12929
|
+
out.push(entry);
|
|
12930
|
+
};
|
|
12931
|
+
const nextPrefix = () => {
|
|
12932
|
+
if (!firstNonBlank)
|
|
12933
|
+
return " ";
|
|
12934
|
+
firstNonBlank = false;
|
|
12935
|
+
return firstPrefix;
|
|
12936
|
+
};
|
|
12038
12937
|
const flushCode = () => {
|
|
12039
|
-
if (codeBuffer.length === 0)
|
|
12938
|
+
if (codeBuffer.length === 0)
|
|
12040
12939
|
return;
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
12940
|
+
if (highlightCode) {
|
|
12941
|
+
const highlighted = highlightFencedBlock(codeLang, codeBuffer);
|
|
12942
|
+
for (const piece of highlighted) {
|
|
12943
|
+
const entry = {
|
|
12944
|
+
prefix: " ",
|
|
12945
|
+
body: piece.body,
|
|
12946
|
+
bodyStyle: "code",
|
|
12947
|
+
fillRow: true
|
|
12948
|
+
};
|
|
12949
|
+
if (prefixStyle !== void 0)
|
|
12950
|
+
entry.prefixStyle = prefixStyle;
|
|
12951
|
+
if (piece.ansi)
|
|
12952
|
+
entry.ansi = true;
|
|
12953
|
+
out.push(entry);
|
|
12052
12954
|
}
|
|
12053
|
-
|
|
12955
|
+
} else {
|
|
12956
|
+
for (const cl of codeBuffer)
|
|
12957
|
+
line(cl.replace(/\^/g, "^^"), proseStyle);
|
|
12054
12958
|
}
|
|
12055
12959
|
codeBuffer = [];
|
|
12056
12960
|
codeLang = "";
|
|
12057
12961
|
};
|
|
12058
12962
|
for (let i = 0; i < lines.length; i++) {
|
|
12059
|
-
const
|
|
12060
|
-
const fence =
|
|
12963
|
+
const l = lines[i];
|
|
12964
|
+
const fence = l.match(/^\s*```\s*(\w*)\s*$/);
|
|
12061
12965
|
if (fence) {
|
|
12062
12966
|
if (!inCode) {
|
|
12063
12967
|
inCode = true;
|
|
@@ -12069,68 +12973,81 @@ function parseAgentMarkdown(text) {
|
|
|
12069
12973
|
continue;
|
|
12070
12974
|
}
|
|
12071
12975
|
if (inCode) {
|
|
12072
|
-
codeBuffer.push(
|
|
12976
|
+
codeBuffer.push(l);
|
|
12073
12977
|
continue;
|
|
12074
12978
|
}
|
|
12075
|
-
const heading =
|
|
12979
|
+
const heading = l.match(/^(#{1,6})\s+(.*)$/);
|
|
12076
12980
|
if (heading) {
|
|
12077
12981
|
const level = heading[1].length;
|
|
12078
|
-
const
|
|
12079
|
-
const
|
|
12080
|
-
|
|
12081
|
-
prefix: " ",
|
|
12082
|
-
body: text2,
|
|
12083
|
-
bodyStyle: style
|
|
12084
|
-
});
|
|
12982
|
+
const headingText = heading[2] ?? "";
|
|
12983
|
+
const headingStyle = highlightCode ? level === 1 ? "heading-1" : level === 2 ? "heading-2" : "heading-3" : proseStyle;
|
|
12984
|
+
line(headingText, headingStyle, nextPrefix());
|
|
12085
12985
|
continue;
|
|
12086
12986
|
}
|
|
12087
12987
|
const next = lines[i + 1];
|
|
12088
|
-
if (
|
|
12089
|
-
const header = parseTableRow(
|
|
12988
|
+
if (l.includes("|") && next !== void 0 && isTableSeparatorLine(next) && parseTableRow(l).length === parseTableRow(next).length) {
|
|
12989
|
+
const header = parseTableRow(l);
|
|
12090
12990
|
const body = [];
|
|
12091
12991
|
let j = i + 2;
|
|
12092
12992
|
while (j < lines.length && lines[j].includes("|")) {
|
|
12093
12993
|
body.push(parseTableRow(lines[j]));
|
|
12094
12994
|
j++;
|
|
12095
12995
|
}
|
|
12096
|
-
|
|
12996
|
+
const tableLines = formatTable(header, body);
|
|
12997
|
+
for (const tl of tableLines) {
|
|
12998
|
+
if (prefixStyle !== void 0)
|
|
12999
|
+
tl.prefixStyle = prefixStyle;
|
|
13000
|
+
out.push(tl);
|
|
13001
|
+
}
|
|
12097
13002
|
i = j - 1;
|
|
12098
13003
|
continue;
|
|
12099
13004
|
}
|
|
12100
|
-
const bullet =
|
|
13005
|
+
const bullet = l.match(/^(\s*)[-*+]\s+(.*)$/);
|
|
12101
13006
|
if (bullet) {
|
|
12102
13007
|
const indent = bullet[1] ?? "";
|
|
12103
13008
|
const item = bullet[2] ?? "";
|
|
12104
|
-
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
13009
|
+
line(
|
|
13010
|
+
`${indent}\u2022 ${applyInlineMarkup(item, inlineOpts)}`,
|
|
13011
|
+
proseStyle,
|
|
13012
|
+
nextPrefix()
|
|
13013
|
+
);
|
|
12109
13014
|
continue;
|
|
12110
13015
|
}
|
|
12111
|
-
const ordered =
|
|
13016
|
+
const ordered = l.match(/^(\s*)(\d+)\.\s+(.*)$/);
|
|
12112
13017
|
if (ordered) {
|
|
12113
13018
|
const indent = ordered[1] ?? "";
|
|
12114
13019
|
const num = ordered[2] ?? "";
|
|
12115
|
-
const item = ordered[3] ?? "";
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
13020
|
+
const item = ordered[3] ?? "";
|
|
13021
|
+
line(
|
|
13022
|
+
`${indent}${num}. ${applyInlineMarkup(item, inlineOpts)}`,
|
|
13023
|
+
proseStyle,
|
|
13024
|
+
nextPrefix()
|
|
13025
|
+
);
|
|
12121
13026
|
continue;
|
|
12122
13027
|
}
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
|
|
12127
|
-
|
|
13028
|
+
const isBlank = l.trim() === "";
|
|
13029
|
+
line(
|
|
13030
|
+
applyInlineMarkup(l, inlineOpts),
|
|
13031
|
+
proseStyle,
|
|
13032
|
+
isBlank ? " " : nextPrefix()
|
|
13033
|
+
);
|
|
12128
13034
|
}
|
|
12129
|
-
if (inCode)
|
|
13035
|
+
if (inCode)
|
|
12130
13036
|
flushCode();
|
|
12131
|
-
}
|
|
12132
13037
|
return out;
|
|
12133
13038
|
}
|
|
13039
|
+
function parseAgentMarkdown(text) {
|
|
13040
|
+
return parseMarkdown(text, { proseStyle: "agent", highlightCode: true });
|
|
13041
|
+
}
|
|
13042
|
+
function parseThoughtMarkdown(text) {
|
|
13043
|
+
return parseMarkdown(text, {
|
|
13044
|
+
proseStyle: "thought",
|
|
13045
|
+
highlightCode: false,
|
|
13046
|
+
prefixStyle: "thought",
|
|
13047
|
+
firstPrefix: "\xB7 ",
|
|
13048
|
+
inlineOpts: { codeOpen: "^c", boldReset: "^-", codeReset: "^K" }
|
|
13049
|
+
});
|
|
13050
|
+
}
|
|
12134
13051
|
function parseTableRow(line) {
|
|
12135
13052
|
let s = line.trim();
|
|
12136
13053
|
if (s.startsWith("|")) {
|
|
@@ -12487,6 +13404,7 @@ async function runTuiApp(opts) {
|
|
|
12487
13404
|
const viewPrefs = {
|
|
12488
13405
|
showThoughts: config.tui.showThoughts
|
|
12489
13406
|
};
|
|
13407
|
+
const pickerPrefs = createPickerPrefs();
|
|
12490
13408
|
let altScreenEngaged = false;
|
|
12491
13409
|
const enterAltScreen = () => {
|
|
12492
13410
|
if (altScreenEngaged) {
|
|
@@ -12514,7 +13432,15 @@ async function runTuiApp(opts) {
|
|
|
12514
13432
|
let nextOpts = opts;
|
|
12515
13433
|
try {
|
|
12516
13434
|
while (nextOpts !== null) {
|
|
12517
|
-
nextOpts = await runSession(
|
|
13435
|
+
nextOpts = await runSession(
|
|
13436
|
+
term,
|
|
13437
|
+
config,
|
|
13438
|
+
target,
|
|
13439
|
+
nextOpts,
|
|
13440
|
+
exitHint,
|
|
13441
|
+
viewPrefs,
|
|
13442
|
+
pickerPrefs
|
|
13443
|
+
);
|
|
12518
13444
|
}
|
|
12519
13445
|
} finally {
|
|
12520
13446
|
leaveAltScreen();
|
|
@@ -12534,8 +13460,8 @@ async function runTuiApp(opts) {
|
|
|
12534
13460
|
);
|
|
12535
13461
|
}
|
|
12536
13462
|
}
|
|
12537
|
-
async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
12538
|
-
const ctx = await resolveSession(term, config, target, opts);
|
|
13463
|
+
async function runSession(term, config, target, opts, exitHint, viewPrefs, pickerPrefs) {
|
|
13464
|
+
const ctx = await resolveSession(term, config, target, opts, pickerPrefs);
|
|
12539
13465
|
if (!ctx) {
|
|
12540
13466
|
term.grabInput(false);
|
|
12541
13467
|
return null;
|
|
@@ -13454,7 +14380,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
13454
14380
|
sessions,
|
|
13455
14381
|
config,
|
|
13456
14382
|
target,
|
|
13457
|
-
currentSessionId: resolvedSessionId
|
|
14383
|
+
currentSessionId: resolvedSessionId,
|
|
14384
|
+
prefs: pickerPrefs
|
|
13458
14385
|
});
|
|
13459
14386
|
if (choice2.kind === "abort") {
|
|
13460
14387
|
screen.start({ skipFullscreen: true });
|
|
@@ -14147,6 +15074,33 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
14147
15074
|
agentKey = null;
|
|
14148
15075
|
agentBuffer = "";
|
|
14149
15076
|
};
|
|
15077
|
+
let thoughtBuffer = "";
|
|
15078
|
+
let thoughtKey = null;
|
|
15079
|
+
let thoughtSeq = 0;
|
|
15080
|
+
const renderThoughtBlock = () => {
|
|
15081
|
+
if (thoughtKey === null)
|
|
15082
|
+
return;
|
|
15083
|
+
const lines = parseThoughtMarkdown(thoughtBuffer);
|
|
15084
|
+
if (lines.length === 0)
|
|
15085
|
+
return;
|
|
15086
|
+
screen.upsertLines(thoughtKey, lines);
|
|
15087
|
+
};
|
|
15088
|
+
const appendThought = (text) => {
|
|
15089
|
+
if (text.length === 0)
|
|
15090
|
+
return;
|
|
15091
|
+
if (thoughtKey === null) {
|
|
15092
|
+
screen.ensureSeparator();
|
|
15093
|
+
thoughtKey = `thought:${thoughtSeq}`;
|
|
15094
|
+
thoughtSeq += 1;
|
|
15095
|
+
thoughtBuffer = "";
|
|
15096
|
+
}
|
|
15097
|
+
thoughtBuffer += text;
|
|
15098
|
+
renderThoughtBlock();
|
|
15099
|
+
};
|
|
15100
|
+
const closeThought = () => {
|
|
15101
|
+
thoughtKey = null;
|
|
15102
|
+
thoughtBuffer = "";
|
|
15103
|
+
};
|
|
14150
15104
|
const renderToolsBlock = () => {
|
|
14151
15105
|
if (toolsBlockStartedAt === null) {
|
|
14152
15106
|
return;
|
|
@@ -14288,6 +15242,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
14288
15242
|
recordHistoryEntry(event.text);
|
|
14289
15243
|
}
|
|
14290
15244
|
closeAgentText();
|
|
15245
|
+
closeThought();
|
|
14291
15246
|
if (toolsBlockStartedAt !== null) {
|
|
14292
15247
|
toolsBlockEndedAt = Date.now();
|
|
14293
15248
|
renderToolsBlock();
|
|
@@ -14311,16 +15266,18 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
14311
15266
|
return;
|
|
14312
15267
|
}
|
|
14313
15268
|
if (event.kind === "agent-text") {
|
|
15269
|
+
closeThought();
|
|
14314
15270
|
appendAgentText(event.text);
|
|
14315
15271
|
return;
|
|
14316
15272
|
}
|
|
14317
15273
|
if (event.kind === "agent-thought") {
|
|
14318
15274
|
closeAgentText();
|
|
14319
|
-
|
|
15275
|
+
appendThought(event.text);
|
|
14320
15276
|
return;
|
|
14321
15277
|
}
|
|
14322
15278
|
if (event.kind === "exit-plan-mode") {
|
|
14323
15279
|
closeAgentText();
|
|
15280
|
+
closeThought();
|
|
14324
15281
|
const existing = exitPlanStates.get(event.toolCallId);
|
|
14325
15282
|
const merged = {
|
|
14326
15283
|
plan: event.plan ?? existing?.plan ?? "",
|
|
@@ -14338,12 +15295,14 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
14338
15295
|
}
|
|
14339
15296
|
if (event.kind === "tool-call") {
|
|
14340
15297
|
closeAgentText();
|
|
15298
|
+
closeThought();
|
|
14341
15299
|
recordToolCall(event.toolCallId, event.title, event.status, void 0);
|
|
14342
15300
|
renderToolsBlock();
|
|
14343
15301
|
return;
|
|
14344
15302
|
}
|
|
14345
15303
|
if (event.kind === "plan") {
|
|
14346
15304
|
closeAgentText();
|
|
15305
|
+
closeThought();
|
|
14347
15306
|
lastPlanEvent = event;
|
|
14348
15307
|
const lines = formatEvent(event);
|
|
14349
15308
|
if (lines.length > 0) {
|
|
@@ -14353,6 +15312,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
14353
15312
|
}
|
|
14354
15313
|
if (event.kind === "tool-call-update") {
|
|
14355
15314
|
closeAgentText();
|
|
15315
|
+
closeThought();
|
|
14356
15316
|
recordToolCall(
|
|
14357
15317
|
event.toolCallId,
|
|
14358
15318
|
event.title,
|
|
@@ -14375,6 +15335,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
14375
15335
|
if (event.kind === "turn-complete") {
|
|
14376
15336
|
currentHeadMessageId = void 0;
|
|
14377
15337
|
closeAgentText();
|
|
15338
|
+
closeThought();
|
|
14378
15339
|
let effectiveStopReason = event.amended ? "amended" : event.stopReason;
|
|
14379
15340
|
if (!event.amended && upstreamInterruptedSeen && (effectiveStopReason === void 0 || effectiveStopReason === "end_turn")) {
|
|
14380
15341
|
effectiveStopReason = "error";
|
|
@@ -14469,6 +15430,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
14469
15430
|
resolve6({ outcome: { outcome: "cancelled" } });
|
|
14470
15431
|
}
|
|
14471
15432
|
closeAgentText();
|
|
15433
|
+
closeThought();
|
|
14472
15434
|
};
|
|
14473
15435
|
const markToolsBlockRecoveryFailed = () => {
|
|
14474
15436
|
if (toolsBlockStartedAt === null) {
|
|
@@ -14596,7 +15558,7 @@ connection lost: ${err.message}
|
|
|
14596
15558
|
}
|
|
14597
15559
|
return await sessionDone;
|
|
14598
15560
|
}
|
|
14599
|
-
async function resolveSession(term, config, target, opts) {
|
|
15561
|
+
async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
14600
15562
|
const cwd = opts.cwd ?? process.cwd();
|
|
14601
15563
|
if (opts.sessionId) {
|
|
14602
15564
|
const ctx = {
|
|
@@ -14632,7 +15594,8 @@ async function resolveSession(term, config, target, opts) {
|
|
|
14632
15594
|
cwd,
|
|
14633
15595
|
sessions,
|
|
14634
15596
|
config,
|
|
14635
|
-
target
|
|
15597
|
+
target,
|
|
15598
|
+
prefs: pickerPrefs
|
|
14636
15599
|
});
|
|
14637
15600
|
if (choice.kind === "abort") {
|
|
14638
15601
|
return null;
|
|
@@ -14916,6 +15879,9 @@ import { dirname as dirname6, resolve as resolve5 } from "path";
|
|
|
14916
15879
|
var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
14917
15880
|
"all",
|
|
14918
15881
|
"detach",
|
|
15882
|
+
"disabled",
|
|
15883
|
+
"follow",
|
|
15884
|
+
"force",
|
|
14919
15885
|
"foreground",
|
|
14920
15886
|
"help",
|
|
14921
15887
|
"info",
|
|
@@ -14928,6 +15894,31 @@ var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
14928
15894
|
"stream",
|
|
14929
15895
|
"version"
|
|
14930
15896
|
]);
|
|
15897
|
+
var KNOWN_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
15898
|
+
"agent",
|
|
15899
|
+
"args",
|
|
15900
|
+
"command",
|
|
15901
|
+
"cwd",
|
|
15902
|
+
"env",
|
|
15903
|
+
"host",
|
|
15904
|
+
"model",
|
|
15905
|
+
"name",
|
|
15906
|
+
"out",
|
|
15907
|
+
"prompt",
|
|
15908
|
+
"session",
|
|
15909
|
+
"stream-bytes",
|
|
15910
|
+
"stream-file-cap",
|
|
15911
|
+
"stream-threshold",
|
|
15912
|
+
"tail"
|
|
15913
|
+
]);
|
|
15914
|
+
function validateKnownFlags(flags) {
|
|
15915
|
+
for (const key of Object.keys(flags)) {
|
|
15916
|
+
if (!KNOWN_BOOLEAN_FLAGS.has(key) && !KNOWN_VALUE_FLAGS.has(key)) {
|
|
15917
|
+
return key;
|
|
15918
|
+
}
|
|
15919
|
+
}
|
|
15920
|
+
return void 0;
|
|
15921
|
+
}
|
|
14931
15922
|
function parseArgs(argv) {
|
|
14932
15923
|
const positional = [];
|
|
14933
15924
|
const flags = {};
|
|
@@ -16430,6 +17421,7 @@ var SessionManager = class {
|
|
|
16430
17421
|
this.defaultTransformers = options.defaultTransformers ?? [];
|
|
16431
17422
|
this.logger = options.logger;
|
|
16432
17423
|
this.npmRegistry = options.npmRegistry;
|
|
17424
|
+
this.extensionCommands = options.extensionCommands;
|
|
16433
17425
|
}
|
|
16434
17426
|
registry;
|
|
16435
17427
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -16448,6 +17440,7 @@ var SessionManager = class {
|
|
|
16448
17440
|
metaWriteQueues = /* @__PURE__ */ new Map();
|
|
16449
17441
|
logger;
|
|
16450
17442
|
npmRegistry;
|
|
17443
|
+
extensionCommands;
|
|
16451
17444
|
async create(params) {
|
|
16452
17445
|
const fresh = await this.bootstrapAgent({
|
|
16453
17446
|
agentId: params.agentId,
|
|
@@ -16501,7 +17494,8 @@ var SessionManager = class {
|
|
|
16501
17494
|
agentModes: fresh.initialModes,
|
|
16502
17495
|
agentModels: fresh.initialModels,
|
|
16503
17496
|
transformChain: params.transformChain,
|
|
16504
|
-
parentSessionId: params.parentSessionId
|
|
17497
|
+
parentSessionId: params.parentSessionId,
|
|
17498
|
+
extensionCommands: this.extensionCommands
|
|
16505
17499
|
});
|
|
16506
17500
|
await this.attachManagerHooks(session);
|
|
16507
17501
|
return session;
|
|
@@ -16572,12 +17566,14 @@ var SessionManager = class {
|
|
|
16572
17566
|
}
|
|
16573
17567
|
let loadResult;
|
|
16574
17568
|
try {
|
|
17569
|
+
const loadMeta = buildSessionLoadMeta(params.agentId, params.currentModel);
|
|
16575
17570
|
loadResult = await agent.connection.request(
|
|
16576
17571
|
"session/load",
|
|
16577
17572
|
{
|
|
16578
17573
|
sessionId: params.upstreamSessionId,
|
|
16579
17574
|
cwd: params.cwd,
|
|
16580
|
-
mcpServers: []
|
|
17575
|
+
mcpServers: [],
|
|
17576
|
+
...loadMeta && { _meta: loadMeta }
|
|
16581
17577
|
}
|
|
16582
17578
|
);
|
|
16583
17579
|
} catch (err) {
|
|
@@ -16593,7 +17589,10 @@ var SessionManager = class {
|
|
|
16593
17589
|
() => void 0
|
|
16594
17590
|
);
|
|
16595
17591
|
} else {
|
|
16596
|
-
agent.connection.drainBuffered("session/update");
|
|
17592
|
+
const drain1Count = agent.connection.drainBuffered("session/update");
|
|
17593
|
+
this.logger?.info(
|
|
17594
|
+
`resurrect: drain1 dropped ${drain1Count} buffered session/update(s) for sessionId=${params.hydraSessionId}`
|
|
17595
|
+
);
|
|
16597
17596
|
}
|
|
16598
17597
|
const agentReportedMode = extractInitialCurrentMode(loadResult ?? {});
|
|
16599
17598
|
const advertisedModes = params.agentModes ?? nonEmptyOrUndefined(extractInitialModes(loadResult ?? {}));
|
|
@@ -16611,6 +17610,30 @@ var SessionManager = class {
|
|
|
16611
17610
|
this.logger?.info(
|
|
16612
17611
|
`resurrect: effectiveMode=${JSON.stringify(effectiveMode)} for sessionId=${params.hydraSessionId}`
|
|
16613
17612
|
);
|
|
17613
|
+
const agentReportedModel = extractInitialModel(loadResult ?? {});
|
|
17614
|
+
const advertisedModels = nonEmptyOrUndefined(extractInitialModels(loadResult ?? {})) ?? params.agentModels;
|
|
17615
|
+
this.logger?.info(
|
|
17616
|
+
`resurrect: sessionId=${params.hydraSessionId} persistedModel=${JSON.stringify(params.currentModel)} agentReportedModel=${JSON.stringify(agentReportedModel)} advertisedModels=${JSON.stringify(advertisedModels?.map((m) => m.modelId))}`
|
|
17617
|
+
);
|
|
17618
|
+
if (params.pendingHistorySync !== true) {
|
|
17619
|
+
const drain2Count = agent.connection.drainBuffered("session/update");
|
|
17620
|
+
this.logger?.info(
|
|
17621
|
+
`resurrect: drain2 (post-mode-restore) dropped ${drain2Count} buffered session/update(s) for sessionId=${params.hydraSessionId}`
|
|
17622
|
+
);
|
|
17623
|
+
}
|
|
17624
|
+
const effectiveModel = await restoreCurrentModel({
|
|
17625
|
+
agent,
|
|
17626
|
+
upstreamSessionId: params.upstreamSessionId,
|
|
17627
|
+
persistedModel: params.currentModel,
|
|
17628
|
+
agentReportedModel,
|
|
17629
|
+
logger: this.logger
|
|
17630
|
+
});
|
|
17631
|
+
if (params.pendingHistorySync !== true) {
|
|
17632
|
+
const drain3Count = agent.connection.drainBuffered("session/update");
|
|
17633
|
+
this.logger?.info(
|
|
17634
|
+
`resurrect: drain3 (post-model-restore) dropped ${drain3Count} buffered session/update(s) for sessionId=${params.hydraSessionId}`
|
|
17635
|
+
);
|
|
17636
|
+
}
|
|
16614
17637
|
const session = new Session({
|
|
16615
17638
|
sessionId: params.hydraSessionId,
|
|
16616
17639
|
cwd: params.cwd,
|
|
@@ -16627,11 +17650,7 @@ var SessionManager = class {
|
|
|
16627
17650
|
listSessions: () => this.list(),
|
|
16628
17651
|
historyStore: this.histories,
|
|
16629
17652
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
16630
|
-
|
|
16631
|
-
// we never captured one (e.g. old opencode sessions on disk before
|
|
16632
|
-
// this fix), fall back to the model the agent ships in its
|
|
16633
|
-
// session/load response body.
|
|
16634
|
-
currentModel: params.currentModel ?? extractInitialModel(loadResult ?? {}),
|
|
17653
|
+
currentModel: effectiveModel,
|
|
16635
17654
|
currentMode: effectiveMode,
|
|
16636
17655
|
currentUsage: params.currentUsage,
|
|
16637
17656
|
agentCommands: params.agentCommands,
|
|
@@ -16640,13 +17659,14 @@ var SessionManager = class {
|
|
|
16640
17659
|
// snapshot — the proxy's available models can change between daemon
|
|
16641
17660
|
// restarts (quota resets, rollouts), so meta.json is intentionally
|
|
16642
17661
|
// treated as a cold fallback here, not the authoritative source.
|
|
16643
|
-
agentModels:
|
|
17662
|
+
agentModels: advertisedModels,
|
|
16644
17663
|
// Only gate the first-prompt title heuristic when we actually have
|
|
16645
17664
|
// a title to preserve. A title-less session (lost to a write race
|
|
16646
17665
|
// or never seeded) should re-derive from the next prompt rather
|
|
16647
17666
|
// than stay stuck.
|
|
16648
17667
|
firstPromptSeeded: !!params.title,
|
|
16649
|
-
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0
|
|
17668
|
+
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
17669
|
+
extensionCommands: this.extensionCommands
|
|
16650
17670
|
});
|
|
16651
17671
|
await this.attachManagerHooks(session);
|
|
16652
17672
|
return session;
|
|
@@ -16665,7 +17685,11 @@ var SessionManager = class {
|
|
|
16665
17685
|
cwd,
|
|
16666
17686
|
agentArgs: params.agentArgs,
|
|
16667
17687
|
mcpServers: [],
|
|
16668
|
-
onInstallProgress: params.onInstallProgress
|
|
17688
|
+
onInstallProgress: params.onInstallProgress,
|
|
17689
|
+
// Pass the persisted model so bootstrapAgent calls session/set_model
|
|
17690
|
+
// during session/new — the only context where the agent reliably
|
|
17691
|
+
// honours the switch.
|
|
17692
|
+
model: params.currentModel
|
|
16669
17693
|
});
|
|
16670
17694
|
const advertisedModes = params.agentModes ?? fresh.initialModes;
|
|
16671
17695
|
const effectiveMode = await restoreCurrentMode({
|
|
@@ -16676,6 +17700,15 @@ var SessionManager = class {
|
|
|
16676
17700
|
advertisedModes,
|
|
16677
17701
|
logger: this.logger
|
|
16678
17702
|
});
|
|
17703
|
+
const advertisedModels = params.agentModels ?? fresh.initialModels;
|
|
17704
|
+
const effectiveModel = await restoreCurrentModel({
|
|
17705
|
+
agent: fresh.agent,
|
|
17706
|
+
upstreamSessionId: fresh.upstreamSessionId,
|
|
17707
|
+
persistedModel: params.currentModel,
|
|
17708
|
+
agentReportedModel: fresh.initialModel,
|
|
17709
|
+
logger: this.logger
|
|
17710
|
+
});
|
|
17711
|
+
fresh.agent.connection.drainBuffered("session/update");
|
|
16679
17712
|
const session = new Session({
|
|
16680
17713
|
sessionId: params.hydraSessionId,
|
|
16681
17714
|
cwd,
|
|
@@ -16692,16 +17725,15 @@ var SessionManager = class {
|
|
|
16692
17725
|
listSessions: () => this.list(),
|
|
16693
17726
|
historyStore: this.histories,
|
|
16694
17727
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
16695
|
-
|
|
16696
|
-
// fall back to whatever the agent ships in its session/new response.
|
|
16697
|
-
currentModel: params.currentModel ?? fresh.initialModel,
|
|
17728
|
+
currentModel: effectiveModel,
|
|
16698
17729
|
currentMode: effectiveMode,
|
|
16699
17730
|
currentUsage: params.currentUsage,
|
|
16700
17731
|
agentCommands: params.agentCommands,
|
|
16701
17732
|
agentModes: advertisedModes,
|
|
16702
|
-
agentModels:
|
|
17733
|
+
agentModels: advertisedModels,
|
|
16703
17734
|
firstPromptSeeded: !!params.title,
|
|
16704
|
-
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0
|
|
17735
|
+
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
17736
|
+
extensionCommands: this.extensionCommands
|
|
16705
17737
|
});
|
|
16706
17738
|
await this.attachManagerHooks(session);
|
|
16707
17739
|
void session.seedFromImport().catch(() => void 0);
|
|
@@ -17553,6 +18585,13 @@ function usageSnapshotToPersisted(usage) {
|
|
|
17553
18585
|
function persistedUsageToSnapshot(usage) {
|
|
17554
18586
|
return usage ? { ...usage } : void 0;
|
|
17555
18587
|
}
|
|
18588
|
+
function buildSessionLoadMeta(agentId, model) {
|
|
18589
|
+
if (!model)
|
|
18590
|
+
return void 0;
|
|
18591
|
+
if (agentId === "claude-acp")
|
|
18592
|
+
return { claudeCode: { options: { model } } };
|
|
18593
|
+
return void 0;
|
|
18594
|
+
}
|
|
17556
18595
|
function extractInitialModel(result) {
|
|
17557
18596
|
const direct = asString(result.currentModelId) ?? asString(result.currentModel) ?? asString(result.modelId) ?? asString(result.model);
|
|
17558
18597
|
if (direct) {
|
|
@@ -17732,6 +18771,33 @@ async function restoreCurrentMode(opts) {
|
|
|
17732
18771
|
return agentReportedMode;
|
|
17733
18772
|
}
|
|
17734
18773
|
}
|
|
18774
|
+
async function restoreCurrentModel(opts) {
|
|
18775
|
+
const { agent, upstreamSessionId, persistedModel, agentReportedModel, logger } = opts;
|
|
18776
|
+
if (!persistedModel) {
|
|
18777
|
+
return agentReportedModel;
|
|
18778
|
+
}
|
|
18779
|
+
if (persistedModel === agentReportedModel) {
|
|
18780
|
+
return persistedModel;
|
|
18781
|
+
}
|
|
18782
|
+
try {
|
|
18783
|
+
logger?.info(
|
|
18784
|
+
`resurrect: pushing persisted modelId=${JSON.stringify(persistedModel)} to agent (agentReported=${JSON.stringify(agentReportedModel)})`
|
|
18785
|
+
);
|
|
18786
|
+
await agent.connection.request("session/set_model", {
|
|
18787
|
+
sessionId: upstreamSessionId,
|
|
18788
|
+
modelId: persistedModel
|
|
18789
|
+
});
|
|
18790
|
+
logger?.info(
|
|
18791
|
+
`resurrect: session/set_model accepted, effectiveModel=${JSON.stringify(persistedModel)}`
|
|
18792
|
+
);
|
|
18793
|
+
return persistedModel;
|
|
18794
|
+
} catch (err) {
|
|
18795
|
+
logger?.warn(
|
|
18796
|
+
`resurrect: session/set_model rejected by agent for modelId=${JSON.stringify(persistedModel)} (${err.message}); session will use ${JSON.stringify(agentReportedModel)}`
|
|
18797
|
+
);
|
|
18798
|
+
return agentReportedModel;
|
|
18799
|
+
}
|
|
18800
|
+
}
|
|
17735
18801
|
function parseModesList(list) {
|
|
17736
18802
|
if (!Array.isArray(list)) {
|
|
17737
18803
|
return [];
|
|
@@ -18687,6 +19753,55 @@ function withCode3(err, code) {
|
|
|
18687
19753
|
return err;
|
|
18688
19754
|
}
|
|
18689
19755
|
|
|
19756
|
+
// src/core/extension-commands.ts
|
|
19757
|
+
var ExtensionCommandRegistry = class {
|
|
19758
|
+
entries = /* @__PURE__ */ new Map();
|
|
19759
|
+
changeHandlers = [];
|
|
19760
|
+
register(name, connection, commands) {
|
|
19761
|
+
this.entries.set(name, { connection, commands: [...commands] });
|
|
19762
|
+
this.fireChanged();
|
|
19763
|
+
}
|
|
19764
|
+
clear(name) {
|
|
19765
|
+
if (this.entries.delete(name)) {
|
|
19766
|
+
this.fireChanged();
|
|
19767
|
+
}
|
|
19768
|
+
}
|
|
19769
|
+
get(name) {
|
|
19770
|
+
return this.entries.get(name);
|
|
19771
|
+
}
|
|
19772
|
+
has(name) {
|
|
19773
|
+
return this.entries.has(name);
|
|
19774
|
+
}
|
|
19775
|
+
// Snapshot of every (name, command) pair. Order is stable per-name
|
|
19776
|
+
// (insertion order of the map and the original commands list).
|
|
19777
|
+
list() {
|
|
19778
|
+
const out = [];
|
|
19779
|
+
for (const [name, entry] of this.entries) {
|
|
19780
|
+
for (const command of entry.commands) {
|
|
19781
|
+
out.push({ name, command });
|
|
19782
|
+
}
|
|
19783
|
+
}
|
|
19784
|
+
return out;
|
|
19785
|
+
}
|
|
19786
|
+
onChange(handler) {
|
|
19787
|
+
this.changeHandlers.push(handler);
|
|
19788
|
+
return () => {
|
|
19789
|
+
const i = this.changeHandlers.indexOf(handler);
|
|
19790
|
+
if (i >= 0) {
|
|
19791
|
+
this.changeHandlers.splice(i, 1);
|
|
19792
|
+
}
|
|
19793
|
+
};
|
|
19794
|
+
}
|
|
19795
|
+
fireChanged() {
|
|
19796
|
+
for (const h of this.changeHandlers) {
|
|
19797
|
+
try {
|
|
19798
|
+
h();
|
|
19799
|
+
} catch {
|
|
19800
|
+
}
|
|
19801
|
+
}
|
|
19802
|
+
}
|
|
19803
|
+
};
|
|
19804
|
+
|
|
18690
19805
|
// src/daemon/server.ts
|
|
18691
19806
|
init_paths();
|
|
18692
19807
|
|
|
@@ -19451,6 +20566,379 @@ function formatNumber(n) {
|
|
|
19451
20566
|
init_types();
|
|
19452
20567
|
init_hydra_version();
|
|
19453
20568
|
init_remote_url();
|
|
20569
|
+
|
|
20570
|
+
// src/core/history-search.ts
|
|
20571
|
+
init_render_update();
|
|
20572
|
+
function parseQuery(raw) {
|
|
20573
|
+
const trimmed = raw.trim();
|
|
20574
|
+
if (trimmed.length === 0) {
|
|
20575
|
+
return { operator: "OR", terms: [] };
|
|
20576
|
+
}
|
|
20577
|
+
const tokenRe = /\w+:"[^"]*"|"[^"]*"|\S+/g;
|
|
20578
|
+
const tokens = [];
|
|
20579
|
+
let m;
|
|
20580
|
+
while ((m = tokenRe.exec(trimmed)) !== null) {
|
|
20581
|
+
tokens.push(m[0]);
|
|
20582
|
+
}
|
|
20583
|
+
let operator = "OR";
|
|
20584
|
+
let sawAnd = false;
|
|
20585
|
+
let sawOr = false;
|
|
20586
|
+
const termTokens = [];
|
|
20587
|
+
for (const tok of tokens) {
|
|
20588
|
+
const upper = tok.toUpperCase();
|
|
20589
|
+
if (upper === "AND") {
|
|
20590
|
+
sawAnd = true;
|
|
20591
|
+
} else if (upper === "OR") {
|
|
20592
|
+
sawOr = true;
|
|
20593
|
+
} else {
|
|
20594
|
+
termTokens.push(tok);
|
|
20595
|
+
}
|
|
20596
|
+
}
|
|
20597
|
+
if (sawAnd) {
|
|
20598
|
+
operator = "AND";
|
|
20599
|
+
} else if (sawOr) {
|
|
20600
|
+
operator = "OR";
|
|
20601
|
+
}
|
|
20602
|
+
const terms = termTokens.map((tok) => parseTermToken(tok)).filter((t) => t.term.length > 0);
|
|
20603
|
+
return { operator, terms };
|
|
20604
|
+
}
|
|
20605
|
+
function parseTermToken(tok) {
|
|
20606
|
+
const pq = /^(\w+):"([^"]*)"$/.exec(tok);
|
|
20607
|
+
if (pq) {
|
|
20608
|
+
return { scope: prefixToScope(pq[1]), term: pq[2] };
|
|
20609
|
+
}
|
|
20610
|
+
const q = /^"([^"]*)"$/.exec(tok);
|
|
20611
|
+
if (q) {
|
|
20612
|
+
return { scope: "all", term: q[1] };
|
|
20613
|
+
}
|
|
20614
|
+
const pb = /^(prompt|response|tool):([\s\S]*)$/i.exec(tok);
|
|
20615
|
+
if (pb) {
|
|
20616
|
+
return { scope: prefixToScope(pb[1]), term: pb[2].trim() };
|
|
20617
|
+
}
|
|
20618
|
+
return { scope: "all", term: tok.trim() };
|
|
20619
|
+
}
|
|
20620
|
+
function prefixToScope(prefix) {
|
|
20621
|
+
switch (prefix.toLowerCase()) {
|
|
20622
|
+
case "prompt":
|
|
20623
|
+
return "user";
|
|
20624
|
+
case "response":
|
|
20625
|
+
return "agent";
|
|
20626
|
+
case "tool":
|
|
20627
|
+
return "tool";
|
|
20628
|
+
default:
|
|
20629
|
+
return "all";
|
|
20630
|
+
}
|
|
20631
|
+
}
|
|
20632
|
+
function scopeMatchesKind(scope, kind) {
|
|
20633
|
+
if (scope === "all") {
|
|
20634
|
+
return true;
|
|
20635
|
+
}
|
|
20636
|
+
if (scope === "user") {
|
|
20637
|
+
return kind === "user";
|
|
20638
|
+
}
|
|
20639
|
+
if (scope === "agent") {
|
|
20640
|
+
return kind === "agent" || kind === "thought";
|
|
20641
|
+
}
|
|
20642
|
+
return kind === "tool" || kind === "tool-input";
|
|
20643
|
+
}
|
|
20644
|
+
var DEFAULT_MAX_SNIPPETS_PER_SESSION = 5;
|
|
20645
|
+
var DEFAULT_MAX_SESSIONS = 200;
|
|
20646
|
+
var SNIPPET_SIDE = 30;
|
|
20647
|
+
async function searchHistories(manager, query, opts = {}) {
|
|
20648
|
+
const parsed = parseQuery(query);
|
|
20649
|
+
if (parsed.terms.length === 0) {
|
|
20650
|
+
return { query, truncated: false, results: [] };
|
|
20651
|
+
}
|
|
20652
|
+
const maxPerSession = opts.maxSnippetsPerSession ?? DEFAULT_MAX_SNIPPETS_PER_SESSION;
|
|
20653
|
+
const maxSessions = opts.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
20654
|
+
const allow = opts.sessionIds ? new Set(opts.sessionIds) : null;
|
|
20655
|
+
const all = await manager.list();
|
|
20656
|
+
const candidates = allow ? all.filter((s) => allow.has(s.sessionId)) : all;
|
|
20657
|
+
const results = [];
|
|
20658
|
+
let truncated = false;
|
|
20659
|
+
for (const candidate of candidates) {
|
|
20660
|
+
if (results.length >= maxSessions) {
|
|
20661
|
+
truncated = true;
|
|
20662
|
+
break;
|
|
20663
|
+
}
|
|
20664
|
+
const entries = await manager.loadHistory(candidate.sessionId).catch(
|
|
20665
|
+
() => []
|
|
20666
|
+
);
|
|
20667
|
+
const found = scanSessionEntries(entries, parsed, maxPerSession);
|
|
20668
|
+
if (found.snippets.length === 0) {
|
|
20669
|
+
continue;
|
|
20670
|
+
}
|
|
20671
|
+
const hit = {
|
|
20672
|
+
sessionId: candidate.sessionId,
|
|
20673
|
+
cwd: candidate.cwd,
|
|
20674
|
+
status: candidate.status,
|
|
20675
|
+
updatedAt: candidate.updatedAt,
|
|
20676
|
+
totalMatches: found.totalMatches,
|
|
20677
|
+
snippets: found.snippets
|
|
20678
|
+
};
|
|
20679
|
+
if (candidate.title !== void 0) {
|
|
20680
|
+
hit.title = candidate.title;
|
|
20681
|
+
}
|
|
20682
|
+
results.push(hit);
|
|
20683
|
+
}
|
|
20684
|
+
return { query, truncated, results };
|
|
20685
|
+
}
|
|
20686
|
+
function scanSessionEntries(entries, query, maxSnippets) {
|
|
20687
|
+
if (query.terms.length === 0) {
|
|
20688
|
+
return { totalMatches: 0, snippets: [] };
|
|
20689
|
+
}
|
|
20690
|
+
let totalMatches = 0;
|
|
20691
|
+
const snippets = [];
|
|
20692
|
+
for (const { scope, term } of query.terms) {
|
|
20693
|
+
const result = scanForTerm(entries, term, scope, maxSnippets - snippets.length);
|
|
20694
|
+
if (query.operator === "AND" && result.totalMatches === 0) {
|
|
20695
|
+
return { totalMatches: 0, snippets: [] };
|
|
20696
|
+
}
|
|
20697
|
+
totalMatches += result.totalMatches;
|
|
20698
|
+
snippets.push(...result.snippets);
|
|
20699
|
+
}
|
|
20700
|
+
return { totalMatches, snippets };
|
|
20701
|
+
}
|
|
20702
|
+
function scanForTerm(entries, term, scope, snippetBudget) {
|
|
20703
|
+
const needle = term.toLowerCase();
|
|
20704
|
+
let totalMatches = 0;
|
|
20705
|
+
const snippets = [];
|
|
20706
|
+
for (const entry of entries) {
|
|
20707
|
+
const fragments = extractSearchableFragments(entry).filter(
|
|
20708
|
+
(f) => scopeMatchesKind(scope, f.kind)
|
|
20709
|
+
);
|
|
20710
|
+
for (const frag of fragments) {
|
|
20711
|
+
const hay = frag.text.toLowerCase();
|
|
20712
|
+
let idx = hay.indexOf(needle);
|
|
20713
|
+
if (idx === -1) {
|
|
20714
|
+
continue;
|
|
20715
|
+
}
|
|
20716
|
+
let occurrences = 0;
|
|
20717
|
+
while (idx !== -1) {
|
|
20718
|
+
occurrences++;
|
|
20719
|
+
idx = hay.indexOf(needle, idx + needle.length);
|
|
20720
|
+
}
|
|
20721
|
+
totalMatches += occurrences;
|
|
20722
|
+
if (snippets.length < snippetBudget) {
|
|
20723
|
+
const first = hay.indexOf(needle);
|
|
20724
|
+
const snippet = {
|
|
20725
|
+
kind: frag.kind,
|
|
20726
|
+
text: buildSnippet(frag.text, first, needle.length),
|
|
20727
|
+
recordedAt: entry.recordedAt
|
|
20728
|
+
};
|
|
20729
|
+
if (frag.toolName !== void 0) {
|
|
20730
|
+
snippet.toolName = frag.toolName;
|
|
20731
|
+
}
|
|
20732
|
+
snippets.push(snippet);
|
|
20733
|
+
}
|
|
20734
|
+
}
|
|
20735
|
+
}
|
|
20736
|
+
return { totalMatches, snippets };
|
|
20737
|
+
}
|
|
20738
|
+
function extractSearchableFragments(entry) {
|
|
20739
|
+
if (entry.method !== "session/update") {
|
|
20740
|
+
return [];
|
|
20741
|
+
}
|
|
20742
|
+
const params = entry.params;
|
|
20743
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
20744
|
+
return [];
|
|
20745
|
+
}
|
|
20746
|
+
const update = params.update;
|
|
20747
|
+
if (!update || typeof update !== "object" || Array.isArray(update)) {
|
|
20748
|
+
return [];
|
|
20749
|
+
}
|
|
20750
|
+
const u = update;
|
|
20751
|
+
const tag = typeof u.sessionUpdate === "string" ? u.sessionUpdate : u.kind;
|
|
20752
|
+
if (typeof tag !== "string") {
|
|
20753
|
+
return [];
|
|
20754
|
+
}
|
|
20755
|
+
switch (tag) {
|
|
20756
|
+
case "agent_message_chunk": {
|
|
20757
|
+
const text = readContentText(u.content);
|
|
20758
|
+
return text ? [{ kind: "agent", text }] : [];
|
|
20759
|
+
}
|
|
20760
|
+
case "agent_thought":
|
|
20761
|
+
case "agent_thought_chunk": {
|
|
20762
|
+
const text = typeof u.text === "string" ? sanitizeWireText(u.text) : readContentText(u.content);
|
|
20763
|
+
return text ? [{ kind: "thought", text }] : [];
|
|
20764
|
+
}
|
|
20765
|
+
case "user_message_chunk": {
|
|
20766
|
+
if (isCompatPromptReceived(u)) {
|
|
20767
|
+
return [];
|
|
20768
|
+
}
|
|
20769
|
+
const text = readContentText(u.content);
|
|
20770
|
+
return text ? [{ kind: "user", text }] : [];
|
|
20771
|
+
}
|
|
20772
|
+
case "prompt_received": {
|
|
20773
|
+
const text = readPromptText(u.prompt);
|
|
20774
|
+
return text ? [{ kind: "user", text }] : [];
|
|
20775
|
+
}
|
|
20776
|
+
case "tool_call":
|
|
20777
|
+
case "tool_call_update": {
|
|
20778
|
+
return extractToolFragments(u);
|
|
20779
|
+
}
|
|
20780
|
+
default:
|
|
20781
|
+
return [];
|
|
20782
|
+
}
|
|
20783
|
+
}
|
|
20784
|
+
function extractToolFragments(u) {
|
|
20785
|
+
const toolName = readString2(u, "name");
|
|
20786
|
+
const title = readString2(u, "title");
|
|
20787
|
+
const out = [];
|
|
20788
|
+
if (title !== void 0) {
|
|
20789
|
+
const sanitized = sanitizeSingleLine(title);
|
|
20790
|
+
if (sanitized.length > 0) {
|
|
20791
|
+
const frag = { kind: "tool", text: sanitized };
|
|
20792
|
+
if (toolName !== void 0) {
|
|
20793
|
+
frag.toolName = toolName;
|
|
20794
|
+
}
|
|
20795
|
+
out.push(frag);
|
|
20796
|
+
}
|
|
20797
|
+
}
|
|
20798
|
+
if (toolName !== void 0 && toolName !== title) {
|
|
20799
|
+
const sanitized = sanitizeSingleLine(toolName);
|
|
20800
|
+
if (sanitized.length > 0) {
|
|
20801
|
+
out.push({ kind: "tool", toolName, text: sanitized });
|
|
20802
|
+
}
|
|
20803
|
+
}
|
|
20804
|
+
const rawInput = u.rawInput;
|
|
20805
|
+
if (rawInput && typeof rawInput === "object") {
|
|
20806
|
+
const serialized = safeStringify(rawInput);
|
|
20807
|
+
if (serialized.length > 0) {
|
|
20808
|
+
const frag = {
|
|
20809
|
+
kind: "tool-input",
|
|
20810
|
+
text: sanitizeSingleLine(serialized)
|
|
20811
|
+
};
|
|
20812
|
+
if (toolName !== void 0) {
|
|
20813
|
+
frag.toolName = toolName;
|
|
20814
|
+
}
|
|
20815
|
+
out.push(frag);
|
|
20816
|
+
}
|
|
20817
|
+
}
|
|
20818
|
+
const locations = u.locations;
|
|
20819
|
+
if (Array.isArray(locations) && locations.length > 0) {
|
|
20820
|
+
const serialized = safeStringify(locations);
|
|
20821
|
+
if (serialized.length > 0) {
|
|
20822
|
+
const frag = {
|
|
20823
|
+
kind: "tool-input",
|
|
20824
|
+
text: sanitizeSingleLine(serialized)
|
|
20825
|
+
};
|
|
20826
|
+
if (toolName !== void 0) {
|
|
20827
|
+
frag.toolName = toolName;
|
|
20828
|
+
}
|
|
20829
|
+
out.push(frag);
|
|
20830
|
+
}
|
|
20831
|
+
}
|
|
20832
|
+
const errorText = extractToolErrorText(u);
|
|
20833
|
+
if (errorText !== null) {
|
|
20834
|
+
const frag = { kind: "tool", text: errorText };
|
|
20835
|
+
if (toolName !== void 0) {
|
|
20836
|
+
frag.toolName = toolName;
|
|
20837
|
+
}
|
|
20838
|
+
out.push(frag);
|
|
20839
|
+
}
|
|
20840
|
+
return out;
|
|
20841
|
+
}
|
|
20842
|
+
function extractToolErrorText(u) {
|
|
20843
|
+
const content = u.content;
|
|
20844
|
+
if (Array.isArray(content)) {
|
|
20845
|
+
for (const block of content) {
|
|
20846
|
+
if (!block || typeof block !== "object") {
|
|
20847
|
+
continue;
|
|
20848
|
+
}
|
|
20849
|
+
const b = block;
|
|
20850
|
+
const inner = b.content;
|
|
20851
|
+
if (!inner || typeof inner !== "object") {
|
|
20852
|
+
continue;
|
|
20853
|
+
}
|
|
20854
|
+
const i = inner;
|
|
20855
|
+
if (i.type === "text" && typeof i.text === "string") {
|
|
20856
|
+
const s = sanitizeSingleLine(i.text);
|
|
20857
|
+
if (s.length > 0) {
|
|
20858
|
+
return s;
|
|
20859
|
+
}
|
|
20860
|
+
}
|
|
20861
|
+
}
|
|
20862
|
+
}
|
|
20863
|
+
const rawOutput = u.rawOutput;
|
|
20864
|
+
if (rawOutput && typeof rawOutput === "object") {
|
|
20865
|
+
const err = rawOutput.error;
|
|
20866
|
+
if (typeof err === "string") {
|
|
20867
|
+
const s = sanitizeSingleLine(err);
|
|
20868
|
+
if (s.length > 0) {
|
|
20869
|
+
return s;
|
|
20870
|
+
}
|
|
20871
|
+
}
|
|
20872
|
+
}
|
|
20873
|
+
return null;
|
|
20874
|
+
}
|
|
20875
|
+
function isCompatPromptReceived(u) {
|
|
20876
|
+
const meta = u._meta;
|
|
20877
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
|
20878
|
+
return false;
|
|
20879
|
+
}
|
|
20880
|
+
const hydra = meta["hydra-acp"];
|
|
20881
|
+
if (!hydra || typeof hydra !== "object" || Array.isArray(hydra)) {
|
|
20882
|
+
return false;
|
|
20883
|
+
}
|
|
20884
|
+
return hydra.compatFor === "prompt_received";
|
|
20885
|
+
}
|
|
20886
|
+
function readContentText(content) {
|
|
20887
|
+
if (typeof content === "string") {
|
|
20888
|
+
return sanitizeWireText(content);
|
|
20889
|
+
}
|
|
20890
|
+
if (!content || typeof content !== "object" || Array.isArray(content)) {
|
|
20891
|
+
return "";
|
|
20892
|
+
}
|
|
20893
|
+
const c = content;
|
|
20894
|
+
if (typeof c.text === "string") {
|
|
20895
|
+
return sanitizeWireText(c.text);
|
|
20896
|
+
}
|
|
20897
|
+
return "";
|
|
20898
|
+
}
|
|
20899
|
+
function readPromptText(prompt) {
|
|
20900
|
+
if (!Array.isArray(prompt)) {
|
|
20901
|
+
return "";
|
|
20902
|
+
}
|
|
20903
|
+
const parts = [];
|
|
20904
|
+
for (const block of prompt) {
|
|
20905
|
+
const text = readContentText(block);
|
|
20906
|
+
if (text.length > 0) {
|
|
20907
|
+
parts.push(text);
|
|
20908
|
+
}
|
|
20909
|
+
}
|
|
20910
|
+
return parts.join("");
|
|
20911
|
+
}
|
|
20912
|
+
function readString2(u, key) {
|
|
20913
|
+
const v = u[key];
|
|
20914
|
+
return typeof v === "string" ? v : void 0;
|
|
20915
|
+
}
|
|
20916
|
+
function safeStringify(value) {
|
|
20917
|
+
try {
|
|
20918
|
+
return JSON.stringify(value);
|
|
20919
|
+
} catch {
|
|
20920
|
+
return "";
|
|
20921
|
+
}
|
|
20922
|
+
}
|
|
20923
|
+
function buildSnippet(text, matchIdx, matchLen) {
|
|
20924
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
20925
|
+
if (flat.length === 0) {
|
|
20926
|
+
return "";
|
|
20927
|
+
}
|
|
20928
|
+
const flatLower = flat.toLowerCase();
|
|
20929
|
+
const needleSlice = text.slice(matchIdx, matchIdx + matchLen).toLowerCase().replace(/\s+/g, " ").trim();
|
|
20930
|
+
let pos = needleSlice.length > 0 ? flatLower.indexOf(needleSlice) : 0;
|
|
20931
|
+
if (pos === -1) {
|
|
20932
|
+
pos = 0;
|
|
20933
|
+
}
|
|
20934
|
+
const start = Math.max(0, pos - SNIPPET_SIDE);
|
|
20935
|
+
const end = Math.min(flat.length, pos + needleSlice.length + SNIPPET_SIDE);
|
|
20936
|
+
const head = start > 0 ? "\u2026" : "";
|
|
20937
|
+
const tail = end < flat.length ? "\u2026" : "";
|
|
20938
|
+
return `${head}${flat.slice(start, end)}${tail}`;
|
|
20939
|
+
}
|
|
20940
|
+
|
|
20941
|
+
// src/daemon/routes/sessions.ts
|
|
19454
20942
|
function resolveHydraHost(defaults) {
|
|
19455
20943
|
if (defaults.publicHost && defaults.publicHost.length > 0) {
|
|
19456
20944
|
return defaults.publicHost;
|
|
@@ -19466,6 +20954,17 @@ function registerSessionRoutes(app, manager, defaults) {
|
|
|
19466
20954
|
const sessions = await manager.list({ cwd: query?.cwd });
|
|
19467
20955
|
return { sessions };
|
|
19468
20956
|
});
|
|
20957
|
+
app.get("/v1/sessions/search", async (request, reply) => {
|
|
20958
|
+
const query = request.query;
|
|
20959
|
+
const q = query?.q ?? "";
|
|
20960
|
+
if (q.trim().length === 0) {
|
|
20961
|
+
reply.code(400).send({ error: "q is required" });
|
|
20962
|
+
return reply;
|
|
20963
|
+
}
|
|
20964
|
+
const ids = query?.sessionIds ? query.sessionIds.split(",").filter((s) => s.length > 0) : void 0;
|
|
20965
|
+
const out = await searchHistories(manager, q, { sessionIds: ids });
|
|
20966
|
+
return out;
|
|
20967
|
+
});
|
|
19469
20968
|
app.post("/v1/sessions", async (request, reply) => {
|
|
19470
20969
|
const body = request.body ?? {};
|
|
19471
20970
|
const cwd = expandHome(body.cwd ?? defaults.cwd);
|
|
@@ -20253,6 +21752,34 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20253
21752
|
}
|
|
20254
21753
|
return buildInitializeResult();
|
|
20255
21754
|
});
|
|
21755
|
+
if (processIdentity && deps.extensionCommands) {
|
|
21756
|
+
const registry = deps.extensionCommands;
|
|
21757
|
+
connection.onRequest("hydra-acp/register_commands", async (raw) => {
|
|
21758
|
+
const params = raw ?? {};
|
|
21759
|
+
const commands = Array.isArray(params.commands) ? params.commands.map((c) => {
|
|
21760
|
+
if (!c || typeof c !== "object") {
|
|
21761
|
+
return void 0;
|
|
21762
|
+
}
|
|
21763
|
+
const obj = c;
|
|
21764
|
+
if (typeof obj.verb !== "string" || obj.verb.length === 0) {
|
|
21765
|
+
return void 0;
|
|
21766
|
+
}
|
|
21767
|
+
const spec = { verb: obj.verb };
|
|
21768
|
+
if (typeof obj.argsHint === "string") {
|
|
21769
|
+
spec.argsHint = obj.argsHint;
|
|
21770
|
+
}
|
|
21771
|
+
if (typeof obj.description === "string") {
|
|
21772
|
+
spec.description = obj.description;
|
|
21773
|
+
}
|
|
21774
|
+
return spec;
|
|
21775
|
+
}).filter((s) => s !== void 0) : [];
|
|
21776
|
+
registry.register(processIdentity.name, connection, commands);
|
|
21777
|
+
return { ok: true, registered: commands.length };
|
|
21778
|
+
});
|
|
21779
|
+
connection.onClose(() => {
|
|
21780
|
+
registry.clear(processIdentity.name);
|
|
21781
|
+
});
|
|
21782
|
+
}
|
|
20256
21783
|
if (processIdentity?.kind === "transformer") {
|
|
20257
21784
|
connection.onRequest("transformer/initialize", async (raw) => {
|
|
20258
21785
|
const params = raw ?? {};
|
|
@@ -20800,7 +22327,10 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20800
22327
|
return null;
|
|
20801
22328
|
}
|
|
20802
22329
|
app.log.info(decision.logMessage);
|
|
20803
|
-
|
|
22330
|
+
const { modelId } = rawParams;
|
|
22331
|
+
const result = await decision.session.forwardRequest("session/set_model", rawParams);
|
|
22332
|
+
decision.session.applyModelChange(modelId);
|
|
22333
|
+
return result;
|
|
20804
22334
|
});
|
|
20805
22335
|
connection.onRequest("session/set_mode", async (rawParams) => {
|
|
20806
22336
|
const params = rawParams;
|
|
@@ -21191,13 +22721,15 @@ async function startDaemon(config, serviceToken) {
|
|
|
21191
22721
|
stderrTailBytes: config.daemon.agentStderrTailBytes,
|
|
21192
22722
|
logger: agentLogger
|
|
21193
22723
|
});
|
|
22724
|
+
const extensionCommands = new ExtensionCommandRegistry();
|
|
21194
22725
|
const manager = new SessionManager(registry, spawner, void 0, {
|
|
21195
22726
|
idleTimeoutMs: config.daemon.sessionIdleTimeoutSeconds * 1e3,
|
|
21196
22727
|
defaultModels: config.defaultModels,
|
|
21197
22728
|
defaultTransformers: config.defaultTransformers,
|
|
21198
22729
|
sessionHistoryMaxEntries: config.daemon.sessionHistoryMaxEntries,
|
|
21199
22730
|
logger: agentLogger,
|
|
21200
|
-
npmRegistry: config.npmRegistry
|
|
22731
|
+
npmRegistry: config.npmRegistry,
|
|
22732
|
+
extensionCommands
|
|
21201
22733
|
});
|
|
21202
22734
|
const extensions = new ExtensionManager(extensionList(config), void 0, {
|
|
21203
22735
|
tokenRegistry: processRegistry
|
|
@@ -21231,7 +22763,8 @@ async function startDaemon(config, serviceToken) {
|
|
|
21231
22763
|
processRegistry,
|
|
21232
22764
|
onExtensionVersion: (name, version) => extensions.reportVersion(name, version),
|
|
21233
22765
|
onTransformerVersion: (name, version) => transformers.reportVersion(name, version),
|
|
21234
|
-
transformers
|
|
22766
|
+
transformers,
|
|
22767
|
+
extensionCommands
|
|
21235
22768
|
});
|
|
21236
22769
|
await app.listen({ host: config.daemon.host, port: config.daemon.port });
|
|
21237
22770
|
const address = app.server.address();
|
|
@@ -24282,6 +25815,7 @@ async function main() {
|
|
|
24282
25815
|
const positionalAgentId = afterLaunch[0];
|
|
24283
25816
|
const agentArgs = afterLaunch.slice(1);
|
|
24284
25817
|
const { flags: flags2 } = parseArgs(beforeLaunch);
|
|
25818
|
+
rejectUnknownFlags(flags2);
|
|
24285
25819
|
if (flags2.reattach === true) {
|
|
24286
25820
|
process.stderr.write(
|
|
24287
25821
|
"hydra-acp launch: --reattach is not valid here. Pass --session <id-or-url> to attach to a specific session.\n"
|
|
@@ -24320,6 +25854,7 @@ async function main() {
|
|
|
24320
25854
|
return;
|
|
24321
25855
|
}
|
|
24322
25856
|
const { positional, flags } = parseArgs(argv);
|
|
25857
|
+
rejectUnknownFlags(flags);
|
|
24323
25858
|
if (flags.version === true || positional[0] === "--version") {
|
|
24324
25859
|
process.stdout.write(`hydra-acp ${readVersion()}
|
|
24325
25860
|
`);
|
|
@@ -24707,6 +26242,17 @@ function parseNumericFlag(flags, name) {
|
|
|
24707
26242
|
}
|
|
24708
26243
|
return void 0;
|
|
24709
26244
|
}
|
|
26245
|
+
function rejectUnknownFlags(flags) {
|
|
26246
|
+
const unknown = validateKnownFlags(flags);
|
|
26247
|
+
if (unknown === void 0) {
|
|
26248
|
+
return;
|
|
26249
|
+
}
|
|
26250
|
+
process.stderr.write(`hydra-acp: unknown flag: --${unknown}
|
|
26251
|
+
|
|
26252
|
+
`);
|
|
26253
|
+
printHelp();
|
|
26254
|
+
process.exit(2);
|
|
26255
|
+
}
|
|
24710
26256
|
function readShortPrompt(argv) {
|
|
24711
26257
|
for (let i = 0; i < argv.length; i += 1) {
|
|
24712
26258
|
const tok = argv[i];
|