@hydra-acp/cli 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1841 -1333
- package/dist/index.d.ts +1 -0
- package/dist/index.js +674 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -966,6 +966,12 @@ function extractHydraMeta(meta) {
|
|
|
966
966
|
if (typeof obj.currentMode === "string") {
|
|
967
967
|
out.currentMode = obj.currentMode;
|
|
968
968
|
}
|
|
969
|
+
if (obj.currentUsage) {
|
|
970
|
+
const parsed = SessionListUsage.safeParse(obj.currentUsage);
|
|
971
|
+
if (parsed.success) {
|
|
972
|
+
out.currentUsage = parsed.data;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
969
975
|
if (typeof obj.turnStartedAt === "number" && obj.turnStartedAt > 0) {
|
|
970
976
|
out.turnStartedAt = obj.turnStartedAt;
|
|
971
977
|
}
|
|
@@ -1470,6 +1476,9 @@ var HYDRA_SESSION_PREFIX = "hydra_session_";
|
|
|
1470
1476
|
function generateMessageId() {
|
|
1471
1477
|
return `m_${generateHydraId()}`;
|
|
1472
1478
|
}
|
|
1479
|
+
function stripHydraSessionPrefix(id) {
|
|
1480
|
+
return id.startsWith(HYDRA_SESSION_PREFIX) ? id.slice(HYDRA_SESSION_PREFIX.length) : id;
|
|
1481
|
+
}
|
|
1473
1482
|
var DEFAULT_HISTORY_MAX_ENTRIES = 1e3;
|
|
1474
1483
|
var Session = class {
|
|
1475
1484
|
sessionId;
|
|
@@ -1721,21 +1730,126 @@ var Session = class {
|
|
|
1721
1730
|
}
|
|
1722
1731
|
this.clients.set(client.clientId, client);
|
|
1723
1732
|
this.updatedAt = Date.now();
|
|
1724
|
-
if (historyPolicy === "none"
|
|
1733
|
+
if (historyPolicy === "none") {
|
|
1725
1734
|
return Promise.resolve({ entries: [], appliedPolicy: historyPolicy });
|
|
1726
1735
|
}
|
|
1736
|
+
if (historyPolicy === "pending_only") {
|
|
1737
|
+
return Promise.resolve({
|
|
1738
|
+
entries: this.buildStateSnapshotReplay(),
|
|
1739
|
+
appliedPolicy: historyPolicy
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1727
1742
|
return this.loadReplay(historyPolicy, opts);
|
|
1728
1743
|
}
|
|
1729
1744
|
async loadReplay(historyPolicy, opts) {
|
|
1730
1745
|
const all = await this.getHistorySnapshot();
|
|
1746
|
+
const state = this.buildStateSnapshotReplay();
|
|
1731
1747
|
if (historyPolicy === "after_message") {
|
|
1732
1748
|
const cutoff = opts.afterMessageId ? findMessageIdIndex(all, opts.afterMessageId) : -1;
|
|
1733
1749
|
if (cutoff < 0) {
|
|
1734
|
-
return { entries: all, appliedPolicy: "full" };
|
|
1750
|
+
return { entries: [...state, ...all], appliedPolicy: "full" };
|
|
1751
|
+
}
|
|
1752
|
+
return {
|
|
1753
|
+
entries: [...state, ...all.slice(cutoff + 1)],
|
|
1754
|
+
appliedPolicy: "after_message"
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
return { entries: [...state, ...all], appliedPolicy: "full" };
|
|
1758
|
+
}
|
|
1759
|
+
// Synthesizes one session/update notification per cached STATE_UPDATE_KIND
|
|
1760
|
+
// so an attaching client receives the current snapshot through the
|
|
1761
|
+
// standard ACP event channel. Without this, third-party clients would
|
|
1762
|
+
// never see current_model/mode/usage/info/commands on resume — they're
|
|
1763
|
+
// filtered out of recorded history (canonical state lives in meta.json
|
|
1764
|
+
// and is *also* surfaced via the attach response _meta, but third-party
|
|
1765
|
+
// clients can't read hydra's namespaced meta).
|
|
1766
|
+
buildStateSnapshotReplay() {
|
|
1767
|
+
const out = [];
|
|
1768
|
+
const sessionId = this.sessionId;
|
|
1769
|
+
const recordedAt = Date.now();
|
|
1770
|
+
if (this.title !== void 0 && this.title.length > 0) {
|
|
1771
|
+
out.push({
|
|
1772
|
+
method: "session/update",
|
|
1773
|
+
params: {
|
|
1774
|
+
sessionId,
|
|
1775
|
+
update: {
|
|
1776
|
+
sessionUpdate: "session_info_update",
|
|
1777
|
+
title: this.title
|
|
1778
|
+
}
|
|
1779
|
+
},
|
|
1780
|
+
recordedAt
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
if (this.currentModel !== void 0 && this.currentModel.length > 0) {
|
|
1784
|
+
out.push({
|
|
1785
|
+
method: "session/update",
|
|
1786
|
+
params: {
|
|
1787
|
+
sessionId,
|
|
1788
|
+
update: {
|
|
1789
|
+
sessionUpdate: "current_model_update",
|
|
1790
|
+
currentModel: this.currentModel
|
|
1791
|
+
}
|
|
1792
|
+
},
|
|
1793
|
+
recordedAt
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
if (this.currentMode !== void 0 && this.currentMode.length > 0) {
|
|
1797
|
+
out.push({
|
|
1798
|
+
method: "session/update",
|
|
1799
|
+
params: {
|
|
1800
|
+
sessionId,
|
|
1801
|
+
update: {
|
|
1802
|
+
sessionUpdate: "current_mode_update",
|
|
1803
|
+
currentMode: this.currentMode
|
|
1804
|
+
}
|
|
1805
|
+
},
|
|
1806
|
+
recordedAt
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
const cmds = this.mergedAvailableCommands();
|
|
1810
|
+
if (cmds.length > 0) {
|
|
1811
|
+
out.push({
|
|
1812
|
+
method: "session/update",
|
|
1813
|
+
params: {
|
|
1814
|
+
sessionId,
|
|
1815
|
+
update: {
|
|
1816
|
+
sessionUpdate: "available_commands_update",
|
|
1817
|
+
availableCommands: cmds
|
|
1818
|
+
}
|
|
1819
|
+
},
|
|
1820
|
+
recordedAt
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
if (this.currentUsage !== void 0) {
|
|
1824
|
+
const u = this.currentUsage;
|
|
1825
|
+
const update = {
|
|
1826
|
+
sessionUpdate: "usage_update"
|
|
1827
|
+
};
|
|
1828
|
+
if (typeof u.used === "number") {
|
|
1829
|
+
update.used = u.used;
|
|
1830
|
+
}
|
|
1831
|
+
if (typeof u.size === "number") {
|
|
1832
|
+
update.size = u.size;
|
|
1833
|
+
}
|
|
1834
|
+
if (typeof u.costAmount === "number" || typeof u.costCurrency === "string") {
|
|
1835
|
+
const cost = {};
|
|
1836
|
+
if (typeof u.costAmount === "number") {
|
|
1837
|
+
cost.amount = u.costAmount;
|
|
1838
|
+
}
|
|
1839
|
+
if (typeof u.costCurrency === "string") {
|
|
1840
|
+
cost.currency = u.costCurrency;
|
|
1841
|
+
}
|
|
1842
|
+
update.cost = cost;
|
|
1843
|
+
}
|
|
1844
|
+
if (Object.keys(update).length > 1) {
|
|
1845
|
+
out.push({
|
|
1846
|
+
method: "session/update",
|
|
1847
|
+
params: { sessionId, update },
|
|
1848
|
+
recordedAt
|
|
1849
|
+
});
|
|
1735
1850
|
}
|
|
1736
|
-
return { entries: all.slice(cutoff + 1), appliedPolicy: "after_message" };
|
|
1737
1851
|
}
|
|
1738
|
-
return
|
|
1852
|
+
return out;
|
|
1739
1853
|
}
|
|
1740
1854
|
// Dispatch in-flight permission requests to a freshly-attached
|
|
1741
1855
|
// client. Called by the daemon's WS handler *after* it finishes
|
|
@@ -4512,6 +4626,533 @@ function decodeBundle(raw) {
|
|
|
4512
4626
|
return Bundle.parse(raw);
|
|
4513
4627
|
}
|
|
4514
4628
|
|
|
4629
|
+
// src/core/render-update.ts
|
|
4630
|
+
import stripAnsi from "strip-ansi";
|
|
4631
|
+
var STRIP_CONTROLS = /[\x00-\x08\x0b-\x1f\x7f]/g;
|
|
4632
|
+
function sanitizeWireText(text) {
|
|
4633
|
+
return stripAnsi(text).replace(STRIP_CONTROLS, "");
|
|
4634
|
+
}
|
|
4635
|
+
function sanitizeSingleLine(text) {
|
|
4636
|
+
return sanitizeWireText(text).replace(/[\n\t]+/g, " ").replace(/ +/g, " ").trim();
|
|
4637
|
+
}
|
|
4638
|
+
function mapUpdate(update) {
|
|
4639
|
+
if (!update || typeof update !== "object") {
|
|
4640
|
+
return null;
|
|
4641
|
+
}
|
|
4642
|
+
const u = update;
|
|
4643
|
+
const tag = u.sessionUpdate ?? u.kind;
|
|
4644
|
+
if (typeof tag !== "string") {
|
|
4645
|
+
return null;
|
|
4646
|
+
}
|
|
4647
|
+
switch (tag) {
|
|
4648
|
+
case "agent_message_chunk":
|
|
4649
|
+
return mapAgentText(u);
|
|
4650
|
+
case "agent_thought_chunk":
|
|
4651
|
+
case "agent_thought":
|
|
4652
|
+
return mapAgentThought(u);
|
|
4653
|
+
case "user_message_chunk":
|
|
4654
|
+
return mapUserText(u);
|
|
4655
|
+
case "prompt_received":
|
|
4656
|
+
return mapPromptReceived(u);
|
|
4657
|
+
case "tool_call":
|
|
4658
|
+
return mapToolCall(u);
|
|
4659
|
+
case "tool_call_update":
|
|
4660
|
+
return mapToolCallUpdate(u);
|
|
4661
|
+
case "plan":
|
|
4662
|
+
return mapPlan(u);
|
|
4663
|
+
case "current_mode_update":
|
|
4664
|
+
return mapMode(u);
|
|
4665
|
+
case "current_model_update":
|
|
4666
|
+
return mapModel(u);
|
|
4667
|
+
case "turn_complete":
|
|
4668
|
+
return mapTurnComplete(u);
|
|
4669
|
+
case "usage_update":
|
|
4670
|
+
return mapUsage(u);
|
|
4671
|
+
case "available_commands_update":
|
|
4672
|
+
return mapAvailableCommands(u);
|
|
4673
|
+
case "session_info_update":
|
|
4674
|
+
return mapSessionInfo(u);
|
|
4675
|
+
default:
|
|
4676
|
+
return { kind: "unknown", sessionUpdate: tag, raw: update };
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
function mapSessionInfo(u) {
|
|
4680
|
+
const rawTitle = readString(u, "title");
|
|
4681
|
+
const title = rawTitle !== void 0 ? sanitizeSingleLine(rawTitle) : void 0;
|
|
4682
|
+
const meta = u._meta;
|
|
4683
|
+
let agentId;
|
|
4684
|
+
if (meta && typeof meta === "object" && !Array.isArray(meta)) {
|
|
4685
|
+
const ns = meta["hydra-acp"];
|
|
4686
|
+
if (ns && typeof ns === "object" && !Array.isArray(ns)) {
|
|
4687
|
+
const candidate = ns.agentId;
|
|
4688
|
+
if (typeof candidate === "string") {
|
|
4689
|
+
agentId = candidate;
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
}
|
|
4693
|
+
if (title === void 0 && agentId === void 0) {
|
|
4694
|
+
return null;
|
|
4695
|
+
}
|
|
4696
|
+
const event = { kind: "session-info" };
|
|
4697
|
+
if (title !== void 0) {
|
|
4698
|
+
event.title = title;
|
|
4699
|
+
}
|
|
4700
|
+
if (agentId !== void 0) {
|
|
4701
|
+
event.agentId = agentId;
|
|
4702
|
+
}
|
|
4703
|
+
return event;
|
|
4704
|
+
}
|
|
4705
|
+
function normalizeAdvertisedCommands(list) {
|
|
4706
|
+
if (!Array.isArray(list)) {
|
|
4707
|
+
return [];
|
|
4708
|
+
}
|
|
4709
|
+
const out = [];
|
|
4710
|
+
for (const raw of list) {
|
|
4711
|
+
if (!raw || typeof raw !== "object") {
|
|
4712
|
+
continue;
|
|
4713
|
+
}
|
|
4714
|
+
const c = raw;
|
|
4715
|
+
if (typeof c.name !== "string" || c.name.length === 0) {
|
|
4716
|
+
continue;
|
|
4717
|
+
}
|
|
4718
|
+
const rawName = c.name.startsWith("/") ? c.name : `/${c.name}`;
|
|
4719
|
+
const cmd = { name: sanitizeSingleLine(rawName) };
|
|
4720
|
+
if (typeof c.description === "string") {
|
|
4721
|
+
cmd.description = sanitizeSingleLine(c.description);
|
|
4722
|
+
}
|
|
4723
|
+
out.push(cmd);
|
|
4724
|
+
}
|
|
4725
|
+
return out;
|
|
4726
|
+
}
|
|
4727
|
+
function mapAvailableCommands(u) {
|
|
4728
|
+
const list = u.availableCommands ?? u.commands;
|
|
4729
|
+
if (!Array.isArray(list)) {
|
|
4730
|
+
return null;
|
|
4731
|
+
}
|
|
4732
|
+
return { kind: "available-commands", commands: normalizeAdvertisedCommands(list) };
|
|
4733
|
+
}
|
|
4734
|
+
function mapUsage(u) {
|
|
4735
|
+
const event = { kind: "usage-update" };
|
|
4736
|
+
if (typeof u.used === "number") {
|
|
4737
|
+
event.used = u.used;
|
|
4738
|
+
}
|
|
4739
|
+
if (typeof u.size === "number") {
|
|
4740
|
+
event.size = u.size;
|
|
4741
|
+
}
|
|
4742
|
+
if (u.cost && typeof u.cost === "object") {
|
|
4743
|
+
const cost = u.cost;
|
|
4744
|
+
if (typeof cost.amount === "number") {
|
|
4745
|
+
event.costAmount = cost.amount;
|
|
4746
|
+
}
|
|
4747
|
+
if (typeof cost.currency === "string") {
|
|
4748
|
+
event.costCurrency = cost.currency;
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
return event;
|
|
4752
|
+
}
|
|
4753
|
+
function mapAgentText(u) {
|
|
4754
|
+
const text = extractContentText(u.content);
|
|
4755
|
+
if (text === null) {
|
|
4756
|
+
return null;
|
|
4757
|
+
}
|
|
4758
|
+
return { kind: "agent-text", text };
|
|
4759
|
+
}
|
|
4760
|
+
function mapAgentThought(u) {
|
|
4761
|
+
const text = typeof u.text === "string" ? sanitizeWireText(u.text) : extractContentText(u.content);
|
|
4762
|
+
if (text === null) {
|
|
4763
|
+
return null;
|
|
4764
|
+
}
|
|
4765
|
+
return { kind: "agent-thought", text };
|
|
4766
|
+
}
|
|
4767
|
+
function mapUserText(u) {
|
|
4768
|
+
const meta = u._meta;
|
|
4769
|
+
if (meta && typeof meta === "object" && !Array.isArray(meta)) {
|
|
4770
|
+
const hydra = meta["hydra-acp"];
|
|
4771
|
+
if (hydra && typeof hydra === "object" && !Array.isArray(hydra) && hydra.compatFor === "prompt_received") {
|
|
4772
|
+
return null;
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
const text = extractContentText(u.content);
|
|
4776
|
+
if (text === null) {
|
|
4777
|
+
return null;
|
|
4778
|
+
}
|
|
4779
|
+
return { kind: "user-text", text };
|
|
4780
|
+
}
|
|
4781
|
+
function mapPromptReceived(u) {
|
|
4782
|
+
const promptText = extractPromptText2(u.prompt);
|
|
4783
|
+
if (promptText === null) {
|
|
4784
|
+
return null;
|
|
4785
|
+
}
|
|
4786
|
+
return { kind: "user-text", text: promptText };
|
|
4787
|
+
}
|
|
4788
|
+
function mapToolCall(u) {
|
|
4789
|
+
const toolCallId = readString(u, "toolCallId") ?? readString(u, "id");
|
|
4790
|
+
if (!toolCallId) {
|
|
4791
|
+
return null;
|
|
4792
|
+
}
|
|
4793
|
+
const rawTitle = readString(u, "title") ?? readString(u, "name") ?? readString(u, "label") ?? "tool call";
|
|
4794
|
+
const title = sanitizeSingleLine(rawTitle);
|
|
4795
|
+
const status = readString(u, "status");
|
|
4796
|
+
const rawKind = readString(u, "kind");
|
|
4797
|
+
const event = { kind: "tool-call", toolCallId, title };
|
|
4798
|
+
if (status !== void 0) {
|
|
4799
|
+
event.status = status;
|
|
4800
|
+
}
|
|
4801
|
+
if (rawKind !== void 0) {
|
|
4802
|
+
event.rawKind = rawKind;
|
|
4803
|
+
}
|
|
4804
|
+
return event;
|
|
4805
|
+
}
|
|
4806
|
+
function mapToolCallUpdate(u) {
|
|
4807
|
+
const toolCallId = readString(u, "toolCallId") ?? readString(u, "id");
|
|
4808
|
+
if (!toolCallId) {
|
|
4809
|
+
return null;
|
|
4810
|
+
}
|
|
4811
|
+
const rawTitle = readString(u, "title");
|
|
4812
|
+
const title = rawTitle !== void 0 ? sanitizeSingleLine(rawTitle) : void 0;
|
|
4813
|
+
const status = readString(u, "status");
|
|
4814
|
+
const meaningful = title !== void 0 || status === "completed" || status === "failed" || status === "rejected" || status === "cancelled";
|
|
4815
|
+
if (!meaningful) {
|
|
4816
|
+
return null;
|
|
4817
|
+
}
|
|
4818
|
+
const event = { kind: "tool-call-update", toolCallId };
|
|
4819
|
+
if (title !== void 0) {
|
|
4820
|
+
event.title = title;
|
|
4821
|
+
}
|
|
4822
|
+
if (status !== void 0) {
|
|
4823
|
+
event.status = status;
|
|
4824
|
+
}
|
|
4825
|
+
return event;
|
|
4826
|
+
}
|
|
4827
|
+
function mapPlan(u) {
|
|
4828
|
+
const entries = u.entries;
|
|
4829
|
+
if (!Array.isArray(entries)) {
|
|
4830
|
+
return null;
|
|
4831
|
+
}
|
|
4832
|
+
const normalized = [];
|
|
4833
|
+
for (const raw of entries) {
|
|
4834
|
+
if (!raw || typeof raw !== "object") {
|
|
4835
|
+
continue;
|
|
4836
|
+
}
|
|
4837
|
+
const e = raw;
|
|
4838
|
+
const content = typeof e.content === "string" ? sanitizeSingleLine(e.content) : void 0;
|
|
4839
|
+
if (!content) {
|
|
4840
|
+
continue;
|
|
4841
|
+
}
|
|
4842
|
+
const entry = { content };
|
|
4843
|
+
if (typeof e.status === "string") {
|
|
4844
|
+
entry.status = e.status;
|
|
4845
|
+
}
|
|
4846
|
+
if (typeof e.priority === "string") {
|
|
4847
|
+
entry.priority = e.priority;
|
|
4848
|
+
}
|
|
4849
|
+
normalized.push(entry);
|
|
4850
|
+
}
|
|
4851
|
+
return { kind: "plan", entries: normalized };
|
|
4852
|
+
}
|
|
4853
|
+
function mapMode(u) {
|
|
4854
|
+
const mode = readString(u, "currentMode") ?? readString(u, "mode");
|
|
4855
|
+
if (!mode) {
|
|
4856
|
+
return null;
|
|
4857
|
+
}
|
|
4858
|
+
return { kind: "mode-changed", mode: sanitizeSingleLine(mode) };
|
|
4859
|
+
}
|
|
4860
|
+
function mapModel(u) {
|
|
4861
|
+
const model = readString(u, "currentModel") ?? readString(u, "model");
|
|
4862
|
+
if (!model) {
|
|
4863
|
+
return null;
|
|
4864
|
+
}
|
|
4865
|
+
return { kind: "model-changed", model: sanitizeSingleLine(model) };
|
|
4866
|
+
}
|
|
4867
|
+
function mapTurnComplete(u) {
|
|
4868
|
+
const stopReason = readString(u, "stopReason");
|
|
4869
|
+
return stopReason !== void 0 ? { kind: "turn-complete", stopReason } : { kind: "turn-complete" };
|
|
4870
|
+
}
|
|
4871
|
+
function extractContentText(content) {
|
|
4872
|
+
if (typeof content === "string") {
|
|
4873
|
+
return sanitizeWireText(content);
|
|
4874
|
+
}
|
|
4875
|
+
if (!content || typeof content !== "object") {
|
|
4876
|
+
return null;
|
|
4877
|
+
}
|
|
4878
|
+
const c = content;
|
|
4879
|
+
if (c.type === "text" && typeof c.text === "string") {
|
|
4880
|
+
return sanitizeWireText(c.text);
|
|
4881
|
+
}
|
|
4882
|
+
if (typeof c.text === "string") {
|
|
4883
|
+
return sanitizeWireText(c.text);
|
|
4884
|
+
}
|
|
4885
|
+
return null;
|
|
4886
|
+
}
|
|
4887
|
+
function extractPromptText2(prompt) {
|
|
4888
|
+
if (!Array.isArray(prompt)) {
|
|
4889
|
+
return null;
|
|
4890
|
+
}
|
|
4891
|
+
const parts = [];
|
|
4892
|
+
for (const block of prompt) {
|
|
4893
|
+
const text = extractContentText(block);
|
|
4894
|
+
if (text !== null) {
|
|
4895
|
+
parts.push(text);
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4898
|
+
if (parts.length === 0) {
|
|
4899
|
+
return null;
|
|
4900
|
+
}
|
|
4901
|
+
return parts.join("");
|
|
4902
|
+
}
|
|
4903
|
+
function readString(u, key) {
|
|
4904
|
+
const v = u[key];
|
|
4905
|
+
return typeof v === "string" ? v : void 0;
|
|
4906
|
+
}
|
|
4907
|
+
|
|
4908
|
+
// src/core/transcript.ts
|
|
4909
|
+
function bundleToMarkdown(bundle) {
|
|
4910
|
+
const events = collectEvents(bundle);
|
|
4911
|
+
const toolFinalStates = collectToolFinalStates(events);
|
|
4912
|
+
const out = [];
|
|
4913
|
+
emitHeader(out, bundle);
|
|
4914
|
+
emitBody(out, events, toolFinalStates);
|
|
4915
|
+
let text = out.join("\n");
|
|
4916
|
+
if (!text.endsWith("\n")) {
|
|
4917
|
+
text += "\n";
|
|
4918
|
+
}
|
|
4919
|
+
return text;
|
|
4920
|
+
}
|
|
4921
|
+
function collectEvents(bundle) {
|
|
4922
|
+
const out = [];
|
|
4923
|
+
for (const entry of bundle.history) {
|
|
4924
|
+
if (entry.method !== "session/update") {
|
|
4925
|
+
continue;
|
|
4926
|
+
}
|
|
4927
|
+
const params = entry.params;
|
|
4928
|
+
if (!params || typeof params !== "object") {
|
|
4929
|
+
continue;
|
|
4930
|
+
}
|
|
4931
|
+
const event = mapUpdate(params.update);
|
|
4932
|
+
if (event === null) {
|
|
4933
|
+
continue;
|
|
4934
|
+
}
|
|
4935
|
+
out.push({ event, recordedAt: entry.recordedAt });
|
|
4936
|
+
}
|
|
4937
|
+
return out;
|
|
4938
|
+
}
|
|
4939
|
+
function collectToolFinalStates(events) {
|
|
4940
|
+
const out = /* @__PURE__ */ new Map();
|
|
4941
|
+
for (const { event } of events) {
|
|
4942
|
+
if (event.kind === "tool-call") {
|
|
4943
|
+
const existing = out.get(event.toolCallId);
|
|
4944
|
+
out.set(event.toolCallId, {
|
|
4945
|
+
title: event.title,
|
|
4946
|
+
status: event.status ?? existing?.status ?? "pending"
|
|
4947
|
+
});
|
|
4948
|
+
continue;
|
|
4949
|
+
}
|
|
4950
|
+
if (event.kind === "tool-call-update") {
|
|
4951
|
+
const existing = out.get(event.toolCallId) ?? {
|
|
4952
|
+
title: "tool call",
|
|
4953
|
+
status: "pending"
|
|
4954
|
+
};
|
|
4955
|
+
out.set(event.toolCallId, {
|
|
4956
|
+
title: event.title ?? existing.title,
|
|
4957
|
+
status: event.status ?? existing.status
|
|
4958
|
+
});
|
|
4959
|
+
}
|
|
4960
|
+
}
|
|
4961
|
+
return out;
|
|
4962
|
+
}
|
|
4963
|
+
function emitHeader(out, bundle) {
|
|
4964
|
+
const session = bundle.session;
|
|
4965
|
+
const shortId = stripHydraSessionPrefix(session.sessionId);
|
|
4966
|
+
const title = session.title?.trim() || `Hydra session ${shortId}`;
|
|
4967
|
+
out.push(`# ${escapeInline(title)}`);
|
|
4968
|
+
out.push("");
|
|
4969
|
+
const lines = [];
|
|
4970
|
+
lines.push(`- **Session:** \`${shortId}\` (lineage \`${session.lineageId}\`)`);
|
|
4971
|
+
const agentBits = [session.agentId];
|
|
4972
|
+
if (session.currentModel) {
|
|
4973
|
+
agentBits.push(`model: ${session.currentModel}`);
|
|
4974
|
+
}
|
|
4975
|
+
if (session.currentMode) {
|
|
4976
|
+
agentBits.push(`mode: ${session.currentMode}`);
|
|
4977
|
+
}
|
|
4978
|
+
lines.push(`- **Agent:** ${agentBits.filter(Boolean).join(" \xB7 ")}`);
|
|
4979
|
+
lines.push(`- **Cwd:** ${session.cwd}`);
|
|
4980
|
+
lines.push(
|
|
4981
|
+
`- **Exported:** ${bundle.exportedAt} from ${bundle.exportedFrom.machine} (hydra ${bundle.exportedFrom.hydraVersion})`
|
|
4982
|
+
);
|
|
4983
|
+
const usage = session.currentUsage;
|
|
4984
|
+
if (usage && (usage.used !== void 0 || usage.costAmount !== void 0)) {
|
|
4985
|
+
const usageBits = [];
|
|
4986
|
+
if (usage.used !== void 0) {
|
|
4987
|
+
const denom = usage.size !== void 0 ? `${formatNumber(usage.size)}` : void 0;
|
|
4988
|
+
usageBits.push(
|
|
4989
|
+
denom ? `${formatNumber(usage.used)} / ${denom} tokens` : `${formatNumber(usage.used)} tokens`
|
|
4990
|
+
);
|
|
4991
|
+
}
|
|
4992
|
+
if (usage.costAmount !== void 0) {
|
|
4993
|
+
const currency = usage.costCurrency ?? "USD";
|
|
4994
|
+
usageBits.push(`$${usage.costAmount.toFixed(2)} ${currency}`);
|
|
4995
|
+
}
|
|
4996
|
+
lines.push(`- **Usage:** ${usageBits.join(" \xB7 ")}`);
|
|
4997
|
+
}
|
|
4998
|
+
out.push(lines.join("\n"));
|
|
4999
|
+
out.push("");
|
|
5000
|
+
}
|
|
5001
|
+
function emitBody(out, events, toolFinalStates) {
|
|
5002
|
+
if (!events.some((e) => isVisible(e.event))) {
|
|
5003
|
+
out.push("_No conversation history recorded._");
|
|
5004
|
+
out.push("");
|
|
5005
|
+
return;
|
|
5006
|
+
}
|
|
5007
|
+
const seenToolIds = /* @__PURE__ */ new Set();
|
|
5008
|
+
let turn = 0;
|
|
5009
|
+
let agentBuffer = "";
|
|
5010
|
+
let inTurn = false;
|
|
5011
|
+
const flushAgent = () => {
|
|
5012
|
+
if (agentBuffer.length === 0) {
|
|
5013
|
+
return;
|
|
5014
|
+
}
|
|
5015
|
+
out.push(agentBuffer.trimEnd());
|
|
5016
|
+
out.push("");
|
|
5017
|
+
agentBuffer = "";
|
|
5018
|
+
};
|
|
5019
|
+
const startTurnIfNeeded = () => {
|
|
5020
|
+
if (inTurn) {
|
|
5021
|
+
return;
|
|
5022
|
+
}
|
|
5023
|
+
turn += 1;
|
|
5024
|
+
out.push("---");
|
|
5025
|
+
out.push("");
|
|
5026
|
+
out.push(`## Turn ${turn}`);
|
|
5027
|
+
out.push("");
|
|
5028
|
+
inTurn = true;
|
|
5029
|
+
};
|
|
5030
|
+
for (const { event } of events) {
|
|
5031
|
+
switch (event.kind) {
|
|
5032
|
+
case "user-text": {
|
|
5033
|
+
flushAgent();
|
|
5034
|
+
turn += 1;
|
|
5035
|
+
out.push("---");
|
|
5036
|
+
out.push("");
|
|
5037
|
+
out.push(`## Turn ${turn}`);
|
|
5038
|
+
out.push("");
|
|
5039
|
+
out.push("**User:**");
|
|
5040
|
+
out.push("");
|
|
5041
|
+
for (const line of event.text.split("\n")) {
|
|
5042
|
+
out.push(`> ${escapeInline(line)}`);
|
|
5043
|
+
}
|
|
5044
|
+
out.push("");
|
|
5045
|
+
out.push("**Assistant:**");
|
|
5046
|
+
out.push("");
|
|
5047
|
+
inTurn = true;
|
|
5048
|
+
break;
|
|
5049
|
+
}
|
|
5050
|
+
case "agent-text":
|
|
5051
|
+
startTurnIfNeeded();
|
|
5052
|
+
agentBuffer += event.text;
|
|
5053
|
+
break;
|
|
5054
|
+
case "agent-thought": {
|
|
5055
|
+
startTurnIfNeeded();
|
|
5056
|
+
flushAgent();
|
|
5057
|
+
const lines = event.text.split("\n");
|
|
5058
|
+
for (const line of lines) {
|
|
5059
|
+
out.push(`> _${escapeInline(line)}_`);
|
|
5060
|
+
}
|
|
5061
|
+
out.push("");
|
|
5062
|
+
break;
|
|
5063
|
+
}
|
|
5064
|
+
case "tool-call": {
|
|
5065
|
+
startTurnIfNeeded();
|
|
5066
|
+
flushAgent();
|
|
5067
|
+
if (seenToolIds.has(event.toolCallId)) {
|
|
5068
|
+
break;
|
|
5069
|
+
}
|
|
5070
|
+
seenToolIds.add(event.toolCallId);
|
|
5071
|
+
const final = toolFinalStates.get(event.toolCallId) ?? {
|
|
5072
|
+
title: event.title,
|
|
5073
|
+
status: event.status ?? "pending"
|
|
5074
|
+
};
|
|
5075
|
+
out.push(`- ${statusGlyph(final.status)} ${formatToolLine(final)}`);
|
|
5076
|
+
out.push("");
|
|
5077
|
+
break;
|
|
5078
|
+
}
|
|
5079
|
+
case "tool-call-update":
|
|
5080
|
+
break;
|
|
5081
|
+
case "plan": {
|
|
5082
|
+
startTurnIfNeeded();
|
|
5083
|
+
flushAgent();
|
|
5084
|
+
out.push("**Plan:**");
|
|
5085
|
+
out.push("");
|
|
5086
|
+
for (const entry of event.entries) {
|
|
5087
|
+
const checked = entry.status === "completed" ? "[x]" : "[ ]";
|
|
5088
|
+
out.push(`- ${checked} ${escapeInline(entry.content)}`);
|
|
5089
|
+
}
|
|
5090
|
+
out.push("");
|
|
5091
|
+
break;
|
|
5092
|
+
}
|
|
5093
|
+
case "mode-changed":
|
|
5094
|
+
startTurnIfNeeded();
|
|
5095
|
+
flushAgent();
|
|
5096
|
+
out.push(`_mode: ${escapeInline(event.mode)}_`);
|
|
5097
|
+
out.push("");
|
|
5098
|
+
break;
|
|
5099
|
+
case "model-changed":
|
|
5100
|
+
startTurnIfNeeded();
|
|
5101
|
+
flushAgent();
|
|
5102
|
+
out.push(`_model: ${escapeInline(event.model)}_`);
|
|
5103
|
+
out.push("");
|
|
5104
|
+
break;
|
|
5105
|
+
case "turn-complete":
|
|
5106
|
+
flushAgent();
|
|
5107
|
+
break;
|
|
5108
|
+
case "usage-update":
|
|
5109
|
+
case "available-commands":
|
|
5110
|
+
case "session-info":
|
|
5111
|
+
case "unknown":
|
|
5112
|
+
break;
|
|
5113
|
+
}
|
|
5114
|
+
}
|
|
5115
|
+
flushAgent();
|
|
5116
|
+
}
|
|
5117
|
+
function isVisible(event) {
|
|
5118
|
+
switch (event.kind) {
|
|
5119
|
+
case "usage-update":
|
|
5120
|
+
case "available-commands":
|
|
5121
|
+
case "session-info":
|
|
5122
|
+
case "unknown":
|
|
5123
|
+
case "turn-complete":
|
|
5124
|
+
return false;
|
|
5125
|
+
default:
|
|
5126
|
+
return true;
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
function formatToolLine(state) {
|
|
5130
|
+
const status = state.status;
|
|
5131
|
+
const suffix = status === "completed" || status === void 0 ? "" : ` _(${status})_`;
|
|
5132
|
+
return `${escapeInline(state.title)}${suffix}`;
|
|
5133
|
+
}
|
|
5134
|
+
function statusGlyph(status) {
|
|
5135
|
+
switch (status) {
|
|
5136
|
+
case "completed":
|
|
5137
|
+
return "\u2713";
|
|
5138
|
+
case "failed":
|
|
5139
|
+
return "\u2717";
|
|
5140
|
+
case "cancelled":
|
|
5141
|
+
case "rejected":
|
|
5142
|
+
return "\u2298";
|
|
5143
|
+
case "in_progress":
|
|
5144
|
+
return "\u21BB";
|
|
5145
|
+
default:
|
|
5146
|
+
return "\xB7";
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5149
|
+
function escapeInline(text) {
|
|
5150
|
+
return text.replace(/</g, "<").replace(/>/g, ">");
|
|
5151
|
+
}
|
|
5152
|
+
function formatNumber(n) {
|
|
5153
|
+
return n.toLocaleString("en-US");
|
|
5154
|
+
}
|
|
5155
|
+
|
|
4515
5156
|
// src/daemon/routes/sessions.ts
|
|
4516
5157
|
function registerSessionRoutes(app, manager, defaults) {
|
|
4517
5158
|
app.get("/v1/sessions", async (request) => {
|
|
@@ -4592,6 +5233,24 @@ function registerSessionRoutes(app, manager, defaults) {
|
|
|
4592
5233
|
);
|
|
4593
5234
|
reply.code(200).send(bundle);
|
|
4594
5235
|
});
|
|
5236
|
+
app.get("/v1/sessions/:id/transcript", async (request, reply) => {
|
|
5237
|
+
const raw = request.params.id;
|
|
5238
|
+
const id = await manager.resolveCanonicalId(raw) ?? raw;
|
|
5239
|
+
const exported = await manager.exportBundle(id);
|
|
5240
|
+
if (!exported) {
|
|
5241
|
+
reply.code(404).send({ error: "session not found" });
|
|
5242
|
+
return;
|
|
5243
|
+
}
|
|
5244
|
+
const bundle = encodeBundle({
|
|
5245
|
+
record: exported.record,
|
|
5246
|
+
history: exported.history,
|
|
5247
|
+
promptHistory: exported.promptHistory.length > 0 ? exported.promptHistory : void 0,
|
|
5248
|
+
hydraVersion: HYDRA_VERSION,
|
|
5249
|
+
machine: os3.hostname()
|
|
5250
|
+
});
|
|
5251
|
+
reply.header("Content-Type", "text/markdown; charset=utf-8");
|
|
5252
|
+
reply.code(200).send(bundleToMarkdown(bundle));
|
|
5253
|
+
});
|
|
4595
5254
|
app.post("/v1/sessions/import", async (request, reply) => {
|
|
4596
5255
|
const body = request.body ?? {};
|
|
4597
5256
|
if (body.bundle === void 0) {
|
|
@@ -4975,11 +5634,18 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
4975
5634
|
model: hydraMeta.model
|
|
4976
5635
|
});
|
|
4977
5636
|
const client = bindClientToSession(connection, session, state);
|
|
4978
|
-
await session.attach(client, "full");
|
|
5637
|
+
const { entries: replay } = await session.attach(client, "full");
|
|
4979
5638
|
state.attached.set(session.sessionId, {
|
|
4980
5639
|
sessionId: session.sessionId,
|
|
4981
5640
|
clientId: client.clientId
|
|
4982
5641
|
});
|
|
5642
|
+
setImmediate(() => {
|
|
5643
|
+
void (async () => {
|
|
5644
|
+
for (const note of replay) {
|
|
5645
|
+
await connection.notify(note.method, note.params).catch(() => void 0);
|
|
5646
|
+
}
|
|
5647
|
+
})();
|
|
5648
|
+
});
|
|
4983
5649
|
return {
|
|
4984
5650
|
sessionId: session.sessionId,
|
|
4985
5651
|
_meta: buildResponseMeta(session)
|
|
@@ -5193,6 +5859,9 @@ function buildResponseMeta(session) {
|
|
|
5193
5859
|
if (session.currentMode !== void 0) {
|
|
5194
5860
|
ours.currentMode = session.currentMode;
|
|
5195
5861
|
}
|
|
5862
|
+
if (session.currentUsage !== void 0) {
|
|
5863
|
+
ours.currentUsage = session.currentUsage;
|
|
5864
|
+
}
|
|
5196
5865
|
const commands = session.mergedAvailableCommands();
|
|
5197
5866
|
if (commands.length > 0) {
|
|
5198
5867
|
ours.availableCommands = commands;
|