@hydra-acp/cli 0.1.48 → 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 +2075 -552
- 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,11 +10150,8 @@ uncaught: ${err.stack ?? err.message}
|
|
|
9982
10150
|
}
|
|
9983
10151
|
});
|
|
9984
10152
|
|
|
9985
|
-
// src/tui/
|
|
9986
|
-
function
|
|
9987
|
-
return { filters: { cwdOnly: false, hostFilter: "__local" } };
|
|
9988
|
-
}
|
|
9989
|
-
async function pickSession(term, opts) {
|
|
10153
|
+
// src/tui/prompt-utils.ts
|
|
10154
|
+
function resetTerminalModes() {
|
|
9990
10155
|
process.stdout.write("\x1B[<u");
|
|
9991
10156
|
process.stdout.write("\x1B[?2004l");
|
|
9992
10157
|
process.stdout.write("\x1B[>4;0m");
|
|
@@ -9996,60 +10161,501 @@ async function pickSession(term, opts) {
|
|
|
9996
10161
|
process.stdout.write("\x1B[?1006l");
|
|
9997
10162
|
process.stdout.write("\x1B[?1l");
|
|
9998
10163
|
process.stdout.write("\x1B>");
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
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
|
|
10013
10206
|
};
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
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" };
|
|
10021
10255
|
}
|
|
10256
|
+
return { kind: "resolve", action: choice.key };
|
|
10022
10257
|
}
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
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
|
+
};
|
|
10028
10277
|
}
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
let widths = computeWidths(rows);
|
|
10035
|
-
let total = 1 + visible.length;
|
|
10036
|
-
let selectedIdx = 0;
|
|
10037
|
-
let scrollOffset = 0;
|
|
10038
|
-
if (opts.currentSessionId !== void 0) {
|
|
10039
|
-
const idx = visible.findIndex((s) => s.sessionId === opts.currentSessionId);
|
|
10040
|
-
if (idx >= 0) {
|
|
10041
|
-
selectedIdx = idx + 1;
|
|
10278
|
+
if (lower === "p") {
|
|
10279
|
+
return {
|
|
10280
|
+
kind: "continue",
|
|
10281
|
+
selected: Math.max(0, selected - 1)
|
|
10282
|
+
};
|
|
10042
10283
|
}
|
|
10043
|
-
|
|
10044
|
-
|
|
10284
|
+
const idx = choices.findIndex((c) => c.hotkey.toLowerCase() === lower);
|
|
10285
|
+
if (idx >= 0) {
|
|
10286
|
+
const choice = choices[idx];
|
|
10287
|
+
if (choice) {
|
|
10288
|
+
return { kind: "resolve", action: choice.key };
|
|
10289
|
+
}
|
|
10290
|
+
}
|
|
10291
|
+
}
|
|
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;
|
|
10045
10643
|
let searchTerm = "";
|
|
10046
10644
|
let mode = "normal";
|
|
10047
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;
|
|
10048
10654
|
let renameBuffer = "";
|
|
10049
10655
|
let transientStatus = null;
|
|
10050
10656
|
const composer = new InputDispatcher({ history: [] });
|
|
10051
|
-
let termHeight =
|
|
10052
|
-
let termWidth =
|
|
10657
|
+
let termHeight = readTermHeight2(term);
|
|
10658
|
+
let termWidth = readTermWidth2(term);
|
|
10053
10659
|
let viewportSize = 0;
|
|
10054
10660
|
let composerTitle = "";
|
|
10055
10661
|
let composerRoom = 0;
|
|
@@ -10061,10 +10667,16 @@ async function pickSession(term, opts) {
|
|
|
10061
10667
|
let headerLine = "";
|
|
10062
10668
|
let sessionLines = [];
|
|
10063
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;
|
|
10064
10676
|
const cwdMaxWidth = opts.config.tui.cwdColumnMaxWidth;
|
|
10065
10677
|
const computeLayout = () => {
|
|
10066
|
-
termHeight =
|
|
10067
|
-
termWidth =
|
|
10678
|
+
termHeight = readTermHeight2(term);
|
|
10679
|
+
termWidth = readTermWidth2(term);
|
|
10068
10680
|
const rowMaxWidth = Math.max(10, termWidth - ROW_PREFIX_WIDTH);
|
|
10069
10681
|
composerRoom = Math.max(10, termWidth - BOX_HORIZONTAL_PAD);
|
|
10070
10682
|
const titleBudget = Math.max(10, termWidth - 8);
|
|
@@ -10115,6 +10727,19 @@ async function pickSession(term, opts) {
|
|
|
10115
10727
|
}
|
|
10116
10728
|
adjustScroll();
|
|
10117
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
|
+
};
|
|
10118
10743
|
const adjustScroll = () => {
|
|
10119
10744
|
if (selectedIdx === 0) {
|
|
10120
10745
|
return;
|
|
@@ -10240,59 +10865,370 @@ async function pickSession(term, opts) {
|
|
|
10240
10865
|
if (visualOffset < 0 || visualOffset >= composerRows) {
|
|
10241
10866
|
return;
|
|
10242
10867
|
}
|
|
10243
|
-
const col = 3 + composerCursorCol;
|
|
10244
|
-
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
|
+
}
|
|
10245
11100
|
};
|
|
10246
|
-
const
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
}
|
|
11101
|
+
const renderFind = () => {
|
|
11102
|
+
computeFindBoxLayout();
|
|
11103
|
+
const focused = findSubMode === "input";
|
|
11104
|
+
const queryText = findComposer.state().buffer.join("\n");
|
|
10251
11105
|
withSync(() => {
|
|
10252
11106
|
term.hideCursor();
|
|
10253
|
-
computeLayout();
|
|
10254
|
-
adjustScroll();
|
|
10255
|
-
startRow = 1;
|
|
10256
11107
|
term.moveTo(1, 1).eraseDisplayBelow();
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
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();
|
|
10269
11149
|
}
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
if (selectedIdx === 0) {
|
|
10273
|
-
placeComposerCursor();
|
|
11150
|
+
if (focused) {
|
|
11151
|
+
term.moveTo(findBoxCursorCol(), findBoxCursorScreenRow());
|
|
10274
11152
|
term.hideCursor(false);
|
|
10275
11153
|
}
|
|
10276
11154
|
});
|
|
10277
11155
|
};
|
|
10278
|
-
const
|
|
11156
|
+
const repaintFindResult = (idx, focused) => {
|
|
11157
|
+
const viewportIdx = idx - findScrollOffset;
|
|
11158
|
+
if (viewportIdx < 0 || viewportIdx >= findViewportSize()) {
|
|
11159
|
+
return;
|
|
11160
|
+
}
|
|
10279
11161
|
withSync(() => {
|
|
10280
|
-
term.
|
|
10281
|
-
|
|
10282
|
-
term.
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
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();
|
|
10287
11192
|
}
|
|
10288
|
-
const [keys, desc] = entry;
|
|
10289
|
-
term.brightCyan.noFormat(` ${keys.padEnd(HELP_KEYS_WIDTH)}`);
|
|
10290
|
-
term.noFormat(desc)("\n");
|
|
10291
11193
|
}
|
|
10292
|
-
term(
|
|
10293
|
-
|
|
11194
|
+
term.moveTo(1, findResultsStartRow() + v * 2);
|
|
11195
|
+
paintFindIndicator();
|
|
10294
11196
|
});
|
|
10295
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
|
+
};
|
|
10296
11232
|
const repaintComposerChrome = () => {
|
|
10297
11233
|
withSync(() => {
|
|
10298
11234
|
const showCursor = selectedIdx === 0;
|
|
@@ -10441,23 +11377,48 @@ async function pickSession(term, opts) {
|
|
|
10441
11377
|
let resolved = false;
|
|
10442
11378
|
let autoRefreshTimer = null;
|
|
10443
11379
|
let autoRefreshInFlight = false;
|
|
10444
|
-
const
|
|
10445
|
-
|
|
10446
|
-
|
|
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();
|
|
10447
11388
|
}
|
|
10448
|
-
|
|
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();
|
|
10449
11409
|
};
|
|
10450
11410
|
const cleanup = () => {
|
|
10451
11411
|
if (resolved) {
|
|
10452
11412
|
return;
|
|
10453
11413
|
}
|
|
10454
11414
|
resolved = true;
|
|
11415
|
+
focusStack.length = 0;
|
|
10455
11416
|
if (autoRefreshTimer) {
|
|
10456
11417
|
clearInterval(autoRefreshTimer);
|
|
10457
11418
|
autoRefreshTimer = null;
|
|
10458
11419
|
}
|
|
10459
|
-
term.off("key",
|
|
10460
|
-
term.off("resize",
|
|
11420
|
+
term.off("key", dispatch);
|
|
11421
|
+
term.off("resize", dispatchResize);
|
|
10461
11422
|
process.stdout.write("\x1B[?2004l");
|
|
10462
11423
|
const tClean = term;
|
|
10463
11424
|
if (tClean.stdin && tkStdinHandler) {
|
|
@@ -10613,18 +11574,231 @@ ${cells}`;
|
|
|
10613
11574
|
paintIndicator();
|
|
10614
11575
|
return true;
|
|
10615
11576
|
};
|
|
10616
|
-
const
|
|
10617
|
-
|
|
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();
|
|
10618
11595
|
return;
|
|
10619
11596
|
}
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
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
|
+
}
|
|
10624
11663
|
return;
|
|
10625
11664
|
}
|
|
10626
|
-
|
|
10627
|
-
|
|
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") {
|
|
10628
11802
|
return;
|
|
10629
11803
|
}
|
|
10630
11804
|
if (mode === "rename") {
|
|
@@ -10688,6 +11862,10 @@ ${cells}`;
|
|
|
10688
11862
|
return;
|
|
10689
11863
|
}
|
|
10690
11864
|
clearTransient();
|
|
11865
|
+
if (name === "CTRL_F") {
|
|
11866
|
+
openFindLayer();
|
|
11867
|
+
return;
|
|
11868
|
+
}
|
|
10691
11869
|
if (selectedIdx === 0 && !searchActive) {
|
|
10692
11870
|
if (name === "ESCAPE" || name === "CTRL_C" || name === "CTRL_D") {
|
|
10693
11871
|
cleanup();
|
|
@@ -10759,8 +11937,7 @@ ${cells}`;
|
|
|
10759
11937
|
return;
|
|
10760
11938
|
}
|
|
10761
11939
|
if (!searchActive && data?.isCharacter && name === "?") {
|
|
10762
|
-
|
|
10763
|
-
renderHelp();
|
|
11940
|
+
openHelpLayer();
|
|
10764
11941
|
return;
|
|
10765
11942
|
}
|
|
10766
11943
|
if (searchActive) {
|
|
@@ -10820,13 +11997,7 @@ ${cells}`;
|
|
|
10820
11997
|
const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
10821
11998
|
prefs.filters.cwdOnly = !prefs.filters.cwdOnly;
|
|
10822
11999
|
applyFilter();
|
|
10823
|
-
|
|
10824
|
-
const idx = visible.findIndex((s) => s.sessionId === keepId);
|
|
10825
|
-
if (idx >= 0) {
|
|
10826
|
-
selectedIdx = idx + 1;
|
|
10827
|
-
adjustScroll();
|
|
10828
|
-
}
|
|
10829
|
-
}
|
|
12000
|
+
restoreCursorAfterFilter(keepId);
|
|
10830
12001
|
renderFromScratch();
|
|
10831
12002
|
return;
|
|
10832
12003
|
}
|
|
@@ -10837,13 +12008,7 @@ ${cells}`;
|
|
|
10837
12008
|
allSessions
|
|
10838
12009
|
);
|
|
10839
12010
|
applyFilter();
|
|
10840
|
-
|
|
10841
|
-
const idx = visible.findIndex((s) => s.sessionId === keepId);
|
|
10842
|
-
if (idx >= 0) {
|
|
10843
|
-
selectedIdx = idx + 1;
|
|
10844
|
-
adjustScroll();
|
|
10845
|
-
}
|
|
10846
|
-
}
|
|
12011
|
+
restoreCursorAfterFilter(keepId);
|
|
10847
12012
|
renderFromScratch();
|
|
10848
12013
|
return;
|
|
10849
12014
|
}
|
|
@@ -10980,6 +12145,12 @@ ${cells}`;
|
|
|
10980
12145
|
return;
|
|
10981
12146
|
}
|
|
10982
12147
|
};
|
|
12148
|
+
pushLayer({
|
|
12149
|
+
onKey: (name, _matches, data) => onKey(name, _matches, data),
|
|
12150
|
+
onResize: () => {
|
|
12151
|
+
if (!resolved) renderFromScratch();
|
|
12152
|
+
}
|
|
12153
|
+
});
|
|
10983
12154
|
term.grabInput({});
|
|
10984
12155
|
const tSetup = term;
|
|
10985
12156
|
if (tSetup.stdin && typeof tSetup.onStdin === "function") {
|
|
@@ -10988,10 +12159,10 @@ ${cells}`;
|
|
|
10988
12159
|
tSetup.stdin.on("data", rawStdinHandler);
|
|
10989
12160
|
process.stdout.write("\x1B[?2004h");
|
|
10990
12161
|
}
|
|
10991
|
-
term.on("key",
|
|
10992
|
-
term.on("resize",
|
|
12162
|
+
term.on("key", dispatch);
|
|
12163
|
+
term.on("resize", dispatchResize);
|
|
10993
12164
|
autoRefreshTimer = setInterval(() => {
|
|
10994
|
-
if (resolved || mode !== "normal" || searchActive || autoRefreshInFlight) {
|
|
12165
|
+
if (resolved || focusStack.length > 1 || mode !== "normal" || searchActive || autoRefreshInFlight) {
|
|
10995
12166
|
return;
|
|
10996
12167
|
}
|
|
10997
12168
|
const currentId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
@@ -11002,10 +12173,10 @@ ${cells}`;
|
|
|
11002
12173
|
}, 3e3);
|
|
11003
12174
|
});
|
|
11004
12175
|
}
|
|
11005
|
-
function
|
|
12176
|
+
function readTermHeight2(term) {
|
|
11006
12177
|
return term.height ?? 24;
|
|
11007
12178
|
}
|
|
11008
|
-
function
|
|
12179
|
+
function readTermWidth2(term) {
|
|
11009
12180
|
return term.width ?? 80;
|
|
11010
12181
|
}
|
|
11011
12182
|
function formatComposerTitle(cwd, maxWidth) {
|
|
@@ -11060,7 +12231,7 @@ function matchesSearch(s, term) {
|
|
|
11060
12231
|
}
|
|
11061
12232
|
return false;
|
|
11062
12233
|
}
|
|
11063
|
-
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;
|
|
11064
12235
|
var init_picker = __esm({
|
|
11065
12236
|
"src/tui/picker.ts"() {
|
|
11066
12237
|
"use strict";
|
|
@@ -11070,9 +12241,11 @@ var init_picker = __esm({
|
|
|
11070
12241
|
init_discovery();
|
|
11071
12242
|
init_input();
|
|
11072
12243
|
init_screen();
|
|
12244
|
+
init_import_action_prompt();
|
|
11073
12245
|
init_sync();
|
|
11074
12246
|
ROW_PREFIX_WIDTH = 2;
|
|
11075
12247
|
PICKER_COMPOSER_MAX_ROWS = 4;
|
|
12248
|
+
FIND_BOX_MAX_ROWS = 4;
|
|
11076
12249
|
BOX_HORIZONTAL_PAD = 4;
|
|
11077
12250
|
HELP_KEYS_WIDTH = 20;
|
|
11078
12251
|
HELP_ENTRIES = [
|
|
@@ -11085,7 +12258,8 @@ var init_picker = __esm({
|
|
|
11085
12258
|
["Enter", "open selected session"],
|
|
11086
12259
|
["v", "view-only (open transcript without spawning the agent)"],
|
|
11087
12260
|
null,
|
|
11088
|
-
["/", "search sessions"],
|
|
12261
|
+
["/", "search sessions (metadata)"],
|
|
12262
|
+
["^f", "find in session history (content + tool inputs)"],
|
|
11089
12263
|
["o", "toggle cwd-only filter"],
|
|
11090
12264
|
["h", "cycle host filter (local / <peer> / all)"],
|
|
11091
12265
|
["r", "refresh from daemon"],
|
|
@@ -11121,100 +12295,10 @@ async function validateLocalCwd(input) {
|
|
|
11121
12295
|
}
|
|
11122
12296
|
return { ok: true, path: resolved };
|
|
11123
12297
|
}
|
|
11124
|
-
var init_cwd = __esm({
|
|
11125
|
-
"src/core/cwd.ts"() {
|
|
11126
|
-
"use strict";
|
|
11127
|
-
init_config();
|
|
11128
|
-
}
|
|
11129
|
-
});
|
|
11130
|
-
|
|
11131
|
-
// src/tui/prompt-utils.ts
|
|
11132
|
-
function resetTerminalModes() {
|
|
11133
|
-
process.stdout.write("\x1B[<u");
|
|
11134
|
-
process.stdout.write("\x1B[?2004l");
|
|
11135
|
-
process.stdout.write("\x1B[>4;0m");
|
|
11136
|
-
process.stdout.write("\x1B[>5;0m");
|
|
11137
|
-
process.stdout.write("\x1B[?1000l");
|
|
11138
|
-
process.stdout.write("\x1B[?1002l");
|
|
11139
|
-
process.stdout.write("\x1B[?1006l");
|
|
11140
|
-
process.stdout.write("\x1B[?1l");
|
|
11141
|
-
process.stdout.write("\x1B>");
|
|
11142
|
-
}
|
|
11143
|
-
function readTermWidth2(term) {
|
|
11144
|
-
return term.width ?? 80;
|
|
11145
|
-
}
|
|
11146
|
-
function readTermHeight2(term) {
|
|
11147
|
-
return term.height ?? 24;
|
|
11148
|
-
}
|
|
11149
|
-
function drawBox(term, opts) {
|
|
11150
|
-
const termW = readTermWidth2(term);
|
|
11151
|
-
const termH = readTermHeight2(term);
|
|
11152
|
-
const desiredContentW = opts.contentWidth ?? MAX_BOX_WIDTH;
|
|
11153
|
-
const maxContentW = Math.max(10, Math.min(MAX_BOX_WIDTH, termW - 4));
|
|
11154
|
-
const contentW = Math.min(desiredContentW, maxContentW);
|
|
11155
|
-
const w = contentW + 2;
|
|
11156
|
-
const contentH = Math.max(1, Math.min(opts.contentHeight, termH - 4));
|
|
11157
|
-
const h = contentH + 2;
|
|
11158
|
-
const x = Math.max(1, Math.floor((termW - w) / 2) + 1);
|
|
11159
|
-
const y = Math.max(1, Math.floor((termH - h) / 2) + 1);
|
|
11160
|
-
term.moveTo(1, 1).eraseDisplayBelow();
|
|
11161
|
-
const topInner = HORIZ.repeat(w - 2);
|
|
11162
|
-
const top = renderTitleStrip(topInner, opts.title);
|
|
11163
|
-
term.moveTo(x, y);
|
|
11164
|
-
term.dim.noFormat(TL);
|
|
11165
|
-
paintTopStrip(term, top);
|
|
11166
|
-
term.dim.noFormat(TR);
|
|
11167
|
-
for (let row = 1; row <= contentH; row++) {
|
|
11168
|
-
term.moveTo(x, y + row);
|
|
11169
|
-
term.dim.noFormat(VERT);
|
|
11170
|
-
term.moveTo(x + w - 1, y + row);
|
|
11171
|
-
term.dim.noFormat(VERT);
|
|
11172
|
-
}
|
|
11173
|
-
term.moveTo(x, y + h - 1);
|
|
11174
|
-
term.dim.noFormat(BL + HORIZ.repeat(w - 2) + BR);
|
|
11175
|
-
return {
|
|
11176
|
-
x,
|
|
11177
|
-
y,
|
|
11178
|
-
w,
|
|
11179
|
-
h,
|
|
11180
|
-
contentX: x + 1,
|
|
11181
|
-
contentY: y + 1,
|
|
11182
|
-
contentW,
|
|
11183
|
-
contentH
|
|
11184
|
-
};
|
|
11185
|
-
}
|
|
11186
|
-
function renderTitleStrip(innerDashes, title) {
|
|
11187
|
-
if (!title) {
|
|
11188
|
-
return { dashes: innerDashes };
|
|
11189
|
-
}
|
|
11190
|
-
const chip = ` ${title} `;
|
|
11191
|
-
if (chip.length + 4 > innerDashes.length) {
|
|
11192
|
-
return { dashes: innerDashes };
|
|
11193
|
-
}
|
|
11194
|
-
const offset = 2;
|
|
11195
|
-
const dashes = innerDashes.slice(0, offset) + " ".repeat(chip.length) + innerDashes.slice(offset + chip.length);
|
|
11196
|
-
return { dashes, title: { offset, text: chip } };
|
|
11197
|
-
}
|
|
11198
|
-
function paintTopStrip(term, strip) {
|
|
11199
|
-
if (!strip.title) {
|
|
11200
|
-
term.dim.noFormat(strip.dashes);
|
|
11201
|
-
return;
|
|
11202
|
-
}
|
|
11203
|
-
term.dim.noFormat(strip.dashes.slice(0, strip.title.offset));
|
|
11204
|
-
term.brightCyan.noFormat(strip.title.text);
|
|
11205
|
-
term.dim.noFormat(strip.dashes.slice(strip.title.offset + strip.title.text.length));
|
|
11206
|
-
}
|
|
11207
|
-
var MAX_BOX_WIDTH, HORIZ, VERT, TL, TR, BL, BR;
|
|
11208
|
-
var init_prompt_utils = __esm({
|
|
11209
|
-
"src/tui/prompt-utils.ts"() {
|
|
12298
|
+
var init_cwd = __esm({
|
|
12299
|
+
"src/core/cwd.ts"() {
|
|
11210
12300
|
"use strict";
|
|
11211
|
-
|
|
11212
|
-
HORIZ = "\u2500";
|
|
11213
|
-
VERT = "\u2502";
|
|
11214
|
-
TL = "\u250C";
|
|
11215
|
-
TR = "\u2510";
|
|
11216
|
-
BL = "\u2514";
|
|
11217
|
-
BR = "\u2518";
|
|
12301
|
+
init_config();
|
|
11218
12302
|
}
|
|
11219
12303
|
});
|
|
11220
12304
|
|
|
@@ -11246,7 +12330,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11246
12330
|
for (const hr of headerRows) {
|
|
11247
12331
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11248
12332
|
term.dim.noFormat(` ${hr.label}`);
|
|
11249
|
-
term.noFormat(
|
|
12333
|
+
term.noFormat(truncate3(hr.value, innerW - hr.label.length - 2));
|
|
11250
12334
|
row++;
|
|
11251
12335
|
}
|
|
11252
12336
|
row++;
|
|
@@ -11257,7 +12341,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11257
12341
|
row += 2;
|
|
11258
12342
|
if (errorLine !== null) {
|
|
11259
12343
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11260
|
-
term.red.noFormat(` ${
|
|
12344
|
+
term.red.noFormat(` ${truncate3(errorLine, innerW - 2)}`);
|
|
11261
12345
|
} else {
|
|
11262
12346
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11263
12347
|
term.dim.noFormat(
|
|
@@ -11293,7 +12377,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11293
12377
|
term.dim.noFormat("\u2502");
|
|
11294
12378
|
term.moveTo(layout.contentX, layout.contentY + errRow);
|
|
11295
12379
|
if (errorLine !== null) {
|
|
11296
|
-
term.red.noFormat(` ${
|
|
12380
|
+
term.red.noFormat(` ${truncate3(errorLine, layout.contentW - 2)}`);
|
|
11297
12381
|
} else {
|
|
11298
12382
|
term.dim.noFormat(
|
|
11299
12383
|
" Enter accept \xB7 Esc back \xB7 ^U clear \xB7 ^W kill word"
|
|
@@ -11389,7 +12473,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
11389
12473
|
term.on("resize", onResize);
|
|
11390
12474
|
});
|
|
11391
12475
|
}
|
|
11392
|
-
function
|
|
12476
|
+
function truncate3(s, max) {
|
|
11393
12477
|
if (max <= 1) {
|
|
11394
12478
|
return "";
|
|
11395
12479
|
}
|
|
@@ -11417,226 +12501,6 @@ var init_import_cwd_prompt = __esm({
|
|
|
11417
12501
|
}
|
|
11418
12502
|
});
|
|
11419
12503
|
|
|
11420
|
-
// src/tui/import-action-prompt.ts
|
|
11421
|
-
function actionPromptStep(selected, key, choices = ACTION_CHOICES) {
|
|
11422
|
-
if (key.kind === "cancel") {
|
|
11423
|
-
return { kind: "cancel" };
|
|
11424
|
-
}
|
|
11425
|
-
if (key.kind === "back") {
|
|
11426
|
-
return { kind: "back" };
|
|
11427
|
-
}
|
|
11428
|
-
if (key.kind === "enter") {
|
|
11429
|
-
const choice = choices[selected];
|
|
11430
|
-
if (!choice) {
|
|
11431
|
-
return { kind: "back" };
|
|
11432
|
-
}
|
|
11433
|
-
return { kind: "resolve", action: choice.key };
|
|
11434
|
-
}
|
|
11435
|
-
if (key.kind === "up") {
|
|
11436
|
-
return {
|
|
11437
|
-
kind: "continue",
|
|
11438
|
-
selected: Math.max(0, selected - 1)
|
|
11439
|
-
};
|
|
11440
|
-
}
|
|
11441
|
-
if (key.kind === "down") {
|
|
11442
|
-
return {
|
|
11443
|
-
kind: "continue",
|
|
11444
|
-
selected: Math.min(choices.length - 1, selected + 1)
|
|
11445
|
-
};
|
|
11446
|
-
}
|
|
11447
|
-
if (key.kind === "char") {
|
|
11448
|
-
const lower = key.ch.toLowerCase();
|
|
11449
|
-
if (lower === "n") {
|
|
11450
|
-
return {
|
|
11451
|
-
kind: "continue",
|
|
11452
|
-
selected: Math.min(choices.length - 1, selected + 1)
|
|
11453
|
-
};
|
|
11454
|
-
}
|
|
11455
|
-
if (lower === "p") {
|
|
11456
|
-
return {
|
|
11457
|
-
kind: "continue",
|
|
11458
|
-
selected: Math.max(0, selected - 1)
|
|
11459
|
-
};
|
|
11460
|
-
}
|
|
11461
|
-
const idx = choices.findIndex((c) => c.hotkey.toLowerCase() === lower);
|
|
11462
|
-
if (idx >= 0) {
|
|
11463
|
-
const choice = choices[idx];
|
|
11464
|
-
if (choice) {
|
|
11465
|
-
return { kind: "resolve", action: choice.key };
|
|
11466
|
-
}
|
|
11467
|
-
}
|
|
11468
|
-
}
|
|
11469
|
-
return { kind: "continue", selected };
|
|
11470
|
-
}
|
|
11471
|
-
async function promptForImportAction(term, session) {
|
|
11472
|
-
resetTerminalModes();
|
|
11473
|
-
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
11474
|
-
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
11475
|
-
const originalCwd = shortenHomePath(session.cwd);
|
|
11476
|
-
let selected = ACTION_CHOICES.findIndex((c) => c.key === "view");
|
|
11477
|
-
if (selected < 0) {
|
|
11478
|
-
selected = 0;
|
|
11479
|
-
}
|
|
11480
|
-
const render = () => {
|
|
11481
|
-
const choiceRows = ACTION_CHOICES.length * 2;
|
|
11482
|
-
const contentHeight = 7 + choiceRows + 2;
|
|
11483
|
-
const layout = drawBox(term, {
|
|
11484
|
-
contentHeight,
|
|
11485
|
-
title: "Imported session"
|
|
11486
|
-
});
|
|
11487
|
-
const innerW = layout.contentW;
|
|
11488
|
-
const headerRows = [
|
|
11489
|
-
{ label: "session: ", value: shortId2 },
|
|
11490
|
-
{ label: "from: ", value: fromMachine },
|
|
11491
|
-
{ label: "cwd: ", value: originalCwd }
|
|
11492
|
-
];
|
|
11493
|
-
let row = 0;
|
|
11494
|
-
for (const hr of headerRows) {
|
|
11495
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11496
|
-
term.dim.noFormat(` ${hr.label}`);
|
|
11497
|
-
term.noFormat(truncate3(hr.value, innerW - hr.label.length - 2));
|
|
11498
|
-
row++;
|
|
11499
|
-
}
|
|
11500
|
-
row++;
|
|
11501
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11502
|
-
term.noFormat(" What do you want to do?");
|
|
11503
|
-
row += 2;
|
|
11504
|
-
for (let i = 0; i < ACTION_CHOICES.length; i++) {
|
|
11505
|
-
const choice = ACTION_CHOICES[i];
|
|
11506
|
-
if (!choice) {
|
|
11507
|
-
continue;
|
|
11508
|
-
}
|
|
11509
|
-
const pointer = i === selected ? "\u276F" : " ";
|
|
11510
|
-
const label = ` ${pointer} ${choice.label}`;
|
|
11511
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11512
|
-
if (i === selected) {
|
|
11513
|
-
term.brightWhite.bgBlue.noFormat(padRight(label, innerW));
|
|
11514
|
-
} else {
|
|
11515
|
-
term.noFormat(label);
|
|
11516
|
-
}
|
|
11517
|
-
row++;
|
|
11518
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11519
|
-
term.dim.noFormat(` ${choice.description}`);
|
|
11520
|
-
row++;
|
|
11521
|
-
}
|
|
11522
|
-
row++;
|
|
11523
|
-
term.moveTo(layout.contentX, layout.contentY + row);
|
|
11524
|
-
term.dim.noFormat(" \u2191/\u2193 navigate \xB7 Enter select \xB7 f/v jump \xB7 Esc back");
|
|
11525
|
-
return layout;
|
|
11526
|
-
};
|
|
11527
|
-
render();
|
|
11528
|
-
term.hideCursor();
|
|
11529
|
-
return await new Promise((resolve6) => {
|
|
11530
|
-
let resolved = false;
|
|
11531
|
-
const cleanup = () => {
|
|
11532
|
-
if (resolved) {
|
|
11533
|
-
return;
|
|
11534
|
-
}
|
|
11535
|
-
resolved = true;
|
|
11536
|
-
term.off("key", onKey);
|
|
11537
|
-
term.off("resize", onResize);
|
|
11538
|
-
term.grabInput(false);
|
|
11539
|
-
term.hideCursor(false);
|
|
11540
|
-
term.moveTo(1, 1).eraseDisplayBelow();
|
|
11541
|
-
};
|
|
11542
|
-
const finish = (value) => {
|
|
11543
|
-
cleanup();
|
|
11544
|
-
resolve6(value);
|
|
11545
|
-
};
|
|
11546
|
-
const onResize = () => {
|
|
11547
|
-
if (resolved) {
|
|
11548
|
-
return;
|
|
11549
|
-
}
|
|
11550
|
-
render();
|
|
11551
|
-
};
|
|
11552
|
-
const onKey = (name, _matches, data) => {
|
|
11553
|
-
const input = mapKey(name, data);
|
|
11554
|
-
if (!input) {
|
|
11555
|
-
return;
|
|
11556
|
-
}
|
|
11557
|
-
const step = actionPromptStep(selected, input);
|
|
11558
|
-
if (step.kind === "cancel") {
|
|
11559
|
-
finish("cancel");
|
|
11560
|
-
return;
|
|
11561
|
-
}
|
|
11562
|
-
if (step.kind === "back") {
|
|
11563
|
-
finish("back");
|
|
11564
|
-
return;
|
|
11565
|
-
}
|
|
11566
|
-
if (step.kind === "resolve") {
|
|
11567
|
-
finish(step.action);
|
|
11568
|
-
return;
|
|
11569
|
-
}
|
|
11570
|
-
if (step.selected !== selected) {
|
|
11571
|
-
selected = step.selected;
|
|
11572
|
-
render();
|
|
11573
|
-
}
|
|
11574
|
-
};
|
|
11575
|
-
term.grabInput({});
|
|
11576
|
-
term.on("key", onKey);
|
|
11577
|
-
term.on("resize", onResize);
|
|
11578
|
-
});
|
|
11579
|
-
}
|
|
11580
|
-
function mapKey(name, data) {
|
|
11581
|
-
if (name === "UP") {
|
|
11582
|
-
return { kind: "up" };
|
|
11583
|
-
}
|
|
11584
|
-
if (name === "DOWN") {
|
|
11585
|
-
return { kind: "down" };
|
|
11586
|
-
}
|
|
11587
|
-
if (name === "ENTER" || name === "KP_ENTER") {
|
|
11588
|
-
return { kind: "enter" };
|
|
11589
|
-
}
|
|
11590
|
-
if (name === "ESCAPE") {
|
|
11591
|
-
return { kind: "back" };
|
|
11592
|
-
}
|
|
11593
|
-
if (name === "CTRL_C" || name === "CTRL_D") {
|
|
11594
|
-
return { kind: "cancel" };
|
|
11595
|
-
}
|
|
11596
|
-
if (data?.isCharacter) {
|
|
11597
|
-
return { kind: "char", ch: name };
|
|
11598
|
-
}
|
|
11599
|
-
return null;
|
|
11600
|
-
}
|
|
11601
|
-
function truncate3(s, max) {
|
|
11602
|
-
if (max <= 1) {
|
|
11603
|
-
return "";
|
|
11604
|
-
}
|
|
11605
|
-
if (s.length <= max) {
|
|
11606
|
-
return s;
|
|
11607
|
-
}
|
|
11608
|
-
return s.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
11609
|
-
}
|
|
11610
|
-
function padRight(s, w) {
|
|
11611
|
-
if (s.length >= w) {
|
|
11612
|
-
return s.slice(0, w);
|
|
11613
|
-
}
|
|
11614
|
-
return s + " ".repeat(w - s.length);
|
|
11615
|
-
}
|
|
11616
|
-
var ACTION_CHOICES;
|
|
11617
|
-
var init_import_action_prompt = __esm({
|
|
11618
|
-
"src/tui/import-action-prompt.ts"() {
|
|
11619
|
-
"use strict";
|
|
11620
|
-
init_paths();
|
|
11621
|
-
init_session();
|
|
11622
|
-
init_prompt_utils();
|
|
11623
|
-
ACTION_CHOICES = [
|
|
11624
|
-
{
|
|
11625
|
-
key: "fork-local",
|
|
11626
|
-
label: "Fork locally",
|
|
11627
|
-
hotkey: "f",
|
|
11628
|
-
description: "spawn a local fork \u2014 original imported copy stays as-is"
|
|
11629
|
-
},
|
|
11630
|
-
{
|
|
11631
|
-
key: "view",
|
|
11632
|
-
label: "View transcript",
|
|
11633
|
-
hotkey: "v",
|
|
11634
|
-
description: "open read-only, no agent spawn"
|
|
11635
|
-
}
|
|
11636
|
-
];
|
|
11637
|
-
}
|
|
11638
|
-
});
|
|
11639
|
-
|
|
11640
12504
|
// src/tui/clipboard.ts
|
|
11641
12505
|
import { spawn as nodeSpawn } from "child_process";
|
|
11642
12506
|
import fs21 from "fs/promises";
|
|
@@ -12035,41 +12899,69 @@ function formatEvent(event) {
|
|
|
12035
12899
|
return [];
|
|
12036
12900
|
}
|
|
12037
12901
|
}
|
|
12038
|
-
function applyInlineMarkup(text) {
|
|
12902
|
+
function applyInlineMarkup(text, opts) {
|
|
12903
|
+
const codeOpen = opts?.codeOpen ?? "^C";
|
|
12904
|
+
const boldReset = opts?.boldReset ?? "^:";
|
|
12905
|
+
const codeReset = opts?.codeReset ?? "^:";
|
|
12039
12906
|
let s = text.replace(/\^/g, "^^");
|
|
12040
|
-
s = s.replace(/\*\*(.+?)\*\*/g,
|
|
12041
|
-
s = s.replace(/`([^`]+)`/g,
|
|
12907
|
+
s = s.replace(/\*\*(.+?)\*\*/g, `^+$1${boldReset}`);
|
|
12908
|
+
s = s.replace(/`([^`]+)`/g, `${codeOpen}$1${codeReset}`);
|
|
12042
12909
|
return s;
|
|
12043
12910
|
}
|
|
12044
|
-
function
|
|
12911
|
+
function parseMarkdown(text, opts) {
|
|
12912
|
+
const {
|
|
12913
|
+
proseStyle,
|
|
12914
|
+
highlightCode,
|
|
12915
|
+
prefixStyle,
|
|
12916
|
+
firstPrefix = " ",
|
|
12917
|
+
inlineOpts
|
|
12918
|
+
} = opts;
|
|
12045
12919
|
const out = [];
|
|
12046
12920
|
const lines = text.split("\n");
|
|
12047
12921
|
let inCode = false;
|
|
12048
12922
|
let codeLang = "";
|
|
12049
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
|
+
};
|
|
12050
12937
|
const flushCode = () => {
|
|
12051
|
-
if (codeBuffer.length === 0)
|
|
12938
|
+
if (codeBuffer.length === 0)
|
|
12052
12939
|
return;
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
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);
|
|
12064
12954
|
}
|
|
12065
|
-
|
|
12955
|
+
} else {
|
|
12956
|
+
for (const cl of codeBuffer)
|
|
12957
|
+
line(cl.replace(/\^/g, "^^"), proseStyle);
|
|
12066
12958
|
}
|
|
12067
12959
|
codeBuffer = [];
|
|
12068
12960
|
codeLang = "";
|
|
12069
12961
|
};
|
|
12070
12962
|
for (let i = 0; i < lines.length; i++) {
|
|
12071
|
-
const
|
|
12072
|
-
const fence =
|
|
12963
|
+
const l = lines[i];
|
|
12964
|
+
const fence = l.match(/^\s*```\s*(\w*)\s*$/);
|
|
12073
12965
|
if (fence) {
|
|
12074
12966
|
if (!inCode) {
|
|
12075
12967
|
inCode = true;
|
|
@@ -12081,68 +12973,81 @@ function parseAgentMarkdown(text) {
|
|
|
12081
12973
|
continue;
|
|
12082
12974
|
}
|
|
12083
12975
|
if (inCode) {
|
|
12084
|
-
codeBuffer.push(
|
|
12976
|
+
codeBuffer.push(l);
|
|
12085
12977
|
continue;
|
|
12086
12978
|
}
|
|
12087
|
-
const heading =
|
|
12979
|
+
const heading = l.match(/^(#{1,6})\s+(.*)$/);
|
|
12088
12980
|
if (heading) {
|
|
12089
12981
|
const level = heading[1].length;
|
|
12090
|
-
const
|
|
12091
|
-
const
|
|
12092
|
-
|
|
12093
|
-
prefix: " ",
|
|
12094
|
-
body: text2,
|
|
12095
|
-
bodyStyle: style
|
|
12096
|
-
});
|
|
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());
|
|
12097
12985
|
continue;
|
|
12098
12986
|
}
|
|
12099
12987
|
const next = lines[i + 1];
|
|
12100
|
-
if (
|
|
12101
|
-
const header = parseTableRow(
|
|
12988
|
+
if (l.includes("|") && next !== void 0 && isTableSeparatorLine(next) && parseTableRow(l).length === parseTableRow(next).length) {
|
|
12989
|
+
const header = parseTableRow(l);
|
|
12102
12990
|
const body = [];
|
|
12103
12991
|
let j = i + 2;
|
|
12104
12992
|
while (j < lines.length && lines[j].includes("|")) {
|
|
12105
12993
|
body.push(parseTableRow(lines[j]));
|
|
12106
12994
|
j++;
|
|
12107
12995
|
}
|
|
12108
|
-
|
|
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
|
+
}
|
|
12109
13002
|
i = j - 1;
|
|
12110
13003
|
continue;
|
|
12111
13004
|
}
|
|
12112
|
-
const bullet =
|
|
13005
|
+
const bullet = l.match(/^(\s*)[-*+]\s+(.*)$/);
|
|
12113
13006
|
if (bullet) {
|
|
12114
13007
|
const indent = bullet[1] ?? "";
|
|
12115
|
-
const item = bullet[2] ?? "";
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
13008
|
+
const item = bullet[2] ?? "";
|
|
13009
|
+
line(
|
|
13010
|
+
`${indent}\u2022 ${applyInlineMarkup(item, inlineOpts)}`,
|
|
13011
|
+
proseStyle,
|
|
13012
|
+
nextPrefix()
|
|
13013
|
+
);
|
|
12121
13014
|
continue;
|
|
12122
13015
|
}
|
|
12123
|
-
const ordered =
|
|
13016
|
+
const ordered = l.match(/^(\s*)(\d+)\.\s+(.*)$/);
|
|
12124
13017
|
if (ordered) {
|
|
12125
13018
|
const indent = ordered[1] ?? "";
|
|
12126
13019
|
const num = ordered[2] ?? "";
|
|
12127
13020
|
const item = ordered[3] ?? "";
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
13021
|
+
line(
|
|
13022
|
+
`${indent}${num}. ${applyInlineMarkup(item, inlineOpts)}`,
|
|
13023
|
+
proseStyle,
|
|
13024
|
+
nextPrefix()
|
|
13025
|
+
);
|
|
12133
13026
|
continue;
|
|
12134
13027
|
}
|
|
12135
|
-
|
|
12136
|
-
|
|
12137
|
-
|
|
12138
|
-
|
|
12139
|
-
|
|
13028
|
+
const isBlank = l.trim() === "";
|
|
13029
|
+
line(
|
|
13030
|
+
applyInlineMarkup(l, inlineOpts),
|
|
13031
|
+
proseStyle,
|
|
13032
|
+
isBlank ? " " : nextPrefix()
|
|
13033
|
+
);
|
|
12140
13034
|
}
|
|
12141
|
-
if (inCode)
|
|
13035
|
+
if (inCode)
|
|
12142
13036
|
flushCode();
|
|
12143
|
-
}
|
|
12144
13037
|
return out;
|
|
12145
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
|
+
}
|
|
12146
13051
|
function parseTableRow(line) {
|
|
12147
13052
|
let s = line.trim();
|
|
12148
13053
|
if (s.startsWith("|")) {
|
|
@@ -14169,6 +15074,33 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14169
15074
|
agentKey = null;
|
|
14170
15075
|
agentBuffer = "";
|
|
14171
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
|
+
};
|
|
14172
15104
|
const renderToolsBlock = () => {
|
|
14173
15105
|
if (toolsBlockStartedAt === null) {
|
|
14174
15106
|
return;
|
|
@@ -14310,6 +15242,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14310
15242
|
recordHistoryEntry(event.text);
|
|
14311
15243
|
}
|
|
14312
15244
|
closeAgentText();
|
|
15245
|
+
closeThought();
|
|
14313
15246
|
if (toolsBlockStartedAt !== null) {
|
|
14314
15247
|
toolsBlockEndedAt = Date.now();
|
|
14315
15248
|
renderToolsBlock();
|
|
@@ -14333,16 +15266,18 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14333
15266
|
return;
|
|
14334
15267
|
}
|
|
14335
15268
|
if (event.kind === "agent-text") {
|
|
15269
|
+
closeThought();
|
|
14336
15270
|
appendAgentText(event.text);
|
|
14337
15271
|
return;
|
|
14338
15272
|
}
|
|
14339
15273
|
if (event.kind === "agent-thought") {
|
|
14340
15274
|
closeAgentText();
|
|
14341
|
-
|
|
15275
|
+
appendThought(event.text);
|
|
14342
15276
|
return;
|
|
14343
15277
|
}
|
|
14344
15278
|
if (event.kind === "exit-plan-mode") {
|
|
14345
15279
|
closeAgentText();
|
|
15280
|
+
closeThought();
|
|
14346
15281
|
const existing = exitPlanStates.get(event.toolCallId);
|
|
14347
15282
|
const merged = {
|
|
14348
15283
|
plan: event.plan ?? existing?.plan ?? "",
|
|
@@ -14360,12 +15295,14 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14360
15295
|
}
|
|
14361
15296
|
if (event.kind === "tool-call") {
|
|
14362
15297
|
closeAgentText();
|
|
15298
|
+
closeThought();
|
|
14363
15299
|
recordToolCall(event.toolCallId, event.title, event.status, void 0);
|
|
14364
15300
|
renderToolsBlock();
|
|
14365
15301
|
return;
|
|
14366
15302
|
}
|
|
14367
15303
|
if (event.kind === "plan") {
|
|
14368
15304
|
closeAgentText();
|
|
15305
|
+
closeThought();
|
|
14369
15306
|
lastPlanEvent = event;
|
|
14370
15307
|
const lines = formatEvent(event);
|
|
14371
15308
|
if (lines.length > 0) {
|
|
@@ -14375,6 +15312,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14375
15312
|
}
|
|
14376
15313
|
if (event.kind === "tool-call-update") {
|
|
14377
15314
|
closeAgentText();
|
|
15315
|
+
closeThought();
|
|
14378
15316
|
recordToolCall(
|
|
14379
15317
|
event.toolCallId,
|
|
14380
15318
|
event.title,
|
|
@@ -14397,6 +15335,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14397
15335
|
if (event.kind === "turn-complete") {
|
|
14398
15336
|
currentHeadMessageId = void 0;
|
|
14399
15337
|
closeAgentText();
|
|
15338
|
+
closeThought();
|
|
14400
15339
|
let effectiveStopReason = event.amended ? "amended" : event.stopReason;
|
|
14401
15340
|
if (!event.amended && upstreamInterruptedSeen && (effectiveStopReason === void 0 || effectiveStopReason === "end_turn")) {
|
|
14402
15341
|
effectiveStopReason = "error";
|
|
@@ -14491,6 +15430,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14491
15430
|
resolve6({ outcome: { outcome: "cancelled" } });
|
|
14492
15431
|
}
|
|
14493
15432
|
closeAgentText();
|
|
15433
|
+
closeThought();
|
|
14494
15434
|
};
|
|
14495
15435
|
const markToolsBlockRecoveryFailed = () => {
|
|
14496
15436
|
if (toolsBlockStartedAt === null) {
|
|
@@ -14939,6 +15879,9 @@ import { dirname as dirname6, resolve as resolve5 } from "path";
|
|
|
14939
15879
|
var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
14940
15880
|
"all",
|
|
14941
15881
|
"detach",
|
|
15882
|
+
"disabled",
|
|
15883
|
+
"follow",
|
|
15884
|
+
"force",
|
|
14942
15885
|
"foreground",
|
|
14943
15886
|
"help",
|
|
14944
15887
|
"info",
|
|
@@ -14951,6 +15894,31 @@ var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
14951
15894
|
"stream",
|
|
14952
15895
|
"version"
|
|
14953
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
|
+
}
|
|
14954
15922
|
function parseArgs(argv) {
|
|
14955
15923
|
const positional = [];
|
|
14956
15924
|
const flags = {};
|
|
@@ -16453,6 +17421,7 @@ var SessionManager = class {
|
|
|
16453
17421
|
this.defaultTransformers = options.defaultTransformers ?? [];
|
|
16454
17422
|
this.logger = options.logger;
|
|
16455
17423
|
this.npmRegistry = options.npmRegistry;
|
|
17424
|
+
this.extensionCommands = options.extensionCommands;
|
|
16456
17425
|
}
|
|
16457
17426
|
registry;
|
|
16458
17427
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -16471,6 +17440,7 @@ var SessionManager = class {
|
|
|
16471
17440
|
metaWriteQueues = /* @__PURE__ */ new Map();
|
|
16472
17441
|
logger;
|
|
16473
17442
|
npmRegistry;
|
|
17443
|
+
extensionCommands;
|
|
16474
17444
|
async create(params) {
|
|
16475
17445
|
const fresh = await this.bootstrapAgent({
|
|
16476
17446
|
agentId: params.agentId,
|
|
@@ -16524,7 +17494,8 @@ var SessionManager = class {
|
|
|
16524
17494
|
agentModes: fresh.initialModes,
|
|
16525
17495
|
agentModels: fresh.initialModels,
|
|
16526
17496
|
transformChain: params.transformChain,
|
|
16527
|
-
parentSessionId: params.parentSessionId
|
|
17497
|
+
parentSessionId: params.parentSessionId,
|
|
17498
|
+
extensionCommands: this.extensionCommands
|
|
16528
17499
|
});
|
|
16529
17500
|
await this.attachManagerHooks(session);
|
|
16530
17501
|
return session;
|
|
@@ -16595,12 +17566,14 @@ var SessionManager = class {
|
|
|
16595
17566
|
}
|
|
16596
17567
|
let loadResult;
|
|
16597
17568
|
try {
|
|
17569
|
+
const loadMeta = buildSessionLoadMeta(params.agentId, params.currentModel);
|
|
16598
17570
|
loadResult = await agent.connection.request(
|
|
16599
17571
|
"session/load",
|
|
16600
17572
|
{
|
|
16601
17573
|
sessionId: params.upstreamSessionId,
|
|
16602
17574
|
cwd: params.cwd,
|
|
16603
|
-
mcpServers: []
|
|
17575
|
+
mcpServers: [],
|
|
17576
|
+
...loadMeta && { _meta: loadMeta }
|
|
16604
17577
|
}
|
|
16605
17578
|
);
|
|
16606
17579
|
} catch (err) {
|
|
@@ -16616,7 +17589,10 @@ var SessionManager = class {
|
|
|
16616
17589
|
() => void 0
|
|
16617
17590
|
);
|
|
16618
17591
|
} else {
|
|
16619
|
-
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
|
+
);
|
|
16620
17596
|
}
|
|
16621
17597
|
const agentReportedMode = extractInitialCurrentMode(loadResult ?? {});
|
|
16622
17598
|
const advertisedModes = params.agentModes ?? nonEmptyOrUndefined(extractInitialModes(loadResult ?? {}));
|
|
@@ -16634,6 +17610,30 @@ var SessionManager = class {
|
|
|
16634
17610
|
this.logger?.info(
|
|
16635
17611
|
`resurrect: effectiveMode=${JSON.stringify(effectiveMode)} for sessionId=${params.hydraSessionId}`
|
|
16636
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
|
+
}
|
|
16637
17637
|
const session = new Session({
|
|
16638
17638
|
sessionId: params.hydraSessionId,
|
|
16639
17639
|
cwd: params.cwd,
|
|
@@ -16650,11 +17650,7 @@ var SessionManager = class {
|
|
|
16650
17650
|
listSessions: () => this.list(),
|
|
16651
17651
|
historyStore: this.histories,
|
|
16652
17652
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
16653
|
-
|
|
16654
|
-
// we never captured one (e.g. old opencode sessions on disk before
|
|
16655
|
-
// this fix), fall back to the model the agent ships in its
|
|
16656
|
-
// session/load response body.
|
|
16657
|
-
currentModel: params.currentModel ?? extractInitialModel(loadResult ?? {}),
|
|
17653
|
+
currentModel: effectiveModel,
|
|
16658
17654
|
currentMode: effectiveMode,
|
|
16659
17655
|
currentUsage: params.currentUsage,
|
|
16660
17656
|
agentCommands: params.agentCommands,
|
|
@@ -16663,13 +17659,14 @@ var SessionManager = class {
|
|
|
16663
17659
|
// snapshot — the proxy's available models can change between daemon
|
|
16664
17660
|
// restarts (quota resets, rollouts), so meta.json is intentionally
|
|
16665
17661
|
// treated as a cold fallback here, not the authoritative source.
|
|
16666
|
-
agentModels:
|
|
17662
|
+
agentModels: advertisedModels,
|
|
16667
17663
|
// Only gate the first-prompt title heuristic when we actually have
|
|
16668
17664
|
// a title to preserve. A title-less session (lost to a write race
|
|
16669
17665
|
// or never seeded) should re-derive from the next prompt rather
|
|
16670
17666
|
// than stay stuck.
|
|
16671
17667
|
firstPromptSeeded: !!params.title,
|
|
16672
|
-
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
|
|
16673
17670
|
});
|
|
16674
17671
|
await this.attachManagerHooks(session);
|
|
16675
17672
|
return session;
|
|
@@ -16688,7 +17685,11 @@ var SessionManager = class {
|
|
|
16688
17685
|
cwd,
|
|
16689
17686
|
agentArgs: params.agentArgs,
|
|
16690
17687
|
mcpServers: [],
|
|
16691
|
-
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
|
|
16692
17693
|
});
|
|
16693
17694
|
const advertisedModes = params.agentModes ?? fresh.initialModes;
|
|
16694
17695
|
const effectiveMode = await restoreCurrentMode({
|
|
@@ -16699,6 +17700,15 @@ var SessionManager = class {
|
|
|
16699
17700
|
advertisedModes,
|
|
16700
17701
|
logger: this.logger
|
|
16701
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");
|
|
16702
17712
|
const session = new Session({
|
|
16703
17713
|
sessionId: params.hydraSessionId,
|
|
16704
17714
|
cwd,
|
|
@@ -16715,16 +17725,15 @@ var SessionManager = class {
|
|
|
16715
17725
|
listSessions: () => this.list(),
|
|
16716
17726
|
historyStore: this.histories,
|
|
16717
17727
|
historyMaxEntries: this.sessionHistoryMaxEntries,
|
|
16718
|
-
|
|
16719
|
-
// fall back to whatever the agent ships in its session/new response.
|
|
16720
|
-
currentModel: params.currentModel ?? fresh.initialModel,
|
|
17728
|
+
currentModel: effectiveModel,
|
|
16721
17729
|
currentMode: effectiveMode,
|
|
16722
17730
|
currentUsage: params.currentUsage,
|
|
16723
17731
|
agentCommands: params.agentCommands,
|
|
16724
17732
|
agentModes: advertisedModes,
|
|
16725
|
-
agentModels:
|
|
17733
|
+
agentModels: advertisedModels,
|
|
16726
17734
|
firstPromptSeeded: !!params.title,
|
|
16727
|
-
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
|
|
16728
17737
|
});
|
|
16729
17738
|
await this.attachManagerHooks(session);
|
|
16730
17739
|
void session.seedFromImport().catch(() => void 0);
|
|
@@ -17576,6 +18585,13 @@ function usageSnapshotToPersisted(usage) {
|
|
|
17576
18585
|
function persistedUsageToSnapshot(usage) {
|
|
17577
18586
|
return usage ? { ...usage } : void 0;
|
|
17578
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
|
+
}
|
|
17579
18595
|
function extractInitialModel(result) {
|
|
17580
18596
|
const direct = asString(result.currentModelId) ?? asString(result.currentModel) ?? asString(result.modelId) ?? asString(result.model);
|
|
17581
18597
|
if (direct) {
|
|
@@ -17755,6 +18771,33 @@ async function restoreCurrentMode(opts) {
|
|
|
17755
18771
|
return agentReportedMode;
|
|
17756
18772
|
}
|
|
17757
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
|
+
}
|
|
17758
18801
|
function parseModesList(list) {
|
|
17759
18802
|
if (!Array.isArray(list)) {
|
|
17760
18803
|
return [];
|
|
@@ -18710,6 +19753,55 @@ function withCode3(err, code) {
|
|
|
18710
19753
|
return err;
|
|
18711
19754
|
}
|
|
18712
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
|
+
|
|
18713
19805
|
// src/daemon/server.ts
|
|
18714
19806
|
init_paths();
|
|
18715
19807
|
|
|
@@ -19474,6 +20566,379 @@ function formatNumber(n) {
|
|
|
19474
20566
|
init_types();
|
|
19475
20567
|
init_hydra_version();
|
|
19476
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
|
|
19477
20942
|
function resolveHydraHost(defaults) {
|
|
19478
20943
|
if (defaults.publicHost && defaults.publicHost.length > 0) {
|
|
19479
20944
|
return defaults.publicHost;
|
|
@@ -19489,6 +20954,17 @@ function registerSessionRoutes(app, manager, defaults) {
|
|
|
19489
20954
|
const sessions = await manager.list({ cwd: query?.cwd });
|
|
19490
20955
|
return { sessions };
|
|
19491
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
|
+
});
|
|
19492
20968
|
app.post("/v1/sessions", async (request, reply) => {
|
|
19493
20969
|
const body = request.body ?? {};
|
|
19494
20970
|
const cwd = expandHome(body.cwd ?? defaults.cwd);
|
|
@@ -20276,6 +21752,34 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20276
21752
|
}
|
|
20277
21753
|
return buildInitializeResult();
|
|
20278
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
|
+
}
|
|
20279
21783
|
if (processIdentity?.kind === "transformer") {
|
|
20280
21784
|
connection.onRequest("transformer/initialize", async (raw) => {
|
|
20281
21785
|
const params = raw ?? {};
|
|
@@ -20823,7 +22327,10 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
20823
22327
|
return null;
|
|
20824
22328
|
}
|
|
20825
22329
|
app.log.info(decision.logMessage);
|
|
20826
|
-
|
|
22330
|
+
const { modelId } = rawParams;
|
|
22331
|
+
const result = await decision.session.forwardRequest("session/set_model", rawParams);
|
|
22332
|
+
decision.session.applyModelChange(modelId);
|
|
22333
|
+
return result;
|
|
20827
22334
|
});
|
|
20828
22335
|
connection.onRequest("session/set_mode", async (rawParams) => {
|
|
20829
22336
|
const params = rawParams;
|
|
@@ -21214,13 +22721,15 @@ async function startDaemon(config, serviceToken) {
|
|
|
21214
22721
|
stderrTailBytes: config.daemon.agentStderrTailBytes,
|
|
21215
22722
|
logger: agentLogger
|
|
21216
22723
|
});
|
|
22724
|
+
const extensionCommands = new ExtensionCommandRegistry();
|
|
21217
22725
|
const manager = new SessionManager(registry, spawner, void 0, {
|
|
21218
22726
|
idleTimeoutMs: config.daemon.sessionIdleTimeoutSeconds * 1e3,
|
|
21219
22727
|
defaultModels: config.defaultModels,
|
|
21220
22728
|
defaultTransformers: config.defaultTransformers,
|
|
21221
22729
|
sessionHistoryMaxEntries: config.daemon.sessionHistoryMaxEntries,
|
|
21222
22730
|
logger: agentLogger,
|
|
21223
|
-
npmRegistry: config.npmRegistry
|
|
22731
|
+
npmRegistry: config.npmRegistry,
|
|
22732
|
+
extensionCommands
|
|
21224
22733
|
});
|
|
21225
22734
|
const extensions = new ExtensionManager(extensionList(config), void 0, {
|
|
21226
22735
|
tokenRegistry: processRegistry
|
|
@@ -21254,7 +22763,8 @@ async function startDaemon(config, serviceToken) {
|
|
|
21254
22763
|
processRegistry,
|
|
21255
22764
|
onExtensionVersion: (name, version) => extensions.reportVersion(name, version),
|
|
21256
22765
|
onTransformerVersion: (name, version) => transformers.reportVersion(name, version),
|
|
21257
|
-
transformers
|
|
22766
|
+
transformers,
|
|
22767
|
+
extensionCommands
|
|
21258
22768
|
});
|
|
21259
22769
|
await app.listen({ host: config.daemon.host, port: config.daemon.port });
|
|
21260
22770
|
const address = app.server.address();
|
|
@@ -24305,6 +25815,7 @@ async function main() {
|
|
|
24305
25815
|
const positionalAgentId = afterLaunch[0];
|
|
24306
25816
|
const agentArgs = afterLaunch.slice(1);
|
|
24307
25817
|
const { flags: flags2 } = parseArgs(beforeLaunch);
|
|
25818
|
+
rejectUnknownFlags(flags2);
|
|
24308
25819
|
if (flags2.reattach === true) {
|
|
24309
25820
|
process.stderr.write(
|
|
24310
25821
|
"hydra-acp launch: --reattach is not valid here. Pass --session <id-or-url> to attach to a specific session.\n"
|
|
@@ -24343,6 +25854,7 @@ async function main() {
|
|
|
24343
25854
|
return;
|
|
24344
25855
|
}
|
|
24345
25856
|
const { positional, flags } = parseArgs(argv);
|
|
25857
|
+
rejectUnknownFlags(flags);
|
|
24346
25858
|
if (flags.version === true || positional[0] === "--version") {
|
|
24347
25859
|
process.stdout.write(`hydra-acp ${readVersion()}
|
|
24348
25860
|
`);
|
|
@@ -24730,6 +26242,17 @@ function parseNumericFlag(flags, name) {
|
|
|
24730
26242
|
}
|
|
24731
26243
|
return void 0;
|
|
24732
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
|
+
}
|
|
24733
26256
|
function readShortPrompt(argv) {
|
|
24734
26257
|
for (let i = 0; i < argv.length; i += 1) {
|
|
24735
26258
|
const tok = argv[i];
|