@hydra-acp/cli 0.1.57 → 0.1.59
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 +634 -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,13 @@ 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
|
}
|
|
8132
8259
|
this.deleteForward();
|
|
8133
8260
|
return [];
|
|
8261
|
+
}
|
|
8134
8262
|
case "ctrl-l":
|
|
8135
8263
|
return [{ type: "redraw" }];
|
|
8136
8264
|
case "ctrl-p":
|
|
@@ -8808,9 +8936,9 @@ var init_input = __esm({
|
|
|
8808
8936
|
});
|
|
8809
8937
|
|
|
8810
8938
|
// src/tui/attachments.ts
|
|
8811
|
-
import
|
|
8939
|
+
import path18 from "path";
|
|
8812
8940
|
function mimeFromExtension(p) {
|
|
8813
|
-
return EXTENSION_TO_MIME[
|
|
8941
|
+
return EXTENSION_TO_MIME[path18.extname(p).toLowerCase()] ?? null;
|
|
8814
8942
|
}
|
|
8815
8943
|
function isSupportedImagePath(p) {
|
|
8816
8944
|
return mimeFromExtension(p) !== null;
|
|
@@ -10698,9 +10826,9 @@ uncaught: ${err.stack ?? err.message}
|
|
|
10698
10826
|
this.permissionPrompt = spec ? { ...spec } : null;
|
|
10699
10827
|
this.repaint();
|
|
10700
10828
|
}
|
|
10701
|
-
// Two-line confirmation modal that takes over the prompt area.
|
|
10702
|
-
//
|
|
10703
|
-
//
|
|
10829
|
+
// Two-line confirmation modal that takes over the prompt area. Pass
|
|
10830
|
+
// null to dismiss. Currently unused — kept as a generic primitive for
|
|
10831
|
+
// any future modal that needs a question + hint footer.
|
|
10704
10832
|
setConfirmPrompt(spec) {
|
|
10705
10833
|
this.confirmPrompt = spec ? { ...spec } : null;
|
|
10706
10834
|
this.repaint();
|
|
@@ -12301,7 +12429,11 @@ var init_import_action_prompt = __esm({
|
|
|
12301
12429
|
// src/tui/picker.ts
|
|
12302
12430
|
function createPickerPrefs() {
|
|
12303
12431
|
return {
|
|
12304
|
-
filters: {
|
|
12432
|
+
filters: {
|
|
12433
|
+
cwdOnly: false,
|
|
12434
|
+
hostFilter: "__local",
|
|
12435
|
+
includeNonInteractive: false
|
|
12436
|
+
}
|
|
12305
12437
|
};
|
|
12306
12438
|
}
|
|
12307
12439
|
async function pickSession(term, opts) {
|
|
@@ -12329,10 +12461,8 @@ async function pickSession(term, opts) {
|
|
|
12329
12461
|
if (prefs.filters.cwdOnly) {
|
|
12330
12462
|
base = base.filter((s) => s.cwd === opts.cwd);
|
|
12331
12463
|
}
|
|
12332
|
-
if (!prefs.filters.
|
|
12333
|
-
base = base.filter(
|
|
12334
|
-
(s) => s.originatingClient?.name !== HYDRA_CAT_CLIENT_NAME
|
|
12335
|
-
);
|
|
12464
|
+
if (!prefs.filters.includeNonInteractive) {
|
|
12465
|
+
base = base.filter((s) => s.interactive === true);
|
|
12336
12466
|
}
|
|
12337
12467
|
base = filterByHost(base, prefs.filters.hostFilter);
|
|
12338
12468
|
return base;
|
|
@@ -12534,8 +12664,8 @@ async function pickSession(term, opts) {
|
|
|
12534
12664
|
prefs.filters.hostFilter === "__local" ? "host: local" : `host: ${prefs.filters.hostFilter}`
|
|
12535
12665
|
);
|
|
12536
12666
|
}
|
|
12537
|
-
if (prefs.filters.
|
|
12538
|
-
parts.push("+
|
|
12667
|
+
if (prefs.filters.includeNonInteractive) {
|
|
12668
|
+
parts.push("+non-interactive");
|
|
12539
12669
|
}
|
|
12540
12670
|
if (above > 0) {
|
|
12541
12671
|
parts.push(`\u2191 ${above} above`);
|
|
@@ -13176,7 +13306,9 @@ ${cells}`;
|
|
|
13176
13306
|
try {
|
|
13177
13307
|
const beforeKey = refreshOpts.silent ? renderFingerprint() : "";
|
|
13178
13308
|
const beforeTotal = total;
|
|
13179
|
-
const next = await listSessions(opts.target
|
|
13309
|
+
const next = await listSessions(opts.target, {
|
|
13310
|
+
includeNonInteractive: true
|
|
13311
|
+
});
|
|
13180
13312
|
const followId = preferredId ?? (selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0);
|
|
13181
13313
|
allSessions = sortSessions(next, opts.cwd);
|
|
13182
13314
|
applyFilter();
|
|
@@ -13654,7 +13786,7 @@ ${cells}`;
|
|
|
13654
13786
|
const effects = composer.feed(event);
|
|
13655
13787
|
const after = composer.state();
|
|
13656
13788
|
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")
|
|
13789
|
+
if (effects.some((e) => e.type === "exit")) {
|
|
13658
13790
|
cleanup();
|
|
13659
13791
|
resolve8({ kind: "abort" });
|
|
13660
13792
|
return;
|
|
@@ -13754,7 +13886,7 @@ ${cells}`;
|
|
|
13754
13886
|
}
|
|
13755
13887
|
if (name === "i" || name === "I") {
|
|
13756
13888
|
const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
|
|
13757
|
-
prefs.filters.
|
|
13889
|
+
prefs.filters.includeNonInteractive = !prefs.filters.includeNonInteractive;
|
|
13758
13890
|
applyFilter();
|
|
13759
13891
|
restoreCursorAfterFilter(keepId);
|
|
13760
13892
|
renderFromScratch();
|
|
@@ -14020,7 +14152,6 @@ var init_picker = __esm({
|
|
|
14020
14152
|
init_session_row();
|
|
14021
14153
|
init_paths();
|
|
14022
14154
|
init_session();
|
|
14023
|
-
init_hydra_version();
|
|
14024
14155
|
init_discovery();
|
|
14025
14156
|
init_history();
|
|
14026
14157
|
init_input();
|
|
@@ -14060,85 +14191,6 @@ var init_picker = __esm({
|
|
|
14060
14191
|
}
|
|
14061
14192
|
});
|
|
14062
14193
|
|
|
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
14194
|
// src/tui/completion.ts
|
|
14143
14195
|
function longestCommonPrefix(names) {
|
|
14144
14196
|
if (names.length === 0) {
|
|
@@ -14189,24 +14241,25 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
14189
14241
|
const defaultCwd = opts.defaultCwd ?? await pickInitialLocalCwd(session.cwd) ?? os6.homedir();
|
|
14190
14242
|
resetTerminalModes();
|
|
14191
14243
|
const shortId2 = stripHydraSessionPrefix(session.sessionId);
|
|
14192
|
-
const fromMachine = session.importedFromMachine ?? "another machine";
|
|
14193
14244
|
const originalCwd = shortenHomePath(session.cwd);
|
|
14245
|
+
const title = opts.title ?? "Fork locally \u2014 choose cwd";
|
|
14246
|
+
const intro = opts.intro ?? "Pick a local cwd for this session:";
|
|
14247
|
+
const headerRows = [
|
|
14248
|
+
{ label: "session: ", value: shortId2 },
|
|
14249
|
+
...session.importedFromMachine ? [{ label: "from: ", value: session.importedFromMachine }] : [],
|
|
14250
|
+
{ label: "cwd: ", value: originalCwd }
|
|
14251
|
+
];
|
|
14194
14252
|
let buffer = defaultCwd;
|
|
14195
14253
|
let errorLine = null;
|
|
14196
14254
|
let busy = false;
|
|
14197
14255
|
let layout = null;
|
|
14198
14256
|
const render = () => {
|
|
14199
|
-
const contentHeight =
|
|
14257
|
+
const contentHeight = headerRows.length + 6;
|
|
14200
14258
|
layout = drawBox(term, {
|
|
14201
14259
|
contentHeight,
|
|
14202
|
-
title
|
|
14260
|
+
title
|
|
14203
14261
|
});
|
|
14204
14262
|
const innerW = layout.contentW;
|
|
14205
|
-
const headerRows = [
|
|
14206
|
-
{ label: "session: ", value: shortId2 },
|
|
14207
|
-
{ label: "from: ", value: fromMachine },
|
|
14208
|
-
{ label: "cwd: ", value: originalCwd }
|
|
14209
|
-
];
|
|
14210
14263
|
let row = 0;
|
|
14211
14264
|
for (const hr of headerRows) {
|
|
14212
14265
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
@@ -14216,7 +14269,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
14216
14269
|
}
|
|
14217
14270
|
row++;
|
|
14218
14271
|
term.moveTo(layout.contentX, layout.contentY + row);
|
|
14219
|
-
term.noFormat(
|
|
14272
|
+
term.noFormat(` ${intro}`);
|
|
14220
14273
|
row += 2;
|
|
14221
14274
|
paintInputRow(row);
|
|
14222
14275
|
row += 2;
|
|
@@ -14230,7 +14283,7 @@ async function promptForImportCwd(term, session, opts = {}) {
|
|
|
14230
14283
|
);
|
|
14231
14284
|
}
|
|
14232
14285
|
};
|
|
14233
|
-
const inputRow = () =>
|
|
14286
|
+
const inputRow = () => headerRows.length + 3;
|
|
14234
14287
|
const paintInputRow = (rowOffset) => {
|
|
14235
14288
|
if (!layout) {
|
|
14236
14289
|
return;
|
|
@@ -14411,7 +14464,7 @@ var init_import_cwd_prompt = __esm({
|
|
|
14411
14464
|
|
|
14412
14465
|
// src/tui/clipboard.ts
|
|
14413
14466
|
import { spawn as nodeSpawn } from "child_process";
|
|
14414
|
-
import
|
|
14467
|
+
import fs23 from "fs/promises";
|
|
14415
14468
|
import os7 from "os";
|
|
14416
14469
|
import path19 from "path";
|
|
14417
14470
|
async function readClipboard(envIn = {}) {
|
|
@@ -14452,7 +14505,7 @@ async function readMacOS(env) {
|
|
|
14452
14505
|
return img;
|
|
14453
14506
|
}
|
|
14454
14507
|
} catch {
|
|
14455
|
-
await
|
|
14508
|
+
await fs23.unlink(tmpPath).catch(() => void 0);
|
|
14456
14509
|
}
|
|
14457
14510
|
try {
|
|
14458
14511
|
const buf = await runCapture(env.spawn, "pbpaste", []);
|
|
@@ -14567,9 +14620,9 @@ async function which(env, cmd) {
|
|
|
14567
14620
|
}
|
|
14568
14621
|
async function readFileAsAttachment(p, unlinkAfter) {
|
|
14569
14622
|
try {
|
|
14570
|
-
const buf = await
|
|
14623
|
+
const buf = await fs23.readFile(p);
|
|
14571
14624
|
if (unlinkAfter) {
|
|
14572
|
-
await
|
|
14625
|
+
await fs23.unlink(p).catch(() => void 0);
|
|
14573
14626
|
}
|
|
14574
14627
|
if (buf.length === 0) {
|
|
14575
14628
|
return { ok: false, reason: "no image on clipboard" };
|
|
@@ -14694,7 +14747,7 @@ function parseReattachResponse(result) {
|
|
|
14694
14747
|
return out;
|
|
14695
14748
|
}
|
|
14696
14749
|
function shouldDriftSnap(args) {
|
|
14697
|
-
return !args.replayDraining && args.pendingTurns > 0 && args.queueSize === 0 && !args.ownTurnInFlight && !args.hasInFlightHead;
|
|
14750
|
+
return !args.replayDraining && !args.amended && args.pendingTurns > 0 && args.queueSize === 0 && !args.ownTurnInFlight && !args.hasInFlightHead;
|
|
14698
14751
|
}
|
|
14699
14752
|
function computeAttachReconcile(args) {
|
|
14700
14753
|
if (args.daemonTurnStartedAt !== void 0) {
|
|
@@ -14715,10 +14768,10 @@ var init_reconnect_state = __esm({
|
|
|
14715
14768
|
});
|
|
14716
14769
|
|
|
14717
14770
|
// src/tui/app.ts
|
|
14718
|
-
import { appendFileSync, statSync, renameSync } from "fs";
|
|
14771
|
+
import { appendFileSync, statSync as statSync2, renameSync as renameSync2 } from "fs";
|
|
14719
14772
|
import { nanoid as nanoid3 } from "nanoid";
|
|
14720
14773
|
import termkit from "terminal-kit";
|
|
14721
|
-
import
|
|
14774
|
+
import fs24 from "fs/promises";
|
|
14722
14775
|
import path20 from "path";
|
|
14723
14776
|
function isReadonlyForbiddenEffect(effect) {
|
|
14724
14777
|
switch (effect.type) {
|
|
@@ -14856,6 +14909,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14856
14909
|
}
|
|
14857
14910
|
};
|
|
14858
14911
|
let pendingTurns = 0;
|
|
14912
|
+
let cancelling = false;
|
|
14859
14913
|
let currentHeadMessageId;
|
|
14860
14914
|
let sessionBusySince = null;
|
|
14861
14915
|
let sessionElapsedTimer = null;
|
|
@@ -14866,6 +14920,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14866
14920
|
pendingTurns = Math.max(0, pendingTurns + delta);
|
|
14867
14921
|
const screenReady = typeof screenRef !== "undefined" && screenRef !== null;
|
|
14868
14922
|
if (before === 0 && pendingTurns > 0) {
|
|
14923
|
+
cancelling = false;
|
|
14869
14924
|
sessionBusySince = Date.now();
|
|
14870
14925
|
lastUpdateAt = Date.now();
|
|
14871
14926
|
dispatcherRef?.setTurnRunning(true);
|
|
@@ -14886,6 +14941,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14886
14941
|
}, 1e3);
|
|
14887
14942
|
}
|
|
14888
14943
|
} else if (before > 0 && pendingTurns === 0) {
|
|
14944
|
+
cancelling = false;
|
|
14889
14945
|
sessionBusySince = null;
|
|
14890
14946
|
lastUpdateAt = null;
|
|
14891
14947
|
dispatcherRef?.setTurnRunning(false);
|
|
@@ -14900,6 +14956,11 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
14900
14956
|
stalled: false
|
|
14901
14957
|
});
|
|
14902
14958
|
}
|
|
14959
|
+
} else if (pendingTurns > 0 && cancelling) {
|
|
14960
|
+
cancelling = false;
|
|
14961
|
+
if (screenReady) {
|
|
14962
|
+
screenRef.setBanner({ status: "busy", stalled: false });
|
|
14963
|
+
}
|
|
14903
14964
|
}
|
|
14904
14965
|
void delta;
|
|
14905
14966
|
};
|
|
@@ -15258,18 +15319,19 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15258
15319
|
historyPolicy: "full",
|
|
15259
15320
|
clientInfo: { name: "hydra-acp-tui", version: HYDRA_VERSION },
|
|
15260
15321
|
...opts.readonly === true ? { readonly: true } : {},
|
|
15261
|
-
// Forward the user-chosen cwd
|
|
15262
|
-
//
|
|
15263
|
-
//
|
|
15264
|
-
//
|
|
15265
|
-
//
|
|
15266
|
-
|
|
15322
|
+
// Forward the user-chosen cwd via a full resume hint. An empty
|
|
15323
|
+
// upstreamSessionId routes through doResurrectFromImport
|
|
15324
|
+
// (first-launch imports); a real one takes the normal session/load
|
|
15325
|
+
// path (repairing a local session whose recorded cwd is gone).
|
|
15326
|
+
// Either way the daemon resurrects with this cwd instead of the
|
|
15327
|
+
// stale recorded one.
|
|
15328
|
+
...ctx.resumeHint !== void 0 ? {
|
|
15267
15329
|
_meta: {
|
|
15268
15330
|
[HYDRA_META_KEY]: {
|
|
15269
15331
|
resume: {
|
|
15270
|
-
upstreamSessionId:
|
|
15271
|
-
agentId: ctx.
|
|
15272
|
-
cwd: ctx.
|
|
15332
|
+
upstreamSessionId: ctx.resumeHint.upstreamSessionId,
|
|
15333
|
+
agentId: ctx.resumeHint.agentId,
|
|
15334
|
+
cwd: ctx.resumeHint.cwd
|
|
15273
15335
|
}
|
|
15274
15336
|
}
|
|
15275
15337
|
}
|
|
@@ -15356,9 +15418,6 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15356
15418
|
if (pendingPermission && tryHandlePermissionKey(ev)) {
|
|
15357
15419
|
continue;
|
|
15358
15420
|
}
|
|
15359
|
-
if (exitConfirmation && tryHandleExitConfirmKey(ev)) {
|
|
15360
|
-
continue;
|
|
15361
|
-
}
|
|
15362
15421
|
if (tryHandleHelpKey(ev)) {
|
|
15363
15422
|
continue;
|
|
15364
15423
|
}
|
|
@@ -15572,86 +15631,35 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15572
15631
|
const cancelRemoteTurn = () => {
|
|
15573
15632
|
conn.notify("session/cancel", { sessionId: resolvedSessionId }).catch(() => void 0);
|
|
15574
15633
|
};
|
|
15575
|
-
const
|
|
15576
|
-
if (
|
|
15577
|
-
turnInFlight.cancel();
|
|
15634
|
+
const markCancelling = () => {
|
|
15635
|
+
if (screenRef === null) {
|
|
15578
15636
|
return;
|
|
15579
15637
|
}
|
|
15580
|
-
if (pendingTurns
|
|
15581
|
-
cancelRemoteTurn();
|
|
15638
|
+
if (pendingTurns !== 1) {
|
|
15582
15639
|
return;
|
|
15583
15640
|
}
|
|
15584
|
-
|
|
15641
|
+
cancelling = true;
|
|
15642
|
+
screenRef.setBanner({
|
|
15643
|
+
status: "cancelling",
|
|
15644
|
+
elapsedMs: void 0,
|
|
15645
|
+
stalled: false
|
|
15646
|
+
});
|
|
15585
15647
|
};
|
|
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);
|
|
15648
|
+
const sigintHandler = () => {
|
|
15649
|
+
if (turnInFlight) {
|
|
15650
|
+
turnInFlight.cancel();
|
|
15651
|
+
markCancelling();
|
|
15603
15652
|
return;
|
|
15604
15653
|
}
|
|
15605
|
-
if (
|
|
15606
|
-
|
|
15654
|
+
if (pendingTurns > 0) {
|
|
15655
|
+
cancelRemoteTurn();
|
|
15656
|
+
markCancelling();
|
|
15607
15657
|
return;
|
|
15608
15658
|
}
|
|
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
|
-
});
|
|
15659
|
+
requestExit();
|
|
15614
15660
|
};
|
|
15615
|
-
const
|
|
15616
|
-
|
|
15617
|
-
screen.setConfirmPrompt(null);
|
|
15618
|
-
};
|
|
15619
|
-
const tryHandleExitConfirmKey = (ev) => {
|
|
15620
|
-
if (!exitConfirmation) {
|
|
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;
|
|
15661
|
+
const requestExit = () => {
|
|
15662
|
+
stop(0);
|
|
15655
15663
|
};
|
|
15656
15664
|
const buildHelpEntries = () => {
|
|
15657
15665
|
const enqueueDesc = "enqueue prompt (sends now, or queues during a turn)";
|
|
@@ -15723,7 +15731,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15723
15731
|
let resolvedChoice = null;
|
|
15724
15732
|
let attachOverrides = null;
|
|
15725
15733
|
while (resolvedChoice === null) {
|
|
15726
|
-
const sessions = await listSessions(target);
|
|
15734
|
+
const sessions = await listSessions(target, { includeNonInteractive: true });
|
|
15727
15735
|
const choice2 = await pickSession(term, {
|
|
15728
15736
|
cwd: resolvedCwd,
|
|
15729
15737
|
sessions,
|
|
@@ -15761,14 +15769,43 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15761
15769
|
readonly: false,
|
|
15762
15770
|
cwd: decided2.ctx.cwd
|
|
15763
15771
|
};
|
|
15764
|
-
if (decided2.ctx.
|
|
15765
|
-
attachOverrides.
|
|
15772
|
+
if (decided2.ctx.resumeHint !== void 0) {
|
|
15773
|
+
attachOverrides.resumeHint = decided2.ctx.resumeHint;
|
|
15766
15774
|
}
|
|
15767
15775
|
break;
|
|
15768
15776
|
}
|
|
15769
15777
|
const chosen = sessions.find((s) => s.sessionId === choice2.sessionId);
|
|
15770
15778
|
const isImportedFirstLaunch = chosen !== void 0 && !!chosen.importedFromMachine && !chosen.upstreamSessionId && choice2.readonly !== true;
|
|
15771
15779
|
if (!isImportedFirstLaunch) {
|
|
15780
|
+
if (target.isLocal && chosen && !chosen.importedFromMachine && choice2.readonly !== true) {
|
|
15781
|
+
const v = await validateLocalCwd(chosen.cwd);
|
|
15782
|
+
if (!v.ok) {
|
|
15783
|
+
const r = await promptForImportCwd(term, chosen, {
|
|
15784
|
+
defaultCwd: expandHome(config.defaultCwd),
|
|
15785
|
+
title: "Working directory missing \u2014 choose cwd",
|
|
15786
|
+
intro: "This session's working directory no longer exists. Pick a new one:"
|
|
15787
|
+
});
|
|
15788
|
+
if (r.kind === "cancel") {
|
|
15789
|
+
screen.start({ skipFullscreen: true });
|
|
15790
|
+
screen.resumeRepaint();
|
|
15791
|
+
return;
|
|
15792
|
+
}
|
|
15793
|
+
if (r.kind === "back") {
|
|
15794
|
+
continue;
|
|
15795
|
+
}
|
|
15796
|
+
resolvedChoice = { choice: choice2, sessions };
|
|
15797
|
+
attachOverrides = {
|
|
15798
|
+
readonly: false,
|
|
15799
|
+
cwd: r.path,
|
|
15800
|
+
resumeHint: {
|
|
15801
|
+
agentId: choice2.agentId ?? chosen.agentId ?? "",
|
|
15802
|
+
cwd: r.path,
|
|
15803
|
+
upstreamSessionId: ""
|
|
15804
|
+
}
|
|
15805
|
+
};
|
|
15806
|
+
break;
|
|
15807
|
+
}
|
|
15808
|
+
}
|
|
15772
15809
|
resolvedChoice = { choice: choice2, sessions };
|
|
15773
15810
|
break;
|
|
15774
15811
|
}
|
|
@@ -15787,8 +15824,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15787
15824
|
readonly: opsShim.readonly === true,
|
|
15788
15825
|
cwd: decided.ctx.cwd
|
|
15789
15826
|
};
|
|
15790
|
-
if (decided.ctx.
|
|
15791
|
-
attachOverrides.
|
|
15827
|
+
if (decided.ctx.resumeHint !== void 0) {
|
|
15828
|
+
attachOverrides.resumeHint = decided.ctx.resumeHint;
|
|
15792
15829
|
}
|
|
15793
15830
|
}
|
|
15794
15831
|
const { choice } = resolvedChoice;
|
|
@@ -15823,10 +15860,10 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15823
15860
|
if (choice.agentId !== void 0) {
|
|
15824
15861
|
nextOpts.agentId = choice.agentId;
|
|
15825
15862
|
}
|
|
15826
|
-
if (attachOverrides?.
|
|
15827
|
-
nextOpts.
|
|
15863
|
+
if (attachOverrides?.resumeHint !== void 0) {
|
|
15864
|
+
nextOpts.resumeHint = attachOverrides.resumeHint;
|
|
15828
15865
|
} else {
|
|
15829
|
-
delete nextOpts.
|
|
15866
|
+
delete nextOpts.resumeHint;
|
|
15830
15867
|
}
|
|
15831
15868
|
resume(nextOpts);
|
|
15832
15869
|
};
|
|
@@ -15929,10 +15966,11 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
15929
15966
|
} else if (pendingTurns > 0) {
|
|
15930
15967
|
cancelRemoteTurn();
|
|
15931
15968
|
}
|
|
15969
|
+
markCancelling();
|
|
15932
15970
|
return;
|
|
15933
15971
|
}
|
|
15934
15972
|
case "exit":
|
|
15935
|
-
|
|
15973
|
+
requestExit();
|
|
15936
15974
|
return;
|
|
15937
15975
|
case "plan-toggle":
|
|
15938
15976
|
void handleModeToggle(effect.on);
|
|
@@ -16020,7 +16058,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16020
16058
|
continue;
|
|
16021
16059
|
}
|
|
16022
16060
|
try {
|
|
16023
|
-
const buf = await
|
|
16061
|
+
const buf = await fs24.readFile(token);
|
|
16024
16062
|
if (buf.length > MAX_ATTACHMENT_BYTES) {
|
|
16025
16063
|
screen.notify(
|
|
16026
16064
|
`image too large (${formatSize(buf.length)}, max ${formatSize(MAX_ATTACHMENT_BYTES)})`
|
|
@@ -16230,7 +16268,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16230
16268
|
switch (cmd) {
|
|
16231
16269
|
case "/quit":
|
|
16232
16270
|
case "/exit":
|
|
16233
|
-
|
|
16271
|
+
requestExit();
|
|
16234
16272
|
return true;
|
|
16235
16273
|
case "/clear":
|
|
16236
16274
|
toolStates.clear();
|
|
@@ -16241,6 +16279,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16241
16279
|
toolsBlockStopReason = null;
|
|
16242
16280
|
toolsExpanded = false;
|
|
16243
16281
|
lastEditMarkPath = null;
|
|
16282
|
+
turnHasShownProse = false;
|
|
16244
16283
|
screen.clearScrollback();
|
|
16245
16284
|
return true;
|
|
16246
16285
|
case "/demo-plan": {
|
|
@@ -16586,6 +16625,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16586
16625
|
}
|
|
16587
16626
|
};
|
|
16588
16627
|
let lastEditMarkPath = null;
|
|
16628
|
+
let turnHasShownProse = false;
|
|
16589
16629
|
const maybeRenderEditDiff = (toolCallId) => {
|
|
16590
16630
|
const mode = config.tui.showFileUpdates;
|
|
16591
16631
|
if (mode === "none") {
|
|
@@ -16602,6 +16642,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16602
16642
|
}
|
|
16603
16643
|
return;
|
|
16604
16644
|
}
|
|
16645
|
+
if (!turnHasShownProse) {
|
|
16646
|
+
return;
|
|
16647
|
+
}
|
|
16605
16648
|
const diff = state.editDiff;
|
|
16606
16649
|
if (diff.path && diff.path === lastEditMarkPath) {
|
|
16607
16650
|
return;
|
|
@@ -16687,6 +16730,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16687
16730
|
toolsExpanded = false;
|
|
16688
16731
|
toolsBlockEndedAt = null;
|
|
16689
16732
|
lastEditMarkPath = null;
|
|
16733
|
+
turnHasShownProse = false;
|
|
16690
16734
|
startToolsBlock();
|
|
16691
16735
|
screen.redraw();
|
|
16692
16736
|
return;
|
|
@@ -16694,6 +16738,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16694
16738
|
if (event.kind === "agent-text") {
|
|
16695
16739
|
closeThought();
|
|
16696
16740
|
if (event.text.length > 0) {
|
|
16741
|
+
turnHasShownProse = true;
|
|
16697
16742
|
lastEditMarkPath = null;
|
|
16698
16743
|
}
|
|
16699
16744
|
appendAgentText(event.text);
|
|
@@ -16702,6 +16747,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16702
16747
|
if (event.kind === "agent-thought") {
|
|
16703
16748
|
closeAgentText();
|
|
16704
16749
|
if (viewPrefs.showThoughts && event.text.length > 0) {
|
|
16750
|
+
turnHasShownProse = true;
|
|
16705
16751
|
lastEditMarkPath = null;
|
|
16706
16752
|
}
|
|
16707
16753
|
appendThought(event.text);
|
|
@@ -16825,13 +16871,15 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16825
16871
|
toolsExpanded = false;
|
|
16826
16872
|
upstreamInterruptedSeen = false;
|
|
16827
16873
|
lastEditMarkPath = null;
|
|
16874
|
+
turnHasShownProse = false;
|
|
16828
16875
|
screen.ensureSeparator();
|
|
16829
16876
|
if (shouldDriftSnap({
|
|
16830
16877
|
pendingTurns,
|
|
16831
16878
|
queueSize: queueCache.size,
|
|
16832
16879
|
ownTurnInFlight: turnInFlight !== null,
|
|
16833
16880
|
hasInFlightHead: currentHeadMessageId !== void 0,
|
|
16834
|
-
replayDraining
|
|
16881
|
+
replayDraining,
|
|
16882
|
+
amended: event.amended === true
|
|
16835
16883
|
})) {
|
|
16836
16884
|
adjustPendingTurns(-pendingTurns);
|
|
16837
16885
|
}
|
|
@@ -16910,6 +16958,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
|
|
|
16910
16958
|
toolsBlockStopReason = null;
|
|
16911
16959
|
toolsExpanded = false;
|
|
16912
16960
|
lastEditMarkPath = null;
|
|
16961
|
+
turnHasShownProse = false;
|
|
16913
16962
|
};
|
|
16914
16963
|
onDisconnectHook = () => {
|
|
16915
16964
|
screen.setBanner({ status: "disconnected", elapsedMs: void 0 });
|
|
@@ -17059,8 +17108,8 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17059
17108
|
agentId: opts.agentId ?? "",
|
|
17060
17109
|
cwd
|
|
17061
17110
|
};
|
|
17062
|
-
if (opts.
|
|
17063
|
-
ctx.
|
|
17111
|
+
if (opts.resumeHint !== void 0) {
|
|
17112
|
+
ctx.resumeHint = opts.resumeHint;
|
|
17064
17113
|
}
|
|
17065
17114
|
return ctx;
|
|
17066
17115
|
}
|
|
@@ -17082,7 +17131,7 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17082
17131
|
};
|
|
17083
17132
|
}
|
|
17084
17133
|
while (true) {
|
|
17085
|
-
const sessions = await listSessions(target);
|
|
17134
|
+
const sessions = await listSessions(target, { includeNonInteractive: true });
|
|
17086
17135
|
const choice = await pickSession(term, {
|
|
17087
17136
|
cwd,
|
|
17088
17137
|
sessions,
|
|
@@ -17122,6 +17171,33 @@ async function resolveSession(term, config, target, opts, pickerPrefs) {
|
|
|
17122
17171
|
}
|
|
17123
17172
|
return decided.ctx;
|
|
17124
17173
|
}
|
|
17174
|
+
if (target.isLocal && chosen && !chosen.importedFromMachine && !opts.readonly) {
|
|
17175
|
+
const v = await validateLocalCwd(chosen.cwd);
|
|
17176
|
+
if (!v.ok) {
|
|
17177
|
+
const r = await promptForImportCwd(term, chosen, {
|
|
17178
|
+
defaultCwd: expandHome(config.defaultCwd),
|
|
17179
|
+
title: "Working directory missing \u2014 choose cwd",
|
|
17180
|
+
intro: "This session's working directory no longer exists. Pick a new one:"
|
|
17181
|
+
});
|
|
17182
|
+
if (r.kind === "cancel") {
|
|
17183
|
+
return null;
|
|
17184
|
+
}
|
|
17185
|
+
if (r.kind === "back") {
|
|
17186
|
+
continue;
|
|
17187
|
+
}
|
|
17188
|
+
const agentId = choice.agentId ?? chosen.agentId ?? "";
|
|
17189
|
+
return {
|
|
17190
|
+
sessionId: choice.sessionId,
|
|
17191
|
+
agentId,
|
|
17192
|
+
cwd: r.path,
|
|
17193
|
+
resumeHint: {
|
|
17194
|
+
agentId,
|
|
17195
|
+
cwd: r.path,
|
|
17196
|
+
upstreamSessionId: ""
|
|
17197
|
+
}
|
|
17198
|
+
};
|
|
17199
|
+
}
|
|
17200
|
+
}
|
|
17125
17201
|
return {
|
|
17126
17202
|
sessionId: choice.sessionId,
|
|
17127
17203
|
agentId: choice.agentId ?? "",
|
|
@@ -17164,7 +17240,9 @@ async function runImportedFirstLaunchFlow(term, chosen, choice, opts) {
|
|
|
17164
17240
|
sessionId: choice.sessionId,
|
|
17165
17241
|
agentId,
|
|
17166
17242
|
cwd: cwdResult.path,
|
|
17167
|
-
|
|
17243
|
+
// Empty upstreamSessionId → import-reseed path for a never-launched
|
|
17244
|
+
// imported session.
|
|
17245
|
+
resumeHint: { agentId, cwd: cwdResult.path, upstreamSessionId: "" }
|
|
17168
17246
|
}
|
|
17169
17247
|
};
|
|
17170
17248
|
}
|
|
@@ -17208,9 +17286,15 @@ fork failed: ${err.message}
|
|
|
17208
17286
|
// For foreign-never-launched forks, the daemon stamped the chosen
|
|
17209
17287
|
// cwd onto meta.json via the POST body, but the very first attach
|
|
17210
17288
|
// still goes through the import-reseed path (upstreamSessionId=""),
|
|
17211
|
-
// and
|
|
17289
|
+
// and the resume hint is what makes attachManagerHooks persist
|
|
17212
17290
|
// the local cwd over the bundle's recorded one.
|
|
17213
|
-
...isForeignNeverLaunched ? {
|
|
17291
|
+
...isForeignNeverLaunched ? {
|
|
17292
|
+
resumeHint: {
|
|
17293
|
+
agentId: choice.sourceAgentId ?? "",
|
|
17294
|
+
cwd,
|
|
17295
|
+
upstreamSessionId: ""
|
|
17296
|
+
}
|
|
17297
|
+
} : {}
|
|
17214
17298
|
}
|
|
17215
17299
|
};
|
|
17216
17300
|
}
|
|
@@ -17336,11 +17420,11 @@ function createInstallStatusLine(term, baseLabel) {
|
|
|
17336
17420
|
}
|
|
17337
17421
|
function rotateIfBig(target) {
|
|
17338
17422
|
try {
|
|
17339
|
-
const stat5 =
|
|
17423
|
+
const stat5 = statSync2(target);
|
|
17340
17424
|
if (stat5.size < logMaxBytes) {
|
|
17341
17425
|
return;
|
|
17342
17426
|
}
|
|
17343
|
-
|
|
17427
|
+
renameSync2(target, `${target}.0`);
|
|
17344
17428
|
} catch {
|
|
17345
17429
|
}
|
|
17346
17430
|
}
|
|
@@ -17352,6 +17436,7 @@ var init_app = __esm({
|
|
|
17352
17436
|
init_types();
|
|
17353
17437
|
init_resilient_ws();
|
|
17354
17438
|
init_config();
|
|
17439
|
+
init_cwd();
|
|
17355
17440
|
init_remote_target();
|
|
17356
17441
|
init_daemon_bootstrap();
|
|
17357
17442
|
init_bin_name();
|
|
@@ -18849,10 +18934,17 @@ var SessionRecord = z5.object({
|
|
|
18849
18934
|
// ended at. Kept so future UI can show "branched from turn N of session X".
|
|
18850
18935
|
forkedFromSessionId: z5.string().optional(),
|
|
18851
18936
|
forkedFromMessageId: z5.string().optional(),
|
|
18852
|
-
// clientInfo from the process that issued session/new.
|
|
18853
|
-
//
|
|
18854
|
-
//
|
|
18937
|
+
// clientInfo from the process that issued session/new. Display only
|
|
18938
|
+
// since the `interactive` flag below; kept on the record for log
|
|
18939
|
+
// attribution and as the legacy hint inside effectiveInteractive
|
|
18940
|
+
// (pre-flag cat sessions can be recognised from this field).
|
|
18855
18941
|
originatingClient: PersistedOriginatingClient.optional(),
|
|
18942
|
+
// Tristate: true once the session has had a real turn, false when
|
|
18943
|
+
// explicitly created as ancillary (e.g. `hydra cat`), undefined for
|
|
18944
|
+
// pre-flag records / freshly-created sessions that haven't decided
|
|
18945
|
+
// yet. effectiveInteractive() in session-manager.ts is the single
|
|
18946
|
+
// resolver — every filter site goes through it.
|
|
18947
|
+
interactive: z5.boolean().optional(),
|
|
18856
18948
|
createdAt: z5.string(),
|
|
18857
18949
|
updatedAt: z5.string()
|
|
18858
18950
|
});
|
|
@@ -18971,6 +19063,7 @@ function recordFromMemorySession(args) {
|
|
|
18971
19063
|
forkedFromSessionId: args.forkedFromSessionId,
|
|
18972
19064
|
forkedFromMessageId: args.forkedFromMessageId,
|
|
18973
19065
|
originatingClient: args.originatingClient,
|
|
19066
|
+
interactive: args.interactive,
|
|
18974
19067
|
createdAt: args.createdAt ?? now,
|
|
18975
19068
|
updatedAt: args.updatedAt ?? now
|
|
18976
19069
|
};
|
|
@@ -19774,6 +19867,7 @@ var HistoryStore = class {
|
|
|
19774
19867
|
|
|
19775
19868
|
// src/core/session-manager.ts
|
|
19776
19869
|
init_paths();
|
|
19870
|
+
init_config();
|
|
19777
19871
|
init_history();
|
|
19778
19872
|
|
|
19779
19873
|
// src/core/bundle.ts
|
|
@@ -19809,6 +19903,14 @@ var BundleSession = z6.object({
|
|
|
19809
19903
|
currentUsage: PersistedUsage.optional(),
|
|
19810
19904
|
agentCommands: z6.array(PersistedAgentCommand).optional(),
|
|
19811
19905
|
agentModes: z6.array(PersistedAgentMode).optional(),
|
|
19906
|
+
// Raw interactive tristate (NOT the resolved effectiveInteractive) so
|
|
19907
|
+
// the value stays promotable on the destination: a cat/empty source
|
|
19908
|
+
// arrives as undefined and a real turn there can still flip it to
|
|
19909
|
+
// true. Carried alongside originatingClient so the importer's
|
|
19910
|
+
// effectiveInteractive can re-apply the cat-name hint at read time
|
|
19911
|
+
// without freezing a sticky `false` into the record.
|
|
19912
|
+
interactive: z6.boolean().optional(),
|
|
19913
|
+
originatingClient: PersistedOriginatingClient.optional(),
|
|
19812
19914
|
createdAt: z6.string(),
|
|
19813
19915
|
updatedAt: z6.string()
|
|
19814
19916
|
});
|
|
@@ -19852,6 +19954,8 @@ function encodeBundle(params) {
|
|
|
19852
19954
|
...params.record.currentUsage !== void 0 ? { currentUsage: params.record.currentUsage } : {},
|
|
19853
19955
|
...params.record.agentCommands !== void 0 ? { agentCommands: params.record.agentCommands } : {},
|
|
19854
19956
|
...params.record.agentModes !== void 0 ? { agentModes: params.record.agentModes } : {},
|
|
19957
|
+
...params.record.interactive !== void 0 ? { interactive: params.record.interactive } : {},
|
|
19958
|
+
...params.record.originatingClient !== void 0 ? { originatingClient: params.record.originatingClient } : {},
|
|
19855
19959
|
createdAt: params.record.createdAt,
|
|
19856
19960
|
updatedAt: params.record.updatedAt
|
|
19857
19961
|
},
|
|
@@ -19890,6 +19994,7 @@ var SessionManager = class {
|
|
|
19890
19994
|
this.logger = options.logger;
|
|
19891
19995
|
this.npmRegistry = options.npmRegistry;
|
|
19892
19996
|
this.extensionCommands = options.extensionCommands;
|
|
19997
|
+
this.defaultCwd = options.defaultCwd ?? "~";
|
|
19893
19998
|
this.synopsisCoordinator = new SynopsisCoordinator({
|
|
19894
19999
|
registry: this.registry,
|
|
19895
20000
|
store: this.store,
|
|
@@ -19923,6 +20028,7 @@ var SessionManager = class {
|
|
|
19923
20028
|
logger;
|
|
19924
20029
|
npmRegistry;
|
|
19925
20030
|
extensionCommands;
|
|
20031
|
+
defaultCwd;
|
|
19926
20032
|
// Background queue for ephemeral-agent synopsis generation. Runs
|
|
19927
20033
|
// out-of-band so session close is instant; persists synopsis/title
|
|
19928
20034
|
// via the same enqueueMetaWrite path the in-session handlers used.
|
|
@@ -19982,6 +20088,7 @@ var SessionManager = class {
|
|
|
19982
20088
|
transformChain: params.transformChain,
|
|
19983
20089
|
parentSessionId: params.parentSessionId,
|
|
19984
20090
|
originatingClient: params.originatingClient,
|
|
20091
|
+
interactive: params.interactive,
|
|
19985
20092
|
extensionCommands: this.extensionCommands,
|
|
19986
20093
|
scheduleSynopsis: () => this.synopsisCoordinator.schedule(session.sessionId)
|
|
19987
20094
|
});
|
|
@@ -20028,6 +20135,9 @@ var SessionManager = class {
|
|
|
20028
20135
|
if (params.upstreamSessionId === "") {
|
|
20029
20136
|
return this.doResurrectFromImport(params);
|
|
20030
20137
|
}
|
|
20138
|
+
if (!await this.dirExists(params.cwd)) {
|
|
20139
|
+
return this.doResurrectFromImport(params);
|
|
20140
|
+
}
|
|
20031
20141
|
const plan = await planSpawn(agentDef, params.agentArgs ?? [], {
|
|
20032
20142
|
npmRegistry: this.npmRegistry,
|
|
20033
20143
|
onInstallProgress: params.onInstallProgress
|
|
@@ -20155,6 +20265,7 @@ var SessionManager = class {
|
|
|
20155
20265
|
firstPromptSeeded: !!params.title,
|
|
20156
20266
|
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
20157
20267
|
originatingClient: params.originatingClient,
|
|
20268
|
+
interactive: params.interactive,
|
|
20158
20269
|
forkedFromSessionId: params.forkedFromSessionId,
|
|
20159
20270
|
forkedFromMessageId: params.forkedFromMessageId,
|
|
20160
20271
|
extensionCommands: this.extensionCommands,
|
|
@@ -20171,7 +20282,7 @@ var SessionManager = class {
|
|
|
20171
20282
|
// so subsequent resurrects of this session use the normal session/load
|
|
20172
20283
|
// path.
|
|
20173
20284
|
async doResurrectFromImport(params) {
|
|
20174
|
-
const cwd = await this.
|
|
20285
|
+
const cwd = await this.resolveResurrectCwd(params.cwd);
|
|
20175
20286
|
const fresh = await this.bootstrapAgent({
|
|
20176
20287
|
agentId: params.agentId,
|
|
20177
20288
|
cwd,
|
|
@@ -20226,6 +20337,7 @@ var SessionManager = class {
|
|
|
20226
20337
|
firstPromptSeeded: !!params.title,
|
|
20227
20338
|
createdAt: params.createdAt ? new Date(params.createdAt).getTime() : void 0,
|
|
20228
20339
|
originatingClient: params.originatingClient,
|
|
20340
|
+
interactive: params.interactive,
|
|
20229
20341
|
forkedFromSessionId: params.forkedFromSessionId,
|
|
20230
20342
|
forkedFromMessageId: params.forkedFromMessageId,
|
|
20231
20343
|
extensionCommands: this.extensionCommands,
|
|
@@ -20235,15 +20347,50 @@ var SessionManager = class {
|
|
|
20235
20347
|
void session.seedFromImport().catch(() => void 0);
|
|
20236
20348
|
return session;
|
|
20237
20349
|
}
|
|
20238
|
-
async
|
|
20350
|
+
async dirExists(cwd) {
|
|
20239
20351
|
try {
|
|
20240
|
-
|
|
20241
|
-
if (stat5.isDirectory()) {
|
|
20242
|
-
return cwd;
|
|
20243
|
-
}
|
|
20352
|
+
return (await fs14.stat(cwd)).isDirectory();
|
|
20244
20353
|
} catch {
|
|
20354
|
+
return false;
|
|
20245
20355
|
}
|
|
20246
|
-
|
|
20356
|
+
}
|
|
20357
|
+
// When the last client detaches from a session that resolves to
|
|
20358
|
+
// non-interactive — e.g. a `hydra cat` run, born interactive:undefined
|
|
20359
|
+
// with originatingClient hydra-acp-cat, whose every prompt is ancillary
|
|
20360
|
+
// — close it so its agent process doesn't linger until the (default 1h)
|
|
20361
|
+
// idle timeout fires. The cold record is kept, so the rare refine-in-TUI
|
|
20362
|
+
// still works via the resurrect/reseed path. Sessions promoted to
|
|
20363
|
+
// interactive (driven by a real, non-ancillary prompt) resolve to true
|
|
20364
|
+
// and are left running.
|
|
20365
|
+
async reapIfOrphanedNonInteractive(sessionId) {
|
|
20366
|
+
const session = this.sessions.get(sessionId);
|
|
20367
|
+
if (!session || session.attachedCount > 0) {
|
|
20368
|
+
return;
|
|
20369
|
+
}
|
|
20370
|
+
const interactive = effectiveInteractive(
|
|
20371
|
+
{
|
|
20372
|
+
interactive: session.interactive,
|
|
20373
|
+
...session.originatingClient ? { originatingClient: session.originatingClient } : {}
|
|
20374
|
+
},
|
|
20375
|
+
true
|
|
20376
|
+
);
|
|
20377
|
+
if (interactive !== false) {
|
|
20378
|
+
return;
|
|
20379
|
+
}
|
|
20380
|
+
this.logger?.info(
|
|
20381
|
+
`reaping orphaned non-interactive session ${sessionId} (agent killed, cold record kept)`
|
|
20382
|
+
);
|
|
20383
|
+
await session.close({ deleteRecord: false }).catch(() => void 0);
|
|
20384
|
+
}
|
|
20385
|
+
// Resolve a recorded cwd for resurrect: use it if it still exists,
|
|
20386
|
+
// otherwise fall back to the configured defaultCwd. Covers both bundles
|
|
20387
|
+
// imported from another machine and local sessions (e.g. `cat`) whose
|
|
20388
|
+
// recorded dir was cleaned up, so the reseed spawn never ENOENTs.
|
|
20389
|
+
async resolveResurrectCwd(cwd) {
|
|
20390
|
+
if (await this.dirExists(cwd)) {
|
|
20391
|
+
return cwd;
|
|
20392
|
+
}
|
|
20393
|
+
return expandHome(this.defaultCwd);
|
|
20247
20394
|
}
|
|
20248
20395
|
// Pull every session the agent itself remembers (across all cwds) and
|
|
20249
20396
|
// persist a cold hydra record for each one we don't already track.
|
|
@@ -20332,6 +20479,10 @@ var SessionManager = class {
|
|
|
20332
20479
|
agentId,
|
|
20333
20480
|
cwd: entry.cwd,
|
|
20334
20481
|
pendingHistorySync: true,
|
|
20482
|
+
// `hydra agent sync` is a user-explicit "show me agent-side
|
|
20483
|
+
// sessions" action; the rows are meant to be visible immediately
|
|
20484
|
+
// even before the first resurrect populates history.jsonl.
|
|
20485
|
+
interactive: true,
|
|
20335
20486
|
createdAt: ts,
|
|
20336
20487
|
updatedAt: ts
|
|
20337
20488
|
};
|
|
@@ -20498,6 +20649,11 @@ var SessionManager = class {
|
|
|
20498
20649
|
() => void 0
|
|
20499
20650
|
);
|
|
20500
20651
|
});
|
|
20652
|
+
session.onInteractiveChange((interactive) => {
|
|
20653
|
+
void this.persistSnapshot(session.sessionId, { interactive }).catch(
|
|
20654
|
+
() => void 0
|
|
20655
|
+
);
|
|
20656
|
+
});
|
|
20501
20657
|
session.onUsageChange((usage) => {
|
|
20502
20658
|
void this.persistSnapshot(session.sessionId, {
|
|
20503
20659
|
currentUsage: usageSnapshotToPersisted(usage)
|
|
@@ -20591,6 +20747,7 @@ var SessionManager = class {
|
|
|
20591
20747
|
createdAt: record.createdAt,
|
|
20592
20748
|
pendingHistorySync: record.pendingHistorySync,
|
|
20593
20749
|
originatingClient: record.originatingClient,
|
|
20750
|
+
interactive: record.interactive,
|
|
20594
20751
|
forkedFromSessionId: record.forkedFromSessionId,
|
|
20595
20752
|
forkedFromMessageId: record.forkedFromMessageId
|
|
20596
20753
|
};
|
|
@@ -20678,12 +20835,27 @@ var SessionManager = class {
|
|
|
20678
20835
|
async list(filter = {}) {
|
|
20679
20836
|
const entries = [];
|
|
20680
20837
|
const liveIds = /* @__PURE__ */ new Set();
|
|
20838
|
+
const includeRow = (interactive) => {
|
|
20839
|
+
if (filter.includeNonInteractive) return true;
|
|
20840
|
+
return interactive === true;
|
|
20841
|
+
};
|
|
20681
20842
|
for (const session of this.sessions.values()) {
|
|
20682
20843
|
if (filter.cwd && session.cwd !== filter.cwd) {
|
|
20683
20844
|
continue;
|
|
20684
20845
|
}
|
|
20685
20846
|
liveIds.add(session.sessionId);
|
|
20686
|
-
const
|
|
20847
|
+
const hist = await historyStatus(session.sessionId);
|
|
20848
|
+
const interactive = effectiveInteractive(
|
|
20849
|
+
{
|
|
20850
|
+
interactive: session.interactive,
|
|
20851
|
+
...session.originatingClient ? { originatingClient: session.originatingClient } : {}
|
|
20852
|
+
},
|
|
20853
|
+
hist.hasContent
|
|
20854
|
+
);
|
|
20855
|
+
if (!includeRow(interactive)) {
|
|
20856
|
+
continue;
|
|
20857
|
+
}
|
|
20858
|
+
const used = hist.mtime ?? new Date(session.updatedAt).toISOString();
|
|
20687
20859
|
entries.push({
|
|
20688
20860
|
sessionId: session.sessionId,
|
|
20689
20861
|
upstreamSessionId: session.upstreamSessionId,
|
|
@@ -20696,6 +20868,7 @@ var SessionManager = class {
|
|
|
20696
20868
|
forkedFromSessionId: session.forkedFromSessionId,
|
|
20697
20869
|
forkedFromMessageId: session.forkedFromMessageId,
|
|
20698
20870
|
originatingClient: session.originatingClient,
|
|
20871
|
+
interactive,
|
|
20699
20872
|
updatedAt: used,
|
|
20700
20873
|
attachedClients: session.attachedCount,
|
|
20701
20874
|
status: "live",
|
|
@@ -20710,7 +20883,12 @@ var SessionManager = class {
|
|
|
20710
20883
|
if (filter.cwd && r.cwd !== filter.cwd) {
|
|
20711
20884
|
continue;
|
|
20712
20885
|
}
|
|
20713
|
-
const
|
|
20886
|
+
const hist = await historyStatus(r.sessionId);
|
|
20887
|
+
const interactive = effectiveInteractive(r, hist.hasContent);
|
|
20888
|
+
if (!includeRow(interactive)) {
|
|
20889
|
+
continue;
|
|
20890
|
+
}
|
|
20891
|
+
const used = hist.mtime ?? r.updatedAt;
|
|
20714
20892
|
entries.push({
|
|
20715
20893
|
sessionId: r.sessionId,
|
|
20716
20894
|
upstreamSessionId: r.upstreamSessionId,
|
|
@@ -20728,6 +20906,7 @@ var SessionManager = class {
|
|
|
20728
20906
|
forkedFromSessionId: r.forkedFromSessionId,
|
|
20729
20907
|
forkedFromMessageId: r.forkedFromMessageId,
|
|
20730
20908
|
originatingClient: r.originatingClient,
|
|
20909
|
+
interactive,
|
|
20731
20910
|
updatedAt: used,
|
|
20732
20911
|
attachedClients: 0,
|
|
20733
20912
|
status: "cold",
|
|
@@ -20962,8 +21141,18 @@ var SessionManager = class {
|
|
|
20962
21141
|
currentUsage: args.bundle.session.currentUsage,
|
|
20963
21142
|
agentCommands: args.bundle.session.agentCommands,
|
|
20964
21143
|
agentModes: args.bundle.session.agentModes,
|
|
21144
|
+
// Carry the source's raw interactive tristate and originating
|
|
21145
|
+
// client rather than forcing true. A real conversation arrives
|
|
21146
|
+
// as true (visible immediately); an empty source arrives as
|
|
21147
|
+
// undefined (hidden until a turn lands here); a cat source
|
|
21148
|
+
// arrives as undefined + cat originatingClient, so
|
|
21149
|
+
// effectiveInteractive hides it via the hint while leaving it
|
|
21150
|
+
// promotable. Legacy bundles (pre-flag) carry neither and fall
|
|
21151
|
+
// back to effectiveInteractive's history-presence inference.
|
|
21152
|
+
interactive: args.bundle.session.interactive,
|
|
21153
|
+
originatingClient: args.bundle.session.originatingClient,
|
|
20965
21154
|
createdAt: args.preservedCreatedAt ?? now,
|
|
20966
|
-
// Fallback path for
|
|
21155
|
+
// Fallback path for historyStatus (used when the history file
|
|
20967
21156
|
// is missing). Keep this consistent with the utimes stamp above.
|
|
20968
21157
|
updatedAt: args.bundle.session.updatedAt
|
|
20969
21158
|
});
|
|
@@ -21072,6 +21261,8 @@ var SessionManager = class {
|
|
|
21072
21261
|
...update.agentCommands !== void 0 ? { agentCommands: update.agentCommands } : {},
|
|
21073
21262
|
...update.agentModes !== void 0 ? { agentModes: update.agentModes } : {},
|
|
21074
21263
|
...update.agentModels !== void 0 ? { agentModels: update.agentModels } : {},
|
|
21264
|
+
...update.interactive !== void 0 ? { interactive: update.interactive } : {},
|
|
21265
|
+
...update.cwd !== void 0 ? { cwd: update.cwd } : {},
|
|
21075
21266
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
21076
21267
|
});
|
|
21077
21268
|
});
|
|
@@ -21248,6 +21439,7 @@ function mergeForPersistence(session, existing) {
|
|
|
21248
21439
|
forkedFromSessionId: session.forkedFromSessionId ?? existing?.forkedFromSessionId,
|
|
21249
21440
|
forkedFromMessageId: session.forkedFromMessageId ?? existing?.forkedFromMessageId,
|
|
21250
21441
|
originatingClient: session.originatingClient ?? existing?.originatingClient,
|
|
21442
|
+
interactive: session.interactive ?? existing?.interactive,
|
|
21251
21443
|
createdAt: existing?.createdAt ?? new Date(session.createdAt).toISOString()
|
|
21252
21444
|
});
|
|
21253
21445
|
}
|
|
@@ -21554,14 +21746,26 @@ async function loadPromptHistorySafely(sessionId) {
|
|
|
21554
21746
|
return [];
|
|
21555
21747
|
}
|
|
21556
21748
|
}
|
|
21557
|
-
async function
|
|
21749
|
+
async function historyStatus(sessionId) {
|
|
21558
21750
|
try {
|
|
21559
21751
|
const st = await fs14.stat(paths.historyFile(sessionId));
|
|
21560
|
-
return
|
|
21752
|
+
return {
|
|
21753
|
+
mtime: new Date(st.mtimeMs).toISOString(),
|
|
21754
|
+
hasContent: st.size > 0
|
|
21755
|
+
};
|
|
21561
21756
|
} catch {
|
|
21562
|
-
return
|
|
21757
|
+
return { hasContent: false };
|
|
21563
21758
|
}
|
|
21564
21759
|
}
|
|
21760
|
+
function effectiveInteractive(record, hasContent) {
|
|
21761
|
+
if (record.interactive !== void 0) {
|
|
21762
|
+
return record.interactive;
|
|
21763
|
+
}
|
|
21764
|
+
if (record.originatingClient?.name === HYDRA_CAT_CLIENT_NAME) {
|
|
21765
|
+
return false;
|
|
21766
|
+
}
|
|
21767
|
+
return hasContent ? true : void 0;
|
|
21768
|
+
}
|
|
21565
21769
|
|
|
21566
21770
|
// src/core/child-supervisor.ts
|
|
21567
21771
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -23338,17 +23542,21 @@ function resolveHydraHost(defaults) {
|
|
|
23338
23542
|
function registerSessionRoutes(app, manager, defaults) {
|
|
23339
23543
|
app.get("/v1/sessions", async (request) => {
|
|
23340
23544
|
const query = request.query;
|
|
23341
|
-
const
|
|
23545
|
+
const includeNonInteractive = query?.includeNonInteractive === "1" || query?.includeNonInteractive === "true";
|
|
23546
|
+
const sessions = await manager.list({
|
|
23547
|
+
cwd: query?.cwd,
|
|
23548
|
+
includeNonInteractive
|
|
23549
|
+
});
|
|
23342
23550
|
return { sessions };
|
|
23343
23551
|
});
|
|
23344
|
-
app.
|
|
23345
|
-
const
|
|
23346
|
-
const q =
|
|
23552
|
+
app.post("/v1/sessions/search", async (request, reply) => {
|
|
23553
|
+
const body = request.body ?? {};
|
|
23554
|
+
const q = typeof body.q === "string" ? body.q : "";
|
|
23347
23555
|
if (q.trim().length === 0) {
|
|
23348
23556
|
reply.code(400).send({ error: "q is required" });
|
|
23349
23557
|
return reply;
|
|
23350
23558
|
}
|
|
23351
|
-
const ids =
|
|
23559
|
+
const ids = Array.isArray(body.sessionIds) ? body.sessionIds.filter((s) => typeof s === "string" && s.length > 0) : void 0;
|
|
23352
23560
|
const out = await searchHistories(manager, q, { sessionIds: ids });
|
|
23353
23561
|
return out;
|
|
23354
23562
|
});
|
|
@@ -23943,13 +24151,8 @@ function parseRegisterBody2(body) {
|
|
|
23943
24151
|
}
|
|
23944
24152
|
|
|
23945
24153
|
// 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
|
-
});
|
|
24154
|
+
function registerConfigRoutes(app, snapshot) {
|
|
24155
|
+
app.get("/v1/config", async () => snapshot);
|
|
23953
24156
|
}
|
|
23954
24157
|
|
|
23955
24158
|
// src/daemon/routes/auth.ts
|
|
@@ -24469,7 +24672,8 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
24469
24672
|
model: hydraMeta.model,
|
|
24470
24673
|
onInstallProgress: makeInstallProgressForwarder(connection),
|
|
24471
24674
|
transformChain,
|
|
24472
|
-
originatingClient: state.clientInfo
|
|
24675
|
+
originatingClient: state.clientInfo,
|
|
24676
|
+
...hydraMeta.interactive !== void 0 ? { interactive: hydraMeta.interactive } : {}
|
|
24473
24677
|
});
|
|
24474
24678
|
} catch (err) {
|
|
24475
24679
|
if (stdinReservation !== void 0) {
|
|
@@ -24596,8 +24800,9 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
24596
24800
|
err.code = JsonRpcErrorCodes.SessionNotFound;
|
|
24597
24801
|
throw err;
|
|
24598
24802
|
}
|
|
24803
|
+
const resurrectWithOriginator = resurrectParams.originatingClient ? resurrectParams : { ...resurrectParams, originatingClient: state.clientInfo };
|
|
24599
24804
|
session = await deps.manager.resurrect({
|
|
24600
|
-
...
|
|
24805
|
+
...resurrectWithOriginator,
|
|
24601
24806
|
onInstallProgress: makeInstallProgressForwarder(connection)
|
|
24602
24807
|
});
|
|
24603
24808
|
wireDefaultTransformers(session, deps);
|
|
@@ -24654,6 +24859,9 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
24654
24859
|
const session = deps.manager.get(params.sessionId);
|
|
24655
24860
|
session?.detach(att.clientId);
|
|
24656
24861
|
state.attached.delete(params.sessionId);
|
|
24862
|
+
if (session) {
|
|
24863
|
+
void deps.manager.reapIfOrphanedNonInteractive(params.sessionId);
|
|
24864
|
+
}
|
|
24657
24865
|
return { sessionId: params.sessionId, status: "detached" };
|
|
24658
24866
|
});
|
|
24659
24867
|
connection.onRequest("session/list", async (raw) => {
|
|
@@ -25901,7 +26109,8 @@ async function startDaemon(config, serviceToken) {
|
|
|
25901
26109
|
sessionHistoryMaxEntries: config.daemon.sessionHistoryMaxEntries,
|
|
25902
26110
|
logger: agentLogger,
|
|
25903
26111
|
npmRegistry: config.npmRegistry,
|
|
25904
|
-
extensionCommands
|
|
26112
|
+
extensionCommands,
|
|
26113
|
+
defaultCwd: config.defaultCwd
|
|
25905
26114
|
});
|
|
25906
26115
|
const extensions = new ExtensionManager(extensionList(config), void 0, {
|
|
25907
26116
|
tokenRegistry: processRegistry
|
|
@@ -25922,7 +26131,12 @@ async function startDaemon(config, serviceToken) {
|
|
|
25922
26131
|
registerTransformerRoutes(app, transformers);
|
|
25923
26132
|
registerConfigRoutes(app, {
|
|
25924
26133
|
defaultAgent: config.defaultAgent,
|
|
25925
|
-
defaultCwd: config.defaultCwd
|
|
26134
|
+
defaultCwd: config.defaultCwd,
|
|
26135
|
+
defaultModels: { ...config.defaultModels },
|
|
26136
|
+
...config.synopsisAgent !== void 0 ? { synopsisAgent: config.synopsisAgent } : {},
|
|
26137
|
+
...config.synopsisModel !== void 0 ? { synopsisModel: config.synopsisModel } : {},
|
|
26138
|
+
synopsisOnClose: config.synopsisOnClose,
|
|
26139
|
+
defaultTransformers: [...config.defaultTransformers]
|
|
25926
26140
|
});
|
|
25927
26141
|
registerAuthRoutes(app, {
|
|
25928
26142
|
store: sessionTokenStore,
|
|
@@ -26410,7 +26624,6 @@ init_remote_target();
|
|
|
26410
26624
|
init_remote_url();
|
|
26411
26625
|
init_session();
|
|
26412
26626
|
init_discovery();
|
|
26413
|
-
init_hydra_version();
|
|
26414
26627
|
import * as fs19 from "fs/promises";
|
|
26415
26628
|
import * as path16 from "path";
|
|
26416
26629
|
init_session_row();
|
|
@@ -26419,6 +26632,9 @@ async function runSessionsList(opts = {}) {
|
|
|
26419
26632
|
const serviceToken = await loadServiceToken();
|
|
26420
26633
|
const baseUrl = httpBase(config.daemon.host, config.daemon.port, !!config.daemon.tls);
|
|
26421
26634
|
const url = new URL(`${baseUrl}/v1/sessions`);
|
|
26635
|
+
if (opts.includeNonInteractive || opts.all) {
|
|
26636
|
+
url.searchParams.set("includeNonInteractive", "true");
|
|
26637
|
+
}
|
|
26422
26638
|
const response = await fetch(url.toString(), {
|
|
26423
26639
|
headers: { Authorization: `Bearer ${serviceToken}` }
|
|
26424
26640
|
});
|
|
@@ -26428,13 +26644,11 @@ async function runSessionsList(opts = {}) {
|
|
|
26428
26644
|
process.exit(1);
|
|
26429
26645
|
}
|
|
26430
26646
|
const body = await response.json();
|
|
26431
|
-
const
|
|
26432
|
-
(s) => s.originatingClient?.name !== HYDRA_CAT_CLIENT_NAME
|
|
26433
|
-
);
|
|
26647
|
+
const sessionsAfterInteractiveFilter = body.sessions;
|
|
26434
26648
|
const host = opts.host ?? "local";
|
|
26435
|
-
const hostFiltered = host === "all" ?
|
|
26649
|
+
const hostFiltered = host === "all" ? sessionsAfterInteractiveFilter : host === "local" ? sessionsAfterInteractiveFilter.filter(
|
|
26436
26650
|
(s) => !s.importedFromMachine || !!s.upstreamSessionId
|
|
26437
|
-
) :
|
|
26651
|
+
) : sessionsAfterInteractiveFilter.filter(
|
|
26438
26652
|
(s) => s.importedFromMachine === host && !s.upstreamSessionId
|
|
26439
26653
|
);
|
|
26440
26654
|
if (opts.json) {
|
|
@@ -28631,15 +28845,17 @@ async function runAgentsLogs(agentId, rest) {
|
|
|
28631
28845
|
const logPath = paths.agentLogFile(agentId);
|
|
28632
28846
|
await runLogTail(logPath, rest, "No log file (agent never ran?)");
|
|
28633
28847
|
}
|
|
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
|
-
}
|
|
28848
|
+
async function runAgentsSet(agentId, modelId) {
|
|
28640
28849
|
const config = await loadConfig();
|
|
28641
28850
|
const serviceToken = await loadServiceToken();
|
|
28642
28851
|
const baseUrl = httpBase(config.daemon.host, config.daemon.port, !!config.daemon.tls);
|
|
28852
|
+
if (!agentId) {
|
|
28853
|
+
const daemonView2 = await fetchDaemonAgentDefaults(baseUrl, serviceToken);
|
|
28854
|
+
const view = daemonView2 ?? readAgentDefaults(await readRawConfig3());
|
|
28855
|
+
process.stdout.write(`${formatDefaultLine(view)}
|
|
28856
|
+
`);
|
|
28857
|
+
return;
|
|
28858
|
+
}
|
|
28643
28859
|
let known;
|
|
28644
28860
|
try {
|
|
28645
28861
|
const r = await fetch(`${baseUrl}/v1/agents`, {
|
|
@@ -28660,13 +28876,62 @@ async function runAgentsSetDefault(agentId) {
|
|
|
28660
28876
|
return;
|
|
28661
28877
|
}
|
|
28662
28878
|
const raw = await readRawConfig3();
|
|
28663
|
-
|
|
28664
|
-
|
|
28879
|
+
if (modelId === void 0) {
|
|
28880
|
+
raw.defaultAgent = agentId;
|
|
28881
|
+
await writeRawConfig3(raw);
|
|
28882
|
+
} else {
|
|
28883
|
+
const models = raw.defaultModels && typeof raw.defaultModels === "object" ? raw.defaultModels : {};
|
|
28884
|
+
models[agentId] = modelId;
|
|
28885
|
+
raw.defaultModels = models;
|
|
28886
|
+
await writeRawConfig3(raw);
|
|
28887
|
+
}
|
|
28888
|
+
const disk = readAgentDefaults(await readRawConfig3());
|
|
28889
|
+
if (modelId !== void 0 && agentId !== disk.agent) {
|
|
28890
|
+
process.stdout.write(
|
|
28891
|
+
`Default model for ${agentId} is now ${modelId}.
|
|
28892
|
+
`
|
|
28893
|
+
);
|
|
28894
|
+
}
|
|
28895
|
+
process.stdout.write(`${formatDefaultLine(disk)}
|
|
28896
|
+
`);
|
|
28897
|
+
const daemonView = await fetchDaemonAgentDefaults(baseUrl, serviceToken);
|
|
28898
|
+
if (daemonView === void 0) {
|
|
28899
|
+
return;
|
|
28900
|
+
}
|
|
28901
|
+
if (daemonView.agent === disk.agent && daemonView.model === disk.model) {
|
|
28902
|
+
return;
|
|
28903
|
+
}
|
|
28665
28904
|
process.stdout.write(
|
|
28666
|
-
`
|
|
28905
|
+
`Daemon still has ${formatAgentModel(daemonView)} \u2014 restart with \`hydra-acp daemon restart\` to apply.
|
|
28667
28906
|
`
|
|
28668
28907
|
);
|
|
28669
28908
|
}
|
|
28909
|
+
function formatDefaultLine(view) {
|
|
28910
|
+
return `Default agent is ${formatAgentModel(view)}`;
|
|
28911
|
+
}
|
|
28912
|
+
function formatAgentModel(view) {
|
|
28913
|
+
return view.model !== void 0 ? `${view.agent} with ${view.model}` : view.agent;
|
|
28914
|
+
}
|
|
28915
|
+
function readAgentDefaults(raw) {
|
|
28916
|
+
const agent = typeof raw.defaultAgent === "string" ? raw.defaultAgent : "(unset)";
|
|
28917
|
+
const models = raw.defaultModels && typeof raw.defaultModels === "object" ? raw.defaultModels : {};
|
|
28918
|
+
const rawModel = models[agent];
|
|
28919
|
+
return typeof rawModel === "string" ? { agent, model: rawModel } : { agent };
|
|
28920
|
+
}
|
|
28921
|
+
async function fetchDaemonAgentDefaults(baseUrl, serviceToken) {
|
|
28922
|
+
try {
|
|
28923
|
+
const r = await fetch(`${baseUrl}/v1/config`, {
|
|
28924
|
+
headers: { Authorization: `Bearer ${serviceToken}` }
|
|
28925
|
+
});
|
|
28926
|
+
if (!r.ok) {
|
|
28927
|
+
return void 0;
|
|
28928
|
+
}
|
|
28929
|
+
const body = await r.json();
|
|
28930
|
+
return readAgentDefaults(body);
|
|
28931
|
+
} catch {
|
|
28932
|
+
return void 0;
|
|
28933
|
+
}
|
|
28934
|
+
}
|
|
28670
28935
|
async function readRawConfig3() {
|
|
28671
28936
|
const raw = await fsp12.readFile(paths.config(), "utf8");
|
|
28672
28937
|
return JSON.parse(raw);
|
|
@@ -28837,6 +29102,7 @@ function maxLen5(headerCell, values) {
|
|
|
28837
29102
|
}
|
|
28838
29103
|
|
|
28839
29104
|
// src/shim/proxy.ts
|
|
29105
|
+
import * as fs21 from "fs";
|
|
28840
29106
|
init_config();
|
|
28841
29107
|
init_remote_target();
|
|
28842
29108
|
init_daemon_bootstrap();
|
|
@@ -29013,6 +29279,8 @@ function isResponse2(msg) {
|
|
|
29013
29279
|
|
|
29014
29280
|
// src/shim/proxy.ts
|
|
29015
29281
|
init_permission_pick();
|
|
29282
|
+
init_hydra_version();
|
|
29283
|
+
init_paths();
|
|
29016
29284
|
|
|
29017
29285
|
// src/core/process-title.ts
|
|
29018
29286
|
init_bin_name();
|
|
@@ -29097,6 +29365,7 @@ function wireShim({
|
|
|
29097
29365
|
tracker
|
|
29098
29366
|
}) {
|
|
29099
29367
|
upstream.onMessage((msg) => {
|
|
29368
|
+
wireLog("daemon\u2192client", msg);
|
|
29100
29369
|
tracker.observeFromServer(msg);
|
|
29101
29370
|
if (opts.dangerouslySkipPermissions === true && isPermissionRequest(msg)) {
|
|
29102
29371
|
void upstream.send({
|
|
@@ -29111,7 +29380,12 @@ function wireShim({
|
|
|
29111
29380
|
});
|
|
29112
29381
|
const namingState = { name: opts.name, used: false };
|
|
29113
29382
|
downstream.onMessage((msg) => {
|
|
29383
|
+
wireLog("client\u2192daemon", msg);
|
|
29114
29384
|
tracker.observeFromClient(msg);
|
|
29385
|
+
if (isInitializeRequest(msg)) {
|
|
29386
|
+
void upstream.send(normaliseInitializeClientInfo(msg));
|
|
29387
|
+
return;
|
|
29388
|
+
}
|
|
29115
29389
|
if (isSessionNewRequest(msg)) {
|
|
29116
29390
|
if (opts.sessionId) {
|
|
29117
29391
|
void upstream.send(buildAttachFromNew(msg, opts.sessionId));
|
|
@@ -29255,6 +29529,29 @@ async function replayAttach(stream, ctx, afterMessageId) {
|
|
|
29255
29529
|
function isSessionNewRequest(msg) {
|
|
29256
29530
|
return "method" in msg && "id" in msg && msg.id !== void 0 && msg.method === "session/new";
|
|
29257
29531
|
}
|
|
29532
|
+
function isInitializeRequest(msg) {
|
|
29533
|
+
return "method" in msg && "id" in msg && msg.id !== void 0 && msg.method === "initialize";
|
|
29534
|
+
}
|
|
29535
|
+
function normaliseInitializeClientInfo(msg) {
|
|
29536
|
+
const params = msg.params ?? {};
|
|
29537
|
+
const existing = params.clientInfo;
|
|
29538
|
+
const existingObj = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : void 0;
|
|
29539
|
+
const existingName = existingObj && typeof existingObj.name === "string" ? existingObj.name.trim() : "";
|
|
29540
|
+
if (existingName.length > 0) {
|
|
29541
|
+
return msg;
|
|
29542
|
+
}
|
|
29543
|
+
return {
|
|
29544
|
+
...msg,
|
|
29545
|
+
params: {
|
|
29546
|
+
...params,
|
|
29547
|
+
clientInfo: {
|
|
29548
|
+
...existingObj ?? {},
|
|
29549
|
+
name: "hydra-acp-shim",
|
|
29550
|
+
version: HYDRA_VERSION
|
|
29551
|
+
}
|
|
29552
|
+
}
|
|
29553
|
+
};
|
|
29554
|
+
}
|
|
29258
29555
|
function isPermissionRequest(msg) {
|
|
29259
29556
|
return "method" in msg && "id" in msg && msg.id !== void 0 && msg.method === "session/request_permission";
|
|
29260
29557
|
}
|
|
@@ -29276,6 +29573,40 @@ function rewriteSessionNewWithAgent(msg, agentId) {
|
|
|
29276
29573
|
params: { ...params, agentId }
|
|
29277
29574
|
};
|
|
29278
29575
|
}
|
|
29576
|
+
var WIRE_LOG_MAX_BYTES = 25 * 1024 * 1024;
|
|
29577
|
+
var wireLogChecked = false;
|
|
29578
|
+
var wireLogPath = null;
|
|
29579
|
+
function wireLog(direction, msg) {
|
|
29580
|
+
if (!process.env.HYDRA_SHIM_WIRE_LOG) {
|
|
29581
|
+
return;
|
|
29582
|
+
}
|
|
29583
|
+
if (!wireLogChecked) {
|
|
29584
|
+
wireLogChecked = true;
|
|
29585
|
+
try {
|
|
29586
|
+
wireLogPath = paths.shimWireLogFile();
|
|
29587
|
+
fs21.mkdirSync(paths.home(), { recursive: true });
|
|
29588
|
+
const st = fs21.statSync(wireLogPath, { throwIfNoEntry: false });
|
|
29589
|
+
if (st && st.size > WIRE_LOG_MAX_BYTES) {
|
|
29590
|
+
fs21.renameSync(wireLogPath, `${wireLogPath}.1`);
|
|
29591
|
+
}
|
|
29592
|
+
} catch {
|
|
29593
|
+
wireLogPath = null;
|
|
29594
|
+
}
|
|
29595
|
+
}
|
|
29596
|
+
if (!wireLogPath) {
|
|
29597
|
+
return;
|
|
29598
|
+
}
|
|
29599
|
+
try {
|
|
29600
|
+
const line = JSON.stringify({
|
|
29601
|
+
t: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29602
|
+
pid: process.pid,
|
|
29603
|
+
dir: direction,
|
|
29604
|
+
msg
|
|
29605
|
+
}) + "\n";
|
|
29606
|
+
fs21.appendFile(wireLogPath, line, () => void 0);
|
|
29607
|
+
} catch {
|
|
29608
|
+
}
|
|
29609
|
+
}
|
|
29279
29610
|
function injectHydraMeta(msg, additions) {
|
|
29280
29611
|
const params = msg.params ?? {};
|
|
29281
29612
|
const existingMeta = params._meta ?? {};
|
|
@@ -29428,6 +29759,13 @@ function applyStyle(text, style) {
|
|
|
29428
29759
|
// src/cli/commands/cat.ts
|
|
29429
29760
|
var DEFAULT_STREAM_THRESHOLD = 1 * 1024 * 1024;
|
|
29430
29761
|
var HYDRA_STDIN_TOOL_PREFIX = "mcp__hydra-acp-stdin__";
|
|
29762
|
+
function catPromptParams(sessionId, prompt) {
|
|
29763
|
+
return {
|
|
29764
|
+
sessionId,
|
|
29765
|
+
prompt,
|
|
29766
|
+
_meta: { [HYDRA_META_KEY]: { ancillary: true } }
|
|
29767
|
+
};
|
|
29768
|
+
}
|
|
29431
29769
|
function deriveTitleFromPrompt(prompt) {
|
|
29432
29770
|
if (!prompt) {
|
|
29433
29771
|
return void 0;
|
|
@@ -29608,10 +29946,7 @@ async function runCatLoop(args) {
|
|
|
29608
29946
|
return;
|
|
29609
29947
|
}
|
|
29610
29948
|
try {
|
|
29611
|
-
await conn.request("session/prompt",
|
|
29612
|
-
sessionId,
|
|
29613
|
-
prompt: promptBlocks
|
|
29614
|
-
});
|
|
29949
|
+
await conn.request("session/prompt", catPromptParams(sessionId, promptBlocks));
|
|
29615
29950
|
firstChunkSent = true;
|
|
29616
29951
|
} catch (err) {
|
|
29617
29952
|
stderr(`hydra-acp cat: prompt failed: ${err.message}
|
|
@@ -29834,10 +30169,10 @@ function runStreamingPath(args) {
|
|
|
29834
30169
|
}
|
|
29835
30170
|
await writeChain.catch(() => void 0);
|
|
29836
30171
|
const promptText = buildStreamPromptText(opts.prompt, open2.capacityBytes);
|
|
29837
|
-
const promptDone = conn.request(
|
|
29838
|
-
|
|
29839
|
-
|
|
29840
|
-
|
|
30172
|
+
const promptDone = conn.request(
|
|
30173
|
+
"session/prompt",
|
|
30174
|
+
catPromptParams(sessionId, [{ type: "text", text: promptText }])
|
|
30175
|
+
).catch((err) => {
|
|
29841
30176
|
args.onPromptFailed(
|
|
29842
30177
|
new Error(`prompt failed: ${err.message}`)
|
|
29843
30178
|
);
|
|
@@ -30167,7 +30502,7 @@ async function main() {
|
|
|
30167
30502
|
all: flags.all === true,
|
|
30168
30503
|
json: flags.json === true,
|
|
30169
30504
|
host: typeof flags.host === "string" ? flags.host : void 0,
|
|
30170
|
-
|
|
30505
|
+
includeNonInteractive: flags["include-non-interactive"] === true
|
|
30171
30506
|
});
|
|
30172
30507
|
return;
|
|
30173
30508
|
}
|
|
@@ -30332,7 +30667,7 @@ async function main() {
|
|
|
30332
30667
|
return;
|
|
30333
30668
|
}
|
|
30334
30669
|
if (sub === "set") {
|
|
30335
|
-
await
|
|
30670
|
+
await runAgentsSet(positional[2], positional[3]);
|
|
30336
30671
|
return;
|
|
30337
30672
|
}
|
|
30338
30673
|
if (sub === "log" || sub === "logs") {
|
|
@@ -30540,10 +30875,10 @@ function printHelp() {
|
|
|
30540
30875
|
" hydra-acp daemon start [--foreground] Start daemon (detached by default; --foreground to attach)",
|
|
30541
30876
|
" hydra-acp daemon stop|restart",
|
|
30542
30877
|
" 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
|
|
30878
|
+
" hydra-acp session [list] [--all] [--json] [--host=<host>] [--include-non-interactive]",
|
|
30879
|
+
" List sessions (live + 20 most-recent cold; --all lifts the cold cap AND surfaces non-interactive sessions; --json emits JSON for scripts).",
|
|
30545
30880
|
" --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-
|
|
30881
|
+
" --include-non-interactive surfaces ancillary (e.g. `hydra cat`) or never-prompted sessions while keeping the cold cap (a narrower --all).",
|
|
30547
30882
|
" hydra-acp session info <id> [--verbose] [--json] [--diff] [--fold] [--no-color] [--no-pager]",
|
|
30548
30883
|
" 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
30884
|
" hydra-acp session diff <id> [--json] [--no-color] [--no-pager] [--fold]",
|
|
@@ -30571,7 +30906,7 @@ function printHelp() {
|
|
|
30571
30906
|
" hydra-acp agent [list] List agents in the cached registry",
|
|
30572
30907
|
" hydra-acp agent refresh Force a registry re-fetch",
|
|
30573
30908
|
" hydra-acp agent install <id> Pre-install <id> from the registry (else lazy on first session)",
|
|
30574
|
-
" hydra-acp agent set <id>
|
|
30909
|
+
" 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
30910
|
" 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
30911
|
" hydra-acp agent log <id> [-f] [-n N] Tail or follow an agent's spawn/stderr log",
|
|
30577
30912
|
" hydra-acp auth password [--force] Set the daemon's master password",
|