@hydra-acp/cli 0.1.57 → 0.1.58
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 +40 -51
- package/dist/cli.js +639 -299
- package/dist/index.d.ts +51 -1
- package/dist/index.js +217 -49
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -108,7 +108,11 @@ var init_paths = __esm({
|
|
|
108
108
|
// line, append-only so concurrent TUIs don't lose each other's
|
|
109
109
|
// writes.
|
|
110
110
|
globalTuiHistoryFile: () => path.join(hydraHome(), "prompt-history"),
|
|
111
|
-
tuiLogFile: () => path.join(hydraHome(), "tui.log")
|
|
111
|
+
tuiLogFile: () => path.join(hydraHome(), "tui.log"),
|
|
112
|
+
// Diagnostic dump of every JSON-RPC message that crosses a `hydra-acp
|
|
113
|
+
// shim` process. Append-only NDJSON. One file shared by every shim;
|
|
114
|
+
// each line carries the writing process's pid for disambiguation.
|
|
115
|
+
shimWireLogFile: () => path.join(hydraHome(), "shim-wire.log")
|
|
112
116
|
};
|
|
113
117
|
}
|
|
114
118
|
});
|
|
@@ -1025,6 +1029,12 @@ function extractHydraMeta(meta) {
|
|
|
1025
1029
|
if (typeof obj.mcpStdin === "boolean") {
|
|
1026
1030
|
out.mcpStdin = obj.mcpStdin;
|
|
1027
1031
|
}
|
|
1032
|
+
if (typeof obj.interactive === "boolean") {
|
|
1033
|
+
out.interactive = obj.interactive;
|
|
1034
|
+
}
|
|
1035
|
+
if (typeof obj.ancillary === "boolean") {
|
|
1036
|
+
out.ancillary = obj.ancillary;
|
|
1037
|
+
}
|
|
1028
1038
|
if (typeof obj.promptAmending === "boolean") {
|
|
1029
1039
|
out.promptAmending = obj.promptAmending;
|
|
1030
1040
|
}
|
|
@@ -1269,10 +1279,14 @@ var init_types = __esm({
|
|
|
1269
1279
|
// local session, an import is a cross-machine takeover.
|
|
1270
1280
|
forkedFromSessionId: z3.string().optional(),
|
|
1271
1281
|
forkedFromMessageId: z3.string().optional(),
|
|
1272
|
-
// clientInfo from the process that issued session/new.
|
|
1273
|
-
//
|
|
1274
|
-
// override flag surface them.
|
|
1282
|
+
// clientInfo from the process that issued session/new. Carried for
|
|
1283
|
+
// log/display; the effective filtering signal is `interactive` below.
|
|
1275
1284
|
originatingClient: z3.object({ name: z3.string(), version: z3.string().optional() }).optional(),
|
|
1285
|
+
// Tristate filter signal computed by effectiveInteractive(): explicit
|
|
1286
|
+
// when the record stored a value, else inferred (legacy cat hint or
|
|
1287
|
+
// history-presence). Clients can use this to render a hint glyph
|
|
1288
|
+
// (e.g. dim non-interactive rows when the user toggles them in).
|
|
1289
|
+
interactive: z3.boolean().optional(),
|
|
1276
1290
|
updatedAt: z3.string(),
|
|
1277
1291
|
attachedClients: z3.number().int().nonnegative(),
|
|
1278
1292
|
status: z3.enum(["live", "cold"]).default("live"),
|
|
@@ -1295,7 +1309,10 @@ var init_types = __esm({
|
|
|
1295
1309
|
});
|
|
1296
1310
|
SessionPromptParams = z3.object({
|
|
1297
1311
|
sessionId: z3.string(),
|
|
1298
|
-
prompt: z3.array(z3.unknown())
|
|
1312
|
+
prompt: z3.array(z3.unknown()),
|
|
1313
|
+
// Hydra extensions ride under _meta["hydra-acp"] (e.g. `ancillary` to
|
|
1314
|
+
// mark a non-promoting turn). Kept so Session.prompt can read them.
|
|
1315
|
+
_meta: z3.record(z3.unknown()).optional()
|
|
1299
1316
|
});
|
|
1300
1317
|
SessionCancelParams = z3.object({
|
|
1301
1318
|
sessionId: z3.string()
|
|
@@ -2622,6 +2639,12 @@ var init_session = __esm({
|
|
|
2622
2639
|
forkedFromSessionId;
|
|
2623
2640
|
forkedFromMessageId;
|
|
2624
2641
|
originatingClient;
|
|
2642
|
+
// Tristate. Mutates from undefined → true on first prompt (or directly
|
|
2643
|
+
// false if init.interactive === false). Persisted via interactiveHandlers.
|
|
2644
|
+
_interactive;
|
|
2645
|
+
get interactive() {
|
|
2646
|
+
return this._interactive;
|
|
2647
|
+
}
|
|
2625
2648
|
title;
|
|
2626
2649
|
// Snapshot state delivered to attaching clients via the attach
|
|
2627
2650
|
// response _meta rather than via history replay (which would be
|
|
@@ -2750,6 +2773,7 @@ var init_session = __esm({
|
|
|
2750
2773
|
agentModelsHandlers = [];
|
|
2751
2774
|
modelHandlers = [];
|
|
2752
2775
|
modeHandlers = [];
|
|
2776
|
+
interactiveHandlers = [];
|
|
2753
2777
|
usageHandlers = [];
|
|
2754
2778
|
cumulativeCost = 0;
|
|
2755
2779
|
// Total cost across all agent lives. costAmount in the returned snapshot
|
|
@@ -2833,6 +2857,7 @@ var init_session = __esm({
|
|
|
2833
2857
|
if (init.firstPromptSeeded) {
|
|
2834
2858
|
this._firstPromptSeeded = true;
|
|
2835
2859
|
}
|
|
2860
|
+
this._interactive = init.interactive;
|
|
2836
2861
|
this.historyStore = init.historyStore;
|
|
2837
2862
|
this.historyMaxEntries = init.historyMaxEntries ?? DEFAULT_HISTORY_MAX_ENTRIES;
|
|
2838
2863
|
this.compactEvery = Math.max(1, Math.floor(this.historyMaxEntries * 0.2));
|
|
@@ -3095,19 +3120,25 @@ var init_session = __esm({
|
|
|
3095
3120
|
return this.loadReplay(historyPolicy, opts);
|
|
3096
3121
|
}
|
|
3097
3122
|
async loadReplay(historyPolicy, opts) {
|
|
3098
|
-
const
|
|
3123
|
+
const raw = await this.getHistorySnapshot();
|
|
3099
3124
|
const state = this.buildStateSnapshotReplay();
|
|
3100
3125
|
if (historyPolicy === "after_message") {
|
|
3101
|
-
const cutoff = opts.afterMessageId ? findMessageIdIndex(
|
|
3126
|
+
const cutoff = opts.afterMessageId ? findMessageIdIndex(raw, opts.afterMessageId) : -1;
|
|
3102
3127
|
if (cutoff < 0) {
|
|
3103
|
-
return {
|
|
3128
|
+
return {
|
|
3129
|
+
entries: [...state, ...coalesceReplay(raw)],
|
|
3130
|
+
appliedPolicy: "full"
|
|
3131
|
+
};
|
|
3104
3132
|
}
|
|
3105
3133
|
return {
|
|
3106
|
-
entries: [...state, ...
|
|
3134
|
+
entries: [...state, ...coalesceReplay(raw.slice(cutoff + 1))],
|
|
3107
3135
|
appliedPolicy: "after_message"
|
|
3108
3136
|
};
|
|
3109
3137
|
}
|
|
3110
|
-
return {
|
|
3138
|
+
return {
|
|
3139
|
+
entries: [...state, ...coalesceReplay(raw)],
|
|
3140
|
+
appliedPolicy: "full"
|
|
3141
|
+
};
|
|
3111
3142
|
}
|
|
3112
3143
|
// Synthesizes one session/update notification per cached STATE_UPDATE_KIND
|
|
3113
3144
|
// so an attaching client receives the current snapshot through the
|
|
@@ -3288,6 +3319,18 @@ var init_session = __esm({
|
|
|
3288
3319
|
const messageId = generateMessageId();
|
|
3289
3320
|
this.maybeSeedTitleFromPrompt(params);
|
|
3290
3321
|
this._firstPromptSeeded = true;
|
|
3322
|
+
const ancillary = extractHydraMeta(
|
|
3323
|
+
(params ?? {})._meta
|
|
3324
|
+
).ancillary === true;
|
|
3325
|
+
if (!ancillary && this._interactive === void 0) {
|
|
3326
|
+
this._interactive = true;
|
|
3327
|
+
for (const handler of this.interactiveHandlers) {
|
|
3328
|
+
try {
|
|
3329
|
+
handler(true);
|
|
3330
|
+
} catch {
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3291
3334
|
return this.enqueueUserPrompt(client, params, messageId);
|
|
3292
3335
|
}
|
|
3293
3336
|
// DEVIATION FROM RFD #533: this broadcast is deliberately deferred
|
|
@@ -4070,13 +4113,7 @@ var init_session = __esm({
|
|
|
4070
4113
|
this.logger?.info(
|
|
4071
4114
|
`live config_option_update(model): sessionId=${this.sessionId} ${JSON.stringify(this.currentModel)} \u2192 ${JSON.stringify(trimmed)}`
|
|
4072
4115
|
);
|
|
4073
|
-
this.
|
|
4074
|
-
for (const handler of this.modelHandlers) {
|
|
4075
|
-
try {
|
|
4076
|
-
handler(trimmed);
|
|
4077
|
-
} catch {
|
|
4078
|
-
}
|
|
4079
|
-
}
|
|
4116
|
+
this.applyModelChange(trimmed);
|
|
4080
4117
|
}
|
|
4081
4118
|
}
|
|
4082
4119
|
break;
|
|
@@ -4272,6 +4309,9 @@ var init_session = __esm({
|
|
|
4272
4309
|
onModeChange(handler) {
|
|
4273
4310
|
this.modeHandlers.push(handler);
|
|
4274
4311
|
}
|
|
4312
|
+
onInteractiveChange(handler) {
|
|
4313
|
+
this.interactiveHandlers.push(handler);
|
|
4314
|
+
}
|
|
4275
4315
|
// Apply a model change initiated by a client request (session/set_model)
|
|
4276
4316
|
// when the agent doesn't emit a current_model_update notification, or
|
|
4277
4317
|
// emits a non-spec shape (e.g. config_option_update). Fires modelHandlers
|
|
@@ -6269,6 +6309,9 @@ async function listSessions(target, opts = {}, fetchImpl = fetch) {
|
|
|
6269
6309
|
if (opts.all) {
|
|
6270
6310
|
url.searchParams.set("all", "true");
|
|
6271
6311
|
}
|
|
6312
|
+
if (opts.includeNonInteractive) {
|
|
6313
|
+
url.searchParams.set("includeNonInteractive", "true");
|
|
6314
|
+
}
|
|
6272
6315
|
const response = await fetchImpl(url.toString(), {
|
|
6273
6316
|
headers: { Authorization: `Bearer ${target.token}` }
|
|
6274
6317
|
});
|
|
@@ -6295,7 +6338,8 @@ async function listSessions(target, opts = {}, fetchImpl = fetch) {
|
|
|
6295
6338
|
forkedFromSessionId: s.forkedFromSessionId,
|
|
6296
6339
|
forkedFromMessageId: s.forkedFromMessageId,
|
|
6297
6340
|
busy: s.busy,
|
|
6298
|
-
originatingClient: s.originatingClient
|
|
6341
|
+
originatingClient: s.originatingClient,
|
|
6342
|
+
interactive: s.interactive
|
|
6299
6343
|
}));
|
|
6300
6344
|
}
|
|
6301
6345
|
async function forkSession(target, id, opts = {}, fetchImpl = fetch) {
|
|
@@ -6359,13 +6403,17 @@ async function regenSessionTitle(target, id, fetchImpl = fetch) {
|
|
|
6359
6403
|
}
|
|
6360
6404
|
}
|
|
6361
6405
|
async function searchSessions(target, query, opts = {}, fetchImpl = fetch) {
|
|
6362
|
-
const
|
|
6363
|
-
url.searchParams.set("q", query);
|
|
6406
|
+
const body = { q: query };
|
|
6364
6407
|
if (opts.sessionIds && opts.sessionIds.length > 0) {
|
|
6365
|
-
|
|
6408
|
+
body.sessionIds = opts.sessionIds;
|
|
6366
6409
|
}
|
|
6367
|
-
const response = await fetchImpl(
|
|
6368
|
-
|
|
6410
|
+
const response = await fetchImpl(`${target.baseUrl}/v1/sessions/search`, {
|
|
6411
|
+
method: "POST",
|
|
6412
|
+
headers: {
|
|
6413
|
+
Authorization: `Bearer ${target.token}`,
|
|
6414
|
+
"Content-Type": "application/json"
|
|
6415
|
+
},
|
|
6416
|
+
body: JSON.stringify(body)
|
|
6369
6417
|
});
|
|
6370
6418
|
if (!response.ok) {
|
|
6371
6419
|
throw new Error(`daemon returned HTTP ${response.status}`);
|
|
@@ -7864,6 +7912,85 @@ var init_update_check = __esm({
|
|
|
7864
7912
|
}
|
|
7865
7913
|
});
|
|
7866
7914
|
|
|
7915
|
+
// src/core/cwd.ts
|
|
7916
|
+
import * as fs22 from "fs/promises";
|
|
7917
|
+
import * as path17 from "path";
|
|
7918
|
+
async function validateLocalCwd(input) {
|
|
7919
|
+
const trimmed = input.trim();
|
|
7920
|
+
if (trimmed.length === 0) {
|
|
7921
|
+
return { ok: false, reason: "path is empty" };
|
|
7922
|
+
}
|
|
7923
|
+
const resolved = path17.resolve(expandHome(trimmed));
|
|
7924
|
+
let stat5;
|
|
7925
|
+
try {
|
|
7926
|
+
stat5 = await fs22.stat(resolved);
|
|
7927
|
+
} catch {
|
|
7928
|
+
return { ok: false, reason: `${resolved} does not exist` };
|
|
7929
|
+
}
|
|
7930
|
+
if (!stat5.isDirectory()) {
|
|
7931
|
+
return { ok: false, reason: `${resolved} is not a directory` };
|
|
7932
|
+
}
|
|
7933
|
+
return { ok: true, path: resolved };
|
|
7934
|
+
}
|
|
7935
|
+
async function pickInitialLocalCwd(sessionCwd) {
|
|
7936
|
+
const candidates = [];
|
|
7937
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7938
|
+
const push = (p) => {
|
|
7939
|
+
if (!seen.has(p)) {
|
|
7940
|
+
seen.add(p);
|
|
7941
|
+
candidates.push(p);
|
|
7942
|
+
}
|
|
7943
|
+
};
|
|
7944
|
+
push(sessionCwd);
|
|
7945
|
+
if (sessionCwd.startsWith("/Users/")) {
|
|
7946
|
+
push("/home/" + sessionCwd.slice("/Users/".length));
|
|
7947
|
+
} else if (sessionCwd.startsWith("/home/")) {
|
|
7948
|
+
push("/Users/" + sessionCwd.slice("/home/".length));
|
|
7949
|
+
}
|
|
7950
|
+
for (const candidate of candidates) {
|
|
7951
|
+
try {
|
|
7952
|
+
const stat5 = await fs22.stat(candidate);
|
|
7953
|
+
if (stat5.isDirectory()) {
|
|
7954
|
+
return candidate;
|
|
7955
|
+
}
|
|
7956
|
+
} catch {
|
|
7957
|
+
}
|
|
7958
|
+
}
|
|
7959
|
+
return null;
|
|
7960
|
+
}
|
|
7961
|
+
async function completeLocalPath(input) {
|
|
7962
|
+
const lastSlash = input.lastIndexOf("/");
|
|
7963
|
+
let prefix;
|
|
7964
|
+
let basePrefix;
|
|
7965
|
+
let dirForRead;
|
|
7966
|
+
if (lastSlash === -1) {
|
|
7967
|
+
prefix = "";
|
|
7968
|
+
basePrefix = input;
|
|
7969
|
+
dirForRead = ".";
|
|
7970
|
+
} else {
|
|
7971
|
+
prefix = input.slice(0, lastSlash + 1);
|
|
7972
|
+
basePrefix = input.slice(lastSlash + 1);
|
|
7973
|
+
dirForRead = lastSlash === 0 ? "/" : prefix;
|
|
7974
|
+
}
|
|
7975
|
+
const resolvedDir = path17.resolve(expandHome(dirForRead));
|
|
7976
|
+
let entries;
|
|
7977
|
+
try {
|
|
7978
|
+
const list = await fs22.readdir(resolvedDir, { withFileTypes: true });
|
|
7979
|
+
entries = list.map((e) => ({ name: e.name, isDir: e.isDirectory() }));
|
|
7980
|
+
} catch {
|
|
7981
|
+
return { prefix, basePrefix, matches: [] };
|
|
7982
|
+
}
|
|
7983
|
+
const showHidden = basePrefix.startsWith(".");
|
|
7984
|
+
const matches = entries.filter((e) => e.name.startsWith(basePrefix)).filter((e) => showHidden || !e.name.startsWith(".")).map((e) => e.isDir ? `${e.name}/` : e.name).sort();
|
|
7985
|
+
return { prefix, basePrefix, matches };
|
|
7986
|
+
}
|
|
7987
|
+
var init_cwd = __esm({
|
|
7988
|
+
"src/core/cwd.ts"() {
|
|
7989
|
+
"use strict";
|
|
7990
|
+
init_config();
|
|
7991
|
+
}
|
|
7992
|
+
});
|
|
7993
|
+
|
|
7867
7994
|
// src/tui/input.ts
|
|
7868
7995
|
function formatPasteToken(id, lineCount) {
|
|
7869
7996
|
return `[pasted #${id} +${lineCount} lines]`;
|
|
@@ -8125,12 +8252,18 @@ var init_input = __esm({
|
|
|
8125
8252
|
return [];
|
|
8126
8253
|
case "ctrl-c":
|
|
8127
8254
|
return this.handleCtrlC();
|
|
8128
|
-
case "ctrl-d":
|
|
8255
|
+
case "ctrl-d": {
|
|
8129
8256
|
if (this.bufferIsEmpty()) {
|
|
8130
8257
|
return [{ type: "exit" }];
|
|
8131
8258
|
}
|
|
8259
|
+
const lastLine = this.buffer[this.buffer.length - 1] ?? "";
|
|
8260
|
+
const atEndOfBuffer = this.row === this.buffer.length - 1 && this.col >= lastLine.length;
|
|
8261
|
+
if (atEndOfBuffer) {
|
|
8262
|
+
return [{ type: "exit" }];
|
|
8263
|
+
}
|
|
8132
8264
|
this.deleteForward();
|
|
8133
8265
|
return [];
|
|
8266
|
+
}
|
|
8134
8267
|
case "ctrl-l":
|
|
8135
8268
|
return [{ type: "redraw" }];
|
|
8136
8269
|
case "ctrl-p":
|
|
@@ -8808,9 +8941,9 @@ var init_input = __esm({
|
|
|
8808
8941
|
});
|
|
8809
8942
|
|
|
8810
8943
|
// src/tui/attachments.ts
|
|
8811
|
-
import
|
|
8944
|
+
import path18 from "path";
|
|
8812
8945
|
function mimeFromExtension(p) {
|
|
8813
|
-
return EXTENSION_TO_MIME[
|
|
8946
|
+
return EXTENSION_TO_MIME[path18.extname(p).toLowerCase()] ?? null;
|
|
8814
8947
|
}
|
|
8815
8948
|
function isSupportedImagePath(p) {
|
|
8816
8949
|
return mimeFromExtension(p) !== null;
|
|
@@ -10698,9 +10831,9 @@ uncaught: ${err.stack ?? err.message}
|
|
|
10698
10831
|
this.permissionPrompt = spec ? { ...spec } : null;
|
|
10699
10832
|
this.repaint();
|
|
10700
10833
|
}
|
|
10701
|
-
// Two-line confirmation modal that takes over the prompt area.
|
|
10702
|
-
//
|
|
10703
|
-
//
|
|
10834
|
+
// Two-line confirmation modal that takes over the prompt area. Pass
|
|
10835
|
+
// null to dismiss. Currently unused — kept as a generic primitive for
|
|
10836
|
+
// any future modal that needs a question + hint footer.
|
|
10704
10837
|
setConfirmPrompt(spec) {
|
|
10705
10838
|
this.confirmPrompt = spec ? { ...spec } : null;
|
|
10706
10839
|
this.repaint();
|
|
@@ -12301,7 +12434,11 @@ var init_import_action_prompt = __esm({
|
|
|
12301
12434
|
// src/tui/picker.ts
|
|
12302
12435
|
function createPickerPrefs() {
|
|
12303
12436
|
return {
|
|
12304
|
-
filters: {
|
|
12437
|
+
filters: {
|
|
12438
|
+
cwdOnly: false,
|
|
12439
|
+
hostFilter: "__local",
|
|
12440
|
+
includeNonInteractive: false
|
|
12441
|
+
}
|
|
12305
12442
|
};
|
|
12306
12443
|
}
|
|
12307
12444
|
async function pickSession(term, opts) {
|
|
@@ -12329,10 +12466,8 @@ async function pickSession(term, opts) {
|
|
|
12329
12466
|
if (prefs.filters.cwdOnly) {
|
|
12330
12467
|
base = base.filter((s) => s.cwd === opts.cwd);
|
|
12331
12468
|
}
|
|
12332
|
-
if (!prefs.filters.
|
|
12333
|
-
base = base.filter(
|
|
12334
|
-
(s) => s.originatingClient?.name !== HYDRA_CAT_CLIENT_NAME
|
|
12335
|
-
);
|
|
12469
|
+
if (!prefs.filters.includeNonInteractive) {
|
|
12470
|
+
base = base.filter((s) => s.interactive === true);
|
|
12336
12471
|
}
|
|
12337
12472
|
base = filterByHost(base, prefs.filters.hostFilter);
|
|
12338
12473
|
return base;
|
|
@@ -12534,8 +12669,8 @@ async function pickSession(term, opts) {
|
|
|
12534
12669
|
prefs.filters.hostFilter === "__local" ? "host: local" : `host: ${prefs.filters.hostFilter}`
|
|
12535
12670
|
);
|
|
12536
12671
|
}
|
|
12537
|
-
if (prefs.filters.
|
|
12538
|
-
parts.push("+
|
|
12672
|
+
if (prefs.filters.includeNonInteractive) {
|
|
12673
|
+
parts.push("+non-interactive");
|
|
12539
12674
|
}
|
|
12540
12675
|
if (above > 0) {
|
|
12541
12676
|
parts.push(`\u2191 ${above} above`);
|
|
@@ -13176,7 +13311,9 @@ ${cells}`;
|
|
|
13176
13311
|
try {
|
|
13177
13312
|
const beforeKey = refreshOpts.silent ? renderFingerprint() : "";
|
|
13178
13313
|
const beforeTotal = total;
|
|
13179
|
-
const next = await listSessions(opts.target
|
|
13314
|
+
const next = await listSessions(opts.target, {
|
|
13315
|
+
includeNonInteractive: true
|
|
13316
|
+
});
|
|
13180
13317
|
const followId = preferredId ?? (selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0);
|
|
13181
13318
|
allSessions = sortSessions(next, opts.cwd);
|
|
13182
13319
|
applyFilter();
|
|
@@ -13654,7 +13791,7 @@ ${cells}`;
|
|
|
13654
13791
|
const effects = composer.feed(event);
|
|
13655
13792
|
const after = composer.state();
|
|
13656
13793
|
const unchanged = before.buffer.length === after.buffer.length && before.buffer.every((line, i) => line === after.buffer[i]) && before.row === after.row && before.col === after.col;
|
|
13657
|
-
if (effects.some((e) => e.type === "exit")
|
|
13794
|
+
if (effects.some((e) => e.type === "exit")) {
|
|
13658
13795
|
cleanup();
|
|
13659
13796
|
resolve8({ kind: "abort" });
|
|
13660
13797
|
return;
|
|
@@ -13754,7 +13891,7 @@ ${cells}`;
|
|
|
13754
13891
|
}
|
|
13755
13892
|
if (name === "i" || name === "I") {
|
|
13756
13893
|
const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
13757
|
-
prefs.filters.
|
|
13894
|
+
prefs.filters.includeNonInteractive = !prefs.filters.includeNonInteractive;
|
|
13758
13895
|
applyFilter();
|
|
13759
13896
|
restoreCursorAfterFilter(keepId);
|
|
13760
13897
|
renderFromScratch();
|
|
@@ -14020,7 +14157,6 @@ var init_picker = __esm({
|
|
|
14020
14157
|
init_session_row();
|
|
14021
14158
|
init_paths();
|
|
14022
14159
|
init_session();
|
|
14023
|
-
init_hydra_version();
|
|
14024
14160
|
init_discovery();
|
|
14025
14161
|
init_history();
|
|
14026
14162
|
init_input();
|
|
@@ -14060,85 +14196,6 @@ var init_picker = __esm({
|
|
|
14060
14196
|
}
|
|
14061
14197
|
});
|
|
14062
14198
|
|
|
14063
|
-
// src/core/cwd.ts
|
|
14064
|
-
import * as fs21 from "fs/promises";
|
|
14065
|
-
import * as path18 from "path";
|
|
14066
|
-
async function validateLocalCwd(input) {
|
|
14067
|
-
const trimmed = input.trim();
|
|
14068
|
-
if (trimmed.length === 0) {
|
|
14069
|
-
return { ok: false, reason: "path is empty" };
|
|
14070
|
-
}
|
|
14071
|
-
const resolved = path18.resolve(expandHome(trimmed));
|
|
14072
|
-
let stat5;
|
|
14073
|
-
try {
|
|
14074
|
-
stat5 = await fs21.stat(resolved);
|
|
14075
|
-
} catch {
|
|
14076
|
-
return { ok: false, reason: `${resolved} does not exist` };
|
|
14077
|
-
}
|
|
14078
|
-
if (!stat5.isDirectory()) {
|
|
14079
|
-
return { ok: false, reason: `${resolved} is not a directory` };
|
|
14080
|
-
}
|
|
14081
|
-
return { ok: true, path: resolved };
|
|
14082
|
-
}
|
|
14083
|
-
async function pickInitialLocalCwd(sessionCwd) {
|
|
14084
|
-
const candidates = [];
|
|
14085
|
-
const seen = /* @__PURE__ */ new Set();
|
|
14086
|
-
const push = (p) => {
|
|
14087
|
-
if (!seen.has(p)) {
|
|
14088
|
-
seen.add(p);
|
|
14089
|
-
candidates.push(p);
|
|
14090
|
-
}
|
|
14091
|
-
};
|
|
14092
|
-
push(sessionCwd);
|
|
14093
|
-
if (sessionCwd.startsWith("/Users/")) {
|
|
14094
|
-
push("/home/" + sessionCwd.slice("/Users/".length));
|
|
14095
|
-
} else if (sessionCwd.startsWith("/home/")) {
|
|
14096
|
-
push("/Users/" + sessionCwd.slice("/home/".length));
|
|
14097
|
-
}
|
|
14098
|
-
for (const candidate of candidates) {
|
|
14099
|
-
try {
|
|
14100
|
-
const stat5 = await fs21.stat(candidate);
|
|
14101
|
-
if (stat5.isDirectory()) {
|
|
14102
|
-
return candidate;
|
|
14103
|
-
}
|
|
14104
|
-
} catch {
|
|
14105
|
-
}
|
|
14106
|
-
}
|
|
14107
|
-
return null;
|
|
14108
|
-
}
|
|
14109
|
-
async function completeLocalPath(input) {
|
|
14110
|
-
const lastSlash = input.lastIndexOf("/");
|
|
14111
|
-
let prefix;
|
|
14112
|
-
let basePrefix;
|
|
14113
|
-
let dirForRead;
|
|
14114
|
-
if (lastSlash === -1) {
|
|
14115
|
-
prefix = "";
|
|
14116
|
-
basePrefix = input;
|
|
14117
|
-
dirForRead = ".";
|
|
14118
|
-
} else {
|
|
14119
|
-
prefix = input.slice(0, lastSlash + 1);
|
|
14120
|
-
basePrefix = input.slice(lastSlash + 1);
|
|
14121
|
-
dirForRead = lastSlash === 0 ? "/" : prefix;
|
|
14122
|
-
}
|
|
14123
|
-
const resolvedDir = path18.resolve(expandHome(dirForRead));
|
|
14124
|
-
let entries;
|
|
14125
|
-
try {
|
|
14126
|
-
const list = await fs21.readdir(resolvedDir, { withFileTypes: true });
|
|
14127
|
-
entries = list.map((e) => ({ name: e.name, isDir: e.isDirectory() }));
|
|
14128
|
-
} catch {
|
|
14129
|
-
return { prefix, basePrefix, matches: [] };
|
|
14130
|
-
}
|
|
14131
|
-
const showHidden = basePrefix.startsWith(".");
|
|
14132
|
-
const matches = entries.filter((e) => e.name.startsWith(basePrefix)).filter((e) => showHidden || !e.name.startsWith(".")).map((e) => e.isDir ? `${e.name}/` : e.name).sort();
|
|
14133
|
-
return { prefix, basePrefix, matches };
|
|
14134
|
-
}
|
|
14135
|
-
var init_cwd = __esm({
|
|
14136
|
-
"src/core/cwd.ts"() {
|
|
14137
|
-
"use strict";
|
|
14138
|
-
init_config();
|
|
14139
|
-
}
|
|
14140
|
-
});
|
|
14141
|
-
|
|
14142
14199
|
// src/tui/completion.ts
|
|
14143
14200
|
function longestCommonPrefix(names) {
|
|
14144
14201
|
if (names.length === 0) {
|
|
@@ -14189,24 +14246,25 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
14189
14246
|
const defaultCwd = opts.defaultCwd ?? await pickInitialLocalCwd(session.cwd) ?? os6.homedir();
|
|
14190
14247
|
resetTerminalModes();
|
|
14191
14248
|
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
14192
|
-
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
14193
14249
|
const originalCwd = shortenHomePath(session.cwd);
|
|
14250
|
+
const title = opts.title ?? "Fork locally \u2014 choose cwd";
|
|
14251
|
+
const intro = opts.intro ?? "Pick a local cwd for this session:";
|
|
14252
|
+
const headerRows = [
|
|
14253
|
+
{ label: "session: ", value: shortId2 },
|
|
14254
|
+
...session.importedFromMachine ? [{ label: "from: ", value: session.importedFromMachine }] : [],
|
|
14255
|
+
{ label: "cwd: ", value: originalCwd }
|
|
14256
|
+
];
|
|
14194
14257
|
let buffer = defaultCwd;
|
|
14195
14258
|
let errorLine = null;
|
|
14196
14259
|
let busy = false;
|
|
14197
14260
|
let layout = null;
|
|
14198
14261
|
const render = () => {
|
|
14199
|
-
const contentHeight =
|
|
14262
|
+
const contentHeight = headerRows.length + 6;
|
|
14200
14263
|
layout = drawBox(term, {
|
|
14201
14264
|
contentHeight,
|
|
14202
|
-
title
|
|
14265
|
+
title
|
|
14203
14266
|
});
|
|
14204
14267
|
const innerW = layout.contentW;
|
|
14205
|
-
const headerRows = [
|
|
14206
|
-
{ label: "session: ", value: shortId2 },
|
|
14207
|
-
{ label: "from: ", value: fromMachine },
|
|
14208
|
-
{ label: "cwd: ", value: originalCwd }
|
|
14209
|
-
];
|
|
14210
14268
|
let row = 0;
|
|
14211
14269
|
for (const hr of headerRows) {
|
|
14212
14270
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
@@ -14216,7 +14274,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
14216
14274
|
}
|
|
14217
14275
|
row++;
|
|
14218
14276
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
14219
|
-
term.noFormat(
|
|
14277
|
+
term.noFormat(` ${intro}`);
|
|
14220
14278
|
row += 2;
|
|
14221
14279
|
paintInputRow(row);
|
|
14222
14280
|
row += 2;
|
|
@@ -14230,7 +14288,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
14230
14288
|
);
|
|
14231
14289
|
}
|
|
14232
14290
|
};
|
|
14233
|
-
const inputRow = () =>
|
|
14291
|
+
const inputRow = () => headerRows.length + 3;
|
|
14234
14292
|
const paintInputRow = (rowOffset) => {
|
|
14235
14293
|
if (!layout) {
|
|
14236
14294
|
return;
|
|
@@ -14411,7 +14469,7 @@ var init_import_cwd_prompt = __esm({
|
|
|
14411
14469
|
|
|
14412
14470
|
// src/tui/clipboard.ts
|
|
14413
14471
|
import { spawn as nodeSpawn } from "child_process";
|
|
14414
|
-
import
|
|
14472
|
+
import fs23 from "fs/promises";
|
|
14415
14473
|
import os7 from "os";
|
|
14416
14474
|
import path19 from "path";
|
|
14417
14475
|
async function readClipboard(envIn = {}) {
|
|
@@ -14452,7 +14510,7 @@ async function readMacOS(env) {
|
|
|
14452
14510
|
return img;
|
|
14453
14511
|
}
|
|
14454
14512
|
} catch {
|
|
14455
|
-
await
|
|
14513
|
+
await fs23.unlink(tmpPath).catch(() => void 0);
|
|
14456
14514
|
}
|
|
14457
14515
|
try {
|
|
14458
14516
|
const buf = await runCapture(env.spawn, "pbpaste", []);
|
|
@@ -14567,9 +14625,9 @@ async function which(env, cmd) {
|
|
|
14567
14625
|
}
|
|
14568
14626
|
async function readFileAsAttachment(p, unlinkAfter) {
|
|
14569
14627
|
try {
|
|
14570
|
-
const buf = await
|
|
14628
|
+
const buf = await fs23.readFile(p);
|
|
14571
14629
|
if (unlinkAfter) {
|
|
14572
|
-
await
|
|
14630
|
+
await fs23.unlink(p).catch(() => void 0);
|
|
14573
14631
|
}
|
|
14574
14632
|
if (buf.length === 0) {
|
|
14575
14633
|
return { ok: false, reason: "no image on clipboard" };
|
|
@@ -14694,7 +14752,7 @@ function parseReattachResponse(result) {
|
|
|
14694
14752
|
return out;
|
|
14695
14753
|
}
|
|
14696
14754
|
function shouldDriftSnap(args) {
|
|
14697
|
-
return !args.replayDraining && args.pendingTurns > 0 && args.queueSize === 0 && !args.ownTurnInFlight && !args.hasInFlightHead;
|
|
14755
|
+
return !args.replayDraining && !args.amended && args.pendingTurns > 0 && args.queueSize === 0 && !args.ownTurnInFlight && !args.hasInFlightHead;
|
|
14698
14756
|
}
|
|
14699
14757
|
function computeAttachReconcile(args) {
|
|
14700
14758
|
if (args.daemonTurnStartedAt !== void 0) {
|
|
@@ -14715,10 +14773,10 @@ var init_reconnect_state = __esm({
|
|
|
14715
14773
|
});
|
|
14716
14774
|
|
|
14717
14775
|
// src/tui/app.ts
|
|
14718
|
-
import { appendFileSync, statSync, renameSync } from "fs";
|
|
14776
|
+
import { appendFileSync, statSync as statSync2, renameSync as renameSync2 } from "fs";
|
|
14719
14777
|
import { nanoid as nanoid3 } from "nanoid";
|
|
14720
14778
|
import termkit from "terminal-kit";
|
|
14721
|
-
import
|
|
14779
|
+
import fs24 from "fs/promises";
|
|
14722
14780
|
import path20 from "path";
|
|
14723
14781
|
function isReadonlyForbiddenEffect(effect) {
|
|
14724
14782
|
switch (effect.type) {
|
|
@@ -14856,6 +14914,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14856
14914
|
}
|
|
14857
14915
|
};
|
|
14858
14916
|
let pendingTurns = 0;
|
|
14917
|
+
let cancelling = false;
|
|
14859
14918
|
let currentHeadMessageId;
|
|
14860
14919
|
let sessionBusySince = null;
|
|
14861
14920
|
let sessionElapsedTimer = null;
|
|
@@ -14866,6 +14925,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14866
14925
|
pendingTurns = Math.max(0, pendingTurns + delta);
|
|
14867
14926
|
const screenReady = typeof screenRef !== "undefined" && screenRef !== null;
|
|
14868
14927
|
if (before === 0 && pendingTurns > 0) {
|
|
14928
|
+
cancelling = false;
|
|
14869
14929
|
sessionBusySince = Date.now();
|
|
14870
14930
|
lastUpdateAt = Date.now();
|
|
14871
14931
|
dispatcherRef?.setTurnRunning(true);
|
|
@@ -14886,6 +14946,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14886
14946
|
}, 1e3);
|
|
14887
14947
|
}
|
|
14888
14948
|
} else if (before > 0 && pendingTurns === 0) {
|
|
14949
|
+
cancelling = false;
|
|
14889
14950
|
sessionBusySince = null;
|
|
14890
14951
|
lastUpdateAt = null;
|
|
14891
14952
|
dispatcherRef?.setTurnRunning(false);
|
|
@@ -14900,6 +14961,11 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14900
14961
|
stalled: false
|
|
14901
14962
|
});
|
|
14902
14963
|
}
|
|
14964
|
+
} else if (pendingTurns > 0 && cancelling) {
|
|
14965
|
+
cancelling = false;
|
|
14966
|
+
if (screenReady) {
|
|
14967
|
+
screenRef.setBanner({ status: "busy", stalled: false });
|
|
14968
|
+
}
|
|
14903
14969
|
}
|
|
14904
14970
|
void delta;
|
|
14905
14971
|
};
|
|
@@ -15258,18 +15324,19 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15258
15324
|
historyPolicy: "full",
|
|
15259
15325
|
clientInfo: { name: "hydra-acp-tui", version: HYDRA_VERSION },
|
|
15260
15326
|
...opts.readonly === true ? { readonly: true } : {},
|
|
15261
|
-
// Forward the user-chosen cwd
|
|
15262
|
-
//
|
|
15263
|
-
//
|
|
15264
|
-
//
|
|
15265
|
-
//
|
|
15266
|
-
|
|
15327
|
+
// Forward the user-chosen cwd via a full resume hint. An empty
|
|
15328
|
+
// upstreamSessionId routes through doResurrectFromImport
|
|
15329
|
+
// (first-launch imports); a real one takes the normal session/load
|
|
15330
|
+
// path (repairing a local session whose recorded cwd is gone).
|
|
15331
|
+
// Either way the daemon resurrects with this cwd instead of the
|
|
15332
|
+
// stale recorded one.
|
|
15333
|
+
...ctx.resumeHint !== void 0 ? {
|
|
15267
15334
|
_meta: {
|
|
15268
15335
|
[HYDRA_META_KEY]: {
|
|
15269
15336
|
resume: {
|
|
15270
|
-
upstreamSessionId:
|
|
15271
|
-
agentId: ctx.
|
|
15272
|
-
cwd: ctx.
|
|
15337
|
+
upstreamSessionId: ctx.resumeHint.upstreamSessionId,
|
|
15338
|
+
agentId: ctx.resumeHint.agentId,
|
|
15339
|
+
cwd: ctx.resumeHint.cwd
|
|
15273
15340
|
}
|
|
15274
15341
|
}
|
|
15275
15342
|
}
|
|
@@ -15356,9 +15423,6 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15356
15423
|
if (pendingPermission && tryHandlePermissionKey(ev)) {
|
|
15357
15424
|
continue;
|
|
15358
15425
|
}
|
|
15359
|
-
if (exitConfirmation && tryHandleExitConfirmKey(ev)) {
|
|
15360
|
-
continue;
|
|
15361
|
-
}
|
|
15362
15426
|
if (tryHandleHelpKey(ev)) {
|
|
15363
15427
|
continue;
|
|
15364
15428
|
}
|
|
@@ -15572,86 +15636,35 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15572
15636
|
const cancelRemoteTurn = () => {
|
|
15573
15637
|
conn.notify("session/cancel", { sessionId: resolvedSessionId }).catch(() => void 0);
|
|
15574
15638
|
};
|
|
15575
|
-
const
|
|
15576
|
-
if (
|
|
15577
|
-
turnInFlight.cancel();
|
|
15639
|
+
const markCancelling = () => {
|
|
15640
|
+
if (screenRef === null) {
|
|
15578
15641
|
return;
|
|
15579
15642
|
}
|
|
15580
|
-
if (pendingTurns
|
|
15581
|
-
cancelRemoteTurn();
|
|
15643
|
+
if (pendingTurns !== 1) {
|
|
15582
15644
|
return;
|
|
15583
15645
|
}
|
|
15584
|
-
|
|
15646
|
+
cancelling = true;
|
|
15647
|
+
screenRef.setBanner({
|
|
15648
|
+
status: "cancelling",
|
|
15649
|
+
elapsedMs: void 0,
|
|
15650
|
+
stalled: false
|
|
15651
|
+
});
|
|
15585
15652
|
};
|
|
15586
|
-
|
|
15587
|
-
|
|
15588
|
-
|
|
15589
|
-
|
|
15590
|
-
return;
|
|
15591
|
-
}
|
|
15592
|
-
if (pendingTurns === 0) {
|
|
15593
|
-
stop(0);
|
|
15594
|
-
return;
|
|
15595
|
-
}
|
|
15596
|
-
let onlyClient = false;
|
|
15597
|
-
try {
|
|
15598
|
-
const sessions = await listSessions(target);
|
|
15599
|
-
const me = sessions.find((s) => s.sessionId === resolvedSessionId);
|
|
15600
|
-
onlyClient = !me || me.attachedClients <= 1;
|
|
15601
|
-
} catch {
|
|
15602
|
-
stop(0);
|
|
15653
|
+
const sigintHandler = () => {
|
|
15654
|
+
if (turnInFlight) {
|
|
15655
|
+
turnInFlight.cancel();
|
|
15656
|
+
markCancelling();
|
|
15603
15657
|
return;
|
|
15604
15658
|
}
|
|
15605
|
-
if (
|
|
15606
|
-
|
|
15659
|
+
if (pendingTurns > 0) {
|
|
15660
|
+
cancelRemoteTurn();
|
|
15661
|
+
markCancelling();
|
|
15607
15662
|
return;
|
|
15608
15663
|
}
|
|
15609
|
-
|
|
15610
|
-
screen.setConfirmPrompt({
|
|
15611
|
-
question: "Agent is still working. Interrupt it before exit?",
|
|
15612
|
-
hint: "y interrupt then exit \xB7 n / Enter detach silently \xB7 Esc cancel"
|
|
15613
|
-
});
|
|
15614
|
-
};
|
|
15615
|
-
const dismissExitConfirmation = () => {
|
|
15616
|
-
exitConfirmation = null;
|
|
15617
|
-
screen.setConfirmPrompt(null);
|
|
15664
|
+
requestExit();
|
|
15618
15665
|
};
|
|
15619
|
-
const
|
|
15620
|
-
|
|
15621
|
-
return false;
|
|
15622
|
-
}
|
|
15623
|
-
if (ev.type === "char") {
|
|
15624
|
-
const ch = ev.ch.toLowerCase();
|
|
15625
|
-
if (ch === "y") {
|
|
15626
|
-
dismissExitConfirmation();
|
|
15627
|
-
conn.notify("session/cancel", { sessionId: resolvedSessionId }).catch(() => void 0);
|
|
15628
|
-
stop(0);
|
|
15629
|
-
return true;
|
|
15630
|
-
}
|
|
15631
|
-
if (ch === "n") {
|
|
15632
|
-
dismissExitConfirmation();
|
|
15633
|
-
stop(0);
|
|
15634
|
-
return true;
|
|
15635
|
-
}
|
|
15636
|
-
return true;
|
|
15637
|
-
}
|
|
15638
|
-
if (ev.type === "key") {
|
|
15639
|
-
if (ev.name === "enter") {
|
|
15640
|
-
dismissExitConfirmation();
|
|
15641
|
-
stop(0);
|
|
15642
|
-
return true;
|
|
15643
|
-
}
|
|
15644
|
-
if (ev.name === "escape") {
|
|
15645
|
-
dismissExitConfirmation();
|
|
15646
|
-
return true;
|
|
15647
|
-
}
|
|
15648
|
-
if (ev.name === "ctrl-c" || ev.name === "ctrl-d") {
|
|
15649
|
-
dismissExitConfirmation();
|
|
15650
|
-
stop(0);
|
|
15651
|
-
return true;
|
|
15652
|
-
}
|
|
15653
|
-
}
|
|
15654
|
-
return true;
|
|
15666
|
+
const requestExit = () => {
|
|
15667
|
+
stop(0);
|
|
15655
15668
|
};
|
|
15656
15669
|
const buildHelpEntries = () => {
|
|
15657
15670
|
const enqueueDesc = "enqueue prompt (sends now, or queues during a turn)";
|
|
@@ -15723,7 +15736,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15723
15736
|
let resolvedChoice = null;
|
|
15724
15737
|
let attachOverrides = null;
|
|
15725
15738
|
while (resolvedChoice === null) {
|
|
15726
|
-
const sessions = await listSessions(target);
|
|
15739
|
+
const sessions = await listSessions(target, { includeNonInteractive: true });
|
|
15727
15740
|
const choice2 = await pickSession(term, {
|
|
15728
15741
|
cwd: resolvedCwd,
|
|
15729
15742
|
sessions,
|
|
@@ -15761,14 +15774,43 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15761
15774
|
readonly: false,
|
|
15762
15775
|
cwd: decided2.ctx.cwd
|
|
15763
15776
|
};
|
|
15764
|
-
if (decided2.ctx.
|
|
15765
|
-
attachOverrides.
|
|
15777
|
+
if (decided2.ctx.resumeHint !== void 0) {
|
|
15778
|
+
attachOverrides.resumeHint = decided2.ctx.resumeHint;
|
|
15766
15779
|
}
|
|
15767
15780
|
break;
|
|
15768
15781
|
}
|
|
15769
15782
|
const chosen = sessions.find((s) => s.sessionId === choice2.sessionId);
|
|
15770
15783
|
const isImportedFirstLaunch = chosen !== void 0 && !!chosen.importedFromMachine && !chosen.upstreamSessionId && choice2.readonly !== true;
|
|
15771
15784
|
if (!isImportedFirstLaunch) {
|
|
15785
|
+
if (target.isLocal && chosen && !chosen.importedFromMachine && choice2.readonly !== true) {
|
|
15786
|
+
const v = await validateLocalCwd(chosen.cwd);
|
|
15787
|
+
if (!v.ok) {
|
|
15788
|
+
const r = await promptForImportCwd(term, chosen, {
|
|
15789
|
+
defaultCwd: expandHome(config.defaultCwd),
|
|
15790
|
+
title: "Working directory missing \u2014 choose cwd",
|
|
15791
|
+
intro: "This session's working directory no longer exists. Pick a new one:"
|
|
15792
|
+
});
|
|
15793
|
+
if (r.kind === "cancel") {
|
|
15794
|
+
screen.start({ skipFullscreen: true });
|
|
15795
|
+
screen.resumeRepaint();
|
|
15796
|
+
return;
|
|
15797
|
+
}
|
|
15798
|
+
if (r.kind === "back") {
|
|
15799
|
+
continue;
|
|
15800
|
+
}
|
|
15801
|
+
resolvedChoice = { choice: choice2, sessions };
|
|
15802
|
+
attachOverrides = {
|
|
15803
|
+
readonly: false,
|
|
15804
|
+
cwd: r.path,
|
|
15805
|
+
resumeHint: {
|
|
15806
|
+
agentId: choice2.agentId ?? chosen.agentId ?? "",
|
|
15807
|
+
cwd: r.path,
|
|
15808
|
+
upstreamSessionId: ""
|
|
15809
|
+
}
|
|
15810
|
+
};
|
|
15811
|
+
break;
|
|
15812
|
+
}
|
|
15813
|
+
}
|
|
15772
15814
|
resolvedChoice = { choice: choice2, sessions };
|
|
15773
15815
|
break;
|
|
15774
15816
|
}
|
|
@@ -15787,8 +15829,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15787
15829
|
readonly: opsShim.readonly === true,
|
|
15788
15830
|
cwd: decided.ctx.cwd
|
|
15789
15831
|
};
|
|
15790
|
-
if (decided.ctx.
|
|
15791
|
-
attachOverrides.
|
|
15832
|
+
if (decided.ctx.resumeHint !== void 0) {
|
|
15833
|
+
attachOverrides.resumeHint = decided.ctx.resumeHint;
|
|
15792
15834
|
}
|
|
15793
15835
|
}
|
|
15794
15836
|
const { choice } = resolvedChoice;
|
|
@@ -15823,10 +15865,10 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15823
15865
|
if (choice.agentId !== void 0) {
|
|
15824
15866
|
nextOpts.agentId = choice.agentId;
|
|
15825
15867
|
}
|
|
15826
|
-
if (attachOverrides?.
|
|
15827
|
-
nextOpts.
|
|
15868
|
+
if (attachOverrides?.resumeHint !== void 0) {
|
|
15869
|
+
nextOpts.resumeHint = attachOverrides.resumeHint;
|
|
15828
15870
|
} else {
|
|
15829
|
-
delete nextOpts.
|
|
15871
|
+
delete nextOpts.resumeHint;
|
|
15830
15872
|
}
|
|
15831
15873
|
resume(nextOpts);
|
|
15832
15874
|
};
|
|
@@ -15929,10 +15971,11 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15929
15971
|
} else if (pendingTurns > 0) {
|
|
15930
15972
|
cancelRemoteTurn();
|
|
15931
15973
|
}
|
|
15974
|
+
markCancelling();
|
|
15932
15975
|
return;
|
|
15933
15976
|
}
|
|
15934
15977
|
case "exit":
|
|
15935
|
-
|
|
15978
|
+
requestExit();
|
|
15936
15979
|
return;
|
|
15937
15980
|
case "plan-toggle":
|
|
15938
15981
|
void handleModeToggle(effect.on);
|
|
@@ -16020,7 +16063,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16020
16063
|
continue;
|
|
16021
16064
|
}
|
|
16022
16065
|
try {
|
|
16023
|
-
const buf = await
|
|
16066
|
+
const buf = await fs24.readFile(token);
|
|
16024
16067
|
if (buf.length > MAX_ATTACHMENT_BYTES) {
|
|
16025
16068
|
screen.notify(
|
|
16026
16069
|
`image too large (${formatSize(buf.length)}, max ${formatSize(MAX_ATTACHMENT_BYTES)})`
|
|
@@ -16230,7 +16273,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16230
16273
|
switch (cmd) {
|
|
16231
16274
|
case "/quit":
|
|
16232
16275
|
case "/exit":
|
|
16233
|
-
|
|
16276
|
+
requestExit();
|
|
16234
16277
|
return true;
|
|
16235
16278
|
case "/clear":
|
|
16236
16279
|
toolStates.clear();
|
|
@@ -16241,6 +16284,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16241
16284
|
toolsBlockStopReason = null;
|
|
16242
16285
|
toolsExpanded = false;
|
|
16243
16286
|
lastEditMarkPath = null;
|
|
16287
|
+
turnHasShownProse = false;
|
|
16244
16288
|
screen.clearScrollback();
|
|
16245
16289
|
return true;
|
|
16246
16290
|
case "/demo-plan": {
|
|
@@ -16586,6 +16630,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16586
16630
|
}
|
|
16587
16631
|
};
|
|
16588
16632
|
let lastEditMarkPath = null;
|
|
16633
|
+
let turnHasShownProse = false;
|
|
16589
16634
|
const maybeRenderEditDiff = (toolCallId) => {
|
|
16590
16635
|
const mode = config.tui.showFileUpdates;
|
|
16591
16636
|
if (mode === "none") {
|
|
@@ -16602,6 +16647,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16602
16647
|
}
|
|
16603
16648
|
return;
|
|
16604
16649
|
}
|
|
16650
|
+
if (!turnHasShownProse) {
|
|
16651
|
+
return;
|
|
16652
|
+
}
|
|
16605
16653
|
const diff = state.editDiff;
|
|
16606
16654
|
if (diff.path && diff.path === lastEditMarkPath) {
|
|
16607
16655
|
return;
|
|
@@ -16687,6 +16735,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16687
16735
|
toolsExpanded = false;
|
|
16688
16736
|
toolsBlockEndedAt = null;
|
|
16689
16737
|
lastEditMarkPath = null;
|
|
16738
|
+
turnHasShownProse = false;
|
|
16690
16739
|
startToolsBlock();
|
|
16691
16740
|
screen.redraw();
|
|
16692
16741
|
return;
|
|
@@ -16694,6 +16743,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16694
16743
|
if (event.kind === "agent-text") {
|
|
16695
16744
|
closeThought();
|
|
16696
16745
|
if (event.text.length > 0) {
|
|
16746
|
+
turnHasShownProse = true;
|
|
16697
16747
|
lastEditMarkPath = null;
|
|
16698
16748
|
}
|
|
16699
16749
|
appendAgentText(event.text);
|
|
@@ -16702,6 +16752,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16702
16752
|
if (event.kind === "agent-thought") {
|
|
16703
16753
|
closeAgentText();
|
|
16704
16754
|
if (viewPrefs.showThoughts && event.text.length > 0) {
|
|
16755
|
+
turnHasShownProse = true;
|
|
16705
16756
|
lastEditMarkPath = null;
|
|
16706
16757
|
}
|
|
16707
16758
|
appendThought(event.text);
|
|
@@ -16825,13 +16876,15 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16825
16876
|
toolsExpanded = false;
|
|
16826
16877
|
upstreamInterruptedSeen = false;
|
|
16827
16878
|
lastEditMarkPath = null;
|
|
16879
|
+
turnHasShownProse = false;
|
|
16828
16880
|
screen.ensureSeparator();
|
|
16829
16881
|
if (shouldDriftSnap({
|
|
16830
16882
|
pendingTurns,
|
|
16831
16883
|
queueSize: queueCache.size,
|
|
16832
16884
|
ownTurnInFlight: turnInFlight !== null,
|
|
16833
16885
|
hasInFlightHead: currentHeadMessageId !== void 0,
|
|
16834
|
-
replayDraining
|
|
16886
|
+
replayDraining,
|
|
16887
|
+
amended: event.amended === true
|
|
16835
16888
|
})) {
|
|
16836
16889
|
adjustPendingTurns(-pendingTurns);
|
|
16837
16890
|
}
|
|
@@ -16910,6 +16963,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16910
16963
|
toolsBlockStopReason = null;
|
|
16911
16964
|
toolsExpanded = false;
|
|
16912
16965
|
lastEditMarkPath = null;
|
|
16966
|
+
turnHasShownProse = false;
|
|
16913
16967
|
};
|
|
16914
16968
|
onDisconnectHook = () => {
|
|
16915
16969
|
screen.setBanner({ status: "disconnected", elapsedMs: void 0 });
|
|
@@ -17059,8 +17113,8 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17059
17113
|
agentId: opts.agentId ?? "",
|
|
17060
17114
|
cwd
|
|
17061
17115
|
};
|
|
17062
|
-
if (opts.
|
|
17063
|
-
ctx.
|
|
17116
|
+
if (opts.resumeHint !== void 0) {
|
|
17117
|
+
ctx.resumeHint = opts.resumeHint;
|
|
17064
17118
|
}
|
|
17065
17119
|
return ctx;
|
|
17066
17120
|
}
|
|
@@ -17082,7 +17136,7 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17082
17136
|
};
|
|
17083
17137
|
}
|
|
17084
17138
|
while (true) {
|
|
17085
|
-
const sessions = await listSessions(target);
|
|
17139
|
+
const sessions = await listSessions(target, { includeNonInteractive: true });
|
|
17086
17140
|
const choice = await pickSession(term, {
|
|
17087
17141
|
cwd,
|
|
17088
17142
|
sessions,
|
|
@@ -17122,6 +17176,33 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17122
17176
|
}
|
|
17123
17177
|
return decided.ctx;
|
|
17124
17178
|
}
|
|
17179
|
+
if (target.isLocal && chosen && !chosen.importedFromMachine && !opts.readonly) {
|
|
17180
|
+
const v = await validateLocalCwd(chosen.cwd);
|
|
17181
|
+
if (!v.ok) {
|
|
17182
|
+
const r = await promptForImportCwd(term, chosen, {
|
|
17183
|
+
defaultCwd: expandHome(config.defaultCwd),
|
|
17184
|
+
title: "Working directory missing \u2014 choose cwd",
|
|
17185
|
+
intro: "This session's working directory no longer exists. Pick a new one:"
|
|
17186
|
+
});
|
|
17187
|
+
if (r.kind === "cancel") {
|
|
17188
|
+
return null;
|
|
17189
|
+
}
|
|
17190
|
+
if (r.kind === "back") {
|
|
17191
|
+
continue;
|
|
17192
|
+
}
|
|
17193
|
+
const agentId = choice.agentId ?? chosen.agentId ?? "";
|
|
17194
|
+
return {
|
|
17195
|
+
sessionId: choice.sessionId,
|
|
17196
|
+
agentId,
|
|
17197
|
+
cwd: r.path,
|
|
17198
|
+
resumeHint: {
|
|
17199
|
+
agentId,
|
|
17200
|
+
cwd: r.path,
|
|
17201
|
+
upstreamSessionId: ""
|
|
17202
|
+
}
|
|
17203
|
+
};
|
|
17204
|
+
}
|
|
17205
|
+
}
|
|
17125
17206
|
return {
|
|
17126
17207
|
sessionId: choice.sessionId,
|
|
17127
17208
|
agentId: choice.agentId ?? "",
|
|
@@ -17164,7 +17245,9 @@ async function runImportedFirstLaunchFlow(term, chosen, choice, opts) {
|
|
|
17164
17245
|
sessionId: choice.sessionId,
|
|
17165
17246
|
agentId,
|
|
17166
17247
|
cwd: cwdResult.path,
|
|
17167
|
-
|
|
17248
|
+
// Empty upstreamSessionId → import-reseed path for a never-launched
|
|
17249
|
+
// imported session.
|
|
17250
|
+
resumeHint: { agentId, cwd: cwdResult.path, upstreamSessionId: "" }
|
|
17168
17251
|
}
|
|
17169
17252
|
};
|
|
17170
17253
|
}
|
|
@@ -17208,9 +17291,15 @@ fork failed: ${err.message}
|
|
|
17208
17291
|
// For foreign-never-launched forks, the daemon stamped the chosen
|
|
17209
17292
|
// cwd onto meta.json via the POST body, but the very first attach
|
|
17210
17293
|
// still goes through the import-reseed path (upstreamSessionId=""),
|
|
17211
|
-
// and
|
|
17294
|
+
// and the resume hint is what makes attachManagerHooks persist
|
|
17212
17295
|
// the local cwd over the bundle's recorded one.
|
|
17213
|
-
...isForeignNeverLaunched ? {
|
|
17296
|
+
...isForeignNeverLaunched ? {
|
|
17297
|
+
resumeHint: {
|
|
17298
|
+
agentId: choice.sourceAgentId ?? "",
|
|
17299
|
+
cwd,
|
|
17300
|
+
upstreamSessionId: ""
|
|
17301
|
+
}
|
|
17302
|
+
} : {}
|
|
17214
17303
|
}
|
|
17215
17304
|
};
|
|
17216
17305
|
}
|
|
@@ -17336,11 +17425,11 @@ function createInstallStatusLine(term, baseLabel) {
|
|
|
17336
17425
|
}
|
|
17337
17426
|
function rotateIfBig(target) {
|
|
17338
17427
|
try {
|
|
17339
|
-
const stat5 =
|
|
17428
|
+
const stat5 = statSync2(target);
|
|
17340
17429
|
if (stat5.size < logMaxBytes) {
|
|
17341
17430
|
return;
|
|
17342
17431
|
}
|
|
17343
|
-
|
|
17432
|
+
renameSync2(target, `${target}.0`);
|
|
17344
17433
|
} catch {
|
|
17345
17434
|
}
|
|
17346
17435
|
}
|
|
@@ -17352,6 +17441,7 @@ var init_app = __esm({
|
|
|
17352
17441
|
init_types();
|
|
17353
17442
|
init_resilient_ws();
|
|
17354
17443
|
init_config();
|
|
17444
|
+
init_cwd();
|
|
17355
17445
|
init_remote_target();
|
|
17356
17446
|
init_daemon_bootstrap();
|
|
17357
17447
|
init_bin_name();
|
|
@@ -18849,10 +18939,17 @@ var SessionRecord = z5.object({
|
|
|
18849
18939
|
// ended at. Kept so future UI can show "branched from turn N of session X".
|
|
18850
18940
|
forkedFromSessionId: z5.string().optional(),
|
|
18851
18941
|
forkedFromMessageId: z5.string().optional(),
|
|
18852
|
-
// clientInfo from the process that issued session/new.
|
|
18853
|
-
//
|
|
18854
|
-
//
|
|
18942
|
+
// clientInfo from the process that issued session/new. Display only
|
|
18943
|
+
// since the `interactive` flag below; kept on the record for log
|
|
18944
|
+
// attribution and as the legacy hint inside effectiveInteractive
|
|
18945
|
+
// (pre-flag cat sessions can be recognised from this field).
|
|
18855
18946
|
originatingClient: PersistedOriginatingClient.optional(),
|
|
18947
|
+
// Tristate: true once the session has had a real turn, false when
|
|
18948
|
+
// explicitly created as ancillary (e.g. `hydra cat`), undefined for
|
|
18949
|
+
// pre-flag records / freshly-created sessions that haven't decided
|
|
18950
|
+
// yet. effectiveInteractive() in session-manager.ts is the single
|
|
18951
|
+
// resolver — every filter site goes through it.
|
|
18952
|
+
interactive: z5.boolean().optional(),
|
|
18856
18953
|
createdAt: z5.string(),
|
|
18857
18954
|
updatedAt: z5.string()
|
|
18858
18955
|
});
|
|
@@ -18971,6 +19068,7 @@ function recordFromMemorySession(args) {
|
|
|
18971
19068
|
forkedFromSessionId: args.forkedFromSessionId,
|
|
18972
19069
|
forkedFromMessageId: args.forkedFromMessageId,
|
|
18973
19070
|
originatingClient: args.originatingClient,
|
|
19071
|
+
interactive: args.interactive,
|
|
18974
19072
|
createdAt: args.createdAt ?? now,
|
|
18975
19073
|
updatedAt: args.updatedAt ?? now
|
|
18976
19074
|
};
|
|
@@ -19774,6 +19872,7 @@ var HistoryStore = class {
|
|
|
19774
19872
|
|
|
19775
19873
|
// src/core/session-manager.ts
|
|
19776
19874
|
init_paths();
|
|
19875
|
+
init_config();
|
|
19777
19876
|
init_history();
|
|
19778
19877
|
|
|
19779
19878
|
// src/core/bundle.ts
|
|
@@ -19809,6 +19908,14 @@ var BundleSession = z6.object({
|
|
|
19809
19908
|
currentUsage: PersistedUsage.optional(),
|
|
19810
19909
|
agentCommands: z6.array(PersistedAgentCommand).optional(),
|
|
19811
19910
|
agentModes: z6.array(PersistedAgentMode).optional(),
|
|
19911
|
+
// Raw interactive tristate (NOT the resolved effectiveInteractive) so
|
|
19912
|
+
// the value stays promotable on the destination: a cat/empty source
|
|
19913
|
+
// arrives as undefined and a real turn there can still flip it to
|
|
19914
|
+
// true. Carried alongside originatingClient so the importer's
|
|
19915
|
+
// effectiveInteractive can re-apply the cat-name hint at read time
|
|
19916
|
+
// without freezing a sticky `false` into the record.
|
|
19917
|
+
interactive: z6.boolean().optional(),
|
|
19918
|
+
originatingClient: PersistedOriginatingClient.optional(),
|
|
19812
19919
|
createdAt: z6.string(),
|
|
19813
19920
|
updatedAt: z6.string()
|
|
19814
19921
|
});
|
|
@@ -19852,6 +19959,8 @@ function encodeBundle(params) {
|
|
|
19852
19959
|
...params.record.currentUsage !== void 0 ? { currentUsage: params.record.currentUsage } : {},
|
|
19853
19960
|
...params.record.agentCommands !== void 0 ? { agentCommands: params.record.agentCommands } : {},
|
|
19854
19961
|
...params.record.agentModes !== void 0 ? { agentModes: params.record.agentModes } : {},
|
|
19962
|
+
...params.record.interactive !== void 0 ? { interactive: params.record.interactive } : {},
|
|
19963
|
+
...params.record.originatingClient !== void 0 ? { originatingClient: params.record.originatingClient } : {},
|
|
19855
19964
|
createdAt: params.record.createdAt,
|
|
19856
19965
|
updatedAt: params.record.updatedAt
|
|
19857
19966
|
},
|
|
@@ -19890,6 +19999,7 @@ var SessionManager = class {
|
|
|
19890
19999
|
this.logger = options.logger;
|
|
19891
20000
|
this.npmRegistry = options.npmRegistry;
|
|
19892
20001
|
this.extensionCommands = options.extensionCommands;
|
|
20002
|
+
this.defaultCwd = options.defaultCwd ?? "~";
|
|
19893
20003
|
this.synopsisCoordinator = new SynopsisCoordinator({
|
|
19894
20004
|
registry: this.registry,
|
|
19895
20005
|
store: this.store,
|
|
@@ -19923,6 +20033,7 @@ var SessionManager = class {
|
|
|
19923
20033
|
logger;
|
|
19924
20034
|
npmRegistry;
|
|
19925
20035
|
extensionCommands;
|
|
20036
|
+
defaultCwd;
|
|
19926
20037
|
// Background queue for ephemeral-agent synopsis generation. Runs
|
|
19927
20038
|
// out-of-band so session close is instant; persists synopsis/title
|
|
19928
20039
|
// via the same enqueueMetaWrite path the in-session handlers used.
|
|
@@ -19982,6 +20093,7 @@ var SessionManager = class {
|
|
|
19982
20093
|
transformChain: params.transformChain,
|
|
19983
20094
|
parentSessionId: params.parentSessionId,
|
|
19984
20095
|
originatingClient: params.originatingClient,
|
|
20096
|
+
interactive: params.interactive,
|
|
19985
20097
|
extensionCommands: this.extensionCommands,
|
|
19986
20098
|
scheduleSynopsis: () => this.synopsisCoordinator.schedule(session.sessionId)
|
|
19987
20099
|
});
|
|
@@ -20028,6 +20140,9 @@ var SessionManager = class {
|
|
|
20028
20140
|
if (params.upstreamSessionId === "") {
|
|
20029
20141
|
return this.doResurrectFromImport(params);
|
|
20030
20142
|
}
|
|
20143
|
+
if (!await this.dirExists(params.cwd)) {
|
|
20144
|
+
return this.doResurrectFromImport(params);
|
|
20145
|
+
}
|
|
20031
20146
|
const plan = await planSpawn(agentDef, params.agentArgs ?? [], {
|
|
20032
20147
|
npmRegistry: this.npmRegistry,
|
|
20033
20148
|
onInstallProgress: params.onInstallProgress
|
|
@@ -20155,6 +20270,7 @@ var SessionManager = class {
|
|
|
20155
20270
|
firstPromptSeeded: !!params.title,
|
|
20156
20271
|
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
20157
20272
|
originatingClient: params.originatingClient,
|
|
20273
|
+
interactive: params.interactive,
|
|
20158
20274
|
forkedFromSessionId: params.forkedFromSessionId,
|
|
20159
20275
|
forkedFromMessageId: params.forkedFromMessageId,
|
|
20160
20276
|
extensionCommands: this.extensionCommands,
|
|
@@ -20171,7 +20287,7 @@ var SessionManager = class {
|
|
|
20171
20287
|
// so subsequent resurrects of this session use the normal session/load
|
|
20172
20288
|
// path.
|
|
20173
20289
|
async doResurrectFromImport(params) {
|
|
20174
|
-
const cwd = await this.
|
|
20290
|
+
const cwd = await this.resolveResurrectCwd(params.cwd);
|
|
20175
20291
|
const fresh = await this.bootstrapAgent({
|
|
20176
20292
|
agentId: params.agentId,
|
|
20177
20293
|
cwd,
|
|
@@ -20226,6 +20342,7 @@ var SessionManager = class {
|
|
|
20226
20342
|
firstPromptSeeded: !!params.title,
|
|
20227
20343
|
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
20228
20344
|
originatingClient: params.originatingClient,
|
|
20345
|
+
interactive: params.interactive,
|
|
20229
20346
|
forkedFromSessionId: params.forkedFromSessionId,
|
|
20230
20347
|
forkedFromMessageId: params.forkedFromMessageId,
|
|
20231
20348
|
extensionCommands: this.extensionCommands,
|
|
@@ -20235,15 +20352,50 @@ var SessionManager = class {
|
|
|
20235
20352
|
void session.seedFromImport().catch(() => void 0);
|
|
20236
20353
|
return session;
|
|
20237
20354
|
}
|
|
20238
|
-
async
|
|
20355
|
+
async dirExists(cwd) {
|
|
20239
20356
|
try {
|
|
20240
|
-
|
|
20241
|
-
if (stat5.isDirectory()) {
|
|
20242
|
-
return cwd;
|
|
20243
|
-
}
|
|
20357
|
+
return (await fs14.stat(cwd)).isDirectory();
|
|
20244
20358
|
} catch {
|
|
20359
|
+
return false;
|
|
20245
20360
|
}
|
|
20246
|
-
|
|
20361
|
+
}
|
|
20362
|
+
// When the last client detaches from a session that resolves to
|
|
20363
|
+
// non-interactive — e.g. a `hydra cat` run, born interactive:undefined
|
|
20364
|
+
// with originatingClient hydra-acp-cat, whose every prompt is ancillary
|
|
20365
|
+
// — close it so its agent process doesn't linger until the (default 1h)
|
|
20366
|
+
// idle timeout fires. The cold record is kept, so the rare refine-in-TUI
|
|
20367
|
+
// still works via the resurrect/reseed path. Sessions promoted to
|
|
20368
|
+
// interactive (driven by a real, non-ancillary prompt) resolve to true
|
|
20369
|
+
// and are left running.
|
|
20370
|
+
async reapIfOrphanedNonInteractive(sessionId) {
|
|
20371
|
+
const session = this.sessions.get(sessionId);
|
|
20372
|
+
if (!session || session.attachedCount > 0) {
|
|
20373
|
+
return;
|
|
20374
|
+
}
|
|
20375
|
+
const interactive = effectiveInteractive(
|
|
20376
|
+
{
|
|
20377
|
+
interactive: session.interactive,
|
|
20378
|
+
...session.originatingClient ? { originatingClient: session.originatingClient } : {}
|
|
20379
|
+
},
|
|
20380
|
+
true
|
|
20381
|
+
);
|
|
20382
|
+
if (interactive !== false) {
|
|
20383
|
+
return;
|
|
20384
|
+
}
|
|
20385
|
+
this.logger?.info(
|
|
20386
|
+
`reaping orphaned non-interactive session ${sessionId} (agent killed, cold record kept)`
|
|
20387
|
+
);
|
|
20388
|
+
await session.close({ deleteRecord: false }).catch(() => void 0);
|
|
20389
|
+
}
|
|
20390
|
+
// Resolve a recorded cwd for resurrect: use it if it still exists,
|
|
20391
|
+
// otherwise fall back to the configured defaultCwd. Covers both bundles
|
|
20392
|
+
// imported from another machine and local sessions (e.g. `cat`) whose
|
|
20393
|
+
// recorded dir was cleaned up, so the reseed spawn never ENOENTs.
|
|
20394
|
+
async resolveResurrectCwd(cwd) {
|
|
20395
|
+
if (await this.dirExists(cwd)) {
|
|
20396
|
+
return cwd;
|
|
20397
|
+
}
|
|
20398
|
+
return expandHome(this.defaultCwd);
|
|
20247
20399
|
}
|
|
20248
20400
|
// Pull every session the agent itself remembers (across all cwds) and
|
|
20249
20401
|
// persist a cold hydra record for each one we don't already track.
|
|
@@ -20332,6 +20484,10 @@ var SessionManager = class {
|
|
|
20332
20484
|
agentId,
|
|
20333
20485
|
cwd: entry.cwd,
|
|
20334
20486
|
pendingHistorySync: true,
|
|
20487
|
+
// `hydra agent sync` is a user-explicit "show me agent-side
|
|
20488
|
+
// sessions" action; the rows are meant to be visible immediately
|
|
20489
|
+
// even before the first resurrect populates history.jsonl.
|
|
20490
|
+
interactive: true,
|
|
20335
20491
|
createdAt: ts,
|
|
20336
20492
|
updatedAt: ts
|
|
20337
20493
|
};
|
|
@@ -20498,6 +20654,11 @@ var SessionManager = class {
|
|
|
20498
20654
|
() => void 0
|
|
20499
20655
|
);
|
|
20500
20656
|
});
|
|
20657
|
+
session.onInteractiveChange((interactive) => {
|
|
20658
|
+
void this.persistSnapshot(session.sessionId, { interactive }).catch(
|
|
20659
|
+
() => void 0
|
|
20660
|
+
);
|
|
20661
|
+
});
|
|
20501
20662
|
session.onUsageChange((usage) => {
|
|
20502
20663
|
void this.persistSnapshot(session.sessionId, {
|
|
20503
20664
|
currentUsage: usageSnapshotToPersisted(usage)
|
|
@@ -20591,6 +20752,7 @@ var SessionManager = class {
|
|
|
20591
20752
|
createdAt: record.createdAt,
|
|
20592
20753
|
pendingHistorySync: record.pendingHistorySync,
|
|
20593
20754
|
originatingClient: record.originatingClient,
|
|
20755
|
+
interactive: record.interactive,
|
|
20594
20756
|
forkedFromSessionId: record.forkedFromSessionId,
|
|
20595
20757
|
forkedFromMessageId: record.forkedFromMessageId
|
|
20596
20758
|
};
|
|
@@ -20678,12 +20840,27 @@ var SessionManager = class {
|
|
|
20678
20840
|
async list(filter = {}) {
|
|
20679
20841
|
const entries = [];
|
|
20680
20842
|
const liveIds = /* @__PURE__ */ new Set();
|
|
20843
|
+
const includeRow = (interactive) => {
|
|
20844
|
+
if (filter.includeNonInteractive) return true;
|
|
20845
|
+
return interactive === true;
|
|
20846
|
+
};
|
|
20681
20847
|
for (const session of this.sessions.values()) {
|
|
20682
20848
|
if (filter.cwd && session.cwd !== filter.cwd) {
|
|
20683
20849
|
continue;
|
|
20684
20850
|
}
|
|
20685
20851
|
liveIds.add(session.sessionId);
|
|
20686
|
-
const
|
|
20852
|
+
const hist = await historyStatus(session.sessionId);
|
|
20853
|
+
const interactive = effectiveInteractive(
|
|
20854
|
+
{
|
|
20855
|
+
interactive: session.interactive,
|
|
20856
|
+
...session.originatingClient ? { originatingClient: session.originatingClient } : {}
|
|
20857
|
+
},
|
|
20858
|
+
hist.hasContent
|
|
20859
|
+
);
|
|
20860
|
+
if (!includeRow(interactive)) {
|
|
20861
|
+
continue;
|
|
20862
|
+
}
|
|
20863
|
+
const used = hist.mtime ?? new Date(session.updatedAt).toISOString();
|
|
20687
20864
|
entries.push({
|
|
20688
20865
|
sessionId: session.sessionId,
|
|
20689
20866
|
upstreamSessionId: session.upstreamSessionId,
|
|
@@ -20696,6 +20873,7 @@ var SessionManager = class {
|
|
|
20696
20873
|
forkedFromSessionId: session.forkedFromSessionId,
|
|
20697
20874
|
forkedFromMessageId: session.forkedFromMessageId,
|
|
20698
20875
|
originatingClient: session.originatingClient,
|
|
20876
|
+
interactive,
|
|
20699
20877
|
updatedAt: used,
|
|
20700
20878
|
attachedClients: session.attachedCount,
|
|
20701
20879
|
status: "live",
|
|
@@ -20710,7 +20888,12 @@ var SessionManager = class {
|
|
|
20710
20888
|
if (filter.cwd && r.cwd !== filter.cwd) {
|
|
20711
20889
|
continue;
|
|
20712
20890
|
}
|
|
20713
|
-
const
|
|
20891
|
+
const hist = await historyStatus(r.sessionId);
|
|
20892
|
+
const interactive = effectiveInteractive(r, hist.hasContent);
|
|
20893
|
+
if (!includeRow(interactive)) {
|
|
20894
|
+
continue;
|
|
20895
|
+
}
|
|
20896
|
+
const used = hist.mtime ?? r.updatedAt;
|
|
20714
20897
|
entries.push({
|
|
20715
20898
|
sessionId: r.sessionId,
|
|
20716
20899
|
upstreamSessionId: r.upstreamSessionId,
|
|
@@ -20728,6 +20911,7 @@ var SessionManager = class {
|
|
|
20728
20911
|
forkedFromSessionId: r.forkedFromSessionId,
|
|
20729
20912
|
forkedFromMessageId: r.forkedFromMessageId,
|
|
20730
20913
|
originatingClient: r.originatingClient,
|
|
20914
|
+
interactive,
|
|
20731
20915
|
updatedAt: used,
|
|
20732
20916
|
attachedClients: 0,
|
|
20733
20917
|
status: "cold",
|
|
@@ -20962,8 +21146,18 @@ var SessionManager = class {
|
|
|
20962
21146
|
currentUsage: args.bundle.session.currentUsage,
|
|
20963
21147
|
agentCommands: args.bundle.session.agentCommands,
|
|
20964
21148
|
agentModes: args.bundle.session.agentModes,
|
|
21149
|
+
// Carry the source's raw interactive tristate and originating
|
|
21150
|
+
// client rather than forcing true. A real conversation arrives
|
|
21151
|
+
// as true (visible immediately); an empty source arrives as
|
|
21152
|
+
// undefined (hidden until a turn lands here); a cat source
|
|
21153
|
+
// arrives as undefined + cat originatingClient, so
|
|
21154
|
+
// effectiveInteractive hides it via the hint while leaving it
|
|
21155
|
+
// promotable. Legacy bundles (pre-flag) carry neither and fall
|
|
21156
|
+
// back to effectiveInteractive's history-presence inference.
|
|
21157
|
+
interactive: args.bundle.session.interactive,
|
|
21158
|
+
originatingClient: args.bundle.session.originatingClient,
|
|
20965
21159
|
createdAt: args.preservedCreatedAt ?? now,
|
|
20966
|
-
// Fallback path for
|
|
21160
|
+
// Fallback path for historyStatus (used when the history file
|
|
20967
21161
|
// is missing). Keep this consistent with the utimes stamp above.
|
|
20968
21162
|
updatedAt: args.bundle.session.updatedAt
|
|
20969
21163
|
});
|
|
@@ -21072,6 +21266,8 @@ var SessionManager = class {
|
|
|
21072
21266
|
...update.agentCommands !== void 0 ? { agentCommands: update.agentCommands } : {},
|
|
21073
21267
|
...update.agentModes !== void 0 ? { agentModes: update.agentModes } : {},
|
|
21074
21268
|
...update.agentModels !== void 0 ? { agentModels: update.agentModels } : {},
|
|
21269
|
+
...update.interactive !== void 0 ? { interactive: update.interactive } : {},
|
|
21270
|
+
...update.cwd !== void 0 ? { cwd: update.cwd } : {},
|
|
21075
21271
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
21076
21272
|
});
|
|
21077
21273
|
});
|
|
@@ -21248,6 +21444,7 @@ function mergeForPersistence(session, existing) {
|
|
|
21248
21444
|
forkedFromSessionId: session.forkedFromSessionId ?? existing?.forkedFromSessionId,
|
|
21249
21445
|
forkedFromMessageId: session.forkedFromMessageId ?? existing?.forkedFromMessageId,
|
|
21250
21446
|
originatingClient: session.originatingClient ?? existing?.originatingClient,
|
|
21447
|
+
interactive: session.interactive ?? existing?.interactive,
|
|
21251
21448
|
createdAt: existing?.createdAt ?? new Date(session.createdAt).toISOString()
|
|
21252
21449
|
});
|
|
21253
21450
|
}
|
|
@@ -21554,13 +21751,25 @@ async function loadPromptHistorySafely(sessionId) {
|
|
|
21554
21751
|
return [];
|
|
21555
21752
|
}
|
|
21556
21753
|
}
|
|
21557
|
-
async function
|
|
21754
|
+
async function historyStatus(sessionId) {
|
|
21558
21755
|
try {
|
|
21559
21756
|
const st = await fs14.stat(paths.historyFile(sessionId));
|
|
21560
|
-
return
|
|
21757
|
+
return {
|
|
21758
|
+
mtime: new Date(st.mtimeMs).toISOString(),
|
|
21759
|
+
hasContent: st.size > 0
|
|
21760
|
+
};
|
|
21561
21761
|
} catch {
|
|
21562
|
-
return
|
|
21762
|
+
return { hasContent: false };
|
|
21763
|
+
}
|
|
21764
|
+
}
|
|
21765
|
+
function effectiveInteractive(record, hasContent) {
|
|
21766
|
+
if (record.interactive !== void 0) {
|
|
21767
|
+
return record.interactive;
|
|
21563
21768
|
}
|
|
21769
|
+
if (record.originatingClient?.name === HYDRA_CAT_CLIENT_NAME) {
|
|
21770
|
+
return false;
|
|
21771
|
+
}
|
|
21772
|
+
return hasContent ? true : void 0;
|
|
21564
21773
|
}
|
|
21565
21774
|
|
|
21566
21775
|
// src/core/child-supervisor.ts
|
|
@@ -23338,17 +23547,21 @@ function resolveHydraHost(defaults) {
|
|
|
23338
23547
|
function registerSessionRoutes(app, manager, defaults) {
|
|
23339
23548
|
app.get("/v1/sessions", async (request) => {
|
|
23340
23549
|
const query = request.query;
|
|
23341
|
-
const
|
|
23550
|
+
const includeNonInteractive = query?.includeNonInteractive === "1" || query?.includeNonInteractive === "true";
|
|
23551
|
+
const sessions = await manager.list({
|
|
23552
|
+
cwd: query?.cwd,
|
|
23553
|
+
includeNonInteractive
|
|
23554
|
+
});
|
|
23342
23555
|
return { sessions };
|
|
23343
23556
|
});
|
|
23344
|
-
app.
|
|
23345
|
-
const
|
|
23346
|
-
const q =
|
|
23557
|
+
app.post("/v1/sessions/search", async (request, reply) => {
|
|
23558
|
+
const body = request.body ?? {};
|
|
23559
|
+
const q = typeof body.q === "string" ? body.q : "";
|
|
23347
23560
|
if (q.trim().length === 0) {
|
|
23348
23561
|
reply.code(400).send({ error: "q is required" });
|
|
23349
23562
|
return reply;
|
|
23350
23563
|
}
|
|
23351
|
-
const ids =
|
|
23564
|
+
const ids = Array.isArray(body.sessionIds) ? body.sessionIds.filter((s) => typeof s === "string" && s.length > 0) : void 0;
|
|
23352
23565
|
const out = await searchHistories(manager, q, { sessionIds: ids });
|
|
23353
23566
|
return out;
|
|
23354
23567
|
});
|
|
@@ -23943,13 +24156,8 @@ function parseRegisterBody2(body) {
|
|
|
23943
24156
|
}
|
|
23944
24157
|
|
|
23945
24158
|
// src/daemon/routes/config.ts
|
|
23946
|
-
function registerConfigRoutes(app,
|
|
23947
|
-
app.get("/v1/config", async () =>
|
|
23948
|
-
return {
|
|
23949
|
-
defaultAgent: defaults.defaultAgent,
|
|
23950
|
-
defaultCwd: defaults.defaultCwd
|
|
23951
|
-
};
|
|
23952
|
-
});
|
|
24159
|
+
function registerConfigRoutes(app, snapshot) {
|
|
24160
|
+
app.get("/v1/config", async () => snapshot);
|
|
23953
24161
|
}
|
|
23954
24162
|
|
|
23955
24163
|
// src/daemon/routes/auth.ts
|
|
@@ -24469,7 +24677,8 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
24469
24677
|
model: hydraMeta.model,
|
|
24470
24678
|
onInstallProgress: makeInstallProgressForwarder(connection),
|
|
24471
24679
|
transformChain,
|
|
24472
|
-
originatingClient: state.clientInfo
|
|
24680
|
+
originatingClient: state.clientInfo,
|
|
24681
|
+
...hydraMeta.interactive !== void 0 ? { interactive: hydraMeta.interactive } : {}
|
|
24473
24682
|
});
|
|
24474
24683
|
} catch (err) {
|
|
24475
24684
|
if (stdinReservation !== void 0) {
|
|
@@ -24596,8 +24805,9 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
24596
24805
|
err.code = JsonRpcErrorCodes.SessionNotFound;
|
|
24597
24806
|
throw err;
|
|
24598
24807
|
}
|
|
24808
|
+
const resurrectWithOriginator = resurrectParams.originatingClient ? resurrectParams : { ...resurrectParams, originatingClient: state.clientInfo };
|
|
24599
24809
|
session = await deps.manager.resurrect({
|
|
24600
|
-
...
|
|
24810
|
+
...resurrectWithOriginator,
|
|
24601
24811
|
onInstallProgress: makeInstallProgressForwarder(connection)
|
|
24602
24812
|
});
|
|
24603
24813
|
wireDefaultTransformers(session, deps);
|
|
@@ -24654,6 +24864,9 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
24654
24864
|
const session = deps.manager.get(params.sessionId);
|
|
24655
24865
|
session?.detach(att.clientId);
|
|
24656
24866
|
state.attached.delete(params.sessionId);
|
|
24867
|
+
if (session) {
|
|
24868
|
+
void deps.manager.reapIfOrphanedNonInteractive(params.sessionId);
|
|
24869
|
+
}
|
|
24657
24870
|
return { sessionId: params.sessionId, status: "detached" };
|
|
24658
24871
|
});
|
|
24659
24872
|
connection.onRequest("session/list", async (raw) => {
|
|
@@ -25901,7 +26114,8 @@ async function startDaemon(config, serviceToken) {
|
|
|
25901
26114
|
sessionHistoryMaxEntries: config.daemon.sessionHistoryMaxEntries,
|
|
25902
26115
|
logger: agentLogger,
|
|
25903
26116
|
npmRegistry: config.npmRegistry,
|
|
25904
|
-
extensionCommands
|
|
26117
|
+
extensionCommands,
|
|
26118
|
+
defaultCwd: config.defaultCwd
|
|
25905
26119
|
});
|
|
25906
26120
|
const extensions = new ExtensionManager(extensionList(config), void 0, {
|
|
25907
26121
|
tokenRegistry: processRegistry
|
|
@@ -25922,7 +26136,12 @@ async function startDaemon(config, serviceToken) {
|
|
|
25922
26136
|
registerTransformerRoutes(app, transformers);
|
|
25923
26137
|
registerConfigRoutes(app, {
|
|
25924
26138
|
defaultAgent: config.defaultAgent,
|
|
25925
|
-
defaultCwd: config.defaultCwd
|
|
26139
|
+
defaultCwd: config.defaultCwd,
|
|
26140
|
+
defaultModels: { ...config.defaultModels },
|
|
26141
|
+
...config.synopsisAgent !== void 0 ? { synopsisAgent: config.synopsisAgent } : {},
|
|
26142
|
+
...config.synopsisModel !== void 0 ? { synopsisModel: config.synopsisModel } : {},
|
|
26143
|
+
synopsisOnClose: config.synopsisOnClose,
|
|
26144
|
+
defaultTransformers: [...config.defaultTransformers]
|
|
25926
26145
|
});
|
|
25927
26146
|
registerAuthRoutes(app, {
|
|
25928
26147
|
store: sessionTokenStore,
|
|
@@ -26410,7 +26629,6 @@ init_remote_target();
|
|
|
26410
26629
|
init_remote_url();
|
|
26411
26630
|
init_session();
|
|
26412
26631
|
init_discovery();
|
|
26413
|
-
init_hydra_version();
|
|
26414
26632
|
import * as fs19 from "fs/promises";
|
|
26415
26633
|
import * as path16 from "path";
|
|
26416
26634
|
init_session_row();
|
|
@@ -26419,6 +26637,9 @@ async function runSessionsList(opts = {}) {
|
|
|
26419
26637
|
const serviceToken = await loadServiceToken();
|
|
26420
26638
|
const baseUrl = httpBase(config.daemon.host, config.daemon.port, !!config.daemon.tls);
|
|
26421
26639
|
const url = new URL(`${baseUrl}/v1/sessions`);
|
|
26640
|
+
if (opts.includeNonInteractive || opts.all) {
|
|
26641
|
+
url.searchParams.set("includeNonInteractive", "true");
|
|
26642
|
+
}
|
|
26422
26643
|
const response = await fetch(url.toString(), {
|
|
26423
26644
|
headers: { Authorization: `Bearer ${serviceToken}` }
|
|
26424
26645
|
});
|
|
@@ -26428,13 +26649,11 @@ async function runSessionsList(opts = {}) {
|
|
|
26428
26649
|
process.exit(1);
|
|
26429
26650
|
}
|
|
26430
26651
|
const body = await response.json();
|
|
26431
|
-
const
|
|
26432
|
-
(s) => s.originatingClient?.name !== HYDRA_CAT_CLIENT_NAME
|
|
26433
|
-
);
|
|
26652
|
+
const sessionsAfterInteractiveFilter = body.sessions;
|
|
26434
26653
|
const host = opts.host ?? "local";
|
|
26435
|
-
const hostFiltered = host === "all" ?
|
|
26654
|
+
const hostFiltered = host === "all" ? sessionsAfterInteractiveFilter : host === "local" ? sessionsAfterInteractiveFilter.filter(
|
|
26436
26655
|
(s) => !s.importedFromMachine || !!s.upstreamSessionId
|
|
26437
|
-
) :
|
|
26656
|
+
) : sessionsAfterInteractiveFilter.filter(
|
|
26438
26657
|
(s) => s.importedFromMachine === host && !s.upstreamSessionId
|
|
26439
26658
|
);
|
|
26440
26659
|
if (opts.json) {
|
|
@@ -28631,15 +28850,17 @@ async function runAgentsLogs(agentId, rest) {
|
|
|
28631
28850
|
const logPath = paths.agentLogFile(agentId);
|
|
28632
28851
|
await runLogTail(logPath, rest, "No log file (agent never ran?)");
|
|
28633
28852
|
}
|
|
28634
|
-
async function
|
|
28635
|
-
if (!agentId) {
|
|
28636
|
-
process.stderr.write("Usage: hydra-acp agent set <agent-id>\n");
|
|
28637
|
-
process.exit(2);
|
|
28638
|
-
return;
|
|
28639
|
-
}
|
|
28853
|
+
async function runAgentsSet(agentId, modelId) {
|
|
28640
28854
|
const config = await loadConfig();
|
|
28641
28855
|
const serviceToken = await loadServiceToken();
|
|
28642
28856
|
const baseUrl = httpBase(config.daemon.host, config.daemon.port, !!config.daemon.tls);
|
|
28857
|
+
if (!agentId) {
|
|
28858
|
+
const daemonView2 = await fetchDaemonAgentDefaults(baseUrl, serviceToken);
|
|
28859
|
+
const view = daemonView2 ?? readAgentDefaults(await readRawConfig3());
|
|
28860
|
+
process.stdout.write(`${formatDefaultLine(view)}
|
|
28861
|
+
`);
|
|
28862
|
+
return;
|
|
28863
|
+
}
|
|
28643
28864
|
let known;
|
|
28644
28865
|
try {
|
|
28645
28866
|
const r = await fetch(`${baseUrl}/v1/agents`, {
|
|
@@ -28660,13 +28881,62 @@ async function runAgentsSetDefault(agentId) {
|
|
|
28660
28881
|
return;
|
|
28661
28882
|
}
|
|
28662
28883
|
const raw = await readRawConfig3();
|
|
28663
|
-
|
|
28664
|
-
|
|
28884
|
+
if (modelId === void 0) {
|
|
28885
|
+
raw.defaultAgent = agentId;
|
|
28886
|
+
await writeRawConfig3(raw);
|
|
28887
|
+
} else {
|
|
28888
|
+
const models = raw.defaultModels && typeof raw.defaultModels === "object" ? raw.defaultModels : {};
|
|
28889
|
+
models[agentId] = modelId;
|
|
28890
|
+
raw.defaultModels = models;
|
|
28891
|
+
await writeRawConfig3(raw);
|
|
28892
|
+
}
|
|
28893
|
+
const disk = readAgentDefaults(await readRawConfig3());
|
|
28894
|
+
if (modelId !== void 0 && agentId !== disk.agent) {
|
|
28895
|
+
process.stdout.write(
|
|
28896
|
+
`Default model for ${agentId} is now ${modelId}.
|
|
28897
|
+
`
|
|
28898
|
+
);
|
|
28899
|
+
}
|
|
28900
|
+
process.stdout.write(`${formatDefaultLine(disk)}
|
|
28901
|
+
`);
|
|
28902
|
+
const daemonView = await fetchDaemonAgentDefaults(baseUrl, serviceToken);
|
|
28903
|
+
if (daemonView === void 0) {
|
|
28904
|
+
return;
|
|
28905
|
+
}
|
|
28906
|
+
if (daemonView.agent === disk.agent && daemonView.model === disk.model) {
|
|
28907
|
+
return;
|
|
28908
|
+
}
|
|
28665
28909
|
process.stdout.write(
|
|
28666
|
-
`
|
|
28910
|
+
`Daemon still has ${formatAgentModel(daemonView)} \u2014 restart with \`hydra-acp daemon restart\` to apply.
|
|
28667
28911
|
`
|
|
28668
28912
|
);
|
|
28669
28913
|
}
|
|
28914
|
+
function formatDefaultLine(view) {
|
|
28915
|
+
return `Default agent is ${formatAgentModel(view)}`;
|
|
28916
|
+
}
|
|
28917
|
+
function formatAgentModel(view) {
|
|
28918
|
+
return view.model !== void 0 ? `${view.agent} with ${view.model}` : view.agent;
|
|
28919
|
+
}
|
|
28920
|
+
function readAgentDefaults(raw) {
|
|
28921
|
+
const agent = typeof raw.defaultAgent === "string" ? raw.defaultAgent : "(unset)";
|
|
28922
|
+
const models = raw.defaultModels && typeof raw.defaultModels === "object" ? raw.defaultModels : {};
|
|
28923
|
+
const rawModel = models[agent];
|
|
28924
|
+
return typeof rawModel === "string" ? { agent, model: rawModel } : { agent };
|
|
28925
|
+
}
|
|
28926
|
+
async function fetchDaemonAgentDefaults(baseUrl, serviceToken) {
|
|
28927
|
+
try {
|
|
28928
|
+
const r = await fetch(`${baseUrl}/v1/config`, {
|
|
28929
|
+
headers: { Authorization: `Bearer ${serviceToken}` }
|
|
28930
|
+
});
|
|
28931
|
+
if (!r.ok) {
|
|
28932
|
+
return void 0;
|
|
28933
|
+
}
|
|
28934
|
+
const body = await r.json();
|
|
28935
|
+
return readAgentDefaults(body);
|
|
28936
|
+
} catch {
|
|
28937
|
+
return void 0;
|
|
28938
|
+
}
|
|
28939
|
+
}
|
|
28670
28940
|
async function readRawConfig3() {
|
|
28671
28941
|
const raw = await fsp12.readFile(paths.config(), "utf8");
|
|
28672
28942
|
return JSON.parse(raw);
|
|
@@ -28837,6 +29107,7 @@ function maxLen5(headerCell, values) {
|
|
|
28837
29107
|
}
|
|
28838
29108
|
|
|
28839
29109
|
// src/shim/proxy.ts
|
|
29110
|
+
import * as fs21 from "fs";
|
|
28840
29111
|
init_config();
|
|
28841
29112
|
init_remote_target();
|
|
28842
29113
|
init_daemon_bootstrap();
|
|
@@ -29013,6 +29284,8 @@ function isResponse2(msg) {
|
|
|
29013
29284
|
|
|
29014
29285
|
// src/shim/proxy.ts
|
|
29015
29286
|
init_permission_pick();
|
|
29287
|
+
init_hydra_version();
|
|
29288
|
+
init_paths();
|
|
29016
29289
|
|
|
29017
29290
|
// src/core/process-title.ts
|
|
29018
29291
|
init_bin_name();
|
|
@@ -29097,6 +29370,7 @@ function wireShim({
|
|
|
29097
29370
|
tracker
|
|
29098
29371
|
}) {
|
|
29099
29372
|
upstream.onMessage((msg) => {
|
|
29373
|
+
wireLog("daemon\u2192client", msg);
|
|
29100
29374
|
tracker.observeFromServer(msg);
|
|
29101
29375
|
if (opts.dangerouslySkipPermissions === true && isPermissionRequest(msg)) {
|
|
29102
29376
|
void upstream.send({
|
|
@@ -29111,7 +29385,12 @@ function wireShim({
|
|
|
29111
29385
|
});
|
|
29112
29386
|
const namingState = { name: opts.name, used: false };
|
|
29113
29387
|
downstream.onMessage((msg) => {
|
|
29388
|
+
wireLog("client\u2192daemon", msg);
|
|
29114
29389
|
tracker.observeFromClient(msg);
|
|
29390
|
+
if (isInitializeRequest(msg)) {
|
|
29391
|
+
void upstream.send(normaliseInitializeClientInfo(msg));
|
|
29392
|
+
return;
|
|
29393
|
+
}
|
|
29115
29394
|
if (isSessionNewRequest(msg)) {
|
|
29116
29395
|
if (opts.sessionId) {
|
|
29117
29396
|
void upstream.send(buildAttachFromNew(msg, opts.sessionId));
|
|
@@ -29255,6 +29534,29 @@ async function replayAttach(stream, ctx, afterMessageId) {
|
|
|
29255
29534
|
function isSessionNewRequest(msg) {
|
|
29256
29535
|
return "method" in msg && "id" in msg && msg.id !== void 0 && msg.method === "session/new";
|
|
29257
29536
|
}
|
|
29537
|
+
function isInitializeRequest(msg) {
|
|
29538
|
+
return "method" in msg && "id" in msg && msg.id !== void 0 && msg.method === "initialize";
|
|
29539
|
+
}
|
|
29540
|
+
function normaliseInitializeClientInfo(msg) {
|
|
29541
|
+
const params = msg.params ?? {};
|
|
29542
|
+
const existing = params.clientInfo;
|
|
29543
|
+
const existingObj = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : void 0;
|
|
29544
|
+
const existingName = existingObj && typeof existingObj.name === "string" ? existingObj.name.trim() : "";
|
|
29545
|
+
if (existingName.length > 0) {
|
|
29546
|
+
return msg;
|
|
29547
|
+
}
|
|
29548
|
+
return {
|
|
29549
|
+
...msg,
|
|
29550
|
+
params: {
|
|
29551
|
+
...params,
|
|
29552
|
+
clientInfo: {
|
|
29553
|
+
...existingObj ?? {},
|
|
29554
|
+
name: "hydra-acp-shim",
|
|
29555
|
+
version: HYDRA_VERSION
|
|
29556
|
+
}
|
|
29557
|
+
}
|
|
29558
|
+
};
|
|
29559
|
+
}
|
|
29258
29560
|
function isPermissionRequest(msg) {
|
|
29259
29561
|
return "method" in msg && "id" in msg && msg.id !== void 0 && msg.method === "session/request_permission";
|
|
29260
29562
|
}
|
|
@@ -29276,6 +29578,40 @@ function rewriteSessionNewWithAgent(msg, agentId) {
|
|
|
29276
29578
|
params: { ...params, agentId }
|
|
29277
29579
|
};
|
|
29278
29580
|
}
|
|
29581
|
+
var WIRE_LOG_MAX_BYTES = 25 * 1024 * 1024;
|
|
29582
|
+
var wireLogChecked = false;
|
|
29583
|
+
var wireLogPath = null;
|
|
29584
|
+
function wireLog(direction, msg) {
|
|
29585
|
+
if (!process.env.HYDRA_SHIM_WIRE_LOG) {
|
|
29586
|
+
return;
|
|
29587
|
+
}
|
|
29588
|
+
if (!wireLogChecked) {
|
|
29589
|
+
wireLogChecked = true;
|
|
29590
|
+
try {
|
|
29591
|
+
wireLogPath = paths.shimWireLogFile();
|
|
29592
|
+
fs21.mkdirSync(paths.home(), { recursive: true });
|
|
29593
|
+
const st = fs21.statSync(wireLogPath, { throwIfNoEntry: false });
|
|
29594
|
+
if (st && st.size > WIRE_LOG_MAX_BYTES) {
|
|
29595
|
+
fs21.renameSync(wireLogPath, `${wireLogPath}.1`);
|
|
29596
|
+
}
|
|
29597
|
+
} catch {
|
|
29598
|
+
wireLogPath = null;
|
|
29599
|
+
}
|
|
29600
|
+
}
|
|
29601
|
+
if (!wireLogPath) {
|
|
29602
|
+
return;
|
|
29603
|
+
}
|
|
29604
|
+
try {
|
|
29605
|
+
const line = JSON.stringify({
|
|
29606
|
+
t: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29607
|
+
pid: process.pid,
|
|
29608
|
+
dir: direction,
|
|
29609
|
+
msg
|
|
29610
|
+
}) + "\n";
|
|
29611
|
+
fs21.appendFile(wireLogPath, line, () => void 0);
|
|
29612
|
+
} catch {
|
|
29613
|
+
}
|
|
29614
|
+
}
|
|
29279
29615
|
function injectHydraMeta(msg, additions) {
|
|
29280
29616
|
const params = msg.params ?? {};
|
|
29281
29617
|
const existingMeta = params._meta ?? {};
|
|
@@ -29428,6 +29764,13 @@ function applyStyle(text, style) {
|
|
|
29428
29764
|
// src/cli/commands/cat.ts
|
|
29429
29765
|
var DEFAULT_STREAM_THRESHOLD = 1 * 1024 * 1024;
|
|
29430
29766
|
var HYDRA_STDIN_TOOL_PREFIX = "mcp__hydra-acp-stdin__";
|
|
29767
|
+
function catPromptParams(sessionId, prompt) {
|
|
29768
|
+
return {
|
|
29769
|
+
sessionId,
|
|
29770
|
+
prompt,
|
|
29771
|
+
_meta: { [HYDRA_META_KEY]: { ancillary: true } }
|
|
29772
|
+
};
|
|
29773
|
+
}
|
|
29431
29774
|
function deriveTitleFromPrompt(prompt) {
|
|
29432
29775
|
if (!prompt) {
|
|
29433
29776
|
return void 0;
|
|
@@ -29608,10 +29951,7 @@ async function runCatLoop(args) {
|
|
|
29608
29951
|
return;
|
|
29609
29952
|
}
|
|
29610
29953
|
try {
|
|
29611
|
-
await conn.request("session/prompt",
|
|
29612
|
-
sessionId,
|
|
29613
|
-
prompt: promptBlocks
|
|
29614
|
-
});
|
|
29954
|
+
await conn.request("session/prompt", catPromptParams(sessionId, promptBlocks));
|
|
29615
29955
|
firstChunkSent = true;
|
|
29616
29956
|
} catch (err) {
|
|
29617
29957
|
stderr(`hydra-acp cat: prompt failed: ${err.message}
|
|
@@ -29834,10 +30174,10 @@ function runStreamingPath(args) {
|
|
|
29834
30174
|
}
|
|
29835
30175
|
await writeChain.catch(() => void 0);
|
|
29836
30176
|
const promptText = buildStreamPromptText(opts.prompt, open2.capacityBytes);
|
|
29837
|
-
const promptDone = conn.request(
|
|
29838
|
-
|
|
29839
|
-
|
|
29840
|
-
|
|
30177
|
+
const promptDone = conn.request(
|
|
30178
|
+
"session/prompt",
|
|
30179
|
+
catPromptParams(sessionId, [{ type: "text", text: promptText }])
|
|
30180
|
+
).catch((err) => {
|
|
29841
30181
|
args.onPromptFailed(
|
|
29842
30182
|
new Error(`prompt failed: ${err.message}`)
|
|
29843
30183
|
);
|
|
@@ -30167,7 +30507,7 @@ async function main() {
|
|
|
30167
30507
|
all: flags.all === true,
|
|
30168
30508
|
json: flags.json === true,
|
|
30169
30509
|
host: typeof flags.host === "string" ? flags.host : void 0,
|
|
30170
|
-
|
|
30510
|
+
includeNonInteractive: flags["include-non-interactive"] === true
|
|
30171
30511
|
});
|
|
30172
30512
|
return;
|
|
30173
30513
|
}
|
|
@@ -30332,7 +30672,7 @@ async function main() {
|
|
|
30332
30672
|
return;
|
|
30333
30673
|
}
|
|
30334
30674
|
if (sub === "set") {
|
|
30335
|
-
await
|
|
30675
|
+
await runAgentsSet(positional[2], positional[3]);
|
|
30336
30676
|
return;
|
|
30337
30677
|
}
|
|
30338
30678
|
if (sub === "log" || sub === "logs") {
|
|
@@ -30540,10 +30880,10 @@ function printHelp() {
|
|
|
30540
30880
|
" hydra-acp daemon start [--foreground] Start daemon (detached by default; --foreground to attach)",
|
|
30541
30881
|
" hydra-acp daemon stop|restart",
|
|
30542
30882
|
" hydra-acp daemon logs [-f] [-n N] Tail or follow the daemon log",
|
|
30543
|
-
" hydra-acp session [list] [--all] [--json] [--host=<host>] [--include-
|
|
30544
|
-
" List sessions (live + 20 most-recent cold; --all
|
|
30883
|
+
" hydra-acp session [list] [--all] [--json] [--host=<host>] [--include-non-interactive]",
|
|
30884
|
+
" List sessions (live + 20 most-recent cold; --all lifts the cold cap AND surfaces non-interactive sessions; --json emits JSON for scripts).",
|
|
30545
30885
|
" --host filters by origin machine: 'local' (default) shows only sessions created here, 'all' shows everything, or pass a hostname (e.g. machine-b) to show only imports from that peer.",
|
|
30546
|
-
" --include-
|
|
30886
|
+
" --include-non-interactive surfaces ancillary (e.g. `hydra cat`) or never-prompted sessions while keeping the cold cap (a narrower --all).",
|
|
30547
30887
|
" hydra-acp session info <id> [--verbose] [--json] [--diff] [--fold] [--no-color] [--no-pager]",
|
|
30548
30888
|
" Aggregate one session: turn count, tool histogram, files touched, cost/duration, synopsis. --diff appends the session diff under the summary and pages the whole thing on a TTY (inherits --fold).",
|
|
30549
30889
|
" hydra-acp session diff <id> [--json] [--no-color] [--no-pager] [--fold]",
|
|
@@ -30571,7 +30911,7 @@ function printHelp() {
|
|
|
30571
30911
|
" hydra-acp agent [list] List agents in the cached registry",
|
|
30572
30912
|
" hydra-acp agent refresh Force a registry re-fetch",
|
|
30573
30913
|
" hydra-acp agent install <id> Pre-install <id> from the registry (else lazy on first session)",
|
|
30574
|
-
" hydra-acp agent set <id>
|
|
30914
|
+
" hydra-acp agent set [<id>] [model] With no args, report the daemon's current default agent and its default model. With <id>, set <id> as the default agent (config.defaultAgent). With <id> and [model], set the per-agent default model (config.defaultModels[<id>]).",
|
|
30575
30915
|
" hydra-acp agent sync <id> Spawn <id> just long enough to ACP session/list it, then persist any sessions it remembers (across every cwd) as cold rows in `session list`",
|
|
30576
30916
|
" hydra-acp agent log <id> [-f] [-n N] Tail or follow an agent's spawn/stderr log",
|
|
30577
30917
|
" hydra-acp auth password [--force] Set the daemon's master password",
|