@songsid/agend 2.1.12-beta.9 → 2.2.0-alpha.1

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 (358) hide show
  1. package/README.md +2 -2
  2. package/README.zh-TW.md +2 -2
  3. package/dist/auth/one-time-code.d.ts +19 -0
  4. package/dist/auth/one-time-code.js +49 -0
  5. package/dist/auth/one-time-code.js.map +1 -0
  6. package/dist/auth-api.d.ts +52 -0
  7. package/dist/auth-api.js +346 -0
  8. package/dist/auth-api.js.map +1 -0
  9. package/dist/backend/claude-code.d.ts +4 -0
  10. package/dist/backend/claude-code.js +97 -0
  11. package/dist/backend/claude-code.js.map +1 -1
  12. package/dist/backend/codex.js +59 -30
  13. package/dist/backend/codex.js.map +1 -1
  14. package/dist/backend/kiro-agent.d.ts +66 -0
  15. package/dist/backend/kiro-agent.js +302 -0
  16. package/dist/backend/kiro-agent.js.map +1 -0
  17. package/dist/backend/kiro-identity.d.ts +80 -0
  18. package/dist/backend/kiro-identity.js +537 -0
  19. package/dist/backend/kiro-identity.js.map +1 -0
  20. package/dist/backend/kiro-v2-store-worker.d.ts +1 -0
  21. package/dist/backend/kiro-v2-store-worker.js +87 -0
  22. package/dist/backend/kiro-v2-store-worker.js.map +1 -0
  23. package/dist/backend/kiro-v2-store.d.ts +54 -0
  24. package/dist/backend/kiro-v2-store.js +131 -0
  25. package/dist/backend/kiro-v2-store.js.map +1 -0
  26. package/dist/backend/kiro-v3-identity.d.ts +5 -0
  27. package/dist/backend/kiro-v3-identity.js +22 -0
  28. package/dist/backend/kiro-v3-identity.js.map +1 -1
  29. package/dist/backend/kiro.d.ts +83 -7
  30. package/dist/backend/kiro.js +418 -91
  31. package/dist/backend/kiro.js.map +1 -1
  32. package/dist/backend/types.d.ts +45 -3
  33. package/dist/backend/types.js.map +1 -1
  34. package/dist/channel/adapters/discord.d.ts +1 -0
  35. package/dist/channel/adapters/discord.js +29 -10
  36. package/dist/channel/adapters/discord.js.map +1 -1
  37. package/dist/channel/adapters/telegram.d.ts +1 -0
  38. package/dist/channel/adapters/telegram.js +13 -1
  39. package/dist/channel/adapters/telegram.js.map +1 -1
  40. package/dist/channel/types.d.ts +9 -1
  41. package/dist/classic-channel-manager.d.ts +18 -10
  42. package/dist/classic-channel-manager.js +45 -18
  43. package/dist/classic-channel-manager.js.map +1 -1
  44. package/dist/cli.js +225 -127
  45. package/dist/cli.js.map +1 -1
  46. package/dist/command-table.d.ts +5 -3
  47. package/dist/command-table.js +29 -27
  48. package/dist/command-table.js.map +1 -1
  49. package/dist/config-validator.js +46 -0
  50. package/dist/config-validator.js.map +1 -1
  51. package/dist/config.js +12 -0
  52. package/dist/config.js.map +1 -1
  53. package/dist/connection-secrets.d.ts +6 -0
  54. package/dist/connection-secrets.js +4 -0
  55. package/dist/connection-secrets.js.map +1 -1
  56. package/dist/daemon.d.ts +89 -4
  57. package/dist/daemon.js +661 -77
  58. package/dist/daemon.js.map +1 -1
  59. package/dist/darwin-memory.d.ts +6 -1
  60. package/dist/darwin-memory.js +17 -6
  61. package/dist/darwin-memory.js.map +1 -1
  62. package/dist/delivery-consumed-watch.d.ts +85 -0
  63. package/dist/delivery-consumed-watch.js +244 -0
  64. package/dist/delivery-consumed-watch.js.map +1 -0
  65. package/dist/delivery-outbox.d.ts +61 -0
  66. package/dist/delivery-outbox.js +120 -2
  67. package/dist/delivery-outbox.js.map +1 -1
  68. package/dist/delivery-reconciliation.d.ts +14 -5
  69. package/dist/delivery-reconciliation.js +27 -4
  70. package/dist/delivery-reconciliation.js.map +1 -1
  71. package/dist/delivery-reconciliation.test.js +35 -1
  72. package/dist/delivery-reconciliation.test.js.map +1 -1
  73. package/dist/event-loop-watch.d.ts +28 -0
  74. package/dist/event-loop-watch.js +75 -3
  75. package/dist/event-loop-watch.js.map +1 -1
  76. package/dist/fleet-context.d.ts +22 -2
  77. package/dist/fleet-level-config.d.ts +2 -2
  78. package/dist/fleet-level-config.js +6 -1
  79. package/dist/fleet-level-config.js.map +1 -1
  80. package/dist/fleet-manager.d.ts +293 -12
  81. package/dist/fleet-manager.js +2364 -449
  82. package/dist/fleet-manager.js.map +1 -1
  83. package/dist/host-memory.d.ts +4 -0
  84. package/dist/host-memory.js.map +1 -1
  85. package/dist/instance-config-impact.js +5 -0
  86. package/dist/instance-config-impact.js.map +1 -1
  87. package/dist/instance-lifecycle.d.ts +10 -5
  88. package/dist/instance-lifecycle.js +162 -53
  89. package/dist/instance-lifecycle.js.map +1 -1
  90. package/dist/locale.js +150 -16
  91. package/dist/locale.js.map +1 -1
  92. package/dist/login-controller.d.ts +2 -0
  93. package/dist/login-controller.js +17 -5
  94. package/dist/login-controller.js.map +1 -1
  95. package/dist/memory-pressure.d.ts +7 -8
  96. package/dist/memory-pressure.js +76 -15
  97. package/dist/memory-pressure.js.map +1 -1
  98. package/dist/monotonic-budget.d.ts +2 -0
  99. package/dist/monotonic-budget.js +28 -0
  100. package/dist/monotonic-budget.js.map +1 -0
  101. package/dist/needs-you-hub.d.ts +114 -0
  102. package/dist/needs-you-hub.js +370 -0
  103. package/dist/needs-you-hub.js.map +1 -0
  104. package/dist/needs-you-live.d.ts +116 -0
  105. package/dist/needs-you-live.js +228 -0
  106. package/dist/needs-you-live.js.map +1 -0
  107. package/dist/needs-you.d.ts +132 -0
  108. package/dist/needs-you.js +164 -0
  109. package/dist/needs-you.js.map +1 -0
  110. package/dist/node-version-guard.d.ts +30 -0
  111. package/dist/node-version-guard.js +50 -0
  112. package/dist/node-version-guard.js.map +1 -0
  113. package/dist/operator-control-paths.d.ts +4 -0
  114. package/dist/operator-control-paths.js +24 -0
  115. package/dist/operator-control-paths.js.map +1 -0
  116. package/dist/outbound-handlers.d.ts +2 -0
  117. package/dist/outbound-handlers.js +9 -2
  118. package/dist/outbound-handlers.js.map +1 -1
  119. package/dist/outbound-schemas.d.ts +5 -5
  120. package/dist/profile-control.js +4 -20
  121. package/dist/profile-control.js.map +1 -1
  122. package/dist/public-web-gateway.d.ts +18 -0
  123. package/dist/public-web-gateway.js +141 -0
  124. package/dist/public-web-gateway.js.map +1 -0
  125. package/dist/public-web-link.d.ts +53 -0
  126. package/dist/public-web-link.js +175 -0
  127. package/dist/public-web-link.js.map +1 -0
  128. package/dist/quickstart-api.d.ts +8 -1
  129. package/dist/quickstart-api.js +86 -48
  130. package/dist/quickstart-api.js.map +1 -1
  131. package/dist/quickstart.d.ts +1 -0
  132. package/dist/quickstart.js +6 -2
  133. package/dist/quickstart.js.map +1 -1
  134. package/dist/scheduler/db.d.ts +7 -1
  135. package/dist/scheduler/db.js +38 -0
  136. package/dist/scheduler/db.js.map +1 -1
  137. package/dist/scheduler/index.d.ts +1 -1
  138. package/dist/scheduler/retry-label.d.ts +11 -0
  139. package/dist/scheduler/retry-label.js +24 -0
  140. package/dist/scheduler/retry-label.js.map +1 -0
  141. package/dist/scheduler/scheduler.d.ts +55 -2
  142. package/dist/scheduler/scheduler.js +220 -8
  143. package/dist/scheduler/scheduler.js.map +1 -1
  144. package/dist/scheduler/types.d.ts +22 -0
  145. package/dist/scheduler/types.js.map +1 -1
  146. package/dist/secret-store.d.ts +5 -0
  147. package/dist/secret-store.js +20 -0
  148. package/dist/secret-store.js.map +1 -1
  149. package/dist/service-activation.d.ts +110 -0
  150. package/dist/service-activation.js +302 -0
  151. package/dist/service-activation.js.map +1 -0
  152. package/dist/service-installer.d.ts +7 -0
  153. package/dist/service-installer.js +7 -4
  154. package/dist/service-installer.js.map +1 -1
  155. package/dist/service-restart-selection.d.ts +6 -5
  156. package/dist/service-restart-selection.js +6 -5
  157. package/dist/service-restart-selection.js.map +1 -1
  158. package/dist/settings-api.d.ts +10 -2
  159. package/dist/settings-api.js +175 -72
  160. package/dist/settings-api.js.map +1 -1
  161. package/dist/settings-baseline.d.ts +20 -0
  162. package/dist/settings-baseline.js +104 -0
  163. package/dist/settings-baseline.js.map +1 -0
  164. package/dist/settings-change.d.ts +21 -0
  165. package/dist/settings-change.js +133 -0
  166. package/dist/settings-change.js.map +1 -0
  167. package/dist/settings-confirmation.d.ts +105 -0
  168. package/dist/settings-confirmation.js +232 -0
  169. package/dist/settings-confirmation.js.map +1 -0
  170. package/dist/settings-control.d.ts +41 -0
  171. package/dist/settings-control.js +294 -0
  172. package/dist/settings-control.js.map +1 -0
  173. package/dist/settings-effect.d.ts +24 -0
  174. package/dist/settings-effect.js +169 -0
  175. package/dist/settings-effect.js.map +1 -0
  176. package/dist/settings-http-confirmation.d.ts +29 -0
  177. package/dist/settings-http-confirmation.js +123 -0
  178. package/dist/settings-http-confirmation.js.map +1 -0
  179. package/dist/settings-instance-patch.d.ts +4 -0
  180. package/dist/settings-instance-patch.js +26 -0
  181. package/dist/settings-instance-patch.js.map +1 -0
  182. package/dist/settings-request-capability.d.ts +16 -0
  183. package/dist/settings-request-capability.js +32 -0
  184. package/dist/settings-request-capability.js.map +1 -0
  185. package/dist/settings-transaction.d.ts +65 -0
  186. package/dist/settings-transaction.js +223 -0
  187. package/dist/settings-transaction.js.map +1 -0
  188. package/dist/setup-auth.d.ts +10 -18
  189. package/dist/setup-auth.js +14 -38
  190. package/dist/setup-auth.js.map +1 -1
  191. package/dist/setup-form.d.ts +1 -1
  192. package/dist/setup-form.js +75 -18
  193. package/dist/setup-form.js.map +1 -1
  194. package/dist/setup-guide.d.ts +13 -0
  195. package/dist/setup-guide.js +25 -0
  196. package/dist/setup-guide.js.map +1 -0
  197. package/dist/setup-host.d.ts +16 -0
  198. package/dist/setup-host.js +197 -32
  199. package/dist/setup-host.js.map +1 -1
  200. package/dist/setup-wizard.js +3 -0
  201. package/dist/setup-wizard.js.map +1 -1
  202. package/dist/slash-authz.d.ts +17 -3
  203. package/dist/slash-authz.js +5 -0
  204. package/dist/slash-authz.js.map +1 -1
  205. package/dist/spawn-gate.js +3 -3
  206. package/dist/spawn-gate.js.map +1 -1
  207. package/dist/statusline-watcher.d.ts +5 -0
  208. package/dist/statusline-watcher.js +2 -0
  209. package/dist/statusline-watcher.js.map +1 -1
  210. package/dist/steer-capability.d.ts +6 -0
  211. package/dist/steer-capability.js +8 -0
  212. package/dist/steer-capability.js.map +1 -1
  213. package/dist/sync-work-attribution.d.ts +14 -0
  214. package/dist/sync-work-attribution.js +43 -8
  215. package/dist/sync-work-attribution.js.map +1 -1
  216. package/dist/tips.js +10 -5
  217. package/dist/tips.js.map +1 -1
  218. package/dist/tmux-control.d.ts +35 -5
  219. package/dist/tmux-control.js +291 -57
  220. package/dist/tmux-control.js.map +1 -1
  221. package/dist/tmux-manager.d.ts +20 -6
  222. package/dist/tmux-manager.js +90 -47
  223. package/dist/tmux-manager.js.map +1 -1
  224. package/dist/tmux-read.d.ts +59 -0
  225. package/dist/tmux-read.js +213 -0
  226. package/dist/tmux-read.js.map +1 -0
  227. package/dist/topic-commands.d.ts +21 -10
  228. package/dist/topic-commands.js +191 -58
  229. package/dist/topic-commands.js.map +1 -1
  230. package/dist/transcript-delta-reader.js +2 -1
  231. package/dist/transcript-delta-reader.js.map +1 -1
  232. package/dist/tunnel/cloudflared-install.d.ts +2 -0
  233. package/dist/tunnel/cloudflared-install.js +1 -1
  234. package/dist/tunnel/cloudflared-install.js.map +1 -1
  235. package/dist/tunnel/purpose-lane.d.ts +14 -0
  236. package/dist/tunnel/purpose-lane.js +43 -0
  237. package/dist/tunnel/purpose-lane.js.map +1 -0
  238. package/dist/turn-reply-guard.d.ts +2 -0
  239. package/dist/turn-reply-guard.js +10 -1
  240. package/dist/turn-reply-guard.js.map +1 -1
  241. package/dist/types.d.ts +33 -0
  242. package/dist/ui/app.html +18 -0
  243. package/dist/ui/chat-render.js +478 -0
  244. package/dist/ui/chat-store.js +289 -0
  245. package/dist/ui/chat-strings.js +69 -0
  246. package/dist/ui/chat-thread.js +209 -0
  247. package/dist/ui/chat-tour.js +116 -0
  248. package/dist/ui/fleet-org.js +142 -0
  249. package/dist/ui/panel-chat.js +418 -0
  250. package/dist/ui/panel-fleet.js +413 -0
  251. package/dist/ui/panel-needs.js +202 -0
  252. package/dist/ui/panel-settings.js +750 -0
  253. package/dist/ui/preview.js +266 -0
  254. package/dist/ui/settings-apply.js +239 -0
  255. package/dist/ui/settings-confirm.js +186 -0
  256. package/dist/ui/settings-dialogs.js +546 -0
  257. package/dist/ui/settings-model.js +277 -0
  258. package/dist/ui/settings-strings.js +652 -0
  259. package/dist/ui/settings-wizard.js +140 -0
  260. package/dist/ui/shared/agend-auth.js +93 -0
  261. package/dist/ui/shared/app-ctx.js +80 -0
  262. package/dist/ui/shared/app-html.js +9 -0
  263. package/dist/ui/shared/app-i18n.js +95 -0
  264. package/dist/ui/shared/app-nav.js +73 -0
  265. package/dist/ui/shared/app-needs.js +114 -0
  266. package/dist/ui/shared/app-route.js +73 -0
  267. package/dist/ui/shared/app-session.js +66 -0
  268. package/dist/ui/shared/app-shell.js +344 -0
  269. package/dist/ui/shared/app-store.js +51 -0
  270. package/dist/ui/shared/app-stream.js +201 -0
  271. package/dist/ui/shared/app.css +676 -0
  272. package/dist/ui/shared/app.js +88 -0
  273. package/dist/ui/shared/fonts/OFL.txt +92 -0
  274. package/dist/ui/shared/fonts/README.md +21 -0
  275. package/dist/ui/shared/fonts/inter.woff2 +0 -0
  276. package/dist/ui/shared/htm.module.js +1 -0
  277. package/dist/ui/shared/panel-view.js +655 -0
  278. package/dist/ui/shared/preact-hooks.module.js +2 -0
  279. package/dist/ui/shared/preact.module.js +2 -0
  280. package/dist/ui/shared/signin.js +130 -0
  281. package/dist/ui/shared/theme.js +29 -0
  282. package/dist/ui/shared/tokens.css +153 -0
  283. package/dist/ui/shared/ui-confirm.js +43 -0
  284. package/dist/ui/shared/ui-dialog.js +45 -0
  285. package/dist/ui/shared/ui-icons.js +61 -0
  286. package/dist/ui/shared/ui-menu.js +39 -0
  287. package/dist/ui/shared/ui-states.js +20 -0
  288. package/dist/ui/shared/ui-toast.js +18 -0
  289. package/dist/ui/shared/vendor/htm.LICENSE +202 -0
  290. package/dist/ui/shared/vendor/preact.LICENSE +21 -0
  291. package/dist/ui/shared/vendor/vendor.json +39 -0
  292. package/dist/ui/shared/view-strings.js +309 -0
  293. package/dist/ui/signin.html +51 -0
  294. package/dist/update-check.d.ts +6 -2
  295. package/dist/update-check.js +12 -3
  296. package/dist/update-check.js.map +1 -1
  297. package/dist/update-install.d.ts +71 -0
  298. package/dist/update-install.js +162 -0
  299. package/dist/update-install.js.map +1 -0
  300. package/dist/upgrade-notices.d.ts +17 -0
  301. package/dist/upgrade-notices.js +93 -0
  302. package/dist/upgrade-notices.js.map +1 -0
  303. package/dist/usage/statusline-usage.d.ts +2 -0
  304. package/dist/usage/statusline-usage.js +14 -10
  305. package/dist/usage/statusline-usage.js.map +1 -1
  306. package/dist/view-api.d.ts +6 -1
  307. package/dist/view-api.js +62 -51
  308. package/dist/view-api.js.map +1 -1
  309. package/dist/web-api.d.ts +71 -14
  310. package/dist/web-api.js +492 -77
  311. package/dist/web-api.js.map +1 -1
  312. package/dist/web-auth.d.ts +71 -27
  313. package/dist/web-auth.js +169 -69
  314. package/dist/web-auth.js.map +1 -1
  315. package/dist/web-body.d.ts +3 -0
  316. package/dist/web-body.js +55 -0
  317. package/dist/web-body.js.map +1 -0
  318. package/dist/web-channel-echo.d.ts +9 -0
  319. package/dist/web-channel-echo.js +55 -0
  320. package/dist/web-channel-echo.js.map +1 -0
  321. package/dist/web-chat-history.d.ts +125 -0
  322. package/dist/web-chat-history.js +184 -0
  323. package/dist/web-chat-history.js.map +1 -0
  324. package/dist/web-continuation.d.ts +7 -0
  325. package/dist/web-continuation.js +22 -0
  326. package/dist/web-continuation.js.map +1 -0
  327. package/dist/web-host-guard.d.ts +34 -0
  328. package/dist/web-host-guard.js +57 -1
  329. package/dist/web-host-guard.js.map +1 -1
  330. package/dist/web-login.d.ts +72 -0
  331. package/dist/web-login.js +132 -0
  332. package/dist/web-login.js.map +1 -0
  333. package/dist/web-org.d.ts +55 -0
  334. package/dist/web-org.js +67 -0
  335. package/dist/web-org.js.map +1 -0
  336. package/dist/web-preview.d.ts +77 -0
  337. package/dist/web-preview.js +240 -0
  338. package/dist/web-preview.js.map +1 -0
  339. package/dist/web-request-context.d.ts +10 -0
  340. package/dist/web-request-context.js +13 -0
  341. package/dist/web-request-context.js.map +1 -0
  342. package/dist/web-session.d.ts +167 -0
  343. package/dist/web-session.js +389 -0
  344. package/dist/web-session.js.map +1 -0
  345. package/dist/web-shell-routes.d.ts +59 -0
  346. package/dist/web-shell-routes.js +96 -0
  347. package/dist/web-shell-routes.js.map +1 -0
  348. package/dist/web-upload.d.ts +183 -0
  349. package/dist/web-upload.js +468 -0
  350. package/dist/web-upload.js.map +1 -0
  351. package/dist/website-tips-renderer.d.ts +2 -2
  352. package/dist/website-tips-renderer.js +3 -3
  353. package/dist/website-tips-renderer.js.map +1 -1
  354. package/package.json +8 -5
  355. package/scripts/preinstall-guard.cjs +36 -0
  356. package/dist/ui/dashboard.html +0 -774
  357. package/dist/ui/settings.html +0 -2165
  358. package/dist/ui/view.html +0 -1139
@@ -0,0 +1,277 @@
1
+ // #1408 step 3: what Settings computes, apart from how it is shown — pure functions over the loaded configuration.
2
+ // Ported from settings.html with their reasons: every rule here was a fix (the review or issue named beside it).
3
+ // No DOM, no fetch; the panel (panel-settings.js) and the tests call these directly.
4
+
5
+ export const BACKENDS = ["claude-code", "kiro-cli", "codex", "opencode", "antigravity", "grok", "muse"];
6
+ export const CH_TYPES = ["discord", "telegram"];
7
+ export const LOCALES = [["en", "English"], ["zh-TW", "繁體中文 (zh-TW)"]];
8
+ export const VISIBILITY_MODES = ["full", "summary", "hidden"];
9
+ export const LOG_LEVELS = ["trace", "debug", "info", "warn", "error"];
10
+ export const TOOL_SETS = ["full", "standard", "minimal"];
11
+ export const TOOL_PROGRESS = ["off", "standard", "verbose"];
12
+ export const AGENT_MODES = ["mcp", "cli"];
13
+ export const ACCESS_MODES = ["locked", "open", "pairing"];
14
+ export const EFFORT_BACKENDS = new Set(["claude-code", "codex", "kiro-cli", "grok", "antigravity"]);
15
+ export const STATUS_ORDER = { running: 0, paused: 1, crashed: 2, stopped: 3 };
16
+
17
+ export const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key);
18
+ export const sameValue = (a, b) => JSON.stringify(a) === JSON.stringify(b);
19
+ /** The fields of `next` that differ from `baseline`. */
20
+ export function changedFields(next, baseline) {
21
+ return Object.fromEntries(Object.entries(next).filter(([key, value]) => !sameValue(value, baseline[key])));
22
+ }
23
+
24
+ /** #1302: cross-instance notices. Unset, or a value the fleet does not know, means full. */
25
+ export const visibilityDefault = (defaults) => (VISIBILITY_MODES.includes(defaults && defaults.cross_instance_visibility) ? defaults.cross_instance_visibility : "full");
26
+
27
+ export const replyGuardSupported = (backend, mode, kiroUi = "legacy") =>
28
+ mode === "mcp" && (backend === "claude-code" || (backend === "kiro-cli" && ["legacy", "tui"].includes(kiroUi || "legacy")));
29
+
30
+ /**
31
+ * The backend picker's options. A current value it does not offer (the removed gemini-cli, #1280 review) stays first
32
+ * and selected: otherwise the select falls to its first option and an unrelated edit switches the CLI and account.
33
+ */
34
+ export function backendOptions(value) {
35
+ return BACKENDS.includes(value) ? BACKENDS.map((b) => ({ value: b, label: b })) : [{ value, label: null, unavailable: true }, ...BACKENDS.map((b) => ({ value: b, label: b }))];
36
+ }
37
+ /**
38
+ * The locale picker (#1310): "Auto" for unset — without it the select falls to English and any unrelated defaults save
39
+ * writes locale: en, pinning the language and disabling timezone auto-detect. A value it does not offer is kept.
40
+ */
41
+ export function localeOptions(value) {
42
+ const out = [{ value: "", auto: true }];
43
+ if (value && !LOCALES.some(([v]) => v === value)) out.push({ value, label: value });
44
+ for (const [v, label] of LOCALES) out.push({ value: v, label });
45
+ return out;
46
+ }
47
+
48
+ // ── Connections ──
49
+ export const channelsOf = (fleet) => (fleet && fleet.channels) || (fleet && fleet.channel ? [fleet.channel] : []);
50
+ export const channelId = (ch, i) => ch.id || ch.type || `channel-${i}`;
51
+ export const chLabel = (i) => (i === 0 ? "primary" : "persona");
52
+
53
+ // ── Impacts: the server's (/api/settings/schema, derived from HOT_INSTANCE_CONFIG_KEYS); this page never decides one ──
54
+ export const DEFAULT_SCHEMA = { impacts: {}, order: ["now", "instance", "fleet"] };
55
+ export const impactOf = (schema, field) => (schema.impacts && schema.impacts[field]) || "instance";
56
+ /** A batch costs as much as its worst member, in the order the server ships. */
57
+ export function batchImpact(schema, keys, impactFor) {
58
+ const order = schema.order;
59
+ return order[keys.reduce((worst, k) => Math.max(worst, order.indexOf(impactFor(k))), 0)];
60
+ }
61
+ /** A defaults key's cost. `startup` holds the fleet's spawn settings, which the schema names on their own. */
62
+ export function defaultsImpact(schema, key) {
63
+ if (key === "startup") return worstImpact(schema, [impactOf(schema, "fleet.spawn_concurrency"), impactOf(schema, "fleet.spawn_stagger_ms")]);
64
+ return impactOf(schema, `defaults.${key}`);
65
+ }
66
+ /** What one write costs, from the schema: the worst of the fields it changes (Developer YAML's writes). */
67
+ export function requestImpact(schema, { method, url, body }) {
68
+ const keys = Object.keys(body && typeof body === "object" && !Array.isArray(body) ? body : {});
69
+ if (/\/fleet\/defaults$/.test(url)) return batchImpact(schema, keys, (k) => defaultsImpact(schema, k));
70
+ if (/\/fleet\/channels$/.test(url)) return impactOf(schema, "fleet.channels");
71
+ if (/\/fleet\/instances\/[^/]+$/.test(url)) {
72
+ if (method === "DELETE") return impactOf(schema, "instance.delete");
73
+ return batchImpact(schema, keys, (k) => impactOf(schema, `instance.${k}`));
74
+ }
75
+ return schema.order[schema.order.length - 1];
76
+ }
77
+ export function worstImpact(schema, impacts) {
78
+ const order = schema.order;
79
+ return order[impacts.reduce((worst, k) => Math.max(worst, order.indexOf(k)), 0)];
80
+ }
81
+
82
+ // ── Agents ──
83
+ /** Profile display_name, else the name without its -t<topicId> suffix. */
84
+ export const shortName = (name, inst) => (inst && inst.display_name) || String(name).replace(/-t\d+$/, "");
85
+ export const groupOf = (inst) => (inst && inst.tags && inst.tags[0]) || "Other";
86
+
87
+ /** What an agent runs: live values first, then its config, then the defaults. */
88
+ export function effectiveSummary(live, config, fleetDefaults, classicDefaults) {
89
+ const defaults = classicDefaults || fleetDefaults || {};
90
+ const backend = live.backend || config.backend || defaults.backend || (fleetDefaults && fleetDefaults.backend) || "claude-code";
91
+ const model = live.model_display || config.model || defaults.model || (fleetDefaults && fleetDefaults.model) || "default";
92
+ const effortSupported = live.effort_supported ?? EFFORT_BACKENDS.has(backend);
93
+ const effort = effortSupported ? (live.effort || config.effort || (fleetDefaults && fleetDefaults.effort) || "auto") : "—";
94
+ return { backend, model, effort };
95
+ }
96
+
97
+ /** Group by first tag, "Other" last; within a group Running → Paused → Crashed → Stopped, then by name. */
98
+ export function groupAgents(instances, live, filter) {
99
+ const needle = String(filter || "").trim().toLowerCase();
100
+ let names = Object.keys(instances || {});
101
+ if (needle) names = names.filter((n) => n.toLowerCase().includes(needle) || shortName(n, instances[n]).toLowerCase().includes(needle));
102
+ const groups = {};
103
+ for (const n of names) (groups[groupOf(instances[n])] ??= []).push(n);
104
+ const order = (n) => STATUS_ORDER[(live[n] && live[n].status) || "stopped"] ?? 9;
105
+ return Object.keys(groups).sort((a, b) => (a === "Other" ? 1 : b === "Other" ? -1 : a.localeCompare(b)))
106
+ .map((g) => ({ group: g, names: groups[g].sort((a, b) => order(a) - order(b) || a.localeCompare(b)) }));
107
+ }
108
+
109
+ /**
110
+ * The agent form's patch: only what changed against what the form showed. An override toggle "inherit" sends null
111
+ * (the key is removed); the visibility baseline is what the picker showed, so a value it cannot show (a typo, read as
112
+ * unset) is never rewritten by an unrelated edit (#1302).
113
+ */
114
+ export function agentPatch(form, inst, defaults, visibilityBaseline, statusEmojisBaseline) {
115
+ const hang = inst.hang_detector || {};
116
+ const next = {
117
+ backend: form.backend, model: form.model.trim() || null,
118
+ working_directory: form.working_directory, channel_id: form.channel_id || undefined,
119
+ description: form.description || undefined, systemPrompt: form.systemPrompt || undefined,
120
+ tags: form.tags.length ? form.tags : undefined, general_topic: form.general_topic,
121
+ auto_pause_after: form.inherit.auto_pause_after ? null : Number(form.auto_pause_after),
122
+ hang_detector: { timeout_minutes: form.inherit.hang ? null : Number(form.hang) },
123
+ agent_mode: form.inherit.agent_mode ? null : form.agent_mode,
124
+ tool_set: form.inherit.tool_set ? null : form.tool_set,
125
+ tool_progress: form.inherit.tool_progress ? null : form.tool_progress,
126
+ cross_instance_visibility: form.inherit.cross_instance_visibility ? null : form.cross_instance_visibility,
127
+ reply_completion_guard: form.inherit.reply_completion_guard ? null : form.reply_completion_guard,
128
+ log_level: form.inherit.log_level ? null : form.log_level,
129
+ lightweight: form.inherit.lightweight ? null : form.lightweight,
130
+ model_failover: form.inherit.model_failover ? null : (form.model_failover.trim() ? [form.model_failover.trim()] : []),
131
+ display_name: form.inherit.display_name ? null : form.display_name.trim(),
132
+ status_emojis: form.status_emojis,
133
+ };
134
+ return changedFields(next, {
135
+ backend: inst.backend || defaults.backend || "claude-code",
136
+ model: (inst.model && inst.model.trim()) || null,
137
+ working_directory: inst.working_directory || "",
138
+ channel_id: inst.channel_id || undefined,
139
+ description: inst.description || undefined,
140
+ systemPrompt: inst.systemPrompt || undefined,
141
+ tags: inst.tags && inst.tags.length ? inst.tags : undefined,
142
+ general_topic: !!inst.general_topic,
143
+ auto_pause_after: hasOwn(inst, "auto_pause_after") ? inst.auto_pause_after : null,
144
+ hang_detector: { timeout_minutes: hasOwn(hang, "timeout_minutes") ? hang.timeout_minutes : null },
145
+ agent_mode: hasOwn(inst, "agent_mode") ? inst.agent_mode : null,
146
+ tool_set: hasOwn(inst, "tool_set") ? inst.tool_set : null,
147
+ tool_progress: hasOwn(inst, "tool_progress") ? inst.tool_progress : null,
148
+ cross_instance_visibility: visibilityBaseline,
149
+ reply_completion_guard: hasOwn(inst, "reply_completion_guard") ? inst.reply_completion_guard : null,
150
+ log_level: hasOwn(inst, "log_level") ? inst.log_level : null,
151
+ lightweight: hasOwn(inst, "lightweight") ? inst.lightweight : null,
152
+ model_failover: hasOwn(inst, "model_failover") ? inst.model_failover : null,
153
+ display_name: hasOwn(inst, "display_name") ? inst.display_name : null,
154
+ status_emojis: statusEmojisBaseline,
155
+ });
156
+ }
157
+
158
+ /** The agent form's starting values (an override shows the inherited value while "inherit" is on). */
159
+ export function agentForm(inst, defaults) {
160
+ const hang = inst.hang_detector || {};
161
+ const inherit = (key) => !hasOwn(inst, key);
162
+ return {
163
+ backend: inst.backend || defaults.backend || "claude-code",
164
+ model: inst.model || "", working_directory: inst.working_directory || "", channel_id: inst.channel_id || "",
165
+ description: inst.description || "", systemPrompt: inst.systemPrompt || "", tags: [...(inst.tags || [])],
166
+ general_topic: !!inst.general_topic,
167
+ auto_pause_after: String(inst.auto_pause_after ?? defaults.auto_pause_after ?? 0),
168
+ hang: String(hang.timeout_minutes ?? (defaults.hang_detector && defaults.hang_detector.timeout_minutes) ?? 15),
169
+ agent_mode: inst.agent_mode || defaults.agent_mode || "mcp",
170
+ tool_set: inst.tool_set || defaults.tool_set || "full",
171
+ tool_progress: inst.tool_progress ?? defaults.tool_progress ?? "off",
172
+ // #1302: a value the fleet does not know (a typo) is read as unset — the picker shows "full", as the old page did,
173
+ // and the baseline is that same "full", so an unrelated edit never writes the typo back (the server refuses it).
174
+ cross_instance_visibility: hasOwn(inst, "cross_instance_visibility")
175
+ ? (VISIBILITY_MODES.includes(inst.cross_instance_visibility) ? inst.cross_instance_visibility : VISIBILITY_MODES[0])
176
+ : visibilityDefault(defaults),
177
+ reply_completion_guard: inst.reply_completion_guard ?? defaults.reply_completion_guard ?? true,
178
+ log_level: inst.log_level || defaults.log_level || "info",
179
+ lightweight: inst.lightweight ?? defaults.lightweight ?? false,
180
+ model_failover: Array.isArray(inst.model_failover) ? (inst.model_failover[0] || "") : ((defaults.model_failover && defaults.model_failover[0]) || ""),
181
+ display_name: inst.display_name ?? defaults.display_name ?? "",
182
+ status_emojis: inst.status_emojis || null,
183
+ inherit: {
184
+ auto_pause_after: inherit("auto_pause_after"), hang: !hasOwn(hang, "timeout_minutes"), agent_mode: inherit("agent_mode"),
185
+ tool_set: inherit("tool_set"), tool_progress: inherit("tool_progress"), cross_instance_visibility: inherit("cross_instance_visibility"),
186
+ reply_completion_guard: inherit("reply_completion_guard"), log_level: inherit("log_level"), lightweight: inherit("lightweight"),
187
+ model_failover: inherit("model_failover"), display_name: inherit("display_name"),
188
+ },
189
+ };
190
+ }
191
+
192
+ /** Every field the agent dialog shows, for its footer's impact summary. */
193
+ export const AGENT_FIELDS = [
194
+ "instance.display_name", "instance.description", "instance.backend", "instance.model", "instance.working_directory",
195
+ "instance.auto_pause_after", "instance.topic_id", "instance.hang_detector", "instance.agent_mode", "instance.tool_set",
196
+ "instance.tool_progress", "instance.cross_instance_visibility", "instance.reply_completion_guard", "instance.log_level",
197
+ "instance.lightweight", "instance.model_failover", "instance.systemPrompt", "instance.tags", "instance.general_topic",
198
+ "instance.status_emojis",
199
+ ];
200
+ export const CLASSIC_FIELDS = ["classic.backend", "classic.model", "classic.auto_pause_after", "classic.tool_progress",
201
+ "classic.reply_completion_guard", "classic.collab", "classic.context_lines", "classic.web_echo"];
202
+ export const BOT_FIELDS = ["fleet.channel.access.mode", "fleet.channel.access.allowed_users", "fleet.channels", "fleet.channel.options.status_emojis"];
203
+
204
+ /** Validation messages (keys of the settings namespace), or "" when the value is fine. */
205
+ export const nonNegative = (v) => (!Number.isFinite(Number(v)) || String(v).trim() === "" || Number(v) < 0 ? "mustNonNegative" : "");
206
+ export const positive = (v) => (!Number.isFinite(Number(v)) || String(v).trim() === "" || Number(v) <= 0 ? "mustPositive" : "");
207
+ export const nonNegativeInteger = (v) => (!Number.isInteger(Number(v)) || String(v).trim() === "" || Number(v) < 0 ? "nonnegativeInteger" : "");
208
+
209
+ /**
210
+ * The confirmations an access change needs (#996): opening a locked connection, and locking one with nobody allowed
211
+ * (which shuts everyone out). Keys of the settings namespace, in order.
212
+ */
213
+ export function accessConfirmations(previousAccess, nextMode, nextUsers) {
214
+ const previousMode = (previousAccess && previousAccess.mode) || "locked";
215
+ const previousUsers = (previousAccess && previousAccess.allowed_users) || [];
216
+ const out = [];
217
+ if (previousMode !== "open" && nextMode === "open") out.push("confirmOpenAccess");
218
+ if (nextMode === "locked" && nextUsers.length === 0 && (previousMode !== "locked" || previousUsers.length > 0)) out.push("confirmLockedEmpty");
219
+ return out;
220
+ }
221
+
222
+ /** #1320: only a short positive numeric id is a Telegram private chat; Discord snowflakes and negative ids are groups. */
223
+ export const webEchoIsGroup = (channelIdValue) => !/^\d{1,12}$/.test(String(channelIdValue || ""));
224
+
225
+ // ── Developer YAML: the config subset's writer and reader, exactly as settings.html had them (round-trip verified) ──
226
+ export function toYaml(value, indent = 0) {
227
+ const pad = " ".repeat(indent);
228
+ const scalar = (v) => { if (v === null || v === undefined) return "null"; if (typeof v === "boolean" || typeof v === "number") return String(v); const s = String(v); if (s === "") return '""'; if (/^(true|false|null|~|-?\d+(\.\d+)?)$/i.test(s) || /[:#\[\]{}&*!|>'"%@`]/.test(s) || /^[\s-]/.test(s) || /\s$/.test(s) || s.includes("\n")) return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; return s; };
229
+ if (Array.isArray(value)) { if (!value.length) return "[]"; return value.map(item => { if (item !== null && typeof item === "object") { const d = toYaml(item, indent + 1); const lines = d.split("\n"); return pad + "- " + lines[0].slice((indent + 1) * 2) + (lines.length > 1 ? "\n" + lines.slice(1).join("\n") : ""); } return pad + "- " + scalar(item); }).join("\n"); }
230
+ if (value !== null && typeof value === "object") { const keys = Object.keys(value); if (!keys.length) return "{}"; return keys.map(k => { const v = value[k]; if (v !== null && typeof v === "object" && (Array.isArray(v) ? v.length : Object.keys(v).length)) return pad + k + ":\n" + toYaml(v, indent + 1); if (v !== null && typeof v === "object") return pad + k + ": " + (Array.isArray(v) ? "[]" : "{}"); return pad + k + ": " + scalar(v); }).join("\n"); }
231
+ return pad + scalar(value);
232
+ }
233
+ function parseScalar(s) { s = s.trim(); if (s === "" || s === "~" || s === "null") return null; if (s === "true") return true; if (s === "false") return false; if (/^-?\d+$/.test(s)) return parseInt(s, 10); if (/^-?\d+\.\d+$/.test(s)) return parseFloat(s); if (s.startsWith('"') && s.endsWith('"')) return s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\"); if (s.startsWith("'") && s.endsWith("'")) return s.slice(1, -1); if (s === "[]") return []; if (s === "{}") return {}; return s; }
234
+ export function fromYaml(text) {
235
+ const lines = [];
236
+ for (const raw of text.replace(/\r/g, "").split("\n")) { let inS = false, inD = false, out = ""; for (let i = 0; i < raw.length; i++) { const c = raw[i]; if (c === "'" && !inD) inS = !inS; else if (c === '"' && !inS) inD = !inD; else if (c === "#" && !inS && !inD) break; out += c; } if (out.trim() === "") continue; lines.push({ indent: out.match(/^ */)[0].length, text: out.trim() }); }
237
+ let pos = 0;
238
+ function block() { if (pos >= lines.length) return null; const first = lines[pos]; if (first.text.startsWith("- ")) { const arr = []; while (pos < lines.length && lines[pos].indent === first.indent && lines[pos].text.startsWith("- ")) { const at = lines[pos].indent, rest = lines[pos].text.slice(2); if (rest.includes(":") && !/^".*"$/.test(rest) && !rest.startsWith("[") && !rest.startsWith("{")) { lines[pos] = { indent: at + 2, text: rest }; arr.push(map(at + 2)); } else { arr.push(parseScalar(rest)); pos++; } } return arr; } return map(first.indent); }
239
+ function map(indent) { const obj = {}; while (pos < lines.length && lines[pos].indent === indent) { const line = lines[pos].text, ci = line.indexOf(":"); if (ci < 0) { pos++; continue; } const key = line.slice(0, ci).trim(), val = line.slice(ci + 1).trim(); if (val === "") { pos++; if (pos < lines.length && lines[pos].indent > indent) obj[key] = block(); else obj[key] = null; } else { obj[key] = parseScalar(val); pos++; } } return obj; }
240
+ return block();
241
+ }
242
+
243
+ /** The fleet model the Developer view shows and edits. */
244
+ export const fleetModel = (fleet) => ({
245
+ ...(fleet.channels ? { channels: fleet.channels } : fleet.channel ? { channel: fleet.channel } : {}),
246
+ defaults: fleet.defaults || {}, instances: fleet.instances || {},
247
+ });
248
+
249
+ /** The same value, whatever the order of its keys (a YAML edit may reorder them). */
250
+ function canon(v) {
251
+ if (Array.isArray(v)) return `[${v.map(canon).join(",")}]`;
252
+ if (v && typeof v === "object") return `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canon(v[k])}`).join(",")}}`;
253
+ return JSON.stringify(v === undefined ? null : v);
254
+ }
255
+ export const sameConfig = (a, b) => canon(a) === canon(b);
256
+
257
+ /**
258
+ * The requests an edited full model makes — only for what it changes against the configuration it was edited from
259
+ * (#1408 step 5: an unchanged section is not written again): defaults, channels, then each instance added, changed
260
+ * or removed.
261
+ */
262
+ export function fullModelRequests(model, current) {
263
+ const out = [];
264
+ const fleet = current || {};
265
+ if (model.defaults && !sameConfig(model.defaults, fleet.defaults || {})) out.push({ method: "PUT", url: "/api/settings/fleet/defaults", body: model.defaults });
266
+ const chs = model.channels || (model.channel ? [model.channel] : null);
267
+ if (chs && !sameConfig(chs, channelsOf(fleet))) out.push({ method: "PUT", url: "/api/settings/fleet/channels", body: chs });
268
+ if (model.instances && typeof model.instances === "object") {
269
+ const cur = fleet.instances || {};
270
+ for (const [n, cfg] of Object.entries(model.instances)) {
271
+ if (cur[n] && sameConfig(cfg, cur[n])) continue;
272
+ out.push({ method: cur[n] ? "PATCH" : "POST", url: `/api/settings/fleet/instances/${encodeURIComponent(n)}`, body: cfg });
273
+ }
274
+ for (const n of Object.keys(cur)) if (!(n in model.instances)) out.push({ method: "DELETE", url: `/api/settings/fleet/instances/${encodeURIComponent(n)}` });
275
+ }
276
+ return out;
277
+ }