@hydra-acp/cli 0.1.15 → 0.1.17

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/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" || historyPolicy === "pending_only") {
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 { entries: all, appliedPolicy: "full" };
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, "&lt;").replace(/>/g, "&gt;");
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;