@hydra-acp/cli 0.1.39 → 0.1.41
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 +35 -14
- package/dist/cli.js +456 -56
- package/dist/index.d.ts +11 -0
- package/dist/index.js +216 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -187,7 +187,7 @@ hydra-acp session
|
|
|
187
187
|
|
|
188
188
|
# 6. Attach a second client to an existing session.
|
|
189
189
|
# Bare invocation auto-detects: TUI in a terminal, ACP shim when piped.
|
|
190
|
-
hydra-acp --session
|
|
190
|
+
hydra-acp --session hydra_session_abc123
|
|
191
191
|
```
|
|
192
192
|
|
|
193
193
|
## CLI
|
|
@@ -198,34 +198,55 @@ hydra-acp shim # explicit shim mode (forces shim re
|
|
|
198
198
|
hydra-acp tui # explicit terminal-UI mode
|
|
199
199
|
hydra-acp launch <agent> # launcher mode: shim that forces the
|
|
200
200
|
# daemon to spawn <agent> on session/new
|
|
201
|
-
hydra-acp
|
|
201
|
+
hydra-acp cat [-p <prompt>] [--detach] # pipe-friendly headless mode: feeds stdin
|
|
202
|
+
# to a session as prompts and streams the
|
|
203
|
+
# agent's reply to stdout
|
|
204
|
+
hydra-acp --session <id-or-url> # attach to existing session
|
|
202
205
|
# (TUI in a TTY, shim otherwise)
|
|
206
|
+
hydra-acp --reattach # pick the most-recent session for cwd
|
|
207
|
+
hydra-acp --new # force a fresh session
|
|
208
|
+
hydra-acp --readonly # open a session as a transcript viewer (with --session)
|
|
203
209
|
|
|
204
210
|
hydra-acp init # generate the service token
|
|
211
|
+
|
|
212
|
+
hydra-acp daemon [status] # output status of daemon
|
|
205
213
|
hydra-acp daemon start [--foreground] # detached by default; --foreground to attach
|
|
206
|
-
hydra-acp daemon stop
|
|
207
|
-
hydra-acp daemon
|
|
214
|
+
hydra-acp daemon stop # stop running daemon
|
|
215
|
+
hydra-acp daemon restart # stop then start the daemon
|
|
216
|
+
hydra-acp daemon logs [-f] [-n N] # tail (default 50) or follow the daemon log
|
|
208
217
|
|
|
209
|
-
hydra-acp session
|
|
210
|
-
hydra-acp session kill <id>
|
|
211
|
-
hydra-acp session remove <id>
|
|
218
|
+
hydra-acp session [list ] # list sessions
|
|
219
|
+
hydra-acp session kill <id> # close a live session (keeps the on-disk record so it can be resurrected)
|
|
220
|
+
hydra-acp session remove <id> # remove a session entirely (live or cold)
|
|
212
221
|
hydra-acp session export <id> [--out <file>|.]
|
|
213
222
|
# write a session bundle (meta + history) to <file>,
|
|
214
223
|
# to a default-named file when --out=., or to stdout
|
|
215
|
-
hydra-acp session
|
|
224
|
+
hydra-acp session transcript <id>|<file> [--out <file>|.]
|
|
225
|
+
# render a session (id via daemon, or a local .hydra
|
|
226
|
+
# bundle) as a markdown transcript
|
|
227
|
+
hydra-acp session import <file>|- [--replace] [--cwd <path>] [--info]
|
|
216
228
|
# import a bundle from <file> or stdin (-);
|
|
217
|
-
# --replace overwrites
|
|
229
|
+
# --replace overwrites a lineage match (kills it
|
|
230
|
+
# if live); --cwd overrides the bundle's recorded
|
|
231
|
+
# working directory; --info prints the bundle's
|
|
232
|
+
# meta without importing
|
|
218
233
|
|
|
219
|
-
hydra-acp extension
|
|
234
|
+
hydra-acp extension [list] # list configured extensions and live state
|
|
220
235
|
hydra-acp extension add <name> # add to config (--command, --args, --env, --disabled)
|
|
221
236
|
hydra-acp extension remove <name> # remove from config
|
|
222
237
|
hydra-acp extension start|stop|restart <n> # lifecycle on a running extension
|
|
223
238
|
hydra-acp extension logs <name> [-f] [-n] # tail (default 50) or follow an extension's log
|
|
224
239
|
|
|
225
|
-
hydra-acp agent
|
|
240
|
+
hydra-acp agent [list] # list agents in the registry
|
|
226
241
|
hydra-acp agent install <id> # pre-install an agent (else lazy on first use)
|
|
242
|
+
hydra-acp agent refresh # force a registry re-fetch
|
|
243
|
+
hydra-acp agent sync <id> # spawn <id> just long enough to ACP session/list it,
|
|
244
|
+
# then persist any sessions it remembers as cold rows
|
|
245
|
+
# (lets you bring in pre-existing agent sessions)
|
|
227
246
|
|
|
228
|
-
hydra-acp
|
|
247
|
+
hydra-acp auth # list active session tokens
|
|
248
|
+
hydra-acp auth password [--force] # set the daemon's master password
|
|
249
|
+
hydra-acp auth revoke <id> # revoke a session token
|
|
229
250
|
```
|
|
230
251
|
|
|
231
252
|
A bare invocation (`hydra-acp` with no subcommand) auto-dispatches based on whether stdout is a TTY: a real terminal launches the TUI, a piped stdio (the editor-spawned case) drops into shim mode. Pass `shim` or `tui` explicitly to force one or the other. Editors should configure `hydra-acp shim` so the choice is unambiguous regardless of how the editor wires stdio.
|
|
@@ -317,11 +338,11 @@ Every config-knob flag has an `HYDRA_ACP_FOO_BAR` env-var equivalent. Flag wins
|
|
|
317
338
|
| `--name` | `HYDRA_ACP_NAME` |
|
|
318
339
|
| `--agent` | `HYDRA_ACP_AGENT` |
|
|
319
340
|
| `--model` | `HYDRA_ACP_MODEL` |
|
|
320
|
-
| `--session
|
|
341
|
+
| `--session` | `HYDRA_ACP_SESSION` |
|
|
321
342
|
|
|
322
343
|
`--model` is a one-shot override for the per-agent `defaultModels` entry in `~/.hydra-acp/config.json`. It only applies at fresh session creation — resurrect and `/hydra agent` switch ignore it (resurrected sessions stay on whatever model they were last using).
|
|
323
344
|
|
|
324
|
-
Action commands (`init`, `daemon`, `
|
|
345
|
+
Action commands (`init`, `daemon`, `session`, `extension`, `agent`, `auth`, `cat`, `--help`, `--version`, `--rotate-token`) are not config knobs and are flag-only.
|
|
325
346
|
|
|
326
347
|
### Registry id resolution
|
|
327
348
|
|
package/dist/cli.js
CHANGED
|
@@ -91,6 +91,11 @@ var init_paths = __esm({
|
|
|
91
91
|
extensionLogFile: (name) => path.join(hydraHome(), "extensions", `${name}.log`),
|
|
92
92
|
extensionPidFile: (name) => path.join(hydraHome(), "extensions", `${name}.pid`),
|
|
93
93
|
tuiHistoryFile: (id) => path.join(hydraHome(), "sessions", id, "prompt-history"),
|
|
94
|
+
// Cross-session prompt history. Up-arrow / ^R fall through to this
|
|
95
|
+
// after the per-session list is exhausted. JSONL, one entry per
|
|
96
|
+
// line, append-only so concurrent TUIs don't lose each other's
|
|
97
|
+
// writes.
|
|
98
|
+
globalTuiHistoryFile: () => path.join(hydraHome(), "prompt-history"),
|
|
94
99
|
tuiLogFile: () => path.join(hydraHome(), "tui.log")
|
|
95
100
|
};
|
|
96
101
|
}
|
|
@@ -3832,7 +3837,7 @@ function parseHistory(text) {
|
|
|
3832
3837
|
}
|
|
3833
3838
|
return out;
|
|
3834
3839
|
}
|
|
3835
|
-
function appendEntry(history, entry) {
|
|
3840
|
+
function appendEntry(history, entry, cap = HISTORY_CAP) {
|
|
3836
3841
|
const trimmed = entry.replace(/\n+$/, "");
|
|
3837
3842
|
if (trimmed.length === 0) {
|
|
3838
3843
|
return history;
|
|
@@ -3841,8 +3846,8 @@ function appendEntry(history, entry) {
|
|
|
3841
3846
|
return history;
|
|
3842
3847
|
}
|
|
3843
3848
|
const out = history.concat(trimmed);
|
|
3844
|
-
if (out.length >
|
|
3845
|
-
return out.slice(out.length -
|
|
3849
|
+
if (out.length > cap) {
|
|
3850
|
+
return out.slice(out.length - cap);
|
|
3846
3851
|
}
|
|
3847
3852
|
return out;
|
|
3848
3853
|
}
|
|
@@ -3851,11 +3856,30 @@ async function saveHistory(file, history) {
|
|
|
3851
3856
|
const lines = history.map((entry) => JSON.stringify(entry));
|
|
3852
3857
|
await fs10.writeFile(file, lines.length > 0 ? lines.join("\n") + "\n" : "");
|
|
3853
3858
|
}
|
|
3854
|
-
|
|
3859
|
+
async function appendHistoryLine(file, entry) {
|
|
3860
|
+
const trimmed = entry.replace(/\n+$/, "");
|
|
3861
|
+
if (trimmed.length === 0) {
|
|
3862
|
+
return;
|
|
3863
|
+
}
|
|
3864
|
+
await fs10.mkdir(path6.dirname(file), { recursive: true });
|
|
3865
|
+
await fs10.appendFile(file, JSON.stringify(trimmed) + "\n", {
|
|
3866
|
+
encoding: "utf8"
|
|
3867
|
+
});
|
|
3868
|
+
}
|
|
3869
|
+
function buildCombinedHistory(global, session) {
|
|
3870
|
+
if (session.length === 0) {
|
|
3871
|
+
return [...global];
|
|
3872
|
+
}
|
|
3873
|
+
const sessionSet = new Set(session);
|
|
3874
|
+
const filteredGlobal = global.filter((e) => !sessionSet.has(e));
|
|
3875
|
+
return [...filteredGlobal, ...session];
|
|
3876
|
+
}
|
|
3877
|
+
var HISTORY_CAP, GLOBAL_HISTORY_CAP;
|
|
3855
3878
|
var init_history = __esm({
|
|
3856
3879
|
"src/tui/history.ts"() {
|
|
3857
3880
|
"use strict";
|
|
3858
3881
|
HISTORY_CAP = 500;
|
|
3882
|
+
GLOBAL_HISTORY_CAP = 2e3;
|
|
3859
3883
|
}
|
|
3860
3884
|
});
|
|
3861
3885
|
|
|
@@ -6134,7 +6158,7 @@ function writeStyled(term, text, style) {
|
|
|
6134
6158
|
term(text);
|
|
6135
6159
|
return;
|
|
6136
6160
|
case "thought":
|
|
6137
|
-
term.dim.
|
|
6161
|
+
term.brightBlack.dim.noFormat(text);
|
|
6138
6162
|
return;
|
|
6139
6163
|
case "tool":
|
|
6140
6164
|
term.brightBlue.noFormat(text);
|
|
@@ -7353,24 +7377,22 @@ uncaught: ${err.stack ?? err.message}
|
|
|
7353
7377
|
this.writeProgressIndicator(this.banner.status === "busy" ? 3 : 0);
|
|
7354
7378
|
this.syncedPartialRepaint(() => this.drawBanner());
|
|
7355
7379
|
}
|
|
7356
|
-
// Wrap a partial repaint (banner-only,
|
|
7380
|
+
// Wrap a partial repaint (banner-only, prompt-only, etc.) in a
|
|
7357
7381
|
// synchronized-output bracket so the row swap is atomic on terminals
|
|
7358
|
-
// that support DEC 2026
|
|
7359
|
-
//
|
|
7360
|
-
//
|
|
7361
|
-
//
|
|
7362
|
-
//
|
|
7382
|
+
// that support DEC 2026. Cursor movement (moveTo) is buffered inside
|
|
7383
|
+
// BSU/ESU, so the cursor appears at its final placeCursor position
|
|
7384
|
+
// without visibly visiting intermediate rows. We intentionally do NOT
|
|
7385
|
+
// hide the cursor here: ?25l/h (cursor visibility) is terminal *state*
|
|
7386
|
+
// applied immediately rather than buffered, so hiding inside a BSU/ESU
|
|
7387
|
+
// block causes a visible blink (cursor disappears → frame commits →
|
|
7388
|
+
// cursor reappears) on every banner tick — worse than any skitter.
|
|
7363
7389
|
syncedPartialRepaint(paint) {
|
|
7364
7390
|
if (!this.started) {
|
|
7365
7391
|
return;
|
|
7366
7392
|
}
|
|
7367
7393
|
withSync(() => {
|
|
7368
|
-
this.term.hideCursor();
|
|
7369
7394
|
paint();
|
|
7370
7395
|
this.placeCursor();
|
|
7371
|
-
if (this.permissionPrompt || this.confirmPrompt || this.helpPrompt) {
|
|
7372
|
-
this.term.hideCursor(false);
|
|
7373
|
-
}
|
|
7374
7396
|
});
|
|
7375
7397
|
}
|
|
7376
7398
|
currentModeId() {
|
|
@@ -8462,7 +8484,7 @@ uncaught: ${err.stack ?? err.message}
|
|
|
8462
8484
|
const body = ` ${marker} ${i + 1}. ${truncate(opt.label, w - 8)}`;
|
|
8463
8485
|
writeRow(`perm|o|${w}|${i}|${isSel ? "1" : "0"}|${opt.label}`, () => {
|
|
8464
8486
|
if (isSel) {
|
|
8465
|
-
this.term.
|
|
8487
|
+
this.term.brightYellow(body);
|
|
8466
8488
|
} else {
|
|
8467
8489
|
this.term.dim(body);
|
|
8468
8490
|
}
|
|
@@ -8891,14 +8913,11 @@ async function pickSession(term, opts) {
|
|
|
8891
8913
|
const composerBoxInner = () => Math.max(2, termWidth - 2);
|
|
8892
8914
|
const paintComposerTopBorder = () => {
|
|
8893
8915
|
const inner = composerBoxInner();
|
|
8894
|
-
const focused = selectedIdx === 0;
|
|
8895
8916
|
const titleFragment = `\u2500 ${composerTitle} `;
|
|
8896
8917
|
const dashCount = Math.max(1, inner - titleFragment.length);
|
|
8897
8918
|
const dashes = "\u2500".repeat(dashCount);
|
|
8898
|
-
if (
|
|
8899
|
-
term.
|
|
8900
|
-
term.brightCyan.bold.noFormat(titleFragment);
|
|
8901
|
-
term.brightCyan.noFormat(`${dashes}\u256E`);
|
|
8919
|
+
if (selectedIdx === 0) {
|
|
8920
|
+
term.brightBlue.noFormat(`\u256D${titleFragment}${dashes}\u256E`);
|
|
8902
8921
|
} else {
|
|
8903
8922
|
term.dim.noFormat(`\u256D${titleFragment}${dashes}\u256E`);
|
|
8904
8923
|
}
|
|
@@ -8907,15 +8926,13 @@ async function pickSession(term, opts) {
|
|
|
8907
8926
|
const inner = composerBoxInner();
|
|
8908
8927
|
const dashes = "\u2500".repeat(inner);
|
|
8909
8928
|
if (selectedIdx === 0) {
|
|
8910
|
-
term.
|
|
8929
|
+
term.brightBlue.noFormat(`\u2570${dashes}\u256F`);
|
|
8911
8930
|
} else {
|
|
8912
8931
|
term.dim.noFormat(`\u2570${dashes}\u256F`);
|
|
8913
8932
|
}
|
|
8914
8933
|
};
|
|
8915
8934
|
const paintComposerBodyRow = (visualIdx) => {
|
|
8916
8935
|
const inner = composerBoxInner();
|
|
8917
|
-
const sideStyle = selectedIdx === 0 ? term.brightCyan : term.dim;
|
|
8918
|
-
sideStyle.noFormat("\u2502");
|
|
8919
8936
|
const vr = composerVisualRows[visualIdx];
|
|
8920
8937
|
let slice = "";
|
|
8921
8938
|
if (vr) {
|
|
@@ -8924,13 +8941,17 @@ async function pickSession(term, opts) {
|
|
|
8924
8941
|
vr.endCol
|
|
8925
8942
|
);
|
|
8926
8943
|
}
|
|
8927
|
-
term.noFormat(" ");
|
|
8928
|
-
term.noFormat(slice);
|
|
8929
8944
|
const padWidth = Math.max(0, inner - 1 - slice.length);
|
|
8930
|
-
|
|
8931
|
-
|
|
8945
|
+
const pad = " ".repeat(padWidth);
|
|
8946
|
+
if (selectedIdx === 0) {
|
|
8947
|
+
term.brightBlue.noFormat("\u2502");
|
|
8948
|
+
term.noFormat(` ${slice}${pad}`);
|
|
8949
|
+
term.brightBlue.noFormat("\u2502");
|
|
8950
|
+
} else {
|
|
8951
|
+
term.dim.noFormat("\u2502");
|
|
8952
|
+
term.noFormat(` ${slice}${pad}`);
|
|
8953
|
+
term.dim.noFormat("\u2502");
|
|
8932
8954
|
}
|
|
8933
|
-
sideStyle.noFormat("\u2502");
|
|
8934
8955
|
};
|
|
8935
8956
|
const paintSessionRow = (sessionIdx) => {
|
|
8936
8957
|
const label = sessionLines[sessionIdx] ?? "";
|
|
@@ -11020,11 +11041,13 @@ function toolIconStyle(status) {
|
|
|
11020
11041
|
}
|
|
11021
11042
|
function formatPlan(event) {
|
|
11022
11043
|
const stopped = event.stopped === true;
|
|
11044
|
+
const amended = event.amended === true;
|
|
11045
|
+
const stoppedStyle = amended ? "tool-status-cancelled" : "tool-status-fail";
|
|
11023
11046
|
if (event.entries.length === 0) {
|
|
11024
11047
|
return [
|
|
11025
11048
|
{
|
|
11026
11049
|
prefix: "\u25A3 ",
|
|
11027
|
-
prefixStyle: stopped ?
|
|
11050
|
+
prefixStyle: stopped ? stoppedStyle : "plan",
|
|
11028
11051
|
body: "(empty plan)",
|
|
11029
11052
|
bodyStyle: "dim"
|
|
11030
11053
|
}
|
|
@@ -11033,7 +11056,7 @@ function formatPlan(event) {
|
|
|
11033
11056
|
const allComplete = event.entries.every(
|
|
11034
11057
|
(e) => (e.status ?? "pending") === "completed"
|
|
11035
11058
|
);
|
|
11036
|
-
const headerStyle = allComplete ? "plan-done" : stopped ?
|
|
11059
|
+
const headerStyle = allComplete ? "plan-done" : stopped ? stoppedStyle : "plan";
|
|
11037
11060
|
const lines = [
|
|
11038
11061
|
{
|
|
11039
11062
|
prefix: "\u25A3 ",
|
|
@@ -11657,9 +11680,35 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
11657
11680
|
initialQueue = hydraMeta.queue;
|
|
11658
11681
|
}
|
|
11659
11682
|
const historyFile = paths.tuiHistoryFile(resolvedSessionId);
|
|
11683
|
+
const globalHistoryFile = paths.globalTuiHistoryFile();
|
|
11660
11684
|
let history = await loadHistory(historyFile).catch(() => []);
|
|
11661
|
-
|
|
11685
|
+
let globalHistory = await loadHistory(globalHistoryFile).catch(() => []);
|
|
11686
|
+
if (globalHistory.length > GLOBAL_HISTORY_CAP) {
|
|
11687
|
+
globalHistory = globalHistory.slice(globalHistory.length - GLOBAL_HISTORY_CAP);
|
|
11688
|
+
}
|
|
11689
|
+
const dispatcher = new InputDispatcher({
|
|
11690
|
+
history: buildCombinedHistory(globalHistory, history)
|
|
11691
|
+
});
|
|
11662
11692
|
dispatcherRef = dispatcher;
|
|
11693
|
+
const recordHistoryEntry = (entry) => {
|
|
11694
|
+
const trimmed = entry.replace(/\n+$/, "");
|
|
11695
|
+
if (trimmed.length === 0) {
|
|
11696
|
+
return;
|
|
11697
|
+
}
|
|
11698
|
+
const nextSession = appendEntry(history, trimmed);
|
|
11699
|
+
const sessionChanged = nextSession !== history;
|
|
11700
|
+
history = nextSession;
|
|
11701
|
+
const nextGlobal = appendEntry(globalHistory, trimmed, GLOBAL_HISTORY_CAP);
|
|
11702
|
+
const globalChanged = nextGlobal !== globalHistory;
|
|
11703
|
+
globalHistory = nextGlobal;
|
|
11704
|
+
dispatcher.setHistory(buildCombinedHistory(globalHistory, history));
|
|
11705
|
+
if (sessionChanged) {
|
|
11706
|
+
saveHistory(historyFile, history).catch(() => void 0);
|
|
11707
|
+
}
|
|
11708
|
+
if (globalChanged) {
|
|
11709
|
+
appendHistoryLine(globalHistoryFile, trimmed).catch(() => void 0);
|
|
11710
|
+
}
|
|
11711
|
+
};
|
|
11663
11712
|
if (pendingTurns > 0) {
|
|
11664
11713
|
dispatcher.setTurnRunning(true);
|
|
11665
11714
|
}
|
|
@@ -12036,8 +12085,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
12036
12085
|
}
|
|
12037
12086
|
const pendingDraft = dispatcher.state().buffer.join("\n");
|
|
12038
12087
|
if (pendingDraft.replace(/\s+$/, "").length > 0) {
|
|
12039
|
-
|
|
12040
|
-
dispatcher.setHistory(history);
|
|
12088
|
+
recordHistoryEntry(pendingDraft);
|
|
12041
12089
|
}
|
|
12042
12090
|
screen.pauseRepaint();
|
|
12043
12091
|
screen.stop({ keepFullscreen: true });
|
|
@@ -12416,9 +12464,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
12416
12464
|
if (handleBuiltinCommand(text)) {
|
|
12417
12465
|
return;
|
|
12418
12466
|
}
|
|
12419
|
-
|
|
12420
|
-
dispatcher.setHistory(history);
|
|
12421
|
-
saveHistory(historyFile, history).catch(() => void 0);
|
|
12467
|
+
recordHistoryEntry(text);
|
|
12422
12468
|
void runPrompt(text, attachments);
|
|
12423
12469
|
};
|
|
12424
12470
|
const amendPrompt = (text, attachments) => {
|
|
@@ -12426,9 +12472,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
12426
12472
|
if (handleBuiltinCommand(text)) {
|
|
12427
12473
|
return;
|
|
12428
12474
|
}
|
|
12429
|
-
|
|
12430
|
-
dispatcher.setHistory(history);
|
|
12431
|
-
saveHistory(historyFile, history).catch(() => void 0);
|
|
12475
|
+
recordHistoryEntry(text);
|
|
12432
12476
|
if (!daemonSupportsAmend || currentHeadMessageId === void 0) {
|
|
12433
12477
|
void runPrompt(text, attachments);
|
|
12434
12478
|
return;
|
|
@@ -12788,16 +12832,18 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
12788
12832
|
const end = toolsBlockEndedAt ?? Date.now();
|
|
12789
12833
|
const elapsed = end - toolsBlockStartedAt;
|
|
12790
12834
|
const stoppedReason = !inProgress && toolsBlockStopReason !== null && toolsBlockStopReason !== "end_turn" ? toolsBlockStopReason : null;
|
|
12835
|
+
const isAmended = stoppedReason === "amended";
|
|
12836
|
+
const stoppedLabel = isAmended ? `amended \xB7 ${formatElapsed(elapsed)}` : `stopped (${stoppedReason}) \xB7 ${formatElapsed(elapsed)}`;
|
|
12791
12837
|
let summary;
|
|
12792
12838
|
if (total === 0) {
|
|
12793
12839
|
if (stoppedReason !== null) {
|
|
12794
|
-
summary =
|
|
12840
|
+
summary = stoppedLabel;
|
|
12795
12841
|
} else {
|
|
12796
12842
|
summary = inProgress ? `thinking \xB7 ${formatElapsed(elapsed)}` : `thought \xB7 ${formatElapsed(elapsed)}`;
|
|
12797
12843
|
}
|
|
12798
12844
|
} else {
|
|
12799
12845
|
const noun = total === 1 ? "tool" : "tools";
|
|
12800
|
-
const timing = stoppedReason !== null ?
|
|
12846
|
+
const timing = stoppedReason !== null ? stoppedLabel : inProgress ? formatElapsed(elapsed) : `took ${formatElapsed(elapsed)}`;
|
|
12801
12847
|
const parts = [`${total} ${noun}`, timing];
|
|
12802
12848
|
if (inProgress) {
|
|
12803
12849
|
if (hidden > 0) {
|
|
@@ -12809,8 +12855,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
12809
12855
|
summary = parts.join(" \xB7 ");
|
|
12810
12856
|
}
|
|
12811
12857
|
const pureThinking = total === 0 && inProgress;
|
|
12812
|
-
const
|
|
12813
|
-
const
|
|
12858
|
+
const stoppedHeaderStyle = isAmended ? "tool-status-cancelled" : "tool-status-fail";
|
|
12859
|
+
const frozenStyle = stoppedReason !== null ? stoppedHeaderStyle : "tool";
|
|
12860
|
+
const frozenBodyStyle = stoppedReason !== null ? stoppedHeaderStyle : "dim";
|
|
12814
12861
|
const lines = [
|
|
12815
12862
|
{
|
|
12816
12863
|
prefix: "\u2699 ",
|
|
@@ -12986,7 +13033,11 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
12986
13033
|
effectiveStopReason = "error";
|
|
12987
13034
|
}
|
|
12988
13035
|
if (lastPlanEvent !== null && effectiveStopReason !== void 0 && effectiveStopReason !== "end_turn") {
|
|
12989
|
-
const lines = formatEvent({
|
|
13036
|
+
const lines = formatEvent({
|
|
13037
|
+
...lastPlanEvent,
|
|
13038
|
+
stopped: true,
|
|
13039
|
+
amended: event.amended === true
|
|
13040
|
+
});
|
|
12990
13041
|
if (lines.length > 0) {
|
|
12991
13042
|
screen.upsertLines("plan", lines);
|
|
12992
13043
|
}
|
|
@@ -12998,7 +13049,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs) {
|
|
|
12998
13049
|
toolsBlockStopReason = effectiveStopReason ?? null;
|
|
12999
13050
|
renderToolsBlock();
|
|
13000
13051
|
screen.clearKey("tools");
|
|
13001
|
-
} else if (effectiveStopReason !== void 0 && effectiveStopReason !== "end_turn") {
|
|
13052
|
+
} else if (effectiveStopReason !== void 0 && effectiveStopReason !== "end_turn" && effectiveStopReason !== "amended") {
|
|
13002
13053
|
screen.appendLines([
|
|
13003
13054
|
{
|
|
13004
13055
|
prefix: "\u26A0 ",
|
|
@@ -14694,6 +14745,12 @@ var SessionRecord = z4.object({
|
|
|
14694
14745
|
agentCommands: z4.array(PersistedAgentCommand).optional(),
|
|
14695
14746
|
agentModes: z4.array(PersistedAgentMode).optional(),
|
|
14696
14747
|
agentModels: z4.array(PersistedAgentModel).optional(),
|
|
14748
|
+
// One-shot flag set when `hydra agent sync` mints a row from an
|
|
14749
|
+
// agent-side session/list entry: signals that the first resurrect
|
|
14750
|
+
// should *keep* the agent's session/load replay (instead of draining
|
|
14751
|
+
// it) so the local history.jsonl gets populated from the agent's
|
|
14752
|
+
// memory. Cleared after that first resurrect completes.
|
|
14753
|
+
pendingHistorySync: z4.boolean().optional(),
|
|
14697
14754
|
createdAt: z4.string(),
|
|
14698
14755
|
updatedAt: z4.string()
|
|
14699
14756
|
});
|
|
@@ -14814,6 +14871,7 @@ function recordFromMemorySession(args) {
|
|
|
14814
14871
|
agentCommands: args.agentCommands,
|
|
14815
14872
|
agentModes: args.agentModes,
|
|
14816
14873
|
agentModels: args.agentModels,
|
|
14874
|
+
pendingHistorySync: args.pendingHistorySync,
|
|
14817
14875
|
createdAt: args.createdAt ?? now,
|
|
14818
14876
|
updatedAt: args.updatedAt ?? now
|
|
14819
14877
|
};
|
|
@@ -15119,7 +15177,13 @@ var SessionManager = class {
|
|
|
15119
15177
|
await agent.kill().catch(() => void 0);
|
|
15120
15178
|
return this.doResurrectFromImport(params);
|
|
15121
15179
|
}
|
|
15122
|
-
|
|
15180
|
+
if (params.pendingHistorySync === true) {
|
|
15181
|
+
void this.clearPendingHistorySync(params.hydraSessionId).catch(
|
|
15182
|
+
() => void 0
|
|
15183
|
+
);
|
|
15184
|
+
} else {
|
|
15185
|
+
agent.connection.drainBuffered("session/update");
|
|
15186
|
+
}
|
|
15123
15187
|
const session = new Session({
|
|
15124
15188
|
sessionId: params.hydraSessionId,
|
|
15125
15189
|
cwd: params.cwd,
|
|
@@ -15209,6 +15273,133 @@ var SessionManager = class {
|
|
|
15209
15273
|
}
|
|
15210
15274
|
return os3.homedir();
|
|
15211
15275
|
}
|
|
15276
|
+
// Pull every session the agent itself remembers (across all cwds) and
|
|
15277
|
+
// persist a cold hydra record for each one we don't already track.
|
|
15278
|
+
// Used by `hydra agent sync <id>` to surface sessions created outside
|
|
15279
|
+
// hydra — or by other tools — in `hydra session list` so the picker
|
|
15280
|
+
// can resurrect them. Spawns a throwaway agent process for the
|
|
15281
|
+
// initialize + session/list pair, then kills it. Records are minted
|
|
15282
|
+
// with pendingHistorySync:true so the first resurrect records the
|
|
15283
|
+
// agent's session/load replay into history.jsonl rather than dropping
|
|
15284
|
+
// it.
|
|
15285
|
+
async syncFromAgent(agentId) {
|
|
15286
|
+
const agentDef = await this.registry.getAgent(agentId);
|
|
15287
|
+
if (!agentDef) {
|
|
15288
|
+
const err = new Error(
|
|
15289
|
+
`agent ${agentId} not found in registry`
|
|
15290
|
+
);
|
|
15291
|
+
err.code = JsonRpcErrorCodes.AgentNotInstalled;
|
|
15292
|
+
throw err;
|
|
15293
|
+
}
|
|
15294
|
+
const plan = await planSpawn(agentDef, [], {
|
|
15295
|
+
npmRegistry: this.npmRegistry
|
|
15296
|
+
});
|
|
15297
|
+
const agent = this.spawner({
|
|
15298
|
+
agentId,
|
|
15299
|
+
cwd: os3.homedir(),
|
|
15300
|
+
plan
|
|
15301
|
+
});
|
|
15302
|
+
let initResult;
|
|
15303
|
+
try {
|
|
15304
|
+
initResult = await agent.connection.request(
|
|
15305
|
+
"initialize",
|
|
15306
|
+
{
|
|
15307
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
15308
|
+
clientCapabilities: {},
|
|
15309
|
+
clientInfo: { name: "hydra", version: HYDRA_VERSION }
|
|
15310
|
+
}
|
|
15311
|
+
);
|
|
15312
|
+
} catch (err) {
|
|
15313
|
+
await agent.kill().catch(() => void 0);
|
|
15314
|
+
throw err;
|
|
15315
|
+
}
|
|
15316
|
+
const caps = initResult.agentCapabilities ?? {};
|
|
15317
|
+
if (caps.sessionCapabilities?.list === void 0) {
|
|
15318
|
+
await agent.kill().catch(() => void 0);
|
|
15319
|
+
throw new Error(
|
|
15320
|
+
`agent ${agentId} does not advertise sessionCapabilities.list; cannot sync`
|
|
15321
|
+
);
|
|
15322
|
+
}
|
|
15323
|
+
let entries;
|
|
15324
|
+
try {
|
|
15325
|
+
entries = await this.collectAgentSessions(agent);
|
|
15326
|
+
} catch (err) {
|
|
15327
|
+
await agent.kill().catch(() => void 0);
|
|
15328
|
+
throw err;
|
|
15329
|
+
}
|
|
15330
|
+
await agent.kill().catch(() => void 0);
|
|
15331
|
+
const existing = /* @__PURE__ */ new Set();
|
|
15332
|
+
for (const live of this.sessions.values()) {
|
|
15333
|
+
existing.add(`${live.agentId}::${live.upstreamSessionId}`);
|
|
15334
|
+
}
|
|
15335
|
+
const stored = await this.store.list().catch(() => []);
|
|
15336
|
+
for (const rec of stored) {
|
|
15337
|
+
existing.add(`${rec.agentId}::${rec.upstreamSessionId}`);
|
|
15338
|
+
}
|
|
15339
|
+
const synced = [];
|
|
15340
|
+
let skipped = 0;
|
|
15341
|
+
for (const entry of entries) {
|
|
15342
|
+
const dedupeKey = `${agentId}::${entry.sessionId}`;
|
|
15343
|
+
if (existing.has(dedupeKey)) {
|
|
15344
|
+
skipped += 1;
|
|
15345
|
+
continue;
|
|
15346
|
+
}
|
|
15347
|
+
existing.add(dedupeKey);
|
|
15348
|
+
const newId = `${HYDRA_SESSION_PREFIX}${generateRawSessionId()}`;
|
|
15349
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15350
|
+
const ts = entry.updatedAt ?? now;
|
|
15351
|
+
const recordArgs = {
|
|
15352
|
+
sessionId: newId,
|
|
15353
|
+
lineageId: generateLineageId(),
|
|
15354
|
+
upstreamSessionId: entry.sessionId,
|
|
15355
|
+
agentId,
|
|
15356
|
+
cwd: entry.cwd,
|
|
15357
|
+
pendingHistorySync: true,
|
|
15358
|
+
createdAt: ts,
|
|
15359
|
+
updatedAt: ts
|
|
15360
|
+
};
|
|
15361
|
+
if (entry.title !== void 0) {
|
|
15362
|
+
recordArgs.title = entry.title;
|
|
15363
|
+
}
|
|
15364
|
+
const record = recordFromMemorySession(recordArgs);
|
|
15365
|
+
await this.store.write(record);
|
|
15366
|
+
synced.push({ version: 1, ...record });
|
|
15367
|
+
}
|
|
15368
|
+
return { synced, skipped };
|
|
15369
|
+
}
|
|
15370
|
+
// Paginate the agent's session/list, threading nextCursor until the
|
|
15371
|
+
// agent stops returning one. Each entry the spec guarantees has
|
|
15372
|
+
// { sessionId, cwd }; title and updatedAt are optional.
|
|
15373
|
+
async collectAgentSessions(agent) {
|
|
15374
|
+
const out = [];
|
|
15375
|
+
let cursor;
|
|
15376
|
+
for (let page = 0; page < 100; page += 1) {
|
|
15377
|
+
const params = {};
|
|
15378
|
+
if (cursor !== void 0) {
|
|
15379
|
+
params.cursor = cursor;
|
|
15380
|
+
}
|
|
15381
|
+
const result = await agent.connection.request("session/list", params);
|
|
15382
|
+
const rows = Array.isArray(result.sessions) ? result.sessions : [];
|
|
15383
|
+
for (const row of rows) {
|
|
15384
|
+
if (typeof row.sessionId !== "string" || typeof row.cwd !== "string") {
|
|
15385
|
+
continue;
|
|
15386
|
+
}
|
|
15387
|
+
const entry = { sessionId: row.sessionId, cwd: row.cwd };
|
|
15388
|
+
if (typeof row.title === "string") {
|
|
15389
|
+
entry.title = row.title;
|
|
15390
|
+
}
|
|
15391
|
+
if (typeof row.updatedAt === "string") {
|
|
15392
|
+
entry.updatedAt = row.updatedAt;
|
|
15393
|
+
}
|
|
15394
|
+
out.push(entry);
|
|
15395
|
+
}
|
|
15396
|
+
if (typeof result.nextCursor !== "string" || result.nextCursor.length === 0) {
|
|
15397
|
+
break;
|
|
15398
|
+
}
|
|
15399
|
+
cursor = result.nextCursor;
|
|
15400
|
+
}
|
|
15401
|
+
return out;
|
|
15402
|
+
}
|
|
15212
15403
|
// Bootstrap a fresh agent process: registry resolve → spawn → initialize
|
|
15213
15404
|
// → session/new. Shared by create() and the /hydra agent path so both
|
|
15214
15405
|
// go through the same env / capabilities / error-handling.
|
|
@@ -15401,9 +15592,21 @@ var SessionManager = class {
|
|
|
15401
15592
|
agentCommands: record.agentCommands,
|
|
15402
15593
|
agentModes: record.agentModes,
|
|
15403
15594
|
agentModels: record.agentModels,
|
|
15404
|
-
createdAt: record.createdAt
|
|
15595
|
+
createdAt: record.createdAt,
|
|
15596
|
+
pendingHistorySync: record.pendingHistorySync
|
|
15405
15597
|
};
|
|
15406
15598
|
}
|
|
15599
|
+
async clearPendingHistorySync(sessionId) {
|
|
15600
|
+
await this.enqueueMetaWrite(sessionId, async () => {
|
|
15601
|
+
const record = await this.store.read(sessionId);
|
|
15602
|
+
if (!record || record.pendingHistorySync !== true) {
|
|
15603
|
+
return;
|
|
15604
|
+
}
|
|
15605
|
+
const next = { ...record };
|
|
15606
|
+
delete next.pendingHistorySync;
|
|
15607
|
+
await this.store.write(next);
|
|
15608
|
+
});
|
|
15609
|
+
}
|
|
15407
15610
|
// Best-effort: peek at the persisted history's first prompt and use
|
|
15408
15611
|
// its first line (capped to 200 chars) as a session title. Returns
|
|
15409
15612
|
// undefined if no usable prompt is found or any I/O fails.
|
|
@@ -17452,7 +17655,8 @@ function registerSessionRoutes(app, manager, defaults) {
|
|
|
17452
17655
|
}
|
|
17453
17656
|
|
|
17454
17657
|
// src/daemon/routes/agents.ts
|
|
17455
|
-
|
|
17658
|
+
init_types();
|
|
17659
|
+
function registerAgentRoutes(app, registry, manager, opts = {}) {
|
|
17456
17660
|
app.get("/v1/agents", async () => {
|
|
17457
17661
|
const doc = await registry.load();
|
|
17458
17662
|
return {
|
|
@@ -17473,6 +17677,61 @@ function registerAgentRoutes(app, registry) {
|
|
|
17473
17677
|
const doc = await registry.refresh();
|
|
17474
17678
|
return { version: doc.version, agentCount: doc.agents.length };
|
|
17475
17679
|
});
|
|
17680
|
+
app.post("/v1/agents/:id/install", async (request, reply) => {
|
|
17681
|
+
const id = request.params.id;
|
|
17682
|
+
const agent = await registry.getAgent(id);
|
|
17683
|
+
if (!agent) {
|
|
17684
|
+
reply.code(404).send({ error: `agent ${id} not found in registry` });
|
|
17685
|
+
return;
|
|
17686
|
+
}
|
|
17687
|
+
if (agent.distribution.uvx && !agent.distribution.npx && !agent.distribution.binary) {
|
|
17688
|
+
reply.send({
|
|
17689
|
+
agentId: agent.id,
|
|
17690
|
+
version: agent.version ?? "current",
|
|
17691
|
+
distribution: "uvx",
|
|
17692
|
+
installed: false,
|
|
17693
|
+
message: "uvx agents resolve on first run; nothing to pre-install."
|
|
17694
|
+
});
|
|
17695
|
+
return;
|
|
17696
|
+
}
|
|
17697
|
+
try {
|
|
17698
|
+
const plan = await planSpawn(agent, [], { npmRegistry: opts.npmRegistry });
|
|
17699
|
+
const distribution = agent.distribution.npx ? "npx" : agent.distribution.binary ? "binary" : "unknown";
|
|
17700
|
+
reply.send({
|
|
17701
|
+
agentId: agent.id,
|
|
17702
|
+
version: plan.version,
|
|
17703
|
+
distribution,
|
|
17704
|
+
installed: true,
|
|
17705
|
+
command: plan.command
|
|
17706
|
+
});
|
|
17707
|
+
} catch (err) {
|
|
17708
|
+
reply.code(500).send({ error: err.message });
|
|
17709
|
+
}
|
|
17710
|
+
});
|
|
17711
|
+
app.post("/v1/agents/:id/sync", async (request, reply) => {
|
|
17712
|
+
const agentId = request.params.id;
|
|
17713
|
+
try {
|
|
17714
|
+
const { synced, skipped } = await manager.syncFromAgent(agentId);
|
|
17715
|
+
return {
|
|
17716
|
+
synced: synced.map((r) => ({
|
|
17717
|
+
sessionId: r.sessionId,
|
|
17718
|
+
upstreamSessionId: r.upstreamSessionId,
|
|
17719
|
+
agentId: r.agentId,
|
|
17720
|
+
cwd: r.cwd,
|
|
17721
|
+
title: r.title,
|
|
17722
|
+
updatedAt: r.updatedAt
|
|
17723
|
+
})),
|
|
17724
|
+
skipped
|
|
17725
|
+
};
|
|
17726
|
+
} catch (err) {
|
|
17727
|
+
const e = err;
|
|
17728
|
+
if (e.code === JsonRpcErrorCodes.AgentNotInstalled) {
|
|
17729
|
+
reply.code(404).send({ error: e.message });
|
|
17730
|
+
return;
|
|
17731
|
+
}
|
|
17732
|
+
reply.code(409).send({ error: e.message });
|
|
17733
|
+
}
|
|
17734
|
+
});
|
|
17476
17735
|
}
|
|
17477
17736
|
|
|
17478
17737
|
// src/daemon/routes/health.ts
|
|
@@ -18543,7 +18802,7 @@ async function startDaemon(config, serviceToken) {
|
|
|
18543
18802
|
agentId: config.defaultAgent,
|
|
18544
18803
|
cwd: config.defaultCwd
|
|
18545
18804
|
});
|
|
18546
|
-
registerAgentRoutes(app, registry);
|
|
18805
|
+
registerAgentRoutes(app, registry, manager, { npmRegistry: config.npmRegistry });
|
|
18547
18806
|
registerExtensionRoutes(app, extensions);
|
|
18548
18807
|
registerConfigRoutes(app, {
|
|
18549
18808
|
defaultAgent: config.defaultAgent,
|
|
@@ -19887,6 +20146,136 @@ async function runAgentsList() {
|
|
|
19887
20146
|
Registry version: ${body.version}
|
|
19888
20147
|
`);
|
|
19889
20148
|
}
|
|
20149
|
+
async function runAgentsInstall(agentId) {
|
|
20150
|
+
if (!agentId) {
|
|
20151
|
+
process.stderr.write("Usage: hydra-acp agent install <agent-id>\n");
|
|
20152
|
+
process.exit(2);
|
|
20153
|
+
return;
|
|
20154
|
+
}
|
|
20155
|
+
const config = await loadConfig();
|
|
20156
|
+
const serviceToken = await loadServiceToken();
|
|
20157
|
+
const baseUrl = httpBase(config.daemon.host, config.daemon.port, !!config.daemon.tls);
|
|
20158
|
+
process.stdout.write(`Installing ${agentId}\u2026
|
|
20159
|
+
`);
|
|
20160
|
+
let body;
|
|
20161
|
+
try {
|
|
20162
|
+
const r = await fetch(
|
|
20163
|
+
`${baseUrl}/v1/agents/${encodeURIComponent(agentId)}/install`,
|
|
20164
|
+
{
|
|
20165
|
+
method: "POST",
|
|
20166
|
+
headers: { Authorization: `Bearer ${serviceToken}` }
|
|
20167
|
+
}
|
|
20168
|
+
);
|
|
20169
|
+
if (!r.ok) {
|
|
20170
|
+
let detail = `HTTP ${r.status}`;
|
|
20171
|
+
try {
|
|
20172
|
+
const j = await r.json();
|
|
20173
|
+
if (j.error) {
|
|
20174
|
+
detail = j.error;
|
|
20175
|
+
}
|
|
20176
|
+
} catch {
|
|
20177
|
+
}
|
|
20178
|
+
process.stderr.write(`hydra agent install ${agentId}: ${detail}
|
|
20179
|
+
`);
|
|
20180
|
+
process.exit(1);
|
|
20181
|
+
}
|
|
20182
|
+
body = await r.json();
|
|
20183
|
+
} catch (err) {
|
|
20184
|
+
process.stderr.write(
|
|
20185
|
+
`Could not reach daemon at ${baseUrl}: ${err.message}
|
|
20186
|
+
`
|
|
20187
|
+
);
|
|
20188
|
+
process.exit(1);
|
|
20189
|
+
return;
|
|
20190
|
+
}
|
|
20191
|
+
if (!body.installed) {
|
|
20192
|
+
process.stdout.write(
|
|
20193
|
+
`${body.agentId} (${body.version}, ${body.distribution}): ${body.message ?? "nothing to install"}
|
|
20194
|
+
`
|
|
20195
|
+
);
|
|
20196
|
+
return;
|
|
20197
|
+
}
|
|
20198
|
+
process.stdout.write(
|
|
20199
|
+
`Installed ${body.agentId} (${body.version}, ${body.distribution})
|
|
20200
|
+
`
|
|
20201
|
+
);
|
|
20202
|
+
if (body.command) {
|
|
20203
|
+
process.stdout.write(` \u2192 ${body.command}
|
|
20204
|
+
`);
|
|
20205
|
+
}
|
|
20206
|
+
}
|
|
20207
|
+
async function runAgentsSync(agentId) {
|
|
20208
|
+
if (!agentId) {
|
|
20209
|
+
process.stderr.write("Usage: hydra-acp agent sync <agent-id>\n");
|
|
20210
|
+
process.exit(2);
|
|
20211
|
+
return;
|
|
20212
|
+
}
|
|
20213
|
+
const config = await loadConfig();
|
|
20214
|
+
const serviceToken = await loadServiceToken();
|
|
20215
|
+
const baseUrl = httpBase(config.daemon.host, config.daemon.port, !!config.daemon.tls);
|
|
20216
|
+
let body;
|
|
20217
|
+
try {
|
|
20218
|
+
const r = await fetch(`${baseUrl}/v1/agents/${encodeURIComponent(agentId)}/sync`, {
|
|
20219
|
+
method: "POST",
|
|
20220
|
+
headers: { Authorization: `Bearer ${serviceToken}` }
|
|
20221
|
+
});
|
|
20222
|
+
if (!r.ok) {
|
|
20223
|
+
let detail = `HTTP ${r.status}`;
|
|
20224
|
+
try {
|
|
20225
|
+
const j = await r.json();
|
|
20226
|
+
if (j.error) {
|
|
20227
|
+
detail = j.error;
|
|
20228
|
+
}
|
|
20229
|
+
} catch {
|
|
20230
|
+
}
|
|
20231
|
+
process.stderr.write(`hydra agent sync ${agentId}: ${detail}
|
|
20232
|
+
`);
|
|
20233
|
+
process.exit(1);
|
|
20234
|
+
}
|
|
20235
|
+
body = await r.json();
|
|
20236
|
+
} catch (err) {
|
|
20237
|
+
process.stderr.write(
|
|
20238
|
+
`Could not reach daemon at ${baseUrl}: ${err.message}
|
|
20239
|
+
`
|
|
20240
|
+
);
|
|
20241
|
+
process.exit(1);
|
|
20242
|
+
return;
|
|
20243
|
+
}
|
|
20244
|
+
if (body.synced.length === 0) {
|
|
20245
|
+
process.stdout.write(
|
|
20246
|
+
`Nothing new to sync (${body.skipped} already tracked).
|
|
20247
|
+
`
|
|
20248
|
+
);
|
|
20249
|
+
return;
|
|
20250
|
+
}
|
|
20251
|
+
const rows = body.synced.map((s) => ({
|
|
20252
|
+
id: s.sessionId,
|
|
20253
|
+
upstream: s.upstreamSessionId,
|
|
20254
|
+
cwd: s.cwd,
|
|
20255
|
+
title: s.title ?? "-"
|
|
20256
|
+
}));
|
|
20257
|
+
const header = { id: "ID", upstream: "UPSTREAM", cwd: "CWD", title: "TITLE" };
|
|
20258
|
+
const widths = {
|
|
20259
|
+
id: maxLen3(header.id, rows.map((r) => r.id)),
|
|
20260
|
+
upstream: maxLen3(header.upstream, rows.map((r) => r.upstream)),
|
|
20261
|
+
cwd: maxLen3(header.cwd, rows.map((r) => r.cwd))
|
|
20262
|
+
};
|
|
20263
|
+
const fmt = (r) => [
|
|
20264
|
+
r.id.padEnd(widths.id),
|
|
20265
|
+
r.upstream.padEnd(widths.upstream),
|
|
20266
|
+
r.cwd.padEnd(widths.cwd),
|
|
20267
|
+
r.title
|
|
20268
|
+
].join(" ");
|
|
20269
|
+
process.stdout.write(fmt(header) + "\n");
|
|
20270
|
+
for (const r of rows) {
|
|
20271
|
+
process.stdout.write(fmt(r) + "\n");
|
|
20272
|
+
}
|
|
20273
|
+
process.stdout.write(
|
|
20274
|
+
`
|
|
20275
|
+
Synced ${body.synced.length} session(s); skipped ${body.skipped} already tracked.
|
|
20276
|
+
`
|
|
20277
|
+
);
|
|
20278
|
+
}
|
|
19890
20279
|
async function runAgentsRefresh() {
|
|
19891
20280
|
const config = await loadConfig();
|
|
19892
20281
|
const serviceToken = await loadServiceToken();
|
|
@@ -20943,7 +21332,11 @@ async function main() {
|
|
|
20943
21332
|
const daemonIdx = argv.indexOf("daemon");
|
|
20944
21333
|
const tail = argv.slice(daemonIdx + 1);
|
|
20945
21334
|
const sub = tail[0];
|
|
20946
|
-
if (sub ===
|
|
21335
|
+
if (sub === void 0 || sub === "status") {
|
|
21336
|
+
await runDaemonStatus();
|
|
21337
|
+
return;
|
|
21338
|
+
}
|
|
21339
|
+
if (sub === "start") {
|
|
20947
21340
|
await runDaemonStart(flags);
|
|
20948
21341
|
return;
|
|
20949
21342
|
}
|
|
@@ -20955,10 +21348,6 @@ async function main() {
|
|
|
20955
21348
|
await runDaemonRestart();
|
|
20956
21349
|
return;
|
|
20957
21350
|
}
|
|
20958
|
-
if (sub === "status") {
|
|
20959
|
-
await runDaemonStatus();
|
|
20960
|
-
return;
|
|
20961
|
-
}
|
|
20962
21351
|
if (sub === "logs") {
|
|
20963
21352
|
await runDaemonLogs(tail.slice(1));
|
|
20964
21353
|
return;
|
|
@@ -21071,6 +21460,14 @@ async function main() {
|
|
|
21071
21460
|
await runAgentsRefresh();
|
|
21072
21461
|
return;
|
|
21073
21462
|
}
|
|
21463
|
+
if (sub === "install") {
|
|
21464
|
+
await runAgentsInstall(positional[2]);
|
|
21465
|
+
return;
|
|
21466
|
+
}
|
|
21467
|
+
if (sub === "sync") {
|
|
21468
|
+
await runAgentsSync(positional[2]);
|
|
21469
|
+
return;
|
|
21470
|
+
}
|
|
21074
21471
|
process.stderr.write(`Unknown agent subcommand: ${sub}
|
|
21075
21472
|
`);
|
|
21076
21473
|
process.exit(2);
|
|
@@ -21227,8 +21624,9 @@ function printHelp() {
|
|
|
21227
21624
|
" --readonly Open a session as a transcript viewer (requires --session).",
|
|
21228
21625
|
" HYDRA_ACP_SESSION Env var equivalent of --session (flag wins).",
|
|
21229
21626
|
" hydra-acp init [--rotate-token] Initialize ~/.hydra-acp/config.json",
|
|
21627
|
+
" hydra-acp daemon [status] Show daemon pid/version (default when no subcommand)",
|
|
21230
21628
|
" hydra-acp daemon start [--foreground] Start daemon (detached by default; --foreground to attach)",
|
|
21231
|
-
" hydra-acp daemon stop|restart
|
|
21629
|
+
" hydra-acp daemon stop|restart",
|
|
21232
21630
|
" hydra-acp daemon logs [-f] [-n N] Tail or follow the daemon log",
|
|
21233
21631
|
" hydra-acp session [list] [--all] [--json] [--host=<host>]",
|
|
21234
21632
|
" List sessions (live + 20 most-recent cold; --all for everything; --json emits JSON for scripts).",
|
|
@@ -21250,6 +21648,8 @@ function printHelp() {
|
|
|
21250
21648
|
" hydra-acp extension logs <name> [-f] [-n N] Tail or follow an extension's log",
|
|
21251
21649
|
" hydra-acp agent [list] List agents in the cached registry",
|
|
21252
21650
|
" hydra-acp agent refresh Force a registry re-fetch",
|
|
21651
|
+
" hydra-acp agent install <id> Pre-install <id> from the registry (else lazy on first session)",
|
|
21652
|
+
" 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`",
|
|
21253
21653
|
" hydra-acp auth password [--force] Set the daemon's master password",
|
|
21254
21654
|
" hydra-acp auth [list] List active session tokens",
|
|
21255
21655
|
" hydra-acp auth revoke <id> Revoke a session token",
|
package/dist/index.d.ts
CHANGED
|
@@ -1984,6 +1984,7 @@ declare const SessionRecord: z.ZodObject<{
|
|
|
1984
1984
|
name?: string | undefined;
|
|
1985
1985
|
description?: string | undefined;
|
|
1986
1986
|
}>, "many">>;
|
|
1987
|
+
pendingHistorySync: z.ZodOptional<z.ZodBoolean>;
|
|
1987
1988
|
createdAt: z.ZodString;
|
|
1988
1989
|
updatedAt: z.ZodString;
|
|
1989
1990
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -2022,6 +2023,7 @@ declare const SessionRecord: z.ZodObject<{
|
|
|
2022
2023
|
name?: string | undefined;
|
|
2023
2024
|
description?: string | undefined;
|
|
2024
2025
|
}[] | undefined;
|
|
2026
|
+
pendingHistorySync?: boolean | undefined;
|
|
2025
2027
|
}, {
|
|
2026
2028
|
sessionId: string;
|
|
2027
2029
|
version: 1;
|
|
@@ -2058,6 +2060,7 @@ declare const SessionRecord: z.ZodObject<{
|
|
|
2058
2060
|
name?: string | undefined;
|
|
2059
2061
|
description?: string | undefined;
|
|
2060
2062
|
}[] | undefined;
|
|
2063
|
+
pendingHistorySync?: boolean | undefined;
|
|
2061
2064
|
}>;
|
|
2062
2065
|
type SessionRecord = z.infer<typeof SessionRecord>;
|
|
2063
2066
|
declare class SessionStore {
|
|
@@ -2305,6 +2308,7 @@ interface ResurrectParams {
|
|
|
2305
2308
|
agentModes?: AdvertisedMode[];
|
|
2306
2309
|
agentModels?: AdvertisedModel[];
|
|
2307
2310
|
createdAt?: string;
|
|
2311
|
+
pendingHistorySync?: boolean;
|
|
2308
2312
|
}
|
|
2309
2313
|
type AgentSpawner = (opts: AgentInstanceOptions) => AgentInstance;
|
|
2310
2314
|
interface SessionManagerOptions {
|
|
@@ -2333,11 +2337,17 @@ declare class SessionManager {
|
|
|
2333
2337
|
private doResurrect;
|
|
2334
2338
|
private doResurrectFromImport;
|
|
2335
2339
|
private resolveImportCwd;
|
|
2340
|
+
syncFromAgent(agentId: string): Promise<{
|
|
2341
|
+
synced: SessionRecord[];
|
|
2342
|
+
skipped: number;
|
|
2343
|
+
}>;
|
|
2344
|
+
private collectAgentSessions;
|
|
2336
2345
|
private bootstrapAgent;
|
|
2337
2346
|
private attachManagerHooks;
|
|
2338
2347
|
getHistory(sessionId: string): Promise<HistoryEntry[] | undefined>;
|
|
2339
2348
|
loadHistory(sessionId: string): Promise<HistoryEntry[]>;
|
|
2340
2349
|
loadFromDisk(sessionId: string): Promise<ResurrectParams | undefined>;
|
|
2350
|
+
private clearPendingHistorySync;
|
|
2341
2351
|
private deriveTitleFromHistory;
|
|
2342
2352
|
get(sessionId: string): Session | undefined;
|
|
2343
2353
|
activeAgentVersions(): Map<string, Set<string>>;
|
|
@@ -2459,6 +2469,7 @@ declare const paths: {
|
|
|
2459
2469
|
extensionLogFile: (name: string) => string;
|
|
2460
2470
|
extensionPidFile: (name: string) => string;
|
|
2461
2471
|
tuiHistoryFile: (id: string) => string;
|
|
2472
|
+
globalTuiHistoryFile: () => string;
|
|
2462
2473
|
tuiLogFile: () => string;
|
|
2463
2474
|
};
|
|
2464
2475
|
|
package/dist/index.js
CHANGED
|
@@ -87,6 +87,11 @@ var paths = {
|
|
|
87
87
|
extensionLogFile: (name) => path.join(hydraHome(), "extensions", `${name}.log`),
|
|
88
88
|
extensionPidFile: (name) => path.join(hydraHome(), "extensions", `${name}.pid`),
|
|
89
89
|
tuiHistoryFile: (id) => path.join(hydraHome(), "sessions", id, "prompt-history"),
|
|
90
|
+
// Cross-session prompt history. Up-arrow / ^R fall through to this
|
|
91
|
+
// after the per-session list is exhausted. JSONL, one entry per
|
|
92
|
+
// line, append-only so concurrent TUIs don't lose each other's
|
|
93
|
+
// writes.
|
|
94
|
+
globalTuiHistoryFile: () => path.join(hydraHome(), "prompt-history"),
|
|
90
95
|
tuiLogFile: () => path.join(hydraHome(), "tui.log")
|
|
91
96
|
};
|
|
92
97
|
|
|
@@ -4325,6 +4330,12 @@ var SessionRecord = z4.object({
|
|
|
4325
4330
|
agentCommands: z4.array(PersistedAgentCommand).optional(),
|
|
4326
4331
|
agentModes: z4.array(PersistedAgentMode).optional(),
|
|
4327
4332
|
agentModels: z4.array(PersistedAgentModel).optional(),
|
|
4333
|
+
// One-shot flag set when `hydra agent sync` mints a row from an
|
|
4334
|
+
// agent-side session/list entry: signals that the first resurrect
|
|
4335
|
+
// should *keep* the agent's session/load replay (instead of draining
|
|
4336
|
+
// it) so the local history.jsonl gets populated from the agent's
|
|
4337
|
+
// memory. Cleared after that first resurrect completes.
|
|
4338
|
+
pendingHistorySync: z4.boolean().optional(),
|
|
4328
4339
|
createdAt: z4.string(),
|
|
4329
4340
|
updatedAt: z4.string()
|
|
4330
4341
|
});
|
|
@@ -4445,6 +4456,7 @@ function recordFromMemorySession(args) {
|
|
|
4445
4456
|
agentCommands: args.agentCommands,
|
|
4446
4457
|
agentModes: args.agentModes,
|
|
4447
4458
|
agentModels: args.agentModels,
|
|
4459
|
+
pendingHistorySync: args.pendingHistorySync,
|
|
4448
4460
|
createdAt: args.createdAt ?? now,
|
|
4449
4461
|
updatedAt: args.updatedAt ?? now
|
|
4450
4462
|
};
|
|
@@ -4780,7 +4792,13 @@ var SessionManager = class {
|
|
|
4780
4792
|
await agent.kill().catch(() => void 0);
|
|
4781
4793
|
return this.doResurrectFromImport(params);
|
|
4782
4794
|
}
|
|
4783
|
-
|
|
4795
|
+
if (params.pendingHistorySync === true) {
|
|
4796
|
+
void this.clearPendingHistorySync(params.hydraSessionId).catch(
|
|
4797
|
+
() => void 0
|
|
4798
|
+
);
|
|
4799
|
+
} else {
|
|
4800
|
+
agent.connection.drainBuffered("session/update");
|
|
4801
|
+
}
|
|
4784
4802
|
const session = new Session({
|
|
4785
4803
|
sessionId: params.hydraSessionId,
|
|
4786
4804
|
cwd: params.cwd,
|
|
@@ -4870,6 +4888,133 @@ var SessionManager = class {
|
|
|
4870
4888
|
}
|
|
4871
4889
|
return os2.homedir();
|
|
4872
4890
|
}
|
|
4891
|
+
// Pull every session the agent itself remembers (across all cwds) and
|
|
4892
|
+
// persist a cold hydra record for each one we don't already track.
|
|
4893
|
+
// Used by `hydra agent sync <id>` to surface sessions created outside
|
|
4894
|
+
// hydra — or by other tools — in `hydra session list` so the picker
|
|
4895
|
+
// can resurrect them. Spawns a throwaway agent process for the
|
|
4896
|
+
// initialize + session/list pair, then kills it. Records are minted
|
|
4897
|
+
// with pendingHistorySync:true so the first resurrect records the
|
|
4898
|
+
// agent's session/load replay into history.jsonl rather than dropping
|
|
4899
|
+
// it.
|
|
4900
|
+
async syncFromAgent(agentId) {
|
|
4901
|
+
const agentDef = await this.registry.getAgent(agentId);
|
|
4902
|
+
if (!agentDef) {
|
|
4903
|
+
const err = new Error(
|
|
4904
|
+
`agent ${agentId} not found in registry`
|
|
4905
|
+
);
|
|
4906
|
+
err.code = JsonRpcErrorCodes.AgentNotInstalled;
|
|
4907
|
+
throw err;
|
|
4908
|
+
}
|
|
4909
|
+
const plan = await planSpawn(agentDef, [], {
|
|
4910
|
+
npmRegistry: this.npmRegistry
|
|
4911
|
+
});
|
|
4912
|
+
const agent = this.spawner({
|
|
4913
|
+
agentId,
|
|
4914
|
+
cwd: os2.homedir(),
|
|
4915
|
+
plan
|
|
4916
|
+
});
|
|
4917
|
+
let initResult;
|
|
4918
|
+
try {
|
|
4919
|
+
initResult = await agent.connection.request(
|
|
4920
|
+
"initialize",
|
|
4921
|
+
{
|
|
4922
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
4923
|
+
clientCapabilities: {},
|
|
4924
|
+
clientInfo: { name: "hydra", version: HYDRA_VERSION }
|
|
4925
|
+
}
|
|
4926
|
+
);
|
|
4927
|
+
} catch (err) {
|
|
4928
|
+
await agent.kill().catch(() => void 0);
|
|
4929
|
+
throw err;
|
|
4930
|
+
}
|
|
4931
|
+
const caps = initResult.agentCapabilities ?? {};
|
|
4932
|
+
if (caps.sessionCapabilities?.list === void 0) {
|
|
4933
|
+
await agent.kill().catch(() => void 0);
|
|
4934
|
+
throw new Error(
|
|
4935
|
+
`agent ${agentId} does not advertise sessionCapabilities.list; cannot sync`
|
|
4936
|
+
);
|
|
4937
|
+
}
|
|
4938
|
+
let entries;
|
|
4939
|
+
try {
|
|
4940
|
+
entries = await this.collectAgentSessions(agent);
|
|
4941
|
+
} catch (err) {
|
|
4942
|
+
await agent.kill().catch(() => void 0);
|
|
4943
|
+
throw err;
|
|
4944
|
+
}
|
|
4945
|
+
await agent.kill().catch(() => void 0);
|
|
4946
|
+
const existing = /* @__PURE__ */ new Set();
|
|
4947
|
+
for (const live of this.sessions.values()) {
|
|
4948
|
+
existing.add(`${live.agentId}::${live.upstreamSessionId}`);
|
|
4949
|
+
}
|
|
4950
|
+
const stored = await this.store.list().catch(() => []);
|
|
4951
|
+
for (const rec of stored) {
|
|
4952
|
+
existing.add(`${rec.agentId}::${rec.upstreamSessionId}`);
|
|
4953
|
+
}
|
|
4954
|
+
const synced = [];
|
|
4955
|
+
let skipped = 0;
|
|
4956
|
+
for (const entry of entries) {
|
|
4957
|
+
const dedupeKey = `${agentId}::${entry.sessionId}`;
|
|
4958
|
+
if (existing.has(dedupeKey)) {
|
|
4959
|
+
skipped += 1;
|
|
4960
|
+
continue;
|
|
4961
|
+
}
|
|
4962
|
+
existing.add(dedupeKey);
|
|
4963
|
+
const newId = `${HYDRA_SESSION_PREFIX}${generateRawSessionId()}`;
|
|
4964
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4965
|
+
const ts = entry.updatedAt ?? now;
|
|
4966
|
+
const recordArgs = {
|
|
4967
|
+
sessionId: newId,
|
|
4968
|
+
lineageId: generateLineageId(),
|
|
4969
|
+
upstreamSessionId: entry.sessionId,
|
|
4970
|
+
agentId,
|
|
4971
|
+
cwd: entry.cwd,
|
|
4972
|
+
pendingHistorySync: true,
|
|
4973
|
+
createdAt: ts,
|
|
4974
|
+
updatedAt: ts
|
|
4975
|
+
};
|
|
4976
|
+
if (entry.title !== void 0) {
|
|
4977
|
+
recordArgs.title = entry.title;
|
|
4978
|
+
}
|
|
4979
|
+
const record = recordFromMemorySession(recordArgs);
|
|
4980
|
+
await this.store.write(record);
|
|
4981
|
+
synced.push({ version: 1, ...record });
|
|
4982
|
+
}
|
|
4983
|
+
return { synced, skipped };
|
|
4984
|
+
}
|
|
4985
|
+
// Paginate the agent's session/list, threading nextCursor until the
|
|
4986
|
+
// agent stops returning one. Each entry the spec guarantees has
|
|
4987
|
+
// { sessionId, cwd }; title and updatedAt are optional.
|
|
4988
|
+
async collectAgentSessions(agent) {
|
|
4989
|
+
const out = [];
|
|
4990
|
+
let cursor;
|
|
4991
|
+
for (let page = 0; page < 100; page += 1) {
|
|
4992
|
+
const params = {};
|
|
4993
|
+
if (cursor !== void 0) {
|
|
4994
|
+
params.cursor = cursor;
|
|
4995
|
+
}
|
|
4996
|
+
const result = await agent.connection.request("session/list", params);
|
|
4997
|
+
const rows = Array.isArray(result.sessions) ? result.sessions : [];
|
|
4998
|
+
for (const row of rows) {
|
|
4999
|
+
if (typeof row.sessionId !== "string" || typeof row.cwd !== "string") {
|
|
5000
|
+
continue;
|
|
5001
|
+
}
|
|
5002
|
+
const entry = { sessionId: row.sessionId, cwd: row.cwd };
|
|
5003
|
+
if (typeof row.title === "string") {
|
|
5004
|
+
entry.title = row.title;
|
|
5005
|
+
}
|
|
5006
|
+
if (typeof row.updatedAt === "string") {
|
|
5007
|
+
entry.updatedAt = row.updatedAt;
|
|
5008
|
+
}
|
|
5009
|
+
out.push(entry);
|
|
5010
|
+
}
|
|
5011
|
+
if (typeof result.nextCursor !== "string" || result.nextCursor.length === 0) {
|
|
5012
|
+
break;
|
|
5013
|
+
}
|
|
5014
|
+
cursor = result.nextCursor;
|
|
5015
|
+
}
|
|
5016
|
+
return out;
|
|
5017
|
+
}
|
|
4873
5018
|
// Bootstrap a fresh agent process: registry resolve → spawn → initialize
|
|
4874
5019
|
// → session/new. Shared by create() and the /hydra agent path so both
|
|
4875
5020
|
// go through the same env / capabilities / error-handling.
|
|
@@ -5062,9 +5207,21 @@ var SessionManager = class {
|
|
|
5062
5207
|
agentCommands: record.agentCommands,
|
|
5063
5208
|
agentModes: record.agentModes,
|
|
5064
5209
|
agentModels: record.agentModels,
|
|
5065
|
-
createdAt: record.createdAt
|
|
5210
|
+
createdAt: record.createdAt,
|
|
5211
|
+
pendingHistorySync: record.pendingHistorySync
|
|
5066
5212
|
};
|
|
5067
5213
|
}
|
|
5214
|
+
async clearPendingHistorySync(sessionId) {
|
|
5215
|
+
await this.enqueueMetaWrite(sessionId, async () => {
|
|
5216
|
+
const record = await this.store.read(sessionId);
|
|
5217
|
+
if (!record || record.pendingHistorySync !== true) {
|
|
5218
|
+
return;
|
|
5219
|
+
}
|
|
5220
|
+
const next = { ...record };
|
|
5221
|
+
delete next.pendingHistorySync;
|
|
5222
|
+
await this.store.write(next);
|
|
5223
|
+
});
|
|
5224
|
+
}
|
|
5068
5225
|
// Best-effort: peek at the persisted history's first prompt and use
|
|
5069
5226
|
// its first line (capped to 200 chars) as a session title. Returns
|
|
5070
5227
|
// undefined if no usable prompt is found or any I/O fails.
|
|
@@ -7461,7 +7618,7 @@ function registerSessionRoutes(app, manager, defaults) {
|
|
|
7461
7618
|
}
|
|
7462
7619
|
|
|
7463
7620
|
// src/daemon/routes/agents.ts
|
|
7464
|
-
function registerAgentRoutes(app, registry) {
|
|
7621
|
+
function registerAgentRoutes(app, registry, manager, opts = {}) {
|
|
7465
7622
|
app.get("/v1/agents", async () => {
|
|
7466
7623
|
const doc = await registry.load();
|
|
7467
7624
|
return {
|
|
@@ -7482,6 +7639,61 @@ function registerAgentRoutes(app, registry) {
|
|
|
7482
7639
|
const doc = await registry.refresh();
|
|
7483
7640
|
return { version: doc.version, agentCount: doc.agents.length };
|
|
7484
7641
|
});
|
|
7642
|
+
app.post("/v1/agents/:id/install", async (request, reply) => {
|
|
7643
|
+
const id = request.params.id;
|
|
7644
|
+
const agent = await registry.getAgent(id);
|
|
7645
|
+
if (!agent) {
|
|
7646
|
+
reply.code(404).send({ error: `agent ${id} not found in registry` });
|
|
7647
|
+
return;
|
|
7648
|
+
}
|
|
7649
|
+
if (agent.distribution.uvx && !agent.distribution.npx && !agent.distribution.binary) {
|
|
7650
|
+
reply.send({
|
|
7651
|
+
agentId: agent.id,
|
|
7652
|
+
version: agent.version ?? "current",
|
|
7653
|
+
distribution: "uvx",
|
|
7654
|
+
installed: false,
|
|
7655
|
+
message: "uvx agents resolve on first run; nothing to pre-install."
|
|
7656
|
+
});
|
|
7657
|
+
return;
|
|
7658
|
+
}
|
|
7659
|
+
try {
|
|
7660
|
+
const plan = await planSpawn(agent, [], { npmRegistry: opts.npmRegistry });
|
|
7661
|
+
const distribution = agent.distribution.npx ? "npx" : agent.distribution.binary ? "binary" : "unknown";
|
|
7662
|
+
reply.send({
|
|
7663
|
+
agentId: agent.id,
|
|
7664
|
+
version: plan.version,
|
|
7665
|
+
distribution,
|
|
7666
|
+
installed: true,
|
|
7667
|
+
command: plan.command
|
|
7668
|
+
});
|
|
7669
|
+
} catch (err) {
|
|
7670
|
+
reply.code(500).send({ error: err.message });
|
|
7671
|
+
}
|
|
7672
|
+
});
|
|
7673
|
+
app.post("/v1/agents/:id/sync", async (request, reply) => {
|
|
7674
|
+
const agentId = request.params.id;
|
|
7675
|
+
try {
|
|
7676
|
+
const { synced, skipped } = await manager.syncFromAgent(agentId);
|
|
7677
|
+
return {
|
|
7678
|
+
synced: synced.map((r) => ({
|
|
7679
|
+
sessionId: r.sessionId,
|
|
7680
|
+
upstreamSessionId: r.upstreamSessionId,
|
|
7681
|
+
agentId: r.agentId,
|
|
7682
|
+
cwd: r.cwd,
|
|
7683
|
+
title: r.title,
|
|
7684
|
+
updatedAt: r.updatedAt
|
|
7685
|
+
})),
|
|
7686
|
+
skipped
|
|
7687
|
+
};
|
|
7688
|
+
} catch (err) {
|
|
7689
|
+
const e = err;
|
|
7690
|
+
if (e.code === JsonRpcErrorCodes.AgentNotInstalled) {
|
|
7691
|
+
reply.code(404).send({ error: e.message });
|
|
7692
|
+
return;
|
|
7693
|
+
}
|
|
7694
|
+
reply.code(409).send({ error: e.message });
|
|
7695
|
+
}
|
|
7696
|
+
});
|
|
7485
7697
|
}
|
|
7486
7698
|
|
|
7487
7699
|
// src/daemon/routes/health.ts
|
|
@@ -8597,7 +8809,7 @@ async function startDaemon(config, serviceToken) {
|
|
|
8597
8809
|
agentId: config.defaultAgent,
|
|
8598
8810
|
cwd: config.defaultCwd
|
|
8599
8811
|
});
|
|
8600
|
-
registerAgentRoutes(app, registry);
|
|
8812
|
+
registerAgentRoutes(app, registry, manager, { npmRegistry: config.npmRegistry });
|
|
8601
8813
|
registerExtensionRoutes(app, extensions);
|
|
8602
8814
|
registerConfigRoutes(app, {
|
|
8603
8815
|
defaultAgent: config.defaultAgent,
|