@hydra-acp/cli 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/cli.js +761 -111
- package/dist/index.d.ts +5 -5
- package/dist/index.js +15 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -271,12 +271,12 @@ var init_config = __esm({
|
|
|
271
271
|
// buffer. Oldest lines are dropped on overflow. The on-disk session
|
|
272
272
|
// history is unaffected; this only bounds the TUI's local view buffer.
|
|
273
273
|
maxScrollbackLines: z.number().int().positive().default(1e4),
|
|
274
|
-
// When true
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
// false
|
|
278
|
-
//
|
|
279
|
-
mouse: z.boolean().default(
|
|
274
|
+
// When true, the TUI captures mouse events so the wheel can drive
|
|
275
|
+
// scrollback. The cost: terminals route clicks to the app, so text
|
|
276
|
+
// selection requires shift+drag to bypass mouse reporting. Default
|
|
277
|
+
// false — wheel scrollback stops working, but plain click-drag
|
|
278
|
+
// selects text via the terminal emulator. Set true to opt back in.
|
|
279
|
+
mouse: z.boolean().default(false),
|
|
280
280
|
// Size at which the TUI's session/update debug log (tui.log) rotates
|
|
281
281
|
// to tui.log.0 and resets. Bounds on-disk use at ~2x this value.
|
|
282
282
|
logMaxBytes: z.number().int().positive().default(5 * 1024 * 1024),
|
|
@@ -291,13 +291,13 @@ var init_config = __esm({
|
|
|
291
291
|
// just don't want it.
|
|
292
292
|
progressIndicator: z.boolean().default(true),
|
|
293
293
|
// What the unmodified Enter key does in the prompt composer.
|
|
294
|
-
// "
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
// "
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
defaultEnterAction: z.enum(["enqueue", "amend"]).default("
|
|
294
|
+
// "amend" (default) — Enter amends the in-flight turn; Shift+Enter
|
|
295
|
+
// enqueues. With no turn in flight either key just enqueues,
|
|
296
|
+
// since there's nothing to amend.
|
|
297
|
+
// "enqueue" — flips the two: Enter enqueues the prompt (sends
|
|
298
|
+
// immediately when idle, queues behind an in-flight turn);
|
|
299
|
+
// Shift+Enter amends the in-flight turn.
|
|
300
|
+
defaultEnterAction: z.enum(["enqueue", "amend"]).default("amend")
|
|
301
301
|
});
|
|
302
302
|
ExtensionName = z.string().min(1).regex(/^[A-Za-z0-9._-]+$/, "extension name must be filename-safe");
|
|
303
303
|
ExtensionBody = z.object({
|
|
@@ -338,11 +338,11 @@ var init_config = __esm({
|
|
|
338
338
|
tui: TuiConfig.default({
|
|
339
339
|
repaintThrottleMs: 1e3,
|
|
340
340
|
maxScrollbackLines: 1e4,
|
|
341
|
-
mouse:
|
|
341
|
+
mouse: false,
|
|
342
342
|
logMaxBytes: 5 * 1024 * 1024,
|
|
343
343
|
cwdColumnMaxWidth: 24,
|
|
344
344
|
progressIndicator: true,
|
|
345
|
-
defaultEnterAction: "
|
|
345
|
+
defaultEnterAction: "amend"
|
|
346
346
|
})
|
|
347
347
|
});
|
|
348
348
|
}
|
|
@@ -819,9 +819,9 @@ var init_connection = __esm({
|
|
|
819
819
|
}
|
|
820
820
|
const id = nanoid();
|
|
821
821
|
const message = { jsonrpc: "2.0", id, method, params };
|
|
822
|
-
const response = new Promise((
|
|
822
|
+
const response = new Promise((resolve6, reject) => {
|
|
823
823
|
this.pending.set(id, {
|
|
824
|
-
resolve: (result) =>
|
|
824
|
+
resolve: (result) => resolve6(result),
|
|
825
825
|
reject
|
|
826
826
|
});
|
|
827
827
|
this.stream.send(message).catch((err) => {
|
|
@@ -3074,7 +3074,7 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
3074
3074
|
}
|
|
3075
3075
|
const clientParams = this.rewriteForClient(params);
|
|
3076
3076
|
const toolCallId = extractToolCallId(clientParams);
|
|
3077
|
-
return new Promise((
|
|
3077
|
+
return new Promise((resolve6, reject) => {
|
|
3078
3078
|
let settled = false;
|
|
3079
3079
|
const outbound = [];
|
|
3080
3080
|
const entry = { addClient: sendTo };
|
|
@@ -3113,7 +3113,7 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
3113
3113
|
update
|
|
3114
3114
|
}).catch(() => void 0);
|
|
3115
3115
|
}
|
|
3116
|
-
|
|
3116
|
+
resolve6(result);
|
|
3117
3117
|
});
|
|
3118
3118
|
}).catch((err) => {
|
|
3119
3119
|
settle(() => reject(err));
|
|
@@ -3129,14 +3129,14 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
3129
3129
|
// in flight, but doesn't emit prompt_queue_* broadcasts — clients
|
|
3130
3130
|
// shouldn't see hydra's housekeeping in their chip list.
|
|
3131
3131
|
async enqueuePrompt(task) {
|
|
3132
|
-
return new Promise((
|
|
3132
|
+
return new Promise((resolve6, reject) => {
|
|
3133
3133
|
const entry = {
|
|
3134
3134
|
kind: "internal",
|
|
3135
3135
|
messageId: generateMessageId(),
|
|
3136
3136
|
enqueuedAt: Date.now(),
|
|
3137
3137
|
cancelled: false,
|
|
3138
3138
|
task,
|
|
3139
|
-
resolve:
|
|
3139
|
+
resolve: resolve6,
|
|
3140
3140
|
reject
|
|
3141
3141
|
};
|
|
3142
3142
|
this.promptQueue.push(entry);
|
|
@@ -3155,7 +3155,7 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
3155
3155
|
if (client.clientInfo?.name) originator.name = client.clientInfo.name;
|
|
3156
3156
|
if (client.clientInfo?.version)
|
|
3157
3157
|
originator.version = client.clientInfo.version;
|
|
3158
|
-
return new Promise((
|
|
3158
|
+
return new Promise((resolve6, reject) => {
|
|
3159
3159
|
const entry = {
|
|
3160
3160
|
kind: "user",
|
|
3161
3161
|
messageId,
|
|
@@ -3164,7 +3164,7 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
3164
3164
|
prompt: promptArray,
|
|
3165
3165
|
enqueuedAt: Date.now(),
|
|
3166
3166
|
cancelled: false,
|
|
3167
|
-
resolve:
|
|
3167
|
+
resolve: resolve6,
|
|
3168
3168
|
reject
|
|
3169
3169
|
};
|
|
3170
3170
|
this.promptQueue.push(entry);
|
|
@@ -4352,13 +4352,13 @@ function wsToMessageStream(ws) {
|
|
|
4352
4352
|
throw new Error("ws is closed");
|
|
4353
4353
|
}
|
|
4354
4354
|
const text = JSON.stringify(message);
|
|
4355
|
-
await new Promise((
|
|
4355
|
+
await new Promise((resolve6, reject) => {
|
|
4356
4356
|
ws.send(text, (err) => {
|
|
4357
4357
|
if (err) {
|
|
4358
4358
|
reject(err);
|
|
4359
4359
|
return;
|
|
4360
4360
|
}
|
|
4361
|
-
|
|
4361
|
+
resolve6();
|
|
4362
4362
|
});
|
|
4363
4363
|
});
|
|
4364
4364
|
},
|
|
@@ -4846,8 +4846,8 @@ async function runSessionsTranscript(idOrFile, outPath) {
|
|
|
4846
4846
|
}
|
|
4847
4847
|
async function readBundleFileIfExists(arg) {
|
|
4848
4848
|
try {
|
|
4849
|
-
const
|
|
4850
|
-
if (!
|
|
4849
|
+
const stat5 = await fs17.stat(arg);
|
|
4850
|
+
if (!stat5.isFile()) {
|
|
4851
4851
|
return null;
|
|
4852
4852
|
}
|
|
4853
4853
|
} catch {
|
|
@@ -4882,8 +4882,8 @@ async function runSessionsImport(file, opts = {}) {
|
|
|
4882
4882
|
if (opts.cwd !== void 0) {
|
|
4883
4883
|
const resolved = path11.resolve(opts.cwd);
|
|
4884
4884
|
try {
|
|
4885
|
-
const
|
|
4886
|
-
if (!
|
|
4885
|
+
const stat5 = await fs17.stat(resolved);
|
|
4886
|
+
if (!stat5.isDirectory()) {
|
|
4887
4887
|
process.stderr.write(`--cwd ${resolved} is not a directory
|
|
4888
4888
|
`);
|
|
4889
4889
|
process.exit(1);
|
|
@@ -5029,11 +5029,11 @@ function isResponse(msg) {
|
|
|
5029
5029
|
return !("method" in msg) && "id" in msg && msg.id !== void 0;
|
|
5030
5030
|
}
|
|
5031
5031
|
async function openWs(url, subprotocols) {
|
|
5032
|
-
return new Promise((
|
|
5032
|
+
return new Promise((resolve6, reject) => {
|
|
5033
5033
|
const ws = new WebSocket(url, subprotocols);
|
|
5034
5034
|
const onOpen = () => {
|
|
5035
5035
|
ws.off("error", onError);
|
|
5036
|
-
|
|
5036
|
+
resolve6(wsToMessageStream(ws));
|
|
5037
5037
|
};
|
|
5038
5038
|
const onError = (err) => {
|
|
5039
5039
|
ws.off("open", onOpen);
|
|
@@ -5104,8 +5104,8 @@ var init_resilient_ws = __esm({
|
|
|
5104
5104
|
throw new Error("resilient ws stream not connected");
|
|
5105
5105
|
}
|
|
5106
5106
|
const id = message.id;
|
|
5107
|
-
const promise = new Promise((
|
|
5108
|
-
this.pendingRequests.set(id, { resolve:
|
|
5107
|
+
const promise = new Promise((resolve6, reject) => {
|
|
5108
|
+
this.pendingRequests.set(id, { resolve: resolve6, reject });
|
|
5109
5109
|
});
|
|
5110
5110
|
try {
|
|
5111
5111
|
await this.current.send(message);
|
|
@@ -5133,8 +5133,8 @@ var init_resilient_ws = __esm({
|
|
|
5133
5133
|
this.bindStream(stream);
|
|
5134
5134
|
const wasFirst = this.firstConnect;
|
|
5135
5135
|
this.firstConnect = false;
|
|
5136
|
-
this.connectGate = new Promise((
|
|
5137
|
-
this.releaseConnectGate =
|
|
5136
|
+
this.connectGate = new Promise((resolve6) => {
|
|
5137
|
+
this.releaseConnectGate = resolve6;
|
|
5138
5138
|
});
|
|
5139
5139
|
try {
|
|
5140
5140
|
if (this.opts.onConnect) {
|
|
@@ -5667,7 +5667,7 @@ async function pickSession(term, opts) {
|
|
|
5667
5667
|
};
|
|
5668
5668
|
renderFromScratch();
|
|
5669
5669
|
term.hideCursor();
|
|
5670
|
-
return await new Promise((
|
|
5670
|
+
return await new Promise((resolve6) => {
|
|
5671
5671
|
let resolved = false;
|
|
5672
5672
|
const onResize = () => {
|
|
5673
5673
|
if (resolved) {
|
|
@@ -5807,7 +5807,7 @@ async function pickSession(term, opts) {
|
|
|
5807
5807
|
if (mode === "help") {
|
|
5808
5808
|
if (name === "CTRL_C") {
|
|
5809
5809
|
cleanup();
|
|
5810
|
-
|
|
5810
|
+
resolve6({ kind: "abort" });
|
|
5811
5811
|
return;
|
|
5812
5812
|
}
|
|
5813
5813
|
mode = "normal";
|
|
@@ -5921,12 +5921,12 @@ async function pickSession(term, opts) {
|
|
|
5921
5921
|
}
|
|
5922
5922
|
if (name === "c" || name === "C") {
|
|
5923
5923
|
cleanup();
|
|
5924
|
-
|
|
5924
|
+
resolve6({ kind: "new" });
|
|
5925
5925
|
return;
|
|
5926
5926
|
}
|
|
5927
5927
|
if (name === "q" || name === "Q") {
|
|
5928
5928
|
cleanup();
|
|
5929
|
-
|
|
5929
|
+
resolve6({ kind: "abort" });
|
|
5930
5930
|
return;
|
|
5931
5931
|
}
|
|
5932
5932
|
if (name === "o" || name === "O") {
|
|
@@ -5976,7 +5976,7 @@ async function pickSession(term, opts) {
|
|
|
5976
5976
|
if (session.agentId !== void 0) {
|
|
5977
5977
|
result.agentId = session.agentId;
|
|
5978
5978
|
}
|
|
5979
|
-
|
|
5979
|
+
resolve6(result);
|
|
5980
5980
|
return;
|
|
5981
5981
|
}
|
|
5982
5982
|
if ((name === "k" || name === "K") && selectedIdx > 0) {
|
|
@@ -6062,12 +6062,12 @@ async function pickSession(term, opts) {
|
|
|
6062
6062
|
case "KP_ENTER": {
|
|
6063
6063
|
cleanup();
|
|
6064
6064
|
if (selectedIdx === 0) {
|
|
6065
|
-
|
|
6065
|
+
resolve6({ kind: "new" });
|
|
6066
6066
|
return;
|
|
6067
6067
|
}
|
|
6068
6068
|
const session = visible[selectedIdx - 1];
|
|
6069
6069
|
if (!session) {
|
|
6070
|
-
|
|
6070
|
+
resolve6({ kind: "abort" });
|
|
6071
6071
|
return;
|
|
6072
6072
|
}
|
|
6073
6073
|
const result = {
|
|
@@ -6077,14 +6077,14 @@ async function pickSession(term, opts) {
|
|
|
6077
6077
|
if (session.agentId !== void 0) {
|
|
6078
6078
|
result.agentId = session.agentId;
|
|
6079
6079
|
}
|
|
6080
|
-
|
|
6080
|
+
resolve6(result);
|
|
6081
6081
|
return;
|
|
6082
6082
|
}
|
|
6083
6083
|
case "ESCAPE":
|
|
6084
6084
|
case "CTRL_C":
|
|
6085
6085
|
case "CTRL_D":
|
|
6086
6086
|
cleanup();
|
|
6087
|
-
|
|
6087
|
+
resolve6({ kind: "abort" });
|
|
6088
6088
|
return;
|
|
6089
6089
|
}
|
|
6090
6090
|
};
|
|
@@ -6185,10 +6185,184 @@ var init_picker = __esm({
|
|
|
6185
6185
|
}
|
|
6186
6186
|
});
|
|
6187
6187
|
|
|
6188
|
+
// src/core/cwd.ts
|
|
6189
|
+
import * as fs18 from "fs/promises";
|
|
6190
|
+
import * as path12 from "path";
|
|
6191
|
+
async function validateLocalCwd(input) {
|
|
6192
|
+
const trimmed = input.trim();
|
|
6193
|
+
if (trimmed.length === 0) {
|
|
6194
|
+
return { ok: false, reason: "path is empty" };
|
|
6195
|
+
}
|
|
6196
|
+
const resolved = path12.resolve(expandHome(trimmed));
|
|
6197
|
+
let stat5;
|
|
6198
|
+
try {
|
|
6199
|
+
stat5 = await fs18.stat(resolved);
|
|
6200
|
+
} catch {
|
|
6201
|
+
return { ok: false, reason: `${resolved} does not exist` };
|
|
6202
|
+
}
|
|
6203
|
+
if (!stat5.isDirectory()) {
|
|
6204
|
+
return { ok: false, reason: `${resolved} is not a directory` };
|
|
6205
|
+
}
|
|
6206
|
+
return { ok: true, path: resolved };
|
|
6207
|
+
}
|
|
6208
|
+
var init_cwd = __esm({
|
|
6209
|
+
"src/core/cwd.ts"() {
|
|
6210
|
+
"use strict";
|
|
6211
|
+
init_config();
|
|
6212
|
+
}
|
|
6213
|
+
});
|
|
6214
|
+
|
|
6215
|
+
// src/tui/import-cwd-prompt.ts
|
|
6216
|
+
import * as os4 from "os";
|
|
6217
|
+
async function promptForImportCwd(term, session, opts = {}) {
|
|
6218
|
+
const defaultCwd = opts.defaultCwd ?? os4.homedir();
|
|
6219
|
+
process.stdout.write("\x1B[<u");
|
|
6220
|
+
process.stdout.write("\x1B[?2004l");
|
|
6221
|
+
process.stdout.write("\x1B[>4;0m");
|
|
6222
|
+
process.stdout.write("\x1B[>5;0m");
|
|
6223
|
+
process.stdout.write("\x1B[?1000l");
|
|
6224
|
+
process.stdout.write("\x1B[?1002l");
|
|
6225
|
+
process.stdout.write("\x1B[?1006l");
|
|
6226
|
+
process.stdout.write("\x1B[?1l");
|
|
6227
|
+
process.stdout.write("\x1B>");
|
|
6228
|
+
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
6229
|
+
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
6230
|
+
const originalCwd = session.cwd;
|
|
6231
|
+
let buffer = defaultCwd;
|
|
6232
|
+
let errorLine = null;
|
|
6233
|
+
let busy = false;
|
|
6234
|
+
const render = () => {
|
|
6235
|
+
term("\n");
|
|
6236
|
+
term.bold.cyan("Imported session: ");
|
|
6237
|
+
term(`${shortId2}
|
|
6238
|
+
`);
|
|
6239
|
+
term.dim(` from machine: `);
|
|
6240
|
+
term(`${fromMachine}
|
|
6241
|
+
`);
|
|
6242
|
+
term.dim(` original cwd: `);
|
|
6243
|
+
term(`${shortenHomePath(originalCwd)}
|
|
6244
|
+
`);
|
|
6245
|
+
term("\n");
|
|
6246
|
+
term(
|
|
6247
|
+
"This session has never been launched on this machine. Pick a local\n"
|
|
6248
|
+
);
|
|
6249
|
+
term("cwd for the agent (Enter to accept, Esc to cancel):\n\n");
|
|
6250
|
+
paintInput();
|
|
6251
|
+
if (errorLine) {
|
|
6252
|
+
term("\n");
|
|
6253
|
+
term.red(` ${errorLine}
|
|
6254
|
+
`);
|
|
6255
|
+
}
|
|
6256
|
+
};
|
|
6257
|
+
const paintInput = () => {
|
|
6258
|
+
term.bold("cwd: ");
|
|
6259
|
+
term(buffer);
|
|
6260
|
+
if (!busy) {
|
|
6261
|
+
term.bgWhite(" ");
|
|
6262
|
+
}
|
|
6263
|
+
};
|
|
6264
|
+
const repaintInput = () => {
|
|
6265
|
+
term.column(1);
|
|
6266
|
+
term.eraseLine();
|
|
6267
|
+
paintInput();
|
|
6268
|
+
if (errorLine !== null) {
|
|
6269
|
+
term("\n");
|
|
6270
|
+
term.eraseLine();
|
|
6271
|
+
term.red(` ${errorLine}`);
|
|
6272
|
+
term.up(1);
|
|
6273
|
+
term.column(1);
|
|
6274
|
+
}
|
|
6275
|
+
};
|
|
6276
|
+
render();
|
|
6277
|
+
return await new Promise((resolve6) => {
|
|
6278
|
+
let resolved = false;
|
|
6279
|
+
const cleanup = () => {
|
|
6280
|
+
if (resolved) {
|
|
6281
|
+
return;
|
|
6282
|
+
}
|
|
6283
|
+
resolved = true;
|
|
6284
|
+
term.off("key", onKey);
|
|
6285
|
+
term.grabInput(false);
|
|
6286
|
+
term.hideCursor(false);
|
|
6287
|
+
term("\n\n");
|
|
6288
|
+
};
|
|
6289
|
+
const finish = (value) => {
|
|
6290
|
+
cleanup();
|
|
6291
|
+
resolve6(value);
|
|
6292
|
+
};
|
|
6293
|
+
const onKey = (name, _matches, data) => {
|
|
6294
|
+
if (busy) {
|
|
6295
|
+
return;
|
|
6296
|
+
}
|
|
6297
|
+
if (name === "ENTER" || name === "KP_ENTER") {
|
|
6298
|
+
const candidate = buffer;
|
|
6299
|
+
busy = true;
|
|
6300
|
+
errorLine = null;
|
|
6301
|
+
repaintInput();
|
|
6302
|
+
void validateLocalCwd(candidate).then((result) => {
|
|
6303
|
+
busy = false;
|
|
6304
|
+
if (result.ok) {
|
|
6305
|
+
finish(result.path);
|
|
6306
|
+
return;
|
|
6307
|
+
}
|
|
6308
|
+
errorLine = result.reason;
|
|
6309
|
+
repaintInput();
|
|
6310
|
+
});
|
|
6311
|
+
return;
|
|
6312
|
+
}
|
|
6313
|
+
if (name === "ESCAPE" || name === "CTRL_C" || name === "CTRL_D") {
|
|
6314
|
+
finish(null);
|
|
6315
|
+
return;
|
|
6316
|
+
}
|
|
6317
|
+
if (name === "BACKSPACE") {
|
|
6318
|
+
if (buffer.length > 0) {
|
|
6319
|
+
buffer = buffer.slice(0, -1);
|
|
6320
|
+
errorLine = null;
|
|
6321
|
+
repaintInput();
|
|
6322
|
+
}
|
|
6323
|
+
return;
|
|
6324
|
+
}
|
|
6325
|
+
if (name === "CTRL_U") {
|
|
6326
|
+
buffer = "";
|
|
6327
|
+
errorLine = null;
|
|
6328
|
+
repaintInput();
|
|
6329
|
+
return;
|
|
6330
|
+
}
|
|
6331
|
+
if (name === "CTRL_W") {
|
|
6332
|
+
const trimmedRight = buffer.replace(/[/\s]+$/, "");
|
|
6333
|
+
const lastSep = Math.max(
|
|
6334
|
+
trimmedRight.lastIndexOf("/"),
|
|
6335
|
+
trimmedRight.lastIndexOf(" ")
|
|
6336
|
+
);
|
|
6337
|
+
buffer = lastSep >= 0 ? trimmedRight.slice(0, lastSep + 1) : "";
|
|
6338
|
+
errorLine = null;
|
|
6339
|
+
repaintInput();
|
|
6340
|
+
return;
|
|
6341
|
+
}
|
|
6342
|
+
if (data?.isCharacter) {
|
|
6343
|
+
buffer += name;
|
|
6344
|
+
errorLine = null;
|
|
6345
|
+
repaintInput();
|
|
6346
|
+
return;
|
|
6347
|
+
}
|
|
6348
|
+
};
|
|
6349
|
+
term.grabInput({});
|
|
6350
|
+
term.on("key", onKey);
|
|
6351
|
+
});
|
|
6352
|
+
}
|
|
6353
|
+
var init_import_cwd_prompt = __esm({
|
|
6354
|
+
"src/tui/import-cwd-prompt.ts"() {
|
|
6355
|
+
"use strict";
|
|
6356
|
+
init_paths();
|
|
6357
|
+
init_session();
|
|
6358
|
+
init_cwd();
|
|
6359
|
+
}
|
|
6360
|
+
});
|
|
6361
|
+
|
|
6188
6362
|
// src/tui/attachments.ts
|
|
6189
|
-
import
|
|
6363
|
+
import path13 from "path";
|
|
6190
6364
|
function mimeFromExtension(p) {
|
|
6191
|
-
return EXTENSION_TO_MIME[
|
|
6365
|
+
return EXTENSION_TO_MIME[path13.extname(p).toLowerCase()] ?? null;
|
|
6192
6366
|
}
|
|
6193
6367
|
function isSupportedImagePath(p) {
|
|
6194
6368
|
return mimeFromExtension(p) !== null;
|
|
@@ -6832,6 +7006,8 @@ function mapKeyName(name) {
|
|
|
6832
7006
|
return "ctrl-v";
|
|
6833
7007
|
case "CTRL_W":
|
|
6834
7008
|
return "ctrl-w";
|
|
7009
|
+
case "CTRL_X":
|
|
7010
|
+
return "ctrl-x";
|
|
6835
7011
|
case "CTRL_Y":
|
|
6836
7012
|
return "ctrl-y";
|
|
6837
7013
|
case "ESCAPE":
|
|
@@ -7097,7 +7273,7 @@ var init_screen = __esm({
|
|
|
7097
7273
|
this.onKey = opts.onKey;
|
|
7098
7274
|
this.contentRepaintThrottleMs = opts.repaintThrottleMs ?? DEFAULT_CONTENT_REPAINT_THROTTLE_MS;
|
|
7099
7275
|
this.maxScrollbackLines = opts.maxScrollbackLines ?? DEFAULT_MAX_SCROLLBACK_LINES;
|
|
7100
|
-
this.mouseEnabled = opts.mouse ??
|
|
7276
|
+
this.mouseEnabled = opts.mouse ?? false;
|
|
7101
7277
|
this.progressIndicatorEnabled = opts.progressIndicator ?? true;
|
|
7102
7278
|
this.readonly = opts.readonly ?? false;
|
|
7103
7279
|
this.resizeHandler = () => this.repaint();
|
|
@@ -7614,6 +7790,57 @@ uncaught: ${err.stack ?? err.message}
|
|
|
7614
7790
|
this.drawBanner();
|
|
7615
7791
|
this.placeCursor();
|
|
7616
7792
|
}
|
|
7793
|
+
// Runtime toggle for terminal mouse capture. With capture on, the
|
|
7794
|
+
// wheel drives scrollback but text selection requires shift+drag
|
|
7795
|
+
// (terminals route mouse events to the app). With capture off, plain
|
|
7796
|
+
// click-drag selects text but the wheel does nothing in the app —
|
|
7797
|
+
// use PgUp/PgDn for scrollback instead. Bound to ^X so users can
|
|
7798
|
+
// flip on demand without a config reload + restart. Idempotent.
|
|
7799
|
+
//
|
|
7800
|
+
// Re-issuing grabInput() reinstalls terminal-kit's own stdin "data"
|
|
7801
|
+
// listener, so we have to redo the same listener swap that
|
|
7802
|
+
// installBracketedPaste() did at startup — otherwise our raw handler
|
|
7803
|
+
// and terminal-kit's both fire for every keystroke (each character
|
|
7804
|
+
// appears twice in the prompt).
|
|
7805
|
+
setMouseEnabled(enabled) {
|
|
7806
|
+
if (this.mouseEnabled === enabled) {
|
|
7807
|
+
return;
|
|
7808
|
+
}
|
|
7809
|
+
this.mouseEnabled = enabled;
|
|
7810
|
+
if (!this.started) {
|
|
7811
|
+
return;
|
|
7812
|
+
}
|
|
7813
|
+
if (enabled) {
|
|
7814
|
+
this.term.grabInput({ mouse: "button" });
|
|
7815
|
+
this.term.on("mouse", this.mouseHandler);
|
|
7816
|
+
} else {
|
|
7817
|
+
this.term.off("mouse", this.mouseHandler);
|
|
7818
|
+
this.term.grabInput(true);
|
|
7819
|
+
}
|
|
7820
|
+
this.reclaimStdinAfterGrabInput();
|
|
7821
|
+
}
|
|
7822
|
+
// After a grabInput() re-issue, terminal-kit has put its own "data"
|
|
7823
|
+
// listener back on stdin. Pull it back off and reinstall hydra's
|
|
7824
|
+
// rawStdinHandler — keeping the captured terminal-kit handler so our
|
|
7825
|
+
// bracketed-paste extractor can still delegate non-paste bytes to it.
|
|
7826
|
+
// No-op if installBracketedPaste() hasn't run yet (start() does it
|
|
7827
|
+
// before any toggle path can reach here).
|
|
7828
|
+
reclaimStdinAfterGrabInput() {
|
|
7829
|
+
if (this.terminalKitStdinHandler === null) {
|
|
7830
|
+
return;
|
|
7831
|
+
}
|
|
7832
|
+
const t = this.term;
|
|
7833
|
+
if (!t.stdin || typeof t.onStdin !== "function") {
|
|
7834
|
+
return;
|
|
7835
|
+
}
|
|
7836
|
+
this.terminalKitStdinHandler = t.onStdin;
|
|
7837
|
+
t.stdin.removeListener("data", t.onStdin);
|
|
7838
|
+
t.stdin.removeListener("data", this.rawStdinHandler);
|
|
7839
|
+
t.stdin.on("data", this.rawStdinHandler);
|
|
7840
|
+
}
|
|
7841
|
+
isMouseEnabled() {
|
|
7842
|
+
return this.mouseEnabled;
|
|
7843
|
+
}
|
|
7617
7844
|
// Pushed by the app each onKey tick to reflect prompt-history
|
|
7618
7845
|
// reverse-search state in the banner — the only place that mode's
|
|
7619
7846
|
// query is visible. Pass null when not searching.
|
|
@@ -9101,6 +9328,8 @@ var init_input = __esm({
|
|
|
9101
9328
|
case "ctrl-w":
|
|
9102
9329
|
this.killWord();
|
|
9103
9330
|
return [];
|
|
9331
|
+
case "ctrl-x":
|
|
9332
|
+
return [{ type: "toggle-mouse" }];
|
|
9104
9333
|
case "ctrl-y":
|
|
9105
9334
|
this.yank();
|
|
9106
9335
|
return [];
|
|
@@ -9683,9 +9912,9 @@ var init_input = __esm({
|
|
|
9683
9912
|
|
|
9684
9913
|
// src/tui/clipboard.ts
|
|
9685
9914
|
import { spawn as nodeSpawn } from "child_process";
|
|
9686
|
-
import
|
|
9687
|
-
import
|
|
9688
|
-
import
|
|
9915
|
+
import fs19 from "fs/promises";
|
|
9916
|
+
import os5 from "os";
|
|
9917
|
+
import path14 from "path";
|
|
9689
9918
|
async function readClipboard(envIn = {}) {
|
|
9690
9919
|
const env = { ...defaultEnv, ...envIn };
|
|
9691
9920
|
if (env.platform === "darwin") {
|
|
@@ -9700,7 +9929,7 @@ async function readClipboard(envIn = {}) {
|
|
|
9700
9929
|
};
|
|
9701
9930
|
}
|
|
9702
9931
|
async function readMacOS(env) {
|
|
9703
|
-
const tmpPath =
|
|
9932
|
+
const tmpPath = path14.join(
|
|
9704
9933
|
env.tmpdir(),
|
|
9705
9934
|
`hydra-clipboard-${Date.now()}-${process.pid}.png`
|
|
9706
9935
|
);
|
|
@@ -9724,7 +9953,7 @@ async function readMacOS(env) {
|
|
|
9724
9953
|
return img;
|
|
9725
9954
|
}
|
|
9726
9955
|
} catch {
|
|
9727
|
-
await
|
|
9956
|
+
await fs19.unlink(tmpPath).catch(() => void 0);
|
|
9728
9957
|
}
|
|
9729
9958
|
try {
|
|
9730
9959
|
const buf = await runCapture(env.spawn, "pbpaste", []);
|
|
@@ -9839,9 +10068,9 @@ async function which(env, cmd) {
|
|
|
9839
10068
|
}
|
|
9840
10069
|
async function readFileAsAttachment(p, unlinkAfter) {
|
|
9841
10070
|
try {
|
|
9842
|
-
const buf = await
|
|
10071
|
+
const buf = await fs19.readFile(p);
|
|
9843
10072
|
if (unlinkAfter) {
|
|
9844
|
-
await
|
|
10073
|
+
await fs19.unlink(p).catch(() => void 0);
|
|
9845
10074
|
}
|
|
9846
10075
|
if (buf.length === 0) {
|
|
9847
10076
|
return { ok: false, reason: "no image on clipboard" };
|
|
@@ -9867,14 +10096,14 @@ async function readFileAsAttachment(p, unlinkAfter) {
|
|
|
9867
10096
|
}
|
|
9868
10097
|
}
|
|
9869
10098
|
function run2(spawn6, cmd, args) {
|
|
9870
|
-
return new Promise((
|
|
10099
|
+
return new Promise((resolve6, reject) => {
|
|
9871
10100
|
const proc = spawn6(cmd, args);
|
|
9872
10101
|
proc.stdout?.on("data", () => void 0);
|
|
9873
10102
|
proc.stderr?.on("data", () => void 0);
|
|
9874
10103
|
proc.on("error", reject);
|
|
9875
10104
|
proc.on("close", (code) => {
|
|
9876
10105
|
if (code === 0) {
|
|
9877
|
-
|
|
10106
|
+
resolve6();
|
|
9878
10107
|
} else {
|
|
9879
10108
|
reject(new Error(`${cmd} exited ${code}`));
|
|
9880
10109
|
}
|
|
@@ -9882,7 +10111,7 @@ function run2(spawn6, cmd, args) {
|
|
|
9882
10111
|
});
|
|
9883
10112
|
}
|
|
9884
10113
|
function runCapture(spawn6, cmd, args) {
|
|
9885
|
-
return new Promise((
|
|
10114
|
+
return new Promise((resolve6, reject) => {
|
|
9886
10115
|
const proc = spawn6(cmd, args);
|
|
9887
10116
|
const chunks = [];
|
|
9888
10117
|
let stdoutEnded = proc.stdout === null;
|
|
@@ -9894,7 +10123,7 @@ function runCapture(spawn6, cmd, args) {
|
|
|
9894
10123
|
}
|
|
9895
10124
|
settled = true;
|
|
9896
10125
|
if (closedCode === 0) {
|
|
9897
|
-
|
|
10126
|
+
resolve6(Buffer.concat(chunks));
|
|
9898
10127
|
} else {
|
|
9899
10128
|
reject(new Error(`${cmd} exited ${closedCode}`));
|
|
9900
10129
|
}
|
|
@@ -9929,7 +10158,7 @@ var init_clipboard = __esm({
|
|
|
9929
10158
|
platform: process.platform,
|
|
9930
10159
|
env: process.env,
|
|
9931
10160
|
spawn: nodeSpawn,
|
|
9932
|
-
tmpdir:
|
|
10161
|
+
tmpdir: os5.tmpdir
|
|
9933
10162
|
};
|
|
9934
10163
|
SUPPORTED_IMAGE_MIMES = [
|
|
9935
10164
|
"image/png",
|
|
@@ -10446,8 +10675,8 @@ var init_format = __esm({
|
|
|
10446
10675
|
import { appendFileSync, statSync, renameSync } from "fs";
|
|
10447
10676
|
import { nanoid as nanoid3 } from "nanoid";
|
|
10448
10677
|
import termkit from "terminal-kit";
|
|
10449
|
-
import
|
|
10450
|
-
import
|
|
10678
|
+
import fs20 from "fs/promises";
|
|
10679
|
+
import path15 from "path";
|
|
10451
10680
|
function isReadonlyForbiddenEffect(effect) {
|
|
10452
10681
|
switch (effect.type) {
|
|
10453
10682
|
case "send":
|
|
@@ -10775,10 +11004,10 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10775
11004
|
if (pendingPermission.toolCallId && toolCallId && pendingPermission.toolCallId !== toolCallId) {
|
|
10776
11005
|
return;
|
|
10777
11006
|
}
|
|
10778
|
-
const
|
|
11007
|
+
const resolve6 = pendingPermission.resolve;
|
|
10779
11008
|
pendingPermission = null;
|
|
10780
11009
|
screen.setPermissionPrompt(null);
|
|
10781
|
-
|
|
11010
|
+
resolve6(result ?? { outcome: { outcome: "cancelled" } });
|
|
10782
11011
|
};
|
|
10783
11012
|
const maybeDismissPermissionByToolUpdate = (update) => {
|
|
10784
11013
|
if (!pendingPermission?.toolCallId) {
|
|
@@ -10811,14 +11040,14 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10811
11040
|
if (!pendingPermission) {
|
|
10812
11041
|
return;
|
|
10813
11042
|
}
|
|
10814
|
-
const { options, resolve:
|
|
11043
|
+
const { options, resolve: resolve6 } = pendingPermission;
|
|
10815
11044
|
pendingPermission = null;
|
|
10816
11045
|
screen.setPermissionPrompt(null);
|
|
10817
11046
|
if (optionId === null) {
|
|
10818
|
-
|
|
11047
|
+
resolve6({ outcome: { outcome: "cancelled" } });
|
|
10819
11048
|
return;
|
|
10820
11049
|
}
|
|
10821
|
-
|
|
11050
|
+
resolve6({ outcome: { outcome: "selected", optionId } });
|
|
10822
11051
|
void options;
|
|
10823
11052
|
};
|
|
10824
11053
|
conn.onRequest("session/request_permission", async (params) => {
|
|
@@ -10845,12 +11074,12 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10845
11074
|
]);
|
|
10846
11075
|
return { outcome: { outcome: "cancelled" } };
|
|
10847
11076
|
}
|
|
10848
|
-
return new Promise((
|
|
11077
|
+
return new Promise((resolve6) => {
|
|
10849
11078
|
pendingPermission = {
|
|
10850
11079
|
title,
|
|
10851
11080
|
options,
|
|
10852
11081
|
selectedIndex: 0,
|
|
10853
|
-
resolve:
|
|
11082
|
+
resolve: resolve6,
|
|
10854
11083
|
toolCallId
|
|
10855
11084
|
};
|
|
10856
11085
|
refreshPermissionPrompt();
|
|
@@ -10939,7 +11168,23 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10939
11168
|
sessionId: ctx.sessionId,
|
|
10940
11169
|
historyPolicy: "full",
|
|
10941
11170
|
clientInfo: { name: "hydra-acp-tui", version: HYDRA_VERSION },
|
|
10942
|
-
...opts.readonly === true ? { readonly: true } : {}
|
|
11171
|
+
...opts.readonly === true ? { readonly: true } : {},
|
|
11172
|
+
// Forward the user-chosen cwd for first-launch imported sessions
|
|
11173
|
+
// via a full resume hint. upstreamSessionId is empty so the
|
|
11174
|
+
// daemon routes through doResurrectFromImport (session-manager.ts)
|
|
11175
|
+
// with the user-supplied cwd instead of silently falling back to
|
|
11176
|
+
// $HOME in resolveImportCwd.
|
|
11177
|
+
...ctx.importAttachHint !== void 0 ? {
|
|
11178
|
+
_meta: {
|
|
11179
|
+
[HYDRA_META_KEY]: {
|
|
11180
|
+
resume: {
|
|
11181
|
+
upstreamSessionId: "",
|
|
11182
|
+
agentId: ctx.importAttachHint.agentId,
|
|
11183
|
+
cwd: ctx.importAttachHint.cwd
|
|
11184
|
+
}
|
|
11185
|
+
}
|
|
11186
|
+
}
|
|
11187
|
+
} : {}
|
|
10943
11188
|
});
|
|
10944
11189
|
resolvedSessionId = attached.sessionId;
|
|
10945
11190
|
if (attached.clientId) {
|
|
@@ -11200,8 +11445,8 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
11200
11445
|
}
|
|
11201
11446
|
});
|
|
11202
11447
|
let finishSession = null;
|
|
11203
|
-
const sessionDone = new Promise((
|
|
11204
|
-
finishSession =
|
|
11448
|
+
const sessionDone = new Promise((resolve6) => {
|
|
11449
|
+
finishSession = resolve6;
|
|
11205
11450
|
});
|
|
11206
11451
|
const cancelRemoteTurn = () => {
|
|
11207
11452
|
conn.notify("session/cancel", { sessionId: resolvedSessionId }).catch(() => void 0);
|
|
@@ -11518,6 +11763,14 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
11518
11763
|
toolsExpanded = !toolsExpanded;
|
|
11519
11764
|
renderToolsBlock();
|
|
11520
11765
|
return;
|
|
11766
|
+
case "toggle-mouse": {
|
|
11767
|
+
const next = !screen.isMouseEnabled();
|
|
11768
|
+
screen.setMouseEnabled(next);
|
|
11769
|
+
screen.notify(
|
|
11770
|
+
next ? "mouse capture on \u2014 wheel scrolls; shift+drag to select text" : "mouse capture off \u2014 click-drag selects text; PgUp/PgDn scrolls"
|
|
11771
|
+
);
|
|
11772
|
+
return;
|
|
11773
|
+
}
|
|
11521
11774
|
case "show-help":
|
|
11522
11775
|
toggleHelpModal();
|
|
11523
11776
|
return;
|
|
@@ -11560,11 +11813,11 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
11560
11813
|
}
|
|
11561
11814
|
const mimeType = mimeFromExtension(token);
|
|
11562
11815
|
if (!mimeType) {
|
|
11563
|
-
screen.notify(`unsupported image type: ${
|
|
11816
|
+
screen.notify(`unsupported image type: ${path15.basename(token)}`);
|
|
11564
11817
|
continue;
|
|
11565
11818
|
}
|
|
11566
11819
|
try {
|
|
11567
|
-
const buf = await
|
|
11820
|
+
const buf = await fs20.readFile(token);
|
|
11568
11821
|
if (buf.length > MAX_ATTACHMENT_BYTES) {
|
|
11569
11822
|
screen.notify(
|
|
11570
11823
|
`image too large (${formatSize(buf.length)}, max ${formatSize(MAX_ATTACHMENT_BYTES)})`
|
|
@@ -11574,13 +11827,13 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
11574
11827
|
dispatcher.addAttachment({
|
|
11575
11828
|
mimeType,
|
|
11576
11829
|
data: buf.toString("base64"),
|
|
11577
|
-
name:
|
|
11830
|
+
name: path15.basename(token),
|
|
11578
11831
|
sizeBytes: buf.length
|
|
11579
11832
|
});
|
|
11580
11833
|
added++;
|
|
11581
11834
|
} catch (err) {
|
|
11582
11835
|
screen.notify(
|
|
11583
|
-
`cannot read ${
|
|
11836
|
+
`cannot read ${path15.basename(token)}: ${err.message}`
|
|
11584
11837
|
);
|
|
11585
11838
|
}
|
|
11586
11839
|
}
|
|
@@ -12297,10 +12550,10 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
12297
12550
|
}
|
|
12298
12551
|
const resetInFlightUiState = () => {
|
|
12299
12552
|
if (pendingPermission) {
|
|
12300
|
-
const
|
|
12553
|
+
const resolve6 = pendingPermission.resolve;
|
|
12301
12554
|
pendingPermission = null;
|
|
12302
12555
|
screen.setPermissionPrompt(null);
|
|
12303
|
-
|
|
12556
|
+
resolve6({ outcome: { outcome: "cancelled" } });
|
|
12304
12557
|
}
|
|
12305
12558
|
closeAgentText();
|
|
12306
12559
|
};
|
|
@@ -12465,6 +12718,21 @@ async function resolveSession(term, config, serviceToken, opts) {
|
|
|
12465
12718
|
return newCtx(opts, cwd, config);
|
|
12466
12719
|
}
|
|
12467
12720
|
opts.readonly = choice.readonly === true;
|
|
12721
|
+
const chosen = sessions.find((s) => s.sessionId === choice.sessionId);
|
|
12722
|
+
const isImportedFirstLaunch = chosen !== void 0 && !!chosen.importedFromMachine && !chosen.upstreamSessionId && !opts.readonly;
|
|
12723
|
+
if (isImportedFirstLaunch) {
|
|
12724
|
+
const promptedCwd = await promptForImportCwd(term, chosen);
|
|
12725
|
+
if (promptedCwd === null) {
|
|
12726
|
+
return null;
|
|
12727
|
+
}
|
|
12728
|
+
const agentId = choice.agentId ?? chosen.agentId ?? "";
|
|
12729
|
+
return {
|
|
12730
|
+
sessionId: choice.sessionId,
|
|
12731
|
+
agentId,
|
|
12732
|
+
cwd: promptedCwd,
|
|
12733
|
+
importAttachHint: { agentId, cwd: promptedCwd }
|
|
12734
|
+
};
|
|
12735
|
+
}
|
|
12468
12736
|
return {
|
|
12469
12737
|
sessionId: choice.sessionId,
|
|
12470
12738
|
agentId: choice.agentId ?? "",
|
|
@@ -12593,8 +12861,8 @@ function createInstallStatusLine(term, baseLabel) {
|
|
|
12593
12861
|
}
|
|
12594
12862
|
function rotateIfBig(target) {
|
|
12595
12863
|
try {
|
|
12596
|
-
const
|
|
12597
|
-
if (
|
|
12864
|
+
const stat5 = statSync(target);
|
|
12865
|
+
if (stat5.size < logMaxBytes) {
|
|
12598
12866
|
return;
|
|
12599
12867
|
}
|
|
12600
12868
|
renameSync(target, `${target}.0`);
|
|
@@ -12618,6 +12886,7 @@ var init_app = __esm({
|
|
|
12618
12886
|
init_history();
|
|
12619
12887
|
init_discovery();
|
|
12620
12888
|
init_picker();
|
|
12889
|
+
init_import_cwd_prompt();
|
|
12621
12890
|
init_screen();
|
|
12622
12891
|
init_input();
|
|
12623
12892
|
init_attachments();
|
|
@@ -12647,6 +12916,7 @@ var init_app = __esm({
|
|
|
12647
12916
|
["^R / ^S", "history reverse / forward search"],
|
|
12648
12917
|
["PgUp / PgDn", "scroll scrollback"],
|
|
12649
12918
|
["Mouse wheel", "scroll scrollback (when mouse capture is on)"],
|
|
12919
|
+
["^X", "toggle mouse capture (wheel scroll vs. text selection)"],
|
|
12650
12920
|
null,
|
|
12651
12921
|
["^C", "cancel turn (twice to exit)"],
|
|
12652
12922
|
["Esc", "cancel turn and prefill draft"],
|
|
@@ -12673,11 +12943,12 @@ var init_tui = __esm({
|
|
|
12673
12943
|
// src/cli.ts
|
|
12674
12944
|
import { readFileSync as readFileSync2 } from "fs";
|
|
12675
12945
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12676
|
-
import { dirname as dirname6, resolve as
|
|
12946
|
+
import { dirname as dirname6, resolve as resolve5 } from "path";
|
|
12677
12947
|
|
|
12678
12948
|
// src/cli/parse-args.ts
|
|
12679
12949
|
var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
12680
12950
|
"all",
|
|
12951
|
+
"detach",
|
|
12681
12952
|
"foreground",
|
|
12682
12953
|
"help",
|
|
12683
12954
|
"info",
|
|
@@ -12971,10 +13242,10 @@ async function downloadTo(args) {
|
|
|
12971
13242
|
logSink(formatProgress(args.agentId, received, total));
|
|
12972
13243
|
}
|
|
12973
13244
|
});
|
|
12974
|
-
await new Promise((
|
|
13245
|
+
await new Promise((resolve6, reject) => {
|
|
12975
13246
|
nodeStream.on("error", reject);
|
|
12976
13247
|
out.on("error", reject);
|
|
12977
|
-
out.on("finish", () =>
|
|
13248
|
+
out.on("finish", () => resolve6());
|
|
12978
13249
|
nodeStream.pipe(out);
|
|
12979
13250
|
});
|
|
12980
13251
|
logSink(formatProgress(
|
|
@@ -13026,14 +13297,14 @@ async function extract(archivePath, dest) {
|
|
|
13026
13297
|
throw new Error(`Unsupported archive format: ${archivePath}`);
|
|
13027
13298
|
}
|
|
13028
13299
|
function run(cmd, args) {
|
|
13029
|
-
return new Promise((
|
|
13300
|
+
return new Promise((resolve6, reject) => {
|
|
13030
13301
|
const child = spawn(cmd, args, {
|
|
13031
13302
|
stdio: ["ignore", "ignore", "inherit"]
|
|
13032
13303
|
});
|
|
13033
13304
|
child.on("error", reject);
|
|
13034
13305
|
child.on("exit", (code, signal) => {
|
|
13035
13306
|
if (code === 0) {
|
|
13036
|
-
|
|
13307
|
+
resolve6();
|
|
13037
13308
|
return;
|
|
13038
13309
|
}
|
|
13039
13310
|
reject(
|
|
@@ -13045,11 +13316,11 @@ function run(cmd, args) {
|
|
|
13045
13316
|
});
|
|
13046
13317
|
}
|
|
13047
13318
|
async function hasCommand(name) {
|
|
13048
|
-
return new Promise((
|
|
13319
|
+
return new Promise((resolve6) => {
|
|
13049
13320
|
const finder = process.platform === "win32" ? "where" : "which";
|
|
13050
13321
|
const child = spawn(finder, [name], { stdio: "ignore" });
|
|
13051
|
-
child.on("error", () =>
|
|
13052
|
-
child.on("exit", (code) =>
|
|
13322
|
+
child.on("error", () => resolve6(false));
|
|
13323
|
+
child.on("exit", (code) => resolve6(code === 0));
|
|
13053
13324
|
});
|
|
13054
13325
|
}
|
|
13055
13326
|
async function fileExists(p) {
|
|
@@ -13167,7 +13438,7 @@ function runNpmInstall(args) {
|
|
|
13167
13438
|
}
|
|
13168
13439
|
async function runNpmInstallOnce(args, attempt) {
|
|
13169
13440
|
try {
|
|
13170
|
-
await new Promise((
|
|
13441
|
+
await new Promise((resolve6, reject) => {
|
|
13171
13442
|
const registryArgs = args.registry ? ["--registry", args.registry] : [];
|
|
13172
13443
|
let child;
|
|
13173
13444
|
try {
|
|
@@ -13209,7 +13480,7 @@ async function runNpmInstallOnce(args, attempt) {
|
|
|
13209
13480
|
});
|
|
13210
13481
|
child.on("exit", (code, signal) => {
|
|
13211
13482
|
if (code === 0) {
|
|
13212
|
-
|
|
13483
|
+
resolve6();
|
|
13213
13484
|
return;
|
|
13214
13485
|
}
|
|
13215
13486
|
const reason = code !== null ? `exit code ${code}` : `signal ${signal ?? "unknown"}`;
|
|
@@ -13537,13 +13808,13 @@ function ndjsonStreamFromStdio(stdout, stdin) {
|
|
|
13537
13808
|
throw new Error("stream is closed");
|
|
13538
13809
|
}
|
|
13539
13810
|
const line = JSON.stringify(message) + "\n";
|
|
13540
|
-
await new Promise((
|
|
13811
|
+
await new Promise((resolve6, reject) => {
|
|
13541
13812
|
stdin.write(line, (err) => {
|
|
13542
13813
|
if (err) {
|
|
13543
13814
|
reject(err);
|
|
13544
13815
|
return;
|
|
13545
13816
|
}
|
|
13546
|
-
|
|
13817
|
+
resolve6();
|
|
13547
13818
|
});
|
|
13548
13819
|
});
|
|
13549
13820
|
},
|
|
@@ -14058,8 +14329,8 @@ var SessionManager = class {
|
|
|
14058
14329
|
}
|
|
14059
14330
|
async resolveImportCwd(cwd) {
|
|
14060
14331
|
try {
|
|
14061
|
-
const
|
|
14062
|
-
if (
|
|
14332
|
+
const stat5 = await fs11.stat(cwd);
|
|
14333
|
+
if (stat5.isDirectory()) {
|
|
14063
14334
|
return cwd;
|
|
14064
14335
|
}
|
|
14065
14336
|
} catch {
|
|
@@ -14982,9 +15253,9 @@ var ExtensionManager = class {
|
|
|
14982
15253
|
} catch {
|
|
14983
15254
|
}
|
|
14984
15255
|
tasks.push(
|
|
14985
|
-
new Promise((
|
|
15256
|
+
new Promise((resolve6) => {
|
|
14986
15257
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
14987
|
-
|
|
15258
|
+
resolve6();
|
|
14988
15259
|
return;
|
|
14989
15260
|
}
|
|
14990
15261
|
const timer = setTimeout(() => {
|
|
@@ -14992,11 +15263,11 @@ var ExtensionManager = class {
|
|
|
14992
15263
|
child.kill("SIGKILL");
|
|
14993
15264
|
} catch {
|
|
14994
15265
|
}
|
|
14995
|
-
|
|
15266
|
+
resolve6();
|
|
14996
15267
|
}, STOP_GRACE_MS);
|
|
14997
15268
|
child.on("exit", () => {
|
|
14998
15269
|
clearTimeout(timer);
|
|
14999
|
-
|
|
15270
|
+
resolve6();
|
|
15000
15271
|
});
|
|
15001
15272
|
})
|
|
15002
15273
|
);
|
|
@@ -15104,8 +15375,8 @@ var ExtensionManager = class {
|
|
|
15104
15375
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
15105
15376
|
return;
|
|
15106
15377
|
}
|
|
15107
|
-
const exited = new Promise((
|
|
15108
|
-
entry.exitWaiters.push(
|
|
15378
|
+
const exited = new Promise((resolve6) => {
|
|
15379
|
+
entry.exitWaiters.push(resolve6);
|
|
15109
15380
|
});
|
|
15110
15381
|
try {
|
|
15111
15382
|
child.kill("SIGTERM");
|
|
@@ -15302,8 +15573,8 @@ var ExtensionManager = class {
|
|
|
15302
15573
|
entry.pid = void 0;
|
|
15303
15574
|
entry.lastExitCode = typeof code === "number" ? code : void 0;
|
|
15304
15575
|
const waiters = entry.exitWaiters.splice(0);
|
|
15305
|
-
for (const
|
|
15306
|
-
|
|
15576
|
+
for (const resolve6 of waiters) {
|
|
15577
|
+
resolve6();
|
|
15307
15578
|
}
|
|
15308
15579
|
if (this.stopping || entry.manuallyStopped) {
|
|
15309
15580
|
try {
|
|
@@ -17174,9 +17445,9 @@ import * as fs16 from "fs";
|
|
|
17174
17445
|
import * as fsp6 from "fs/promises";
|
|
17175
17446
|
async function runLogTail(logPath, argv, notFoundMessage) {
|
|
17176
17447
|
const opts = parseLogTailFlags(argv);
|
|
17177
|
-
let
|
|
17448
|
+
let stat5;
|
|
17178
17449
|
try {
|
|
17179
|
-
|
|
17450
|
+
stat5 = await fsp6.stat(logPath);
|
|
17180
17451
|
} catch (err) {
|
|
17181
17452
|
const e = err;
|
|
17182
17453
|
if (e.code === "ENOENT") {
|
|
@@ -17187,7 +17458,7 @@ async function runLogTail(logPath, argv, notFoundMessage) {
|
|
|
17187
17458
|
}
|
|
17188
17459
|
throw err;
|
|
17189
17460
|
}
|
|
17190
|
-
let position = await printTail(logPath,
|
|
17461
|
+
let position = await printTail(logPath, stat5.size, opts.tail);
|
|
17191
17462
|
if (!opts.follow) {
|
|
17192
17463
|
return;
|
|
17193
17464
|
}
|
|
@@ -17222,10 +17493,10 @@ async function runLogTail(logPath, argv, notFoundMessage) {
|
|
|
17222
17493
|
}
|
|
17223
17494
|
});
|
|
17224
17495
|
});
|
|
17225
|
-
await new Promise((
|
|
17496
|
+
await new Promise((resolve6) => {
|
|
17226
17497
|
const finish = () => {
|
|
17227
17498
|
watcher.close();
|
|
17228
|
-
|
|
17499
|
+
resolve6();
|
|
17229
17500
|
};
|
|
17230
17501
|
process.once("SIGINT", finish);
|
|
17231
17502
|
process.once("SIGTERM", finish);
|
|
@@ -17305,6 +17576,7 @@ async function runDaemonStart(flags = {}) {
|
|
|
17305
17576
|
return;
|
|
17306
17577
|
}
|
|
17307
17578
|
if (flagBool(flags, "foreground")) {
|
|
17579
|
+
process.title = "hydra-daemon";
|
|
17308
17580
|
const handle = await startDaemon(config, serviceToken);
|
|
17309
17581
|
process.stdout.write(
|
|
17310
17582
|
`hydra-acp daemon listening on ${config.daemon.host}:${config.daemon.port}
|
|
@@ -18031,7 +18303,7 @@ async function promptPassword(prompt) {
|
|
|
18031
18303
|
if (!process.stdin.isTTY) {
|
|
18032
18304
|
return readLineFromStdin();
|
|
18033
18305
|
}
|
|
18034
|
-
return new Promise((
|
|
18306
|
+
return new Promise((resolve6, reject) => {
|
|
18035
18307
|
const stdin = process.stdin;
|
|
18036
18308
|
const wasRaw = stdin.isRaw === true;
|
|
18037
18309
|
let buffer = "";
|
|
@@ -18048,7 +18320,7 @@ async function promptPassword(prompt) {
|
|
|
18048
18320
|
if (byte === 10 || byte === 13) {
|
|
18049
18321
|
process.stdout.write("\n");
|
|
18050
18322
|
cleanup();
|
|
18051
|
-
|
|
18323
|
+
resolve6(buffer);
|
|
18052
18324
|
return;
|
|
18053
18325
|
}
|
|
18054
18326
|
if (byte === 3) {
|
|
@@ -18074,7 +18346,7 @@ async function promptPassword(prompt) {
|
|
|
18074
18346
|
});
|
|
18075
18347
|
}
|
|
18076
18348
|
function readLineFromStdin() {
|
|
18077
|
-
return new Promise((
|
|
18349
|
+
return new Promise((resolve6, reject) => {
|
|
18078
18350
|
let buffer = "";
|
|
18079
18351
|
process.stdin.setEncoding("utf8");
|
|
18080
18352
|
const onData = (chunk) => {
|
|
@@ -18083,7 +18355,7 @@ function readLineFromStdin() {
|
|
|
18083
18355
|
if (nl !== -1) {
|
|
18084
18356
|
process.stdin.removeListener("data", onData);
|
|
18085
18357
|
process.stdin.removeListener("error", onError);
|
|
18086
|
-
|
|
18358
|
+
resolve6(buffer.slice(0, nl).replace(/\r$/, ""));
|
|
18087
18359
|
}
|
|
18088
18360
|
};
|
|
18089
18361
|
const onError = (err) => {
|
|
@@ -18385,8 +18657,34 @@ function isResponse2(msg) {
|
|
|
18385
18657
|
return !("method" in msg) && "id" in msg;
|
|
18386
18658
|
}
|
|
18387
18659
|
|
|
18660
|
+
// src/core/process-title.ts
|
|
18661
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
18662
|
+
var COMM_ANCHOR = "hydra";
|
|
18663
|
+
var defaultWriteComm = (text) => {
|
|
18664
|
+
writeFileSync2("/proc/self/comm", text);
|
|
18665
|
+
};
|
|
18666
|
+
function setHydraProcessTitle(fullTitle, deps = {}) {
|
|
18667
|
+
process.title = fullTitle;
|
|
18668
|
+
const platform = deps.platform ?? process.platform;
|
|
18669
|
+
if (platform !== "linux") {
|
|
18670
|
+
return;
|
|
18671
|
+
}
|
|
18672
|
+
const writeComm = deps.writeComm ?? defaultWriteComm;
|
|
18673
|
+
try {
|
|
18674
|
+
writeComm(COMM_ANCHOR);
|
|
18675
|
+
} catch {
|
|
18676
|
+
}
|
|
18677
|
+
}
|
|
18678
|
+
function buildTitleFromArgv(argv) {
|
|
18679
|
+
if (argv.length === 0) {
|
|
18680
|
+
return COMM_ANCHOR;
|
|
18681
|
+
}
|
|
18682
|
+
return `${COMM_ANCHOR} ${argv.join(" ")}`;
|
|
18683
|
+
}
|
|
18684
|
+
|
|
18388
18685
|
// src/shim/proxy.ts
|
|
18389
18686
|
async function runShim(opts) {
|
|
18687
|
+
setHydraProcessTitle(buildTitleFromArgv(process.argv.slice(2)));
|
|
18390
18688
|
const config = await loadConfig();
|
|
18391
18689
|
const serviceToken = await ensureServiceToken();
|
|
18392
18690
|
await ensureDaemonReachable(config);
|
|
@@ -18626,6 +18924,309 @@ function injectHydraMeta(msg, additions) {
|
|
|
18626
18924
|
};
|
|
18627
18925
|
}
|
|
18628
18926
|
|
|
18927
|
+
// src/cli/commands/cat.ts
|
|
18928
|
+
init_connection();
|
|
18929
|
+
init_ws_stream();
|
|
18930
|
+
init_config();
|
|
18931
|
+
init_service_token();
|
|
18932
|
+
init_daemon_bootstrap();
|
|
18933
|
+
init_render_update();
|
|
18934
|
+
init_types();
|
|
18935
|
+
init_hydra_version();
|
|
18936
|
+
import { WebSocket as WebSocket2 } from "ws";
|
|
18937
|
+
|
|
18938
|
+
// src/cli/commands/cat-chunker.ts
|
|
18939
|
+
function createChunker(opts) {
|
|
18940
|
+
let buffer = "";
|
|
18941
|
+
let dataArrivedSinceSchedule = false;
|
|
18942
|
+
let scheduled = false;
|
|
18943
|
+
let ended = false;
|
|
18944
|
+
const flush = () => {
|
|
18945
|
+
if (buffer.length === 0) {
|
|
18946
|
+
return;
|
|
18947
|
+
}
|
|
18948
|
+
const out = buffer;
|
|
18949
|
+
buffer = "";
|
|
18950
|
+
opts.onChunk(out);
|
|
18951
|
+
};
|
|
18952
|
+
const scheduleCheck = () => {
|
|
18953
|
+
if (scheduled) {
|
|
18954
|
+
return;
|
|
18955
|
+
}
|
|
18956
|
+
scheduled = true;
|
|
18957
|
+
dataArrivedSinceSchedule = false;
|
|
18958
|
+
opts.scheduleFlushCheck(() => {
|
|
18959
|
+
scheduled = false;
|
|
18960
|
+
if (dataArrivedSinceSchedule) {
|
|
18961
|
+
scheduleCheck();
|
|
18962
|
+
return;
|
|
18963
|
+
}
|
|
18964
|
+
flush();
|
|
18965
|
+
});
|
|
18966
|
+
};
|
|
18967
|
+
return {
|
|
18968
|
+
feed(data) {
|
|
18969
|
+
if (ended || data.length === 0) {
|
|
18970
|
+
return;
|
|
18971
|
+
}
|
|
18972
|
+
buffer += data;
|
|
18973
|
+
if (scheduled) {
|
|
18974
|
+
dataArrivedSinceSchedule = true;
|
|
18975
|
+
} else {
|
|
18976
|
+
scheduleCheck();
|
|
18977
|
+
}
|
|
18978
|
+
},
|
|
18979
|
+
eof() {
|
|
18980
|
+
if (ended) {
|
|
18981
|
+
return;
|
|
18982
|
+
}
|
|
18983
|
+
ended = true;
|
|
18984
|
+
flush();
|
|
18985
|
+
}
|
|
18986
|
+
};
|
|
18987
|
+
}
|
|
18988
|
+
|
|
18989
|
+
// src/cli/commands/cat.ts
|
|
18990
|
+
async function runCat(opts) {
|
|
18991
|
+
setHydraProcessTitle(buildTitleFromArgv(process.argv.slice(2)));
|
|
18992
|
+
if (process.stdin.isTTY && !opts.prompt && !opts.sessionId) {
|
|
18993
|
+
process.stderr.write(
|
|
18994
|
+
"hydra-acp cat: nothing to send. Pipe input on stdin, pass -p <text>, or attach to an existing session with --session-id.\n"
|
|
18995
|
+
);
|
|
18996
|
+
process.exit(2);
|
|
18997
|
+
return;
|
|
18998
|
+
}
|
|
18999
|
+
const config = await loadConfig();
|
|
19000
|
+
const serviceToken = await ensureServiceToken();
|
|
19001
|
+
await ensureDaemonReachable(config);
|
|
19002
|
+
const protocol = config.daemon.tls ? "wss" : "ws";
|
|
19003
|
+
const url = `${protocol}://${config.daemon.host}:${config.daemon.port}/acp`;
|
|
19004
|
+
const subprotocols = ["acp.v1", `hydra-acp-token.${serviceToken}`];
|
|
19005
|
+
const ws = await openWs2(url, subprotocols);
|
|
19006
|
+
const stream = wsToMessageStream(ws);
|
|
19007
|
+
const conn = new JsonRpcConnection(stream);
|
|
19008
|
+
const result = await runCatLoop({
|
|
19009
|
+
conn,
|
|
19010
|
+
opts,
|
|
19011
|
+
stdin: process.stdin,
|
|
19012
|
+
stdinIsTty: process.stdin.isTTY === true,
|
|
19013
|
+
stdout: (chunk) => process.stdout.write(chunk),
|
|
19014
|
+
stderr: (chunk) => {
|
|
19015
|
+
process.stderr.write(chunk);
|
|
19016
|
+
}
|
|
19017
|
+
});
|
|
19018
|
+
process.exit(result.exitCode);
|
|
19019
|
+
}
|
|
19020
|
+
async function runCatLoop(args) {
|
|
19021
|
+
const { conn, opts, stdin, stdinIsTty, stdout, stderr } = args;
|
|
19022
|
+
conn.setDefaultHandler(async () => {
|
|
19023
|
+
return { error: { code: -32601, message: "method not implemented" } };
|
|
19024
|
+
});
|
|
19025
|
+
try {
|
|
19026
|
+
await conn.request("initialize", {
|
|
19027
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
19028
|
+
clientCapabilities: {
|
|
19029
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
19030
|
+
terminal: false
|
|
19031
|
+
},
|
|
19032
|
+
clientInfo: { name: "hydra-acp-cat", version: HYDRA_VERSION }
|
|
19033
|
+
});
|
|
19034
|
+
} catch {
|
|
19035
|
+
}
|
|
19036
|
+
const sessionId = await openOrAttachSession(conn, opts);
|
|
19037
|
+
let turnHadOutput = false;
|
|
19038
|
+
let lastCharWasNewline = true;
|
|
19039
|
+
const writeStdout = (text) => {
|
|
19040
|
+
if (text.length === 0) {
|
|
19041
|
+
return;
|
|
19042
|
+
}
|
|
19043
|
+
stdout(text);
|
|
19044
|
+
lastCharWasNewline = text.charCodeAt(text.length - 1) === 10;
|
|
19045
|
+
};
|
|
19046
|
+
const finalizeTurn = () => {
|
|
19047
|
+
if (turnHadOutput && !lastCharWasNewline) {
|
|
19048
|
+
writeStdout("\n");
|
|
19049
|
+
}
|
|
19050
|
+
turnHadOutput = false;
|
|
19051
|
+
};
|
|
19052
|
+
conn.onNotification("session/update", (params) => {
|
|
19053
|
+
const update = params?.update;
|
|
19054
|
+
const event = mapUpdate(update);
|
|
19055
|
+
if (!event) {
|
|
19056
|
+
return;
|
|
19057
|
+
}
|
|
19058
|
+
if (event.kind === "agent-text") {
|
|
19059
|
+
turnHadOutput = true;
|
|
19060
|
+
writeStdout(event.text);
|
|
19061
|
+
} else if (event.kind === "turn-complete") {
|
|
19062
|
+
finalizeTurn();
|
|
19063
|
+
}
|
|
19064
|
+
});
|
|
19065
|
+
const sendChunk = async (text) => {
|
|
19066
|
+
const promptBlocks = [];
|
|
19067
|
+
if (opts.prompt) {
|
|
19068
|
+
promptBlocks.push({ type: "text", text: opts.prompt });
|
|
19069
|
+
}
|
|
19070
|
+
if (text.length > 0) {
|
|
19071
|
+
promptBlocks.push({ type: "text", text });
|
|
19072
|
+
}
|
|
19073
|
+
if (promptBlocks.length === 0) {
|
|
19074
|
+
return;
|
|
19075
|
+
}
|
|
19076
|
+
try {
|
|
19077
|
+
await conn.request("session/prompt", {
|
|
19078
|
+
sessionId,
|
|
19079
|
+
prompt: promptBlocks
|
|
19080
|
+
});
|
|
19081
|
+
} catch (err) {
|
|
19082
|
+
stderr(`hydra-acp cat: prompt failed: ${err.message}
|
|
19083
|
+
`);
|
|
19084
|
+
return;
|
|
19085
|
+
}
|
|
19086
|
+
finalizeTurn();
|
|
19087
|
+
};
|
|
19088
|
+
let exitCode = 0;
|
|
19089
|
+
let resolveDone;
|
|
19090
|
+
const done = new Promise((resolve6) => {
|
|
19091
|
+
resolveDone = resolve6;
|
|
19092
|
+
});
|
|
19093
|
+
let settled = false;
|
|
19094
|
+
const settle = async (code) => {
|
|
19095
|
+
if (settled) {
|
|
19096
|
+
return;
|
|
19097
|
+
}
|
|
19098
|
+
settled = true;
|
|
19099
|
+
if (!opts.detach) {
|
|
19100
|
+
conn.request("session/detach", { sessionId }).catch(() => void 0);
|
|
19101
|
+
}
|
|
19102
|
+
await conn.close().catch(() => void 0);
|
|
19103
|
+
resolveDone({ exitCode: code });
|
|
19104
|
+
};
|
|
19105
|
+
conn.onClose((err) => {
|
|
19106
|
+
if (err) {
|
|
19107
|
+
stderr(`hydra-acp cat: ${err.message}
|
|
19108
|
+
`);
|
|
19109
|
+
exitCode = 1;
|
|
19110
|
+
}
|
|
19111
|
+
if (!settled) {
|
|
19112
|
+
settled = true;
|
|
19113
|
+
resolveDone({ exitCode });
|
|
19114
|
+
}
|
|
19115
|
+
});
|
|
19116
|
+
const chunkQueue = [];
|
|
19117
|
+
let draining = false;
|
|
19118
|
+
let stdinEnded = false;
|
|
19119
|
+
const drainQueue = async () => {
|
|
19120
|
+
if (draining) {
|
|
19121
|
+
return;
|
|
19122
|
+
}
|
|
19123
|
+
draining = true;
|
|
19124
|
+
try {
|
|
19125
|
+
while (chunkQueue.length > 0) {
|
|
19126
|
+
const next = chunkQueue.shift();
|
|
19127
|
+
if (next === void 0) {
|
|
19128
|
+
break;
|
|
19129
|
+
}
|
|
19130
|
+
await sendChunk(next);
|
|
19131
|
+
}
|
|
19132
|
+
} finally {
|
|
19133
|
+
draining = false;
|
|
19134
|
+
if (stdinEnded && chunkQueue.length === 0) {
|
|
19135
|
+
await settle(exitCode);
|
|
19136
|
+
}
|
|
19137
|
+
}
|
|
19138
|
+
};
|
|
19139
|
+
const chunker = createChunker({
|
|
19140
|
+
// setImmediate fires in the libuv "check" phase, after pending
|
|
19141
|
+
// I/O has been polled and any back-to-back "data" events have
|
|
19142
|
+
// been emitted. That makes it the natural hook for "the writer
|
|
19143
|
+
// has paused, time to flush": if more bytes were sitting in the
|
|
19144
|
+
// pipe buffer, Node would have emitted another "data" event
|
|
19145
|
+
// before this fires, and the chunker would detect that and defer.
|
|
19146
|
+
scheduleFlushCheck: (cb) => {
|
|
19147
|
+
const h = setImmediate(cb);
|
|
19148
|
+
return () => clearImmediate(h);
|
|
19149
|
+
},
|
|
19150
|
+
onChunk: (text) => {
|
|
19151
|
+
chunkQueue.push(text);
|
|
19152
|
+
void drainQueue();
|
|
19153
|
+
}
|
|
19154
|
+
});
|
|
19155
|
+
if (stdinIsTty && !opts.sessionId) {
|
|
19156
|
+
if (opts.prompt) {
|
|
19157
|
+
await sendChunk("");
|
|
19158
|
+
}
|
|
19159
|
+
await settle(0);
|
|
19160
|
+
return done;
|
|
19161
|
+
}
|
|
19162
|
+
if (typeof stdin.setEncoding === "function") {
|
|
19163
|
+
stdin.setEncoding("utf8");
|
|
19164
|
+
}
|
|
19165
|
+
stdin.on("data", (data) => {
|
|
19166
|
+
chunker.feed(typeof data === "string" ? data : data.toString("utf8"));
|
|
19167
|
+
});
|
|
19168
|
+
stdin.on("end", () => {
|
|
19169
|
+
chunker.eof();
|
|
19170
|
+
stdinEnded = true;
|
|
19171
|
+
if (!draining && chunkQueue.length === 0) {
|
|
19172
|
+
void settle(exitCode);
|
|
19173
|
+
}
|
|
19174
|
+
});
|
|
19175
|
+
stdin.on("error", (err) => {
|
|
19176
|
+
stderr(`hydra-acp cat: stdin error: ${err.message}
|
|
19177
|
+
`);
|
|
19178
|
+
exitCode = 1;
|
|
19179
|
+
stdinEnded = true;
|
|
19180
|
+
if (!draining && chunkQueue.length === 0) {
|
|
19181
|
+
void settle(exitCode);
|
|
19182
|
+
}
|
|
19183
|
+
});
|
|
19184
|
+
return done;
|
|
19185
|
+
}
|
|
19186
|
+
async function openOrAttachSession(conn, opts) {
|
|
19187
|
+
if (opts.sessionId) {
|
|
19188
|
+
const attached = await conn.request("session/attach", {
|
|
19189
|
+
sessionId: opts.sessionId,
|
|
19190
|
+
historyPolicy: "pending_only",
|
|
19191
|
+
clientInfo: { name: "hydra-acp-cat", version: HYDRA_VERSION }
|
|
19192
|
+
});
|
|
19193
|
+
return attached.sessionId;
|
|
19194
|
+
}
|
|
19195
|
+
const hydraMeta = {};
|
|
19196
|
+
if (opts.name) {
|
|
19197
|
+
hydraMeta.name = opts.name;
|
|
19198
|
+
}
|
|
19199
|
+
if (opts.model) {
|
|
19200
|
+
hydraMeta.model = opts.model;
|
|
19201
|
+
}
|
|
19202
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
19203
|
+
const params = { cwd };
|
|
19204
|
+
if (opts.agentId) {
|
|
19205
|
+
params.agentId = opts.agentId;
|
|
19206
|
+
}
|
|
19207
|
+
if (Object.keys(hydraMeta).length > 0) {
|
|
19208
|
+
params._meta = { [HYDRA_META_KEY]: hydraMeta };
|
|
19209
|
+
}
|
|
19210
|
+
const created = await conn.request("session/new", params);
|
|
19211
|
+
void extractHydraMeta(created._meta);
|
|
19212
|
+
return created.sessionId;
|
|
19213
|
+
}
|
|
19214
|
+
async function openWs2(url, subprotocols) {
|
|
19215
|
+
return new Promise((resolve6, reject) => {
|
|
19216
|
+
const ws = new WebSocket2(url, subprotocols);
|
|
19217
|
+
const onOpen = () => {
|
|
19218
|
+
ws.off("error", onError);
|
|
19219
|
+
resolve6(ws);
|
|
19220
|
+
};
|
|
19221
|
+
const onError = (err) => {
|
|
19222
|
+
ws.off("open", onOpen);
|
|
19223
|
+
reject(err);
|
|
19224
|
+
};
|
|
19225
|
+
ws.once("open", onOpen);
|
|
19226
|
+
ws.once("error", onError);
|
|
19227
|
+
});
|
|
19228
|
+
}
|
|
19229
|
+
|
|
18629
19230
|
// src/cli.ts
|
|
18630
19231
|
init_update_check();
|
|
18631
19232
|
var suppressUpdateNotice = false;
|
|
@@ -18703,6 +19304,26 @@ async function main() {
|
|
|
18703
19304
|
suppressUpdateNotice = true;
|
|
18704
19305
|
await runShim({ sessionId, name, agentId: agentIdFromFlag, model });
|
|
18705
19306
|
return;
|
|
19307
|
+
case "cat": {
|
|
19308
|
+
const promptFromShort = readShortPrompt(argv);
|
|
19309
|
+
const longPrompt = typeof flags.prompt === "string" ? flags.prompt : void 0;
|
|
19310
|
+
const prompt = promptFromShort ?? longPrompt;
|
|
19311
|
+
const cwd = resolveOption(flags, "cwd");
|
|
19312
|
+
const catOpts = {
|
|
19313
|
+
prompt,
|
|
19314
|
+
sessionId,
|
|
19315
|
+
name,
|
|
19316
|
+
model,
|
|
19317
|
+
agentId: agentIdFromFlag,
|
|
19318
|
+
detach: flags.detach === true
|
|
19319
|
+
};
|
|
19320
|
+
if (cwd !== void 0) {
|
|
19321
|
+
catOpts.cwd = cwd;
|
|
19322
|
+
}
|
|
19323
|
+
suppressUpdateNotice = true;
|
|
19324
|
+
await runCat(catOpts);
|
|
19325
|
+
return;
|
|
19326
|
+
}
|
|
18706
19327
|
case "init":
|
|
18707
19328
|
await runInit(flags);
|
|
18708
19329
|
return;
|
|
@@ -18887,6 +19508,7 @@ async function dispatchTui(flags, base) {
|
|
|
18887
19508
|
);
|
|
18888
19509
|
process.exit(2);
|
|
18889
19510
|
}
|
|
19511
|
+
setHydraProcessTitle(buildTitleFromArgv(process.argv.slice(2)));
|
|
18890
19512
|
const { runTui } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
18891
19513
|
const tuiOpts = { resume, forceNew, readonly };
|
|
18892
19514
|
if (base.sessionId !== void 0) {
|
|
@@ -18906,6 +19528,21 @@ async function dispatchTui(flags, base) {
|
|
|
18906
19528
|
}
|
|
18907
19529
|
await runTui(tuiOpts);
|
|
18908
19530
|
}
|
|
19531
|
+
function readShortPrompt(argv) {
|
|
19532
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
19533
|
+
const tok = argv[i];
|
|
19534
|
+
if (tok === void 0) {
|
|
19535
|
+
continue;
|
|
19536
|
+
}
|
|
19537
|
+
if (tok === "-p") {
|
|
19538
|
+
return argv[i + 1];
|
|
19539
|
+
}
|
|
19540
|
+
if (tok.startsWith("-p") && !tok.startsWith("--")) {
|
|
19541
|
+
return tok.slice(2);
|
|
19542
|
+
}
|
|
19543
|
+
}
|
|
19544
|
+
return void 0;
|
|
19545
|
+
}
|
|
18909
19546
|
function bareResumeError() {
|
|
18910
19547
|
process.stderr.write(
|
|
18911
19548
|
"hydra-acp: --resume requires a session id. Use --resume <id> to attach to a specific session, or --reattach to pick the most recent one in cwd.\n"
|
|
@@ -18916,7 +19553,7 @@ function readVersion() {
|
|
|
18916
19553
|
try {
|
|
18917
19554
|
const here = dirname6(fileURLToPath2(import.meta.url));
|
|
18918
19555
|
const pkg = JSON.parse(
|
|
18919
|
-
readFileSync2(
|
|
19556
|
+
readFileSync2(resolve5(here, "../package.json"), "utf8")
|
|
18920
19557
|
);
|
|
18921
19558
|
return pkg.version ?? "unknown";
|
|
18922
19559
|
} catch {
|
|
@@ -18932,6 +19569,19 @@ function printHelp() {
|
|
|
18932
19569
|
" hydra-acp Auto: TUI when stdout is a TTY, shim otherwise (the editor-spawned case)",
|
|
18933
19570
|
" hydra-acp shim Run as ACP shim explicitly (forces shim mode regardless of TTY)",
|
|
18934
19571
|
" hydra-acp tui [opts] Run the terminal UI explicitly (see below for opts)",
|
|
19572
|
+
" hydra-acp cat [-p <prompt>] [--session-id <id>] [--detach] [--agent <id>] [--model <id>] [--name <label>]",
|
|
19573
|
+
" Pipe-friendly headless mode. Reads stdin and sends it",
|
|
19574
|
+
" as a prompt to a fresh session, streams the agent's",
|
|
19575
|
+
" response to stdout, exits when stdin closes. A bounded",
|
|
19576
|
+
' input (e.g. `cat file.log | hydra cat -p "..."`) goes in',
|
|
19577
|
+
" as one turn; a streaming input (e.g. `tail -f`) is",
|
|
19578
|
+
" chunked by the natural pauses in the writer. -p is an",
|
|
19579
|
+
" optional standing instruction prepended to every chunk;",
|
|
19580
|
+
" if stdin already contains the question, -p is not needed.",
|
|
19581
|
+
" With --session-id, attach to an existing session instead",
|
|
19582
|
+
" of creating a new one. With --detach, the session",
|
|
19583
|
+
" survives in the daemon for slack/browser/notifier",
|
|
19584
|
+
" extensions.",
|
|
18935
19585
|
" hydra-acp launch <agent> [agent-args...]",
|
|
18936
19586
|
" Shim mode, force daemon to spawn <agent>",
|
|
18937
19587
|
" from the registry. Args after <agent>",
|