bazilion 0.9.0 → 0.11.0

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.
Files changed (189) hide show
  1. package/dist/cli.js +1257 -150
  2. package/dist/cli.js.map +1 -1
  3. package/dist/daemon.js +3049 -471
  4. package/dist/daemon.js.map +1 -1
  5. package/dist/migrations/0001_init.sql +20 -0
  6. package/dist/web/client/assets/AgentTabs-B3hq5a3e.js +1 -0
  7. package/dist/web/client/assets/ConfigPage-BCwfgyjD.js +1 -0
  8. package/dist/web/client/assets/FieldRow-avAFLgYW.js +1 -0
  9. package/dist/web/client/assets/Page-CwNuDhWz.js +1 -0
  10. package/dist/web/client/assets/ProductionTeamPolicyEditor-B6dbpuYy.js +7 -0
  11. package/dist/web/client/assets/{ProfileCommunicationEditor-D1OZ8WE5.js → ProfileCommunicationEditor-Bf8UvmZT.js} +1 -1
  12. package/dist/web/client/assets/SectionTabs-DZzy01az.js +1 -0
  13. package/dist/web/client/assets/TeamTabs-sQBHChtd.js +1 -0
  14. package/dist/web/client/assets/TemplatesTabs-DPsuwsWB.js +1 -0
  15. package/dist/web/client/assets/_id-C9rkCclz.js +1 -0
  16. package/dist/web/client/assets/_id-D65IzC8H.js +1 -0
  17. package/dist/web/client/assets/_id-DE4IY5bL.js +1 -0
  18. package/dist/web/client/assets/_id-DYASsGQ8.js +1 -0
  19. package/dist/web/client/assets/activity-C7o6-xHg.js +1 -0
  20. package/dist/web/client/assets/agents-UroO4d25.js +1 -0
  21. package/dist/web/client/assets/agents-h8yWXQ2H.js +1 -0
  22. package/dist/web/client/assets/approvals-DKhF0YLC.js +1 -0
  23. package/dist/web/client/assets/chevron-down-BK6NPMMP.js +1 -0
  24. package/dist/web/client/assets/config-q8JDresK.js +3 -0
  25. package/dist/web/client/assets/context-CBucAoi6.js +1 -0
  26. package/dist/web/client/assets/inbox-BwoRikj1.js +1 -0
  27. package/dist/web/client/assets/index-DUXAevpZ.js +12 -0
  28. package/dist/web/client/assets/login-CNAV9Wer.js +1 -0
  29. package/dist/web/client/assets/mcp-BKdEFwHO.js +1 -0
  30. package/dist/web/client/assets/members-BDqZQh26.js +1 -0
  31. package/dist/web/client/assets/memory-DVdEVr6L.js +1 -0
  32. package/dist/web/client/assets/plus-b4yFXP0O.js +1 -0
  33. package/dist/web/client/assets/policy-DdDctqvg.js +1 -0
  34. package/dist/web/client/assets/preload-helper-mNuBjNXU.js +1 -0
  35. package/dist/web/client/assets/react-3W8R1pOO.js +1 -0
  36. package/dist/web/client/assets/routes-BEfALSZv.js +1 -0
  37. package/dist/web/client/assets/services-DZP2ybzb.js +1 -0
  38. package/dist/web/client/assets/skills-DoeIjITs.js +1 -0
  39. package/dist/web/client/assets/styles-BYM0UGdF.css +2 -0
  40. package/dist/web/client/assets/team-policy-D0jpnn2b.js +1 -0
  41. package/dist/web/client/assets/teams-DlHRnrQw.js +1 -0
  42. package/dist/web/client/assets/teams-W3j-UoJ9.js +1 -0
  43. package/dist/web/client/assets/telegram-2OdeGv4E.js +1 -0
  44. package/dist/web/client/assets/tokens-MuBfmgue.js +1 -0
  45. package/dist/web/client/assets/triggers-CX7RMQFQ.js +1 -0
  46. package/dist/web/client/assets/welcome-CvNr5IMi.js +1 -0
  47. package/dist/web/client/assets/wire-constants-BhVKOCQa.js +74 -0
  48. package/dist/web/server/assets/{AgentTabs-BOjTN7QF.js → AgentTabs-DoMDJBXH.js} +10 -7
  49. package/dist/web/server/assets/{ChatPane-Dh3qxzdb.js → ChatPane-Ba5wTsVk.js} +340 -44
  50. package/dist/web/server/assets/ConfigPage-BKlh5vOU.js +68 -0
  51. package/dist/web/server/assets/{FieldRow-DwyqycWI.js → FieldRow-B4eTegH9.js} +5 -5
  52. package/dist/web/server/assets/Page-LENulSXY.js +111 -0
  53. package/dist/web/server/assets/{ProductionTeamPolicyEditor-Bke5doyl.js → ProductionTeamPolicyEditor-Y4mzFFvR.js} +4 -4
  54. package/dist/web/server/assets/SectionTabs-BJTPr5nU.js +8 -0
  55. package/dist/web/server/assets/{Sidebar-CGmtCnYU.js → Sidebar-DsRVCszF.js} +229 -118
  56. package/dist/web/server/assets/{TeamTabs-BPxDBfqy.js → TeamTabs-zZzbP1t_.js} +9 -8
  57. package/dist/web/server/assets/{TemplatesTabs-zf4YhStd.js → TemplatesTabs-CcRxZhJe.js} +5 -4
  58. package/dist/web/server/assets/{_id-fpFQ9_Ya.js → _id-BhOB8Umi.js} +1 -1
  59. package/dist/web/server/assets/{_id-CCHA0gdR.js → _id-C3HY4lWg.js} +7 -10
  60. package/dist/web/server/assets/{_id-B6g9wmPR.js → _id-Cg7x6q3g.js} +22 -28
  61. package/dist/web/server/assets/{_id-B04VaQlh.js → _id-CwyGjJeP.js} +2 -2
  62. package/dist/web/server/assets/{_id-_nFwi9gD.js → _id-D27T85mU.js} +1 -1
  63. package/dist/web/server/assets/{_id-5lLzLwcv.js → _id-D3Ap7zZA.js} +142 -144
  64. package/dist/web/server/assets/{_id-ButXHnRH.js → _id-DinTp_7X.js} +2 -2
  65. package/dist/web/server/assets/{_id-DdC9wpNN.js → _id-Dj1yZ3zJ.js} +2 -2
  66. package/dist/web/server/assets/{_id-DkFgCIb-.js → _id-DqON-59C.js} +2 -2
  67. package/dist/web/server/assets/{_id-CUWgTqMS.js → _id-IzadFZV9.js} +1 -1
  68. package/dist/web/server/assets/{_id-C4AOhstq.js → _id-LrvXU4Nr.js} +6 -5
  69. package/dist/web/server/assets/{_id-DJkzwHjM.js → _id-tB47Vsfp.js} +1 -1
  70. package/dist/web/server/assets/{_tanstack-start-manifest_v-Div-TukY.js → _tanstack-start-manifest_v-BXauUXg6.js} +96 -51
  71. package/dist/web/server/assets/{activity-C2ZKg1Q9.js → activity-BCI4mfNS.js} +28 -30
  72. package/dist/web/server/assets/{activity-DedraoFj.js → activity-D9SMqBhy.js} +2 -2
  73. package/dist/web/server/assets/{activity-GPx8NCeB.js → activity-DohitgwW.js} +1 -1
  74. package/dist/web/server/assets/{agents-BR2qXlXF.js → agents-BPKOshR5.js} +1 -1
  75. package/dist/web/server/assets/{agents-LHowEI6S.js → agents-BY74v7fM.js} +1 -1
  76. package/dist/web/server/assets/{agents-B8kinIPW.js → agents-BvttypqB.js} +2 -2
  77. package/dist/web/server/assets/{agents-Bp1nsVs2.js → agents-C9xlWK0Z.js} +2 -2
  78. package/dist/web/server/assets/agents-CgZMHj80.js +265 -0
  79. package/dist/web/server/assets/{agents-CUQmWhTp.js → agents-DZuFEj0D.js} +59 -77
  80. package/dist/web/server/assets/{approvals-VPc0YpLN.js → approvals-BGU-XGIO.js} +1 -1
  81. package/dist/web/server/assets/{approvals-Dw2LJoSA.js → approvals-CWcFHlor.js} +2 -2
  82. package/dist/web/server/assets/{approvals-DHgARzd8.js → approvals-DCaVw1Mz.js} +116 -78
  83. package/dist/web/server/assets/{auth-l-Vf8wlu.js → auth-byfSx6Ja.js} +1 -1
  84. package/dist/web/server/assets/{config-CHaCv89D.js → config-BDCJdtj2.js} +1 -1
  85. package/dist/web/server/assets/{config-BvKctDOX.js → config-D2LAsud2.js} +28 -49
  86. package/dist/web/server/assets/{config-QvHaxGlP.js → config-TeIoyYYc.js} +2 -2
  87. package/dist/web/server/assets/{context-5lrf2ame.js → context-B-XDf6Ym.js} +1 -1
  88. package/dist/web/server/assets/{context-D3Egm-vY.js → context-BXGo5Vfi.js} +2 -2
  89. package/dist/web/server/assets/context-DrkrIWun.js +47 -0
  90. package/dist/web/server/assets/{createServerRpc-CeKECg65.js → createServerRpc-pU-IQV2Y.js} +1 -1
  91. package/dist/web/server/assets/{createSsrRpc-ZBGxhl8t.js → createSsrRpc-DFsIAfFd.js} +1 -1
  92. package/dist/web/server/assets/{empty-plugin-adapters-DKUlnwwl.js → empty-plugin-adapters-Cq4--YW-.js} +1 -1
  93. package/dist/web/server/assets/{inbox-CkiSvD14.js → inbox-B3-TRsNI.js} +1 -1
  94. package/dist/web/server/assets/{inbox-Bxb3W04M.js → inbox-BardsGyy.js} +2 -2
  95. package/dist/web/server/assets/inbox-CUIyf9MT.js +131 -0
  96. package/dist/web/server/assets/login-Bx5LEyGP.js +118 -0
  97. package/dist/web/server/assets/login-CmWvxfTK.js +136 -0
  98. package/dist/web/server/assets/{mcp-BsFKEoXt.js → mcp-2pwT9HU8.js} +1 -1
  99. package/dist/web/server/assets/{mcp-BU_kKENi.js → mcp-BJjLIdP0.js} +105 -112
  100. package/dist/web/server/assets/{mcp-CKqwG4mt.js → mcp-DIWTogHM.js} +2 -2
  101. package/dist/web/server/assets/members-CCv17cjQ.js +46 -0
  102. package/dist/web/server/assets/{members-DlGBKZNl.js → members-CW5aBe81.js} +1 -1
  103. package/dist/web/server/assets/{members-ksELRsyk.js → members-VdPwD6CE.js} +2 -2
  104. package/dist/web/server/assets/{memory-CO0jKGzJ.js → memory-Dngc13Nw.js} +1 -1
  105. package/dist/web/server/assets/memory-a8Kz57Kh.js +287 -0
  106. package/dist/web/server/assets/{memory-B5moRnnp.js → memory-pZ4pZT7p.js} +2 -2
  107. package/dist/web/server/assets/{policy-Dxs78_GO.js → policy-Bb-vEgqg.js} +1 -1
  108. package/dist/web/server/assets/{policy-Dr55y-Mh.js → policy-BcGELcc9.js} +16 -22
  109. package/dist/web/server/assets/{policy-DNcGZLOB.js → policy-C30AeFIb.js} +2 -2
  110. package/dist/web/server/assets/{router-_izWQUeG.js → router-C5_NjTsP.js} +100 -111
  111. package/dist/web/server/assets/{routes-BSSKp3Rv.js → routes-B6cMLIPk.js} +16 -13
  112. package/dist/web/server/assets/{routes-BrTOoeth.js → routes-BrA-U_Hm.js} +2 -2
  113. package/dist/web/server/assets/{routes-DOj6ljcm.js → routes-C19CMLZt.js} +2 -2
  114. package/dist/web/server/assets/services-B9yBWDX0.js +79 -0
  115. package/dist/web/server/assets/{services-Pv_V88VA.js → services-C4szdhPx.js} +2 -2
  116. package/dist/web/server/assets/{services-DhRiNeq0.js → services-wG8I06oS.js} +1 -1
  117. package/dist/web/server/assets/skills-BZdqiDig.js +358 -0
  118. package/dist/web/server/assets/{skills-l2cyQ3zS.js → skills-C-UgkuCW.js} +2 -2
  119. package/dist/web/server/assets/{skills-CFIWVg32.js → skills-a9VSWHWq.js} +1 -1
  120. package/dist/web/server/assets/{start-DlHq0RaQ.js → start-WH8WzTL_.js} +1 -1
  121. package/dist/web/server/assets/{team-policy-Csvc5EDd.js → team-policy-vPG01onj.js} +3 -3
  122. package/dist/web/server/assets/{teams-B9O-WDH5.js → teams-CpQ0YgWf.js} +38 -34
  123. package/dist/web/server/assets/{teams-35Oprf6e.js → teams-DEpQR2QA.js} +2 -2
  124. package/dist/web/server/assets/{teams-DFkN7ic7.js → teams-DqfrEoH6.js} +1 -1
  125. package/dist/web/server/assets/{teams-B5IF65p2.js → teams-MjqgsmC3.js} +1 -1
  126. package/dist/web/server/assets/{teams-DH1CvWmT.js → teams-OIkHuxy1.js} +2 -2
  127. package/dist/web/server/assets/teams-jBtvnk59.js +193 -0
  128. package/dist/web/server/assets/{telegram-C9sQFwWj.js → telegram-CBF8SJzB.js} +36 -71
  129. package/dist/web/server/assets/{telegram-BdaUIjvs.js → telegram-DNgCWebD.js} +1 -1
  130. package/dist/web/server/assets/{telegram-mTnJCOmn.js → telegram-XwyKSEtj.js} +2 -2
  131. package/dist/web/server/assets/{tokens-DdpnfV_Y.js → tokens-BnwFbV7T.js} +2 -2
  132. package/dist/web/server/assets/{tokens-BCz3PSoT.js → tokens-DPDMSN5O.js} +1 -1
  133. package/dist/web/server/assets/tokens-yyTSfBSN.js +186 -0
  134. package/dist/web/server/assets/{triggers-BcTTwMfy.js → triggers-CBl4yMyw.js} +2 -2
  135. package/dist/web/server/assets/{triggers-BcH_6PW1.js → triggers-D79NoSXk.js} +4 -3
  136. package/dist/web/server/assets/{triggers-BzJG1nN5.js → triggers-_3rdhwSN.js} +31 -6
  137. package/dist/web/server/assets/{welcome-CDGE2Sdk.js → welcome-IYHGurWx.js} +21 -13
  138. package/dist/web/server/server.js +82 -73
  139. package/dist/worker.js +1614 -153
  140. package/dist/worker.js.map +1 -1
  141. package/package.json +5 -3
  142. package/dist/web/client/assets/AgentTabs-DioGqUVR.js +0 -1
  143. package/dist/web/client/assets/ConfigTabs-pY-V0Rie.js +0 -1
  144. package/dist/web/client/assets/FieldRow-DmwAFfbF.js +0 -1
  145. package/dist/web/client/assets/ProductionTeamPolicyEditor-1a5jQqUB.js +0 -7
  146. package/dist/web/client/assets/TeamTabs-BViZ4sti.js +0 -1
  147. package/dist/web/client/assets/TemplatesTabs-DVcY9pMD.js +0 -1
  148. package/dist/web/client/assets/_id-8P7brhGy.js +0 -1
  149. package/dist/web/client/assets/_id-C3o3MHq8.js +0 -1
  150. package/dist/web/client/assets/_id-DGRrSF1e.js +0 -1
  151. package/dist/web/client/assets/_id-KnJe-aW9.js +0 -1
  152. package/dist/web/client/assets/activity-WWvJSo4t.js +0 -1
  153. package/dist/web/client/assets/agents-C32T1voV.js +0 -1
  154. package/dist/web/client/assets/agents-nH4m4gZ1.js +0 -1
  155. package/dist/web/client/assets/approvals-l8jIZjqz.js +0 -1
  156. package/dist/web/client/assets/config-C7wvi1nR.js +0 -3
  157. package/dist/web/client/assets/context-BEGWE3RC.js +0 -1
  158. package/dist/web/client/assets/inbox--4aBaAgG.js +0 -1
  159. package/dist/web/client/assets/index-BCXoh1c8.js +0 -12
  160. package/dist/web/client/assets/login-Bk2wwIHQ.js +0 -1
  161. package/dist/web/client/assets/mcp-vM7xgh9e.js +0 -1
  162. package/dist/web/client/assets/members-cPKfnrSL.js +0 -1
  163. package/dist/web/client/assets/memory-BOcsaemD.js +0 -1
  164. package/dist/web/client/assets/policy-BGLCJYvS.js +0 -1
  165. package/dist/web/client/assets/preload-helper-eOOCz1Fu.js +0 -1
  166. package/dist/web/client/assets/routes-DnrP3dwv.js +0 -1
  167. package/dist/web/client/assets/services-C2_O3ifw.js +0 -1
  168. package/dist/web/client/assets/skills-B__fLZjx.js +0 -1
  169. package/dist/web/client/assets/styles-Dn24-ZvP.css +0 -2
  170. package/dist/web/client/assets/team-policy-BdX340m4.js +0 -1
  171. package/dist/web/client/assets/teams-DZMB4_iw.js +0 -1
  172. package/dist/web/client/assets/teams-rpaYKNIb.js +0 -1
  173. package/dist/web/client/assets/telegram-B1O4Ko0V.js +0 -1
  174. package/dist/web/client/assets/tokens-D7FH9VgA.js +0 -1
  175. package/dist/web/client/assets/triggers-3PLy2_Fo.js +0 -1
  176. package/dist/web/client/assets/welcome-B-QQobzq.js +0 -1
  177. package/dist/web/client/assets/wire-constants-BY9Q9Z7d.js +0 -74
  178. package/dist/web/server/assets/ConfigTabs-CyuxzesF.js +0 -41
  179. package/dist/web/server/assets/agents-DU9Hqs58.js +0 -249
  180. package/dist/web/server/assets/context-CEr4-xiA.js +0 -49
  181. package/dist/web/server/assets/inbox-DU27iHv_.js +0 -133
  182. package/dist/web/server/assets/login-CNznncgA.js +0 -58
  183. package/dist/web/server/assets/login-CQybKk7Y.js +0 -74
  184. package/dist/web/server/assets/members-40DxRdVj.js +0 -48
  185. package/dist/web/server/assets/memory-CiTl4cN9.js +0 -289
  186. package/dist/web/server/assets/services-CHe4ihJk.js +0 -94
  187. package/dist/web/server/assets/skills-D89ND2kR.js +0 -321
  188. package/dist/web/server/assets/teams-zcm9XaQU.js +0 -174
  189. package/dist/web/server/assets/tokens-CkKqrtk-.js +0 -193
@@ -1,5 +1,6 @@
1
+ import { t as Button } from "./Button-Dsnw22OF.js";
1
2
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
4
  import DOMPurify from "dompurify";
4
5
  import { marked } from "marked";
5
6
  //#region src/lib/md.ts
@@ -40,6 +41,30 @@ var AUTOSCROLL_THRESHOLD_PX = 40;
40
41
  var SESSION_POLL_MS = 1e4;
41
42
  var TOOL_GROUP_MAX_HEIGHT_PX = 320;
42
43
  var MAX_INPUT_HEIGHT = 200;
44
+ /** Keep one inline card per ephemeral shell approval while preserving its first position. */
45
+ function upsertCommandApprovalEntry(entries, approval) {
46
+ const index = entries.findIndex((entry) => entry.type === "command_approval" && entry.approval.id === approval.id);
47
+ if (index === -1) return [...entries, {
48
+ type: "command_approval",
49
+ approval
50
+ }];
51
+ const next = [...entries];
52
+ next[index] = {
53
+ type: "command_approval",
54
+ approval
55
+ };
56
+ return next;
57
+ }
58
+ function interactiveChatRequest(message, attachments) {
59
+ return {
60
+ message,
61
+ attachments,
62
+ bashApprovalMode: "interactive"
63
+ };
64
+ }
65
+ function shellApprovalsUrl(agentId) {
66
+ return `/api/shell-approvals?agentId=${encodeURIComponent(agentId)}`;
67
+ }
43
68
  var SLASH_HELP = "slash commands:\n /context — context breakdown (system prompt, tools, skills, history)\n /compact [N] — summarize the head; keep the last N messages verbatim (default 10)\n /reset — reset chat history for this agent\n /help — show this list";
44
69
  function prettyArgs(raw) {
45
70
  if (!raw) return "";
@@ -141,10 +166,16 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
141
166
  const [attachments, setAttachments] = useState([]);
142
167
  const [dragging, setDragging] = useState(false);
143
168
  const [staleBanner, setStaleBanner] = useState(false);
169
+ const [recoveredTurn, setRecoveredTurn] = useState(false);
170
+ const [approvalBusy, setApprovalBusy] = useState({});
171
+ const [approvalErrors, setApprovalErrors] = useState({});
144
172
  const messagesRef = useRef(null);
145
173
  const fileInputRef = useRef(null);
146
174
  const inputRef = useRef(null);
147
175
  const currentAbortRef = useRef(null);
176
+ const currentAgentIdRef = useRef(agentId);
177
+ const commandApprovalTurnRef = useRef(false);
178
+ currentAgentIdRef.current = agentId;
148
179
  const systemIdRef = useRef(0);
149
180
  const visibleEntryCountRef = useRef(0);
150
181
  const knownHeadRef = useRef(initialSessionHead ?? {
@@ -153,7 +184,11 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
153
184
  });
154
185
  const streamingRef = useRef(false);
155
186
  streamingRef.current = streaming;
187
+ const turnBusy = streaming || recoveredTurn;
156
188
  useEffect(() => {
189
+ currentAbortRef.current?.abort();
190
+ currentAbortRef.current = null;
191
+ commandApprovalTurnRef.current = false;
157
192
  setServerMessages(initialMessages);
158
193
  setLiveEntries([]);
159
194
  setSystemBubbles([]);
@@ -162,11 +197,37 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
162
197
  setThinking(false);
163
198
  setStreaming(false);
164
199
  setStaleBanner(false);
200
+ setRecoveredTurn(false);
201
+ setApprovalBusy({});
202
+ setApprovalErrors({});
165
203
  knownHeadRef.current = initialSessionHead ?? {
166
204
  file: null,
167
205
  size: 0
168
206
  };
169
207
  }, [agentId]);
208
+ useEffect(() => {
209
+ const controller = new AbortController();
210
+ let stopped = false;
211
+ async function recoverPendingCommandApprovals() {
212
+ try {
213
+ const response = await fetch(shellApprovalsUrl(agentId), { signal: controller.signal });
214
+ if (!response.ok) return;
215
+ const pending = (await response.json()).approvals.filter((approval) => approval.agentId === agentId && approval.status === "pending");
216
+ if (stopped || pending.length === 0) return;
217
+ sessionStorage.removeItem(`bz_pending_${agentId}`);
218
+ commandApprovalTurnRef.current = true;
219
+ setLiveEntries((entries) => pending.reduce(upsertCommandApprovalEntry, entries));
220
+ setRecoveredTurn(true);
221
+ } catch (error) {
222
+ if (error.name === "AbortError") return;
223
+ }
224
+ }
225
+ recoverPendingCommandApprovals();
226
+ return () => {
227
+ stopped = true;
228
+ controller.abort();
229
+ };
230
+ }, [agentId]);
170
231
  const wasNearBottomRef = useRef(true);
171
232
  useLayoutEffect(() => {
172
233
  const el = messagesRef.current;
@@ -227,11 +288,12 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
227
288
  staleBanner
228
289
  ]);
229
290
  async function refreshKnownHead() {
291
+ if (currentAgentIdRef.current !== agentId) return;
230
292
  try {
231
293
  const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/sessions/head`);
232
294
  if (!res.ok) return;
233
295
  const body = await res.json();
234
- if (typeof body.size === "number") knownHeadRef.current = {
296
+ if (currentAgentIdRef.current === agentId && typeof body.size === "number") knownHeadRef.current = {
235
297
  file: body.file ?? null,
236
298
  size: body.size
237
299
  };
@@ -369,7 +431,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
369
431
  return -1;
370
432
  }
371
433
  function enterEditMode() {
372
- if (liveEntries.length > 0 || streaming) return;
434
+ if (liveEntries.length > 0 || turnBusy) return;
373
435
  const idx = findLastUserIdx();
374
436
  if (idx === -1) return;
375
437
  const userMsg = serverMessages[idx];
@@ -382,7 +444,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
382
444
  setEditIdx(null);
383
445
  }
384
446
  async function addFiles(files) {
385
- if (!files || streaming) return;
447
+ if (!files || turnBusy) return;
386
448
  const arr = Array.from(files);
387
449
  if (arr.length === 0) return;
388
450
  const encoded = await Promise.all(arr.map(fileToAttachment));
@@ -398,7 +460,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
398
460
  function onDragOver(e) {
399
461
  if (!Array.from(e.dataTransfer.types).includes("Files")) return;
400
462
  e.preventDefault();
401
- if (streaming) {
463
+ if (turnBusy) {
402
464
  setDragging(false);
403
465
  return;
404
466
  }
@@ -411,12 +473,12 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
411
473
  if (e.dataTransfer.files.length === 0) return;
412
474
  e.preventDefault();
413
475
  setDragging(false);
414
- if (streaming) return;
476
+ if (turnBusy) return;
415
477
  addFiles(e.dataTransfer.files);
416
478
  }
417
479
  const send = useCallback(async (text) => {
418
480
  const atts = attachments;
419
- if (!text.trim() && atts.length === 0 || streaming) return;
481
+ if (!text.trim() && atts.length === 0 || turnBusy) return;
420
482
  setInput("");
421
483
  if (await maybeHandleSlashCommand(text)) return;
422
484
  setAttachments([]);
@@ -456,19 +518,21 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
456
518
  ...previewImages.length > 0 ? { images: previewImages } : {},
457
519
  ...previewFiles.length > 0 ? { files: previewFiles.map((f) => ({ name: f.name ?? "file" })) } : {}
458
520
  }]);
521
+ setRecoveredTurn(false);
522
+ setApprovalBusy({});
523
+ setApprovalErrors({});
524
+ commandApprovalTurnRef.current = false;
459
525
  sessionStorage.setItem(`bz_pending_${agentId}`, "1");
460
526
  const abort = new AbortController();
461
527
  currentAbortRef.current = abort;
462
528
  setStreaming(true);
463
529
  setThinking(true);
530
+ let terminalFrameReceived = false;
464
531
  try {
465
532
  const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/chat`, {
466
533
  method: "POST",
467
534
  headers: { "content-type": "application/json" },
468
- body: JSON.stringify({
469
- message: text,
470
- attachments: atts
471
- }),
535
+ body: JSON.stringify(interactiveChatRequest(text, atts)),
472
536
  signal: abort.signal
473
537
  });
474
538
  if (res.status === 202) {
@@ -508,11 +572,14 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
508
572
  } catch {
509
573
  continue;
510
574
  }
575
+ if (frame.kind === "done" || frame.kind === "fatal") terminalFrameReceived = true;
511
576
  handleFrame(frame);
512
577
  }
513
578
  }
514
579
  if (buffer.trim()) try {
515
- handleFrame(JSON.parse(buffer));
580
+ const frame = JSON.parse(buffer);
581
+ if (frame.kind === "done" || frame.kind === "fatal") terminalFrameReceived = true;
582
+ handleFrame(frame);
516
583
  } catch {}
517
584
  await refreshKnownHead();
518
585
  sessionStorage.removeItem(`bz_pending_${agentId}`);
@@ -523,28 +590,44 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
523
590
  }]);
524
591
  sessionStorage.removeItem(`bz_pending_${agentId}`);
525
592
  } finally {
526
- setThinking(false);
527
- setStreaming(false);
528
- currentAbortRef.current = null;
593
+ if (currentAgentIdRef.current === agentId && currentAbortRef.current === abort) {
594
+ if (!terminalFrameReceived && commandApprovalTurnRef.current) setRecoveredTurn(true);
595
+ setThinking(false);
596
+ setStreaming(false);
597
+ currentAbortRef.current = null;
598
+ }
529
599
  }
530
600
  }, [
531
601
  agentId,
532
602
  editIdx,
533
603
  serverMessages,
534
- streaming,
604
+ turnBusy,
535
605
  attachments
536
606
  ]);
537
607
  function handleFrame(frame) {
608
+ if (currentAgentIdRef.current !== agentId) return;
538
609
  if (frame.kind === "fatal") {
539
- setLiveEntries((prev) => [...prev, {
610
+ commandApprovalTurnRef.current = false;
611
+ setLiveEntries((prev) => [...prev.map((entry) => entry.type === "command_approval" && entry.approval.status === "pending" ? {
612
+ type: "command_approval",
613
+ approval: {
614
+ ...entry.approval,
615
+ status: "cancelled"
616
+ }
617
+ } : entry), {
540
618
  type: "error",
541
619
  content: `[fatal] ${frame.error}`
542
620
  }]);
621
+ setRecoveredTurn(false);
543
622
  return;
544
623
  }
545
624
  if (frame.kind === "done") {
625
+ commandApprovalTurnRef.current = false;
546
626
  setServerMessages(frame.messages);
547
627
  setLiveEntries([]);
628
+ setRecoveredTurn(false);
629
+ setApprovalBusy({});
630
+ setApprovalErrors({});
548
631
  return;
549
632
  }
550
633
  if (frame.kind !== "event") return;
@@ -584,6 +667,13 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
584
667
  });
585
668
  return;
586
669
  }
670
+ if (ev.type === "command_approval") {
671
+ if (ev.approval.agentId !== agentId) return;
672
+ commandApprovalTurnRef.current = true;
673
+ setThinking(ev.approval.status !== "pending" && ev.approval.status !== "cancelled");
674
+ setLiveEntries((entries) => upsertCommandApprovalEntry(entries, ev.approval));
675
+ return;
676
+ }
587
677
  if (ev.type === "tool_call" || ev.type === "tool_result" || ev.type === "tool_error") {
588
678
  setThinking(ev.type !== "tool_call");
589
679
  const item = ev.type === "tool_call" ? {
@@ -636,12 +726,92 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
636
726
  content: `[error] ${ev.error}`
637
727
  }]);
638
728
  }
729
+ async function reconcileCommandApproval(approvalId) {
730
+ if (currentAgentIdRef.current !== agentId) return null;
731
+ try {
732
+ const response = await fetch(shellApprovalsUrl(agentId));
733
+ if (!response.ok) return null;
734
+ const body = await response.json();
735
+ if (currentAgentIdRef.current !== agentId) return null;
736
+ const approval = body.approvals.find((item) => item.id === approvalId && item.agentId === agentId);
737
+ if (approval) setLiveEntries((entries) => upsertCommandApprovalEntry(entries, approval));
738
+ return approval ?? null;
739
+ } catch {
740
+ return null;
741
+ }
742
+ }
743
+ async function decideCommandApproval(approval, decision) {
744
+ if (approval.agentId !== agentId || approval.status !== "pending") return;
745
+ setApprovalBusy((current) => ({
746
+ ...current,
747
+ [approval.id]: true
748
+ }));
749
+ setApprovalErrors((current) => {
750
+ const next = { ...current };
751
+ delete next[approval.id];
752
+ return next;
753
+ });
754
+ try {
755
+ const response = await fetch(`/api/shell-approvals/${encodeURIComponent(approval.id)}`, {
756
+ method: "POST",
757
+ headers: { "content-type": "application/json" },
758
+ body: JSON.stringify({ decision })
759
+ });
760
+ const body = await response.json().catch(() => null);
761
+ if (currentAgentIdRef.current !== agentId) return;
762
+ if (body?.approval?.agentId === agentId) setLiveEntries((entries) => upsertCommandApprovalEntry(entries, body.approval));
763
+ if (!response.ok) {
764
+ const message = body?.error ?? `Decision failed (${response.status})`;
765
+ const terminalStatus = commandApprovalStatusFromConflict(message);
766
+ if (terminalStatus) {
767
+ setLiveEntries((entries) => upsertCommandApprovalEntry(entries, {
768
+ ...approval,
769
+ status: terminalStatus
770
+ }));
771
+ return;
772
+ }
773
+ throw new Error(message);
774
+ }
775
+ } catch (error) {
776
+ const reconciled = await reconcileCommandApproval(approval.id);
777
+ if (currentAgentIdRef.current !== agentId) return;
778
+ if (!reconciled || reconciled.status === "pending") setApprovalErrors((current) => ({
779
+ ...current,
780
+ [approval.id]: error instanceof Error ? error.message : String(error)
781
+ }));
782
+ } finally {
783
+ if (currentAgentIdRef.current === agentId) setApprovalBusy((current) => ({
784
+ ...current,
785
+ [approval.id]: false
786
+ }));
787
+ }
788
+ }
639
789
  async function cancel() {
790
+ const hasLocalStream = currentAbortRef.current !== null;
640
791
  try {
641
792
  const res = await fetch(`/api/agents/${encodeURIComponent(agentId)}/cancel`, { method: "POST" });
642
- if (res.ok || res.status === 204) return;
793
+ if (currentAgentIdRef.current !== agentId) return;
794
+ if (res.ok || res.status === 204) {
795
+ if (!hasLocalStream) {
796
+ commandApprovalTurnRef.current = false;
797
+ setLiveEntries((entries) => entries.map((entry) => entry.type === "command_approval" && entry.approval.status === "pending" ? {
798
+ type: "command_approval",
799
+ approval: {
800
+ ...entry.approval,
801
+ status: "cancelled"
802
+ }
803
+ } : entry));
804
+ setRecoveredTurn(false);
805
+ }
806
+ return;
807
+ }
643
808
  } catch {}
644
- currentAbortRef.current?.abort();
809
+ if (currentAgentIdRef.current !== agentId) return;
810
+ if (hasLocalStream) currentAbortRef.current?.abort();
811
+ else setLiveEntries((entries) => [...entries, {
812
+ type: "error",
813
+ content: "[cancel failed] could not reach the active turn"
814
+ }]);
645
815
  }
646
816
  function onKeyDown(e) {
647
817
  if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
@@ -652,7 +822,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
652
822
  const baseEntries = projectMessages(serverMessages);
653
823
  visibleEntryCountRef.current = baseEntries.length + liveEntries.length;
654
824
  const lastUserIdx = (() => {
655
- if (liveEntries.length > 0 || streaming) return -1;
825
+ if (liveEntries.length > 0 || turnBusy) return -1;
656
826
  for (let i = baseEntries.length - 1; i >= 0; i--) if (baseEntries[i]?.type === "user") return i;
657
827
  return -1;
658
828
  })();
@@ -676,13 +846,20 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
676
846
  children: "drop images or files to attach"
677
847
  }),
678
848
  /* @__PURE__ */ jsxs("div", {
679
- className: "flex items-baseline justify-between border-b border-frost px-4 py-2.5",
680
- children: [/* @__PURE__ */ jsx("h1", {
681
- className: "font-display text-[1.2rem] text-charcoal",
682
- children: agentName
849
+ className: "flex items-center justify-between gap-3 border-b border-frost px-4 py-2.5",
850
+ children: [/* @__PURE__ */ jsxs("div", {
851
+ className: "flex min-w-0 items-center gap-2",
852
+ children: [/* @__PURE__ */ jsx("a", {
853
+ href: "/",
854
+ className: "inline-flex h-7 items-center rounded-md border border-frost px-2 text-xs font-semibold text-mocha lg:hidden",
855
+ children: "← agents"
856
+ }), /* @__PURE__ */ jsx("h1", {
857
+ className: "truncate font-display text-[1.2rem] text-charcoal",
858
+ children: agentName
859
+ })]
683
860
  }), /* @__PURE__ */ jsx("a", {
684
861
  href: `/agents/${agentId}`,
685
- className: "text-xs text-mocha-light hover:text-sapphire",
862
+ className: "shrink-0 text-xs text-mocha-light hover:text-sapphire",
686
863
  children: "settings →"
687
864
  })]
688
865
  }),
@@ -695,7 +872,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
695
872
  }),
696
873
  /* @__PURE__ */ jsx("span", {
697
874
  className: "flex-1",
698
- children: "new activity from another source — reload to see it"
875
+ children: recoveredTurn ? "the recovered turn has new activity — reload to see it" : "new activity from another source — reload to see it"
699
876
  }),
700
877
  /* @__PURE__ */ jsx("button", {
701
878
  type: "button",
@@ -727,10 +904,13 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
727
904
  const flushSysUpTo = (idx) => {
728
905
  while (sysIdx < systemBubbles.length && systemBubbles[sysIdx].afterIdx <= idx) {
729
906
  const sb = systemBubbles[sysIdx];
730
- out.push(/* @__PURE__ */ jsx(Bubble, { entry: {
731
- type: "system",
732
- content: sb.content
733
- } }, `y-${sb.id}`));
907
+ out.push(/* @__PURE__ */ jsx(Bubble, {
908
+ entry: {
909
+ type: "system",
910
+ content: sb.content
911
+ },
912
+ onCommandApprovalDecision: decideCommandApproval
913
+ }, `y-${sb.id}`));
734
914
  sysIdx++;
735
915
  }
736
916
  };
@@ -741,21 +921,32 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
741
921
  entry,
742
922
  isLastUser: i === lastUserIdx && editIdx === null,
743
923
  isWillDrop: willDropFromIdx !== -1 && i >= willDropFromIdx,
744
- onEdit: enterEditMode
924
+ onEdit: enterEditMode,
925
+ onCommandApprovalDecision: decideCommandApproval,
926
+ commandApprovalBusy: entry.type === "command_approval" && Boolean(approvalBusy[entry.approval.id]),
927
+ commandApprovalError: entry.type === "command_approval" ? approvalErrors[entry.approval.id] : void 0
745
928
  }, `s-${i}`));
746
929
  flushSysUpTo(i + 1);
747
930
  }
748
931
  for (let i = 0; i < liveEntries.length; i++) {
749
932
  const entry = liveEntries[i];
750
- out.push(/* @__PURE__ */ jsx(Bubble, { entry }, `l-${i}`));
933
+ out.push(/* @__PURE__ */ jsx(Bubble, {
934
+ entry,
935
+ onCommandApprovalDecision: decideCommandApproval,
936
+ commandApprovalBusy: entry.type === "command_approval" && Boolean(approvalBusy[entry.approval.id]),
937
+ commandApprovalError: entry.type === "command_approval" ? approvalErrors[entry.approval.id] : void 0
938
+ }, `l-${i}`));
751
939
  flushSysUpTo(baseEntries.length + i + 1);
752
940
  }
753
941
  while (sysIdx < systemBubbles.length) {
754
942
  const sb = systemBubbles[sysIdx];
755
- out.push(/* @__PURE__ */ jsx(Bubble, { entry: {
756
- type: "system",
757
- content: sb.content
758
- } }, `y-${sb.id}`));
943
+ out.push(/* @__PURE__ */ jsx(Bubble, {
944
+ entry: {
945
+ type: "system",
946
+ content: sb.content
947
+ },
948
+ onCommandApprovalDecision: decideCommandApproval
949
+ }, `y-${sb.id}`));
759
950
  sysIdx++;
760
951
  }
761
952
  return out;
@@ -836,7 +1027,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
836
1027
  /* @__PURE__ */ jsx("button", {
837
1028
  type: "button",
838
1029
  onClick: () => fileInputRef.current?.click(),
839
- disabled: streaming,
1030
+ disabled: turnBusy,
840
1031
  title: "attach images or files",
841
1032
  "aria-label": "attach files",
842
1033
  className: "rounded-md border-[1.5px] border-frost bg-snow px-3 py-2 text-[1em] text-mocha transition-colors hover:border-sapphire hover:text-sapphire disabled:cursor-not-allowed disabled:opacity-50",
@@ -849,18 +1040,18 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
849
1040
  onChange: (e) => setInput(e.target.value),
850
1041
  onKeyDown,
851
1042
  onPaste,
852
- disabled: streaming,
1043
+ disabled: turnBusy,
853
1044
  placeholder: "say something… (Shift+Enter for newline; paste or 📎 to attach images/files)",
854
1045
  autoComplete: "off",
855
1046
  className: "max-h-[200px] min-h-[2.4rem] flex-1 resize-none overflow-y-auto rounded-md border-[1.5px] border-frost bg-snow px-3 py-2 text-[0.93em] leading-[1.45] text-chocolate outline-none transition-colors focus:border-sapphire focus:shadow-[0_0_0_3px_var(--color-sapphire-glow)]"
856
1047
  }),
857
1048
  /* @__PURE__ */ jsx("button", {
858
1049
  type: "submit",
859
- disabled: streaming || !input.trim() && attachments.length === 0,
1050
+ disabled: turnBusy || !input.trim() && attachments.length === 0,
860
1051
  className: "rounded-md bg-sapphire px-4 py-2 text-[0.92em] font-semibold text-snow transition-colors hover:bg-sapphire-deep disabled:cursor-not-allowed disabled:opacity-50",
861
1052
  children: "send"
862
1053
  }),
863
- streaming && /* @__PURE__ */ jsx("button", {
1054
+ turnBusy && /* @__PURE__ */ jsx("button", {
864
1055
  type: "button",
865
1056
  onClick: cancel,
866
1057
  className: "rounded-md border-[1.5px] border-frost bg-transparent px-3 py-1.5 text-[0.92em] font-medium text-mocha hover:border-sapphire-light hover:bg-sapphire-glow hover:text-sapphire",
@@ -871,7 +1062,7 @@ function ChatPane({ agentId, agentName, initialMessages, initialSessionHead }) {
871
1062
  ]
872
1063
  });
873
1064
  }
874
- function Bubble({ entry, isLastUser, isWillDrop, onEdit }) {
1065
+ function Bubble({ entry, isLastUser, isWillDrop, onEdit, onCommandApprovalDecision, commandApprovalBusy, commandApprovalError }) {
875
1066
  const dropCls = isWillDrop ? "opacity-40 [&_.bubble-content]:line-through" : "";
876
1067
  if (entry.type === "user") {
877
1068
  if (entry.content.startsWith(INBOX_WAKE_PREFIX)) {
@@ -943,10 +1134,10 @@ function Bubble({ entry, isLastUser, isWillDrop, onEdit }) {
943
1134
  if (entry.content.startsWith("[error]") || entry.content.startsWith("[fatal]") || entry.content.startsWith("[network error]")) return /* @__PURE__ */ jsxs("div", {
944
1135
  className: `my-4 flex flex-col items-start ${dropCls}`,
945
1136
  children: [/* @__PURE__ */ jsx("span", {
946
- className: "mb-1 text-[0.72em] font-semibold uppercase tracking-wider text-[#9B3D3D]",
1137
+ className: "mb-1 text-[0.72em] font-semibold uppercase tracking-wider text-danger",
947
1138
  children: "error"
948
1139
  }), /* @__PURE__ */ jsx("div", {
949
- className: "bubble-content rounded-r-sm border-l-[3px] border-l-[#9B3D3D] bg-[rgba(196,135,138,0.06)] py-1 pl-3 pr-2 leading-[1.55]",
1140
+ className: "bubble-content rounded-r-sm border-l-[3px] border-l-danger bg-danger/10 py-1 pl-3 pr-2 leading-[1.55]",
950
1141
  dangerouslySetInnerHTML: { __html: renderMd(entry.content) }
951
1142
  })]
952
1143
  });
@@ -1000,6 +1191,12 @@ function Bubble({ entry, isLastUser, isWillDrop, onEdit }) {
1000
1191
  })]
1001
1192
  });
1002
1193
  }
1194
+ if (entry.type === "command_approval") return /* @__PURE__ */ jsx(CommandApprovalCard, {
1195
+ approval: entry.approval,
1196
+ busy: commandApprovalBusy,
1197
+ error: commandApprovalError,
1198
+ onDecision: onCommandApprovalDecision
1199
+ });
1003
1200
  if (entry.type === "system") return /* @__PURE__ */ jsxs("div", {
1004
1201
  className: `my-3 rounded-r-sm border-l-[3px] border-sapphire bg-sapphire-glow px-3 py-1 font-mono text-[0.88em] text-sapphire-deep ${dropCls}`,
1005
1202
  children: [/* @__PURE__ */ jsx("span", {
@@ -1011,11 +1208,110 @@ function Bubble({ entry, isLastUser, isWillDrop, onEdit }) {
1011
1208
  })]
1012
1209
  });
1013
1210
  if (entry.type === "error") return /* @__PURE__ */ jsx("div", {
1014
- className: `my-3 rounded-r-sm border-l-[3px] border-[#9B3D3D] bg-[rgba(196,135,138,0.08)] px-3 py-1 text-[0.92em] text-[#9B3D3D] ${dropCls}`,
1211
+ className: `my-3 rounded-r-sm border-l-[3px] border-danger bg-danger/10 px-3 py-1 text-[0.92em] text-danger ${dropCls}`,
1015
1212
  children: entry.content
1016
1213
  });
1017
1214
  return null;
1018
1215
  }
1216
+ function CommandApprovalCard({ approval, busy = false, error, onDecision }) {
1217
+ const pending = approval.status === "pending";
1218
+ return /* @__PURE__ */ jsxs("section", {
1219
+ className: "my-3 rounded-lg border border-danger/50 bg-danger/5 p-4 text-chocolate",
1220
+ "aria-label": "Shell command approval",
1221
+ children: [
1222
+ /* @__PURE__ */ jsxs("div", {
1223
+ className: "flex flex-wrap items-start justify-between gap-2",
1224
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
1225
+ className: "m-0 text-[0.72em] font-semibold uppercase tracking-wider text-danger",
1226
+ children: "Shell command approval"
1227
+ }), /* @__PURE__ */ jsx("h2", {
1228
+ className: "m-0 mt-1 font-body text-[0.95em] font-semibold",
1229
+ children: pending ? "A risky command is waiting for you" : "Command decision recorded"
1230
+ })] }), /* @__PURE__ */ jsx("span", {
1231
+ className: "rounded-full border border-danger/30 px-2 py-0.5 text-[0.75em] font-semibold uppercase tracking-wide text-danger",
1232
+ children: approval.status.replaceAll("_", " ")
1233
+ })]
1234
+ }),
1235
+ /* @__PURE__ */ jsx("pre", {
1236
+ className: "mt-3 max-h-48 overflow-auto whitespace-pre-wrap break-words rounded-md border border-fawn bg-snow p-3 font-mono text-[0.82em] leading-[1.45] text-charcoal",
1237
+ children: approval.command
1238
+ }),
1239
+ /* @__PURE__ */ jsx("ul", {
1240
+ className: "mt-3 space-y-1.5 pl-5 text-[0.84em] leading-[1.4] text-mocha",
1241
+ children: approval.risks.map((risk) => /* @__PURE__ */ jsxs("li", { children: [
1242
+ /* @__PURE__ */ jsx("span", {
1243
+ className: "font-medium text-chocolate",
1244
+ children: risk.message
1245
+ }),
1246
+ " ",
1247
+ /* @__PURE__ */ jsx("code", {
1248
+ className: "text-[0.9em]",
1249
+ children: risk.code
1250
+ })
1251
+ ] }, `${risk.code}:${risk.span.start}:${risk.span.end}`))
1252
+ }),
1253
+ pending ? /* @__PURE__ */ jsxs(Fragment, { children: [
1254
+ /* @__PURE__ */ jsxs("p", {
1255
+ className: "mt-3 text-[0.78em] text-mocha-light",
1256
+ children: [
1257
+ "This approval expires",
1258
+ " ",
1259
+ /* @__PURE__ */ jsx("time", {
1260
+ dateTime: new Date(approval.expiresAt).toISOString(),
1261
+ children: new Date(approval.expiresAt).toLocaleString()
1262
+ }),
1263
+ ". Deny blocks only this command; cancel stops the whole turn."
1264
+ ]
1265
+ }),
1266
+ error && /* @__PURE__ */ jsx("p", {
1267
+ role: "alert",
1268
+ className: "mt-2 rounded-md bg-danger/10 px-3 py-2 text-[0.82em] text-danger",
1269
+ children: error
1270
+ }),
1271
+ onDecision && /* @__PURE__ */ jsxs("div", {
1272
+ className: "mt-3 flex flex-wrap gap-2",
1273
+ children: [/* @__PURE__ */ jsx(Button, {
1274
+ variant: "primary",
1275
+ disabled: busy,
1276
+ onClick: () => onDecision(approval, "deny"),
1277
+ children: "Deny"
1278
+ }), /* @__PURE__ */ jsx(Button, {
1279
+ variant: "danger",
1280
+ disabled: busy,
1281
+ onClick: () => onDecision(approval, "allow"),
1282
+ children: "Allow once"
1283
+ })]
1284
+ })
1285
+ ] }) : /* @__PURE__ */ jsx("p", {
1286
+ role: "status",
1287
+ className: "mt-3 text-[0.84em] font-medium text-mocha",
1288
+ children: commandApprovalStatusCopy(approval.status)
1289
+ })
1290
+ ]
1291
+ });
1292
+ }
1293
+ function commandApprovalStatusCopy(status) {
1294
+ switch (status) {
1295
+ case "pending": return "Waiting for a decision.";
1296
+ case "allowed": return "Allowed once — the command may run for this tool call only.";
1297
+ case "denied": return "Denied — the command was not run.";
1298
+ case "auto_denied": return "Automatically denied — no interactive approval path was available.";
1299
+ case "expired": return "Expired — the command was not run.";
1300
+ case "cancelled": return "Cancelled — the command was not run.";
1301
+ }
1302
+ }
1303
+ function commandApprovalStatusFromConflict(message) {
1304
+ if (!message.startsWith("shell approval already decided: ")) return null;
1305
+ const status = message.slice(32);
1306
+ switch (status) {
1307
+ case "allowed":
1308
+ case "denied":
1309
+ case "auto_denied":
1310
+ case "expired":
1311
+ case "cancelled": return status;
1312
+ default: return null;
1313
+ }
1314
+ }
1019
1315
  function ToolGroup({ items, dropCls }) {
1020
1316
  const [expanded, setExpanded] = useState(false);
1021
1317
  const contentRef = useRef(null);