@songsid/agend 2.3.2-beta.1 → 2.3.2-beta.11

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 (298) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/dist/agent-cli.js +69 -2
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.js +11 -0
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/agent-whats-new.js +3 -0
  7. package/dist/agent-whats-new.js.map +1 -1
  8. package/dist/antigravity-usage-scan.d.ts +58 -0
  9. package/dist/antigravity-usage-scan.js +257 -0
  10. package/dist/antigravity-usage-scan.js.map +1 -0
  11. package/dist/antigravity-usage-worker.d.ts +1 -0
  12. package/dist/antigravity-usage-worker.js +10 -0
  13. package/dist/antigravity-usage-worker.js.map +1 -0
  14. package/dist/auth-api.js +4 -0
  15. package/dist/auth-api.js.map +1 -1
  16. package/dist/backend/kiro-classic-archive.d.ts +52 -0
  17. package/dist/backend/kiro-classic-archive.js +444 -0
  18. package/dist/backend/kiro-classic-archive.js.map +1 -0
  19. package/dist/backend/kiro-identity.d.ts +83 -0
  20. package/dist/backend/kiro-identity.js +144 -0
  21. package/dist/backend/kiro-identity.js.map +1 -1
  22. package/dist/backend/kiro-migration-verify.d.ts +10 -0
  23. package/dist/backend/kiro-migration-verify.js +52 -0
  24. package/dist/backend/kiro-migration-verify.js.map +1 -0
  25. package/dist/backend/kiro-retired-model.d.ts +62 -0
  26. package/dist/backend/kiro-retired-model.js +160 -0
  27. package/dist/backend/kiro-retired-model.js.map +1 -0
  28. package/dist/backend/kiro-v2-store-worker.js +21 -1
  29. package/dist/backend/kiro-v2-store-worker.js.map +1 -1
  30. package/dist/backend/kiro-v2-store.d.ts +8 -0
  31. package/dist/backend/kiro-v2-store.js.map +1 -1
  32. package/dist/backend/kiro.d.ts +169 -2
  33. package/dist/backend/kiro.js +703 -28
  34. package/dist/backend/kiro.js.map +1 -1
  35. package/dist/backend/types.d.ts +108 -0
  36. package/dist/backend/types.js +9 -0
  37. package/dist/backend/types.js.map +1 -1
  38. package/dist/backend-signin-guide.d.ts +1 -0
  39. package/dist/backend-signin-guide.js +9 -0
  40. package/dist/backend-signin-guide.js.map +1 -0
  41. package/dist/backend-signin-pending.d.ts +10 -0
  42. package/dist/backend-signin-pending.js +44 -0
  43. package/dist/backend-signin-pending.js.map +1 -0
  44. package/dist/browser-backend-signins.d.ts +56 -0
  45. package/dist/browser-backend-signins.js +189 -0
  46. package/dist/browser-backend-signins.js.map +1 -0
  47. package/dist/cache-ledger.d.ts +38 -2
  48. package/dist/cache-ledger.js +133 -8
  49. package/dist/cache-ledger.js.map +1 -1
  50. package/dist/cache-prices.d.ts +2 -1
  51. package/dist/cache-prices.js +5 -2
  52. package/dist/cache-prices.js.map +1 -1
  53. package/dist/cache-service.d.ts +68 -0
  54. package/dist/cache-service.js +355 -13
  55. package/dist/cache-service.js.map +1 -1
  56. package/dist/channel/adapters/discord.d.ts +11 -0
  57. package/dist/channel/adapters/discord.js +71 -14
  58. package/dist/channel/adapters/discord.js.map +1 -1
  59. package/dist/channel/ipc-timeouts.js +3 -0
  60. package/dist/channel/ipc-timeouts.js.map +1 -1
  61. package/dist/channel/mcp-tools.js +41 -2
  62. package/dist/channel/mcp-tools.js.map +1 -1
  63. package/dist/channel/tool-router.js +2 -1
  64. package/dist/channel/tool-router.js.map +1 -1
  65. package/dist/classic-channel-manager.d.ts +11 -0
  66. package/dist/classic-channel-manager.js +39 -1
  67. package/dist/classic-channel-manager.js.map +1 -1
  68. package/dist/cli.js +54 -2
  69. package/dist/cli.js.map +1 -1
  70. package/dist/config-validator.d.ts +7 -1
  71. package/dist/config-validator.js +55 -10
  72. package/dist/config-validator.js.map +1 -1
  73. package/dist/config.d.ts +19 -0
  74. package/dist/config.js +33 -1
  75. package/dist/config.js.map +1 -1
  76. package/dist/cpu-quota.d.ts +23 -0
  77. package/dist/cpu-quota.js +86 -0
  78. package/dist/cpu-quota.js.map +1 -0
  79. package/dist/daemon.d.ts +68 -0
  80. package/dist/daemon.js +448 -26
  81. package/dist/daemon.js.map +1 -1
  82. package/dist/dashboard-permissions.d.ts +31 -6
  83. package/dist/dashboard-permissions.js +84 -9
  84. package/dist/dashboard-permissions.js.map +1 -1
  85. package/dist/doctor.js +2 -1
  86. package/dist/doctor.js.map +1 -1
  87. package/dist/fleet-control-audit.d.ts +1 -1
  88. package/dist/fleet-control-audit.js +3 -0
  89. package/dist/fleet-control-audit.js.map +1 -1
  90. package/dist/fleet-manager.d.ts +133 -2
  91. package/dist/fleet-manager.js +905 -94
  92. package/dist/fleet-manager.js.map +1 -1
  93. package/dist/forged-envelope-scan.d.ts +9 -0
  94. package/dist/forged-envelope-scan.js +30 -5
  95. package/dist/forged-envelope-scan.js.map +1 -1
  96. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
  97. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +24 -1
  98. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +49 -1
  99. package/dist/general-knowledge/skills/web-appearance/SKILL.md +49 -1
  100. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +47 -9
  101. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +54 -11
  102. package/dist/instance-config-impact.js +10 -5
  103. package/dist/instance-config-impact.js.map +1 -1
  104. package/dist/instance-lifecycle.d.ts +23 -0
  105. package/dist/instance-lifecycle.js +62 -12
  106. package/dist/instance-lifecycle.js.map +1 -1
  107. package/dist/kiro-db-reader.d.ts +5 -0
  108. package/dist/kiro-db-reader.js +18 -0
  109. package/dist/kiro-db-reader.js.map +1 -1
  110. package/dist/kiro-engine-status.d.ts +2 -0
  111. package/dist/kiro-engine-status.js +9 -0
  112. package/dist/kiro-engine-status.js.map +1 -1
  113. package/dist/kiro-migrate.d.ts +60 -0
  114. package/dist/kiro-migrate.js +160 -0
  115. package/dist/kiro-migrate.js.map +1 -0
  116. package/dist/kiro-session-proof.d.ts +41 -0
  117. package/dist/kiro-session-proof.js +89 -0
  118. package/dist/kiro-session-proof.js.map +1 -0
  119. package/dist/kiro-transcript-lane.d.ts +2 -0
  120. package/dist/kiro-transcript-lane.js.map +1 -1
  121. package/dist/kiro-transcript-worker.js +1 -0
  122. package/dist/kiro-transcript-worker.js.map +1 -1
  123. package/dist/kiro-tui-forensic.d.ts +12 -0
  124. package/dist/kiro-tui-forensic.js +110 -0
  125. package/dist/kiro-tui-forensic.js.map +1 -0
  126. package/dist/kiro-usage-run.d.ts +7 -0
  127. package/dist/kiro-usage-run.js +39 -0
  128. package/dist/kiro-usage-run.js.map +1 -0
  129. package/dist/kiro-usage-scan.d.ts +49 -0
  130. package/dist/kiro-usage-scan.js +223 -0
  131. package/dist/kiro-usage-scan.js.map +1 -0
  132. package/dist/kiro-usage-worker.d.ts +1 -0
  133. package/dist/kiro-usage-worker.js +10 -0
  134. package/dist/kiro-usage-worker.js.map +1 -0
  135. package/dist/kiro-usage.d.ts +58 -0
  136. package/dist/kiro-usage.js +310 -0
  137. package/dist/kiro-usage.js.map +1 -0
  138. package/dist/locale.js +230 -8
  139. package/dist/locale.js.map +1 -1
  140. package/dist/log-service.js +1 -1
  141. package/dist/log-service.js.map +1 -1
  142. package/dist/login-url.d.ts +4 -0
  143. package/dist/login-url.js +22 -0
  144. package/dist/login-url.js.map +1 -0
  145. package/dist/needs-you-hub.d.ts +2 -1
  146. package/dist/needs-you-hub.js +1 -1
  147. package/dist/needs-you-hub.js.map +1 -1
  148. package/dist/needs-you.d.ts +10 -2
  149. package/dist/needs-you.js +10 -2
  150. package/dist/needs-you.js.map +1 -1
  151. package/dist/notice-delivery.d.ts +16 -0
  152. package/dist/notice-delivery.js +35 -0
  153. package/dist/notice-delivery.js.map +1 -0
  154. package/dist/office-share-page.d.ts +16 -0
  155. package/dist/office-share-page.js +85 -0
  156. package/dist/office-share-page.js.map +1 -0
  157. package/dist/office-share-service.d.ts +91 -0
  158. package/dist/office-share-service.js +176 -0
  159. package/dist/office-share-service.js.map +1 -0
  160. package/dist/office-share.d.ts +268 -0
  161. package/dist/office-share.js +574 -0
  162. package/dist/office-share.js.map +1 -0
  163. package/dist/opencode-usage-scan.d.ts +28 -0
  164. package/dist/opencode-usage-scan.js +92 -0
  165. package/dist/opencode-usage-scan.js.map +1 -0
  166. package/dist/opencode-usage-worker.d.ts +1 -0
  167. package/dist/opencode-usage-worker.js +10 -0
  168. package/dist/opencode-usage-worker.js.map +1 -0
  169. package/dist/outbound-handlers.js +22 -2
  170. package/dist/outbound-handlers.js.map +1 -1
  171. package/dist/outbound-schemas.d.ts +65 -2
  172. package/dist/outbound-schemas.js +56 -2
  173. package/dist/outbound-schemas.js.map +1 -1
  174. package/dist/public-web-gateway.js +7 -1
  175. package/dist/public-web-gateway.js.map +1 -1
  176. package/dist/quickstart-api.js +13 -2
  177. package/dist/quickstart-api.js.map +1 -1
  178. package/dist/quickstart.js +2 -1
  179. package/dist/quickstart.js.map +1 -1
  180. package/dist/remote-card-answer.d.ts +83 -0
  181. package/dist/remote-card-answer.js +208 -0
  182. package/dist/remote-card-answer.js.map +1 -0
  183. package/dist/remote-card-prompt.d.ts +38 -0
  184. package/dist/remote-card-prompt.js +86 -0
  185. package/dist/remote-card-prompt.js.map +1 -0
  186. package/dist/reply-triggers.d.ts +2 -0
  187. package/dist/reply-triggers.js +3 -0
  188. package/dist/reply-triggers.js.map +1 -0
  189. package/dist/settings-api.d.ts +2 -0
  190. package/dist/settings-api.js +41 -16
  191. package/dist/settings-api.js.map +1 -1
  192. package/dist/settings-change.js +20 -14
  193. package/dist/settings-change.js.map +1 -1
  194. package/dist/settings-effect.js +7 -1
  195. package/dist/settings-effect.js.map +1 -1
  196. package/dist/settings-instance-patch.js +4 -1
  197. package/dist/settings-instance-patch.js.map +1 -1
  198. package/dist/setup-form.d.ts +1 -1
  199. package/dist/setup-form.js +55 -6
  200. package/dist/setup-form.js.map +1 -1
  201. package/dist/setup-host.d.ts +9 -0
  202. package/dist/setup-host.js +127 -6
  203. package/dist/setup-host.js.map +1 -1
  204. package/dist/setup-signin-assets.d.ts +2 -0
  205. package/dist/setup-signin-assets.js +6 -0
  206. package/dist/setup-signin-assets.js.map +1 -0
  207. package/dist/setup-wizard.js +2 -0
  208. package/dist/setup-wizard.js.map +1 -1
  209. package/dist/structured-signin-transport.d.ts +8 -0
  210. package/dist/structured-signin-transport.js +79 -0
  211. package/dist/structured-signin-transport.js.map +1 -0
  212. package/dist/structured-signin.d.ts +131 -0
  213. package/dist/structured-signin.js +388 -0
  214. package/dist/structured-signin.js.map +1 -0
  215. package/dist/tool-permissions.js +29 -0
  216. package/dist/tool-permissions.js.map +1 -1
  217. package/dist/topic-commands.js +6 -2
  218. package/dist/topic-commands.js.map +1 -1
  219. package/dist/transcript-sources.d.ts +27 -2
  220. package/dist/transcript-sources.js +104 -40
  221. package/dist/transcript-sources.js.map +1 -1
  222. package/dist/turn-reply-guard.d.ts +1 -1
  223. package/dist/turn-reply-guard.js +1 -1
  224. package/dist/turn-reply-guard.js.map +1 -1
  225. package/dist/types.d.ts +33 -8
  226. package/dist/ui/backend-signin.js +63 -10
  227. package/dist/ui/chat-store.js +3 -2
  228. package/dist/ui/chat-strings.js +4 -4
  229. package/dist/ui/fleet-office-editor.js +68 -11
  230. package/dist/ui/fleet-office-layout.js +110 -35
  231. package/dist/ui/fleet-office-model.js +6 -3
  232. package/dist/ui/fleet-office-overview.js +167 -0
  233. package/dist/ui/fleet-office-pack.d.ts +13 -3
  234. package/dist/ui/fleet-office-pack.js +214 -24
  235. package/dist/ui/fleet-office-skin.js +98 -23
  236. package/dist/ui/fleet-office-store.js +82 -5
  237. package/dist/ui/fleet-office-view.js +5 -4
  238. package/dist/ui/fleet-office-zones.js +232 -21
  239. package/dist/ui/fleet-office.js +549 -186
  240. package/dist/ui/fleet-usage.js +19 -15
  241. package/dist/ui/office-share-dialog.js +145 -0
  242. package/dist/ui/office-share-page.js +164 -0
  243. package/dist/ui/panel-chat.js +22 -3
  244. package/dist/ui/panel-log.js +4 -4
  245. package/dist/ui/panel-needs.js +5 -5
  246. package/dist/ui/panel-settings.js +38 -12
  247. package/dist/ui/settings-dialogs.js +8 -1
  248. package/dist/ui/settings-guests.js +17 -7
  249. package/dist/ui/settings-model.js +22 -6
  250. package/dist/ui/settings-registry.js +4 -0
  251. package/dist/ui/settings-strings.js +18 -2
  252. package/dist/ui/shared/app-i18n.js +4 -2
  253. package/dist/ui/shared/app-shell.js +14 -6
  254. package/dist/ui/shared/app-stream.js +2 -1
  255. package/dist/ui/shared/app.css +85 -4
  256. package/dist/ui/shared/office/cozy.png +0 -0
  257. package/dist/ui/shared/side-panel.js +95 -0
  258. package/dist/ui/structured-signin.js +115 -0
  259. package/dist/usage-report.d.ts +20 -0
  260. package/dist/usage-report.js +54 -17
  261. package/dist/usage-report.js.map +1 -1
  262. package/dist/usage-rollup.d.ts +9 -1
  263. package/dist/usage-rollup.js +20 -4
  264. package/dist/usage-rollup.js.map +1 -1
  265. package/dist/web-api.d.ts +23 -1
  266. package/dist/web-api.js +292 -3
  267. package/dist/web-api.js.map +1 -1
  268. package/dist/web-auth.js +1 -1
  269. package/dist/web-auth.js.map +1 -1
  270. package/dist/web-backend-login.d.ts +1 -7
  271. package/dist/web-backend-login.js +2 -24
  272. package/dist/web-backend-login.js.map +1 -1
  273. package/dist/web-office-acts.d.ts +173 -0
  274. package/dist/web-office-acts.js +285 -0
  275. package/dist/web-office-acts.js.map +1 -0
  276. package/dist/web-office-pack-read.d.ts +35 -0
  277. package/dist/web-office-pack-read.js +252 -0
  278. package/dist/web-office-pack-read.js.map +1 -0
  279. package/dist/web-office-pack-tools.d.ts +232 -0
  280. package/dist/web-office-pack-tools.js +754 -0
  281. package/dist/web-office-pack-tools.js.map +1 -0
  282. package/dist/web-office-skins.d.ts +71 -6
  283. package/dist/web-office-skins.js +321 -35
  284. package/dist/web-office-skins.js.map +1 -1
  285. package/dist/web-office-store.d.ts +5 -2
  286. package/dist/web-office-store.js +69 -10
  287. package/dist/web-office-store.js.map +1 -1
  288. package/dist/web-settings-patch.d.ts +2 -1
  289. package/dist/web-settings-patch.js +4 -1
  290. package/dist/web-settings-patch.js.map +1 -1
  291. package/dist/working-recovery.d.ts +2 -2
  292. package/dist/working-recovery.js +43 -8
  293. package/dist/working-recovery.js.map +1 -1
  294. package/dist/working-state-journal.d.ts +2 -0
  295. package/dist/working-state-journal.js +16 -1
  296. package/dist/working-state-journal.js.map +1 -1
  297. package/package.json +3 -2
  298. package/templates/windows-shortcuts.ps1 +155 -140
@@ -573,6 +573,9 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
573
573
  the message's overflow-wrap: anywhere that is one character wide (the #1672 status line's time, one glyph per line). */
574
574
  .msg .time { font-family: var(--font-mono); white-space: nowrap; flex: none; }
575
575
  .msg.user { align-self: flex-end; max-width: min(80%, 620px); }
576
+ /* Full width (the header's ⋯ menu, this browser's choice): the thread and the composer take the panel, less their own
577
+ padding, for wide tables and code; a person's bubble keeps its cap (above). A phone's column is the screen already. */
578
+ @media (min-width: 900px) { .p-chat[data-width="full"] .thread, .p-chat[data-width="full"] .dock-col { max-width: none; } }
576
579
  .msg.user .meta { justify-content: flex-end; }
577
580
  /* #1773: the bubble hugs its text. The message is as wide as its widest child — usually the meta row (sender, a time
578
581
  such as 「昨天 下午03:58」, ticks) — and a block body would stretch to it; fit-content keeps a short message short, on
@@ -870,6 +873,9 @@ img.amb-p { object-fit: contain; }
870
873
  .st-link:hover { text-decoration-thickness: 2px; }
871
874
  .prompt .acts { display: flex; gap: var(--s-2); margin-top: var(--s-2); align-items: center; flex-wrap: wrap; }
872
875
  .prompt .wait { font-size: var(--fs-xs); color: var(--text-3); }
876
+ /* #2675: a prompt's typed answer. 16px keeps iOS from zooming in on focus (#1612). */
877
+ .prompt .prompt-text { display: flex; gap: var(--s-2); margin-top: var(--s-2); }
878
+ .prompt .prompt-text input { flex: 1; min-width: 0; font-size: 16px; }
873
879
  .work-bar { display: flex; gap: var(--s-2); align-items: center; padding: 0 var(--s-2); font-size: var(--fs-sm); color: var(--text-2); }
874
880
  .work-bar .lbl { flex: 1; min-width: 0; }
875
881
  .work-bar .pulse { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); animation: pulse 1.2s ease-in-out infinite; flex-shrink: 0; }
@@ -928,6 +934,8 @@ img.amb-p { object-fit: contain; }
928
934
  .s-guests .guest-names { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 2px var(--s-3); max-height: 220px; overflow: auto;
929
935
  padding: var(--s-1) 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
930
936
  .s-guests .guest-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
937
+ /* #2667: the ClassicBot rooms follow the fleet's instances under their own heading, across the grid. */
938
+ .s-guests .guest-names-group { grid-column: 1 / -1; margin-top: var(--s-2); }
931
939
  .s-guests .guest-list, .s-guests .g-pick { list-style: none; margin: 0 0 var(--s-3); padding: 0; display: flex; flex-direction: column; border-top: 1px solid var(--border); }
932
940
  .s-guests .guest-row, .s-guests .g-cand { padding: var(--s-3) 0; border-bottom: 1px solid var(--border); min-width: 0; }
933
941
  .s-guests .guest-row.s-dirty { border-radius: 0; margin-left: 0; align-self: stretch; padding-left: calc(var(--s-2) + 3px); padding-right: var(--s-2); }
@@ -1345,6 +1353,18 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
1345
1353
  @media (prefers-reduced-motion: no-preference) { .u-crow { transition: background-color 0.15s var(--ease); } }
1346
1354
  .u-rail-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--s-3) var(--s-4); display: flex; flex-direction: column; gap: var(--s-3); }
1347
1355
  @media (max-width: 1199px) { .u-rail { display: none; } }
1356
+ /* #2585: the docked side panel (side-panel.js), in the usage rail's place beside the page — a non-modal dialog, so the
1357
+ shared dialog's head, body and actions, laid out as a full-height column. Below the rail's width it is never drawn
1358
+ (the editor is the centred Dialog there). */
1359
+ .dlg.side-panel { position: static; inset: auto; margin: 0; width: 400px; max-width: 400px; height: auto; max-height: none; flex-shrink: 0;
1360
+ display: flex; flex-direction: column; min-height: 0; border-radius: 0; box-shadow: none; border-left: 1px solid var(--border); background: var(--bg); color: var(--text); }
1361
+ .side-panel .dlg-box { flex: 1; min-height: 0; max-height: none; }
1362
+ .side-panel .dlg-head { padding: var(--s-3) var(--s-3) var(--s-3) var(--s-5); border-bottom: 1px solid var(--border); }
1363
+ .side-panel .dlg-body { flex: 1; min-height: 0; padding-top: var(--s-4); }
1364
+ .side-panel .dlg-actions { padding: var(--s-3) var(--s-5); border-top: 1px solid var(--border); }
1365
+ @media (max-width: 1199px) { .dlg.side-panel { display: none; } }
1366
+ /* A toast (Offices saved.) beside a docked panel, not over its Cancel and Save. */
1367
+ @media (min-width: 1200px) { .shell:has(> .side-panel) .toasts { right: calc(400px + var(--s-6)); } }
1348
1368
  @media (max-width: 899px) {
1349
1369
  .hide-narrow { display: none; }
1350
1370
  .v-id { padding-left: var(--s-3); padding-right: var(--s-3); }
@@ -1728,7 +1748,7 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1728
1748
  .oc-office > .oc-bar { margin: 0; padding: var(--s-3) var(--s-5) var(--s-2); }
1729
1749
  .oc-office > .oc-legend { margin: 0 var(--s-5) var(--s-2); }
1730
1750
  .oo { flex: 1 0 auto; display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; width: 100%; }
1731
- .oo > :not(.oo-stage) { margin-left: var(--s-5); margin-right: var(--s-5); }
1751
+ .oo > :not(.oo-stage):not(dialog) { margin-left: var(--s-5); margin-right: var(--s-5); } /* not the editors' dialogs: they centre themselves (#2585) */
1732
1752
  .oo-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
1733
1753
  .oo-pick { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-xs); color: var(--text-2); }
1734
1754
  .oo-pick .tb-select { max-width: 200px; }
@@ -1762,6 +1782,8 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1762
1782
  .oo-people-list label { display: flex; align-items: center; gap: 8px; padding: 3px 2px; font-size: var(--fs-xs); color: var(--text); cursor: pointer; overflow-wrap: anywhere; }
1763
1783
  .oo-note { font-size: var(--fs-xs); }
1764
1784
  .oo-stage { position: relative; flex: 1 1 auto; min-height: 340px; overflow: hidden; background: var(--bg); touch-action: none; user-select: none; -webkit-user-select: none; }
1785
+ /* #2554: clip, not a scroll container — nothing past the edge (a tile's label) can scroll the stage and its controls. */
1786
+ .oo-stage { overflow: clip; }
1765
1787
  .oo-canvas { position: absolute; left: 0; top: 0; display: block; image-rendering: pixelated; cursor: grab; touch-action: none; }
1766
1788
  .oo-canvas:active { cursor: grabbing; }
1767
1789
  .oo-canvas:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
@@ -1785,6 +1807,16 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1785
1807
  .oo-mini-dot.match { fill: var(--focus); }
1786
1808
  .oo-tip { position: absolute; left: 0; top: 0; z-index: 5; max-width: 240px; pointer-events: none; padding: 6px 10px; font-size: var(--fs-xs); line-height: 1.4;
1787
1809
  color: var(--text); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--shadow-2); overflow-wrap: anywhere; }
1810
+ /* #2554: All offices — each tile's label (office name and head count) just above the tile, placed by the page (left, top,
1811
+ max-width through the CSSOM); a button that opens the office, the lobby's a plain chip. */
1812
+ .oo-tile-label { position: absolute; left: 0; top: 0; z-index: 3; transform: translateY(calc(-100% - 4px)); max-width: 240px; overflow: hidden; text-overflow: ellipsis;
1813
+ white-space: nowrap; padding: 2px 8px; font: inherit; font-size: var(--fs-xs); font-weight: var(--fw-semibold); line-height: 18px; color: var(--text);
1814
+ background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-sm); }
1815
+ button.oo-tile-label { cursor: pointer; }
1816
+ button.oo-tile-label:hover { border-color: var(--accent); }
1817
+ button.oo-tile-label:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
1818
+ .oo-tile-label.lobby { color: var(--text-2); }
1819
+ .oo-lobby-tile { font-size: var(--fs-xs); color: var(--text-2); }
1788
1820
  /* The desks as a list: for a screen reader, and shown (over the stage's corner) while the keyboard is in it. */
1789
1821
  .oo-desks { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; list-style: none; }
1790
1822
  .oo-desks:focus-within { position: absolute; z-index: 7; left: var(--s-2); bottom: var(--s-2); width: auto; height: auto; max-height: 40%; margin: 0; padding: var(--s-1); clip: auto; overflow-y: auto;
@@ -1819,22 +1851,42 @@ html.oo-immersive .p-fleet > .panel-body { z-index: 50; }
1819
1851
  .ol-seat > circle { fill: var(--ok); }
1820
1852
  .ol-furniture > rect { fill: var(--warn); }
1821
1853
  .ol-door > rect { fill: var(--danger); }
1854
+ /* #2665: an item's night place — a moon on a faint disc, a thin dashed line to its day place. */
1855
+ .ol-night > circle { fill: var(--surface); fill-opacity: 0.85; }
1856
+ .ol-night > .ol-moon { fill: var(--warn); stroke: none; pointer-events: none; }
1857
+ .ol-night-line { stroke: var(--warn); stroke-width: 1.5; stroke-dasharray: 4 3; stroke-opacity: 0.8; pointer-events: none; }
1858
+ /* #2691: a reserved seat — a dashed ring round its dot; the selected seat's "Reserved for" picks. */
1859
+ .ol-seat > .ol-for-ring { fill: none; stroke: var(--warn); stroke-width: 2; stroke-dasharray: 3 3; pointer-events: none; }
1860
+ .ol-for { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px var(--s-2); }
1861
+ .ol-for > legend { float: left; margin-right: var(--s-2); }
1822
1862
  .ol-waypoint > circle { fill: var(--focus); }
1823
1863
  .ol-handle.on > circle, .ol-handle.on > rect, .ol-handle:focus-visible > circle, .ol-handle:focus-visible > rect { stroke: var(--focus); stroke-width: 4; }
1824
1864
  .ol-bar { position: absolute; left: 0; right: 0; bottom: 0; z-index: 8; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); padding: var(--s-2);
1825
1865
  background: var(--surface); border-top: 1px solid var(--border); }
1826
1866
  .ol-actions { display: inline-flex; gap: var(--s-2); margin-left: auto; }
1867
+ /* #2585: Edit layout's controls docked in the side panel (a wide desk): one column, the waypoint's own row wrapping. */
1868
+ .ol-side { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-3); }
1869
+ .ol-side .ol-corridors { display: flex; flex-wrap: wrap; gap: var(--s-2); }
1827
1870
  .ol-corridors { display: inline-flex; flex-wrap: wrap; gap: 4px; }
1828
1871
  .ol-msg { flex-basis: 100%; margin: 0; }
1829
1872
  @media (max-width: 640px) {
1830
- /* A phone: the bars at the column's narrower padding; the stage at least most of a screen; no overview (as the canvas). */
1873
+ /* A phone: the bars at the column's narrower padding; no overview (as the canvas). #2689: the stage takes the panel's
1874
+ height that is left under the bars (the column, .oc-office and .oo grow into it) and no more, so zoomed in the
1875
+ scene's bottom is on screen: a floor fixed to the window (100dvh less a guess at the bars) put it under the tab bar
1876
+ once the bars wrapped. Its own floor is low enough for a 375 × 667 phone with the Office bar on five rows (215 px
1877
+ left); only a shorter screen scrolls the panel (on the bars: the stage's touches are its own). */
1831
1878
  .oc-office > .oc-bar { padding: var(--s-2) var(--s-3); }
1832
1879
  .oc-office > .oc-legend { margin: 0 var(--s-3) var(--s-2); }
1833
- .oo > :not(.oo-stage) { margin-left: var(--s-3); margin-right: var(--s-3); }
1834
- .oo-stage { min-height: max(300px, calc(100dvh - 300px)); }
1880
+ .oo > :not(.oo-stage):not(dialog) { margin-left: var(--s-3); margin-right: var(--s-3); }
1881
+ .oo-stage { min-height: 200px; }
1835
1882
  .oo-pick .tb-select { max-width: 150px; }
1836
1883
  .oo-mini { display: none; }
1837
1884
  .oo-zoom .icon-btn { width: 40px; height: 40px; }
1885
+ /* #2689: a column of the label and five 40 px buttons (~240 px) is taller than that stage: a row along its top
1886
+ instead, out of fullscreen (there the stage is the screen, and the office switcher is on the left of its top). */
1887
+ .oo:not(.fs) .oo-zoom { flex-direction: row; }
1888
+ .oo:not(.fs) .oo-zoom-pct { display: flex; align-items: center; padding: 0 6px; border-bottom: 0; border-right: 1px solid var(--border); }
1889
+ .oo:not(.fs) .oo-zoom .icon-btn + .icon-btn { border-top: 0; border-left: 1px solid var(--border); }
1838
1890
  }
1839
1891
  /* A phone: Fleet's five tabs fit when only the open one shows its label (the others keep theirs for screen readers). */
1840
1892
  @media (max-width: 479px) {
@@ -1906,6 +1958,7 @@ html.oo-immersive .p-fleet > .panel-body { z-index: 50; }
1906
1958
  .us-bar { display: block; width: 100%; height: 6px; margin: 4px 0; }
1907
1959
  .us-bar-bg { fill: var(--surface-2); } .us-bar-fg { fill: var(--us-0); }
1908
1960
  .us-meta { display: block; font-size: var(--fs-xs); color: var(--text-3); overflow-wrap: anywhere; }
1961
+ .us-est { display: block; font-size: var(--fs-xs); font-weight: var(--fw-regular); color: var(--text-3); }
1909
1962
  .us-flags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
1910
1963
  .us-trend, .us-heat { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
1911
1964
  .us-trend h3, .us-heat h3, .us-rest h3 { font-size: var(--fs-sm); font-weight: var(--fw-semibold); }
@@ -2473,3 +2526,31 @@ html.oo-immersive .p-fleet > .panel-body { z-index: 50; }
2473
2526
  .msg.user .msg-reactions { justify-content: flex-end; }
2474
2527
  .msg-reactions .reaction { display: inline-flex; align-items: center; justify-content: center; min-width: 28px; height: 24px; padding: 0 6px;
2475
2528
  border: 1px solid var(--border); border-radius: var(--r-full); background: var(--surface-2); font-family: var(--font-emoji); font-size: 15px; line-height: 1; }
2529
+
2530
+ /* #2636: an Office share link's page (office-share-page.js) — the Office alone, filling the window under a one-line head. */
2531
+ .share-page { display: flex; flex-direction: column; min-height: 100dvh; background: var(--bg); color: var(--text); }
2532
+ .share-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-1) var(--s-3); padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--border); }
2533
+ .share-head .share-note { margin-left: auto; }
2534
+ .share-stage { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; padding: var(--s-2); }
2535
+ .share-loading, .share-page > .note { padding: var(--s-4); }
2536
+ .share-ended { align-items: center; justify-content: center; padding: var(--s-6); text-align: center; }
2537
+ @media (max-width: 640px) { .share-head .share-note { margin-left: 0; flex-basis: 100%; } }
2538
+
2539
+ /* #2636: Office share links — the Share… dialog and the open links (office-share-dialog.js; Settings → Access & security). */
2540
+ .os { display: flex; flex-direction: column; gap: var(--s-3); }
2541
+ .os > p { margin: 0; }
2542
+ .os-form { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); align-items: end; }
2543
+ .os-form .check, .os-form .note { grid-column: 1 / -1; margin: 0; }
2544
+ .os-made { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
2545
+ .os-url { flex: 1 1 260px; min-width: 0; min-height: 40px; padding: var(--s-2) var(--s-3); border: 1px solid var(--border-strong); border-radius: var(--r-sm);
2546
+ background: var(--bg); color: var(--text); font-size: var(--fs-sm); outline: none; }
2547
+ .os-url:focus { border-color: var(--focus); box-shadow: 0 0 0 3px var(--accent-dim); }
2548
+ .os-made .note { flex-basis: 100%; margin: 0; }
2549
+ .os-head { margin: var(--s-2) 0 0; font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-2); }
2550
+ .os-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-2); }
2551
+ .os-row { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-2) var(--s-3); border: 1px solid var(--border); border-radius: var(--r-sm); }
2552
+ .os-what { flex: 1; min-width: 0; overflow-wrap: anywhere; font-size: var(--fs-sm); }
2553
+ .os-all { margin-top: var(--s-2); }
2554
+ .os-none { margin: 0; }
2555
+ .os-form .os-err { grid-column: 1 / -1; margin: 0; padding: var(--s-2) var(--s-3); border: 1px solid var(--danger); border-radius: var(--r-sm); }
2556
+ @media (max-width: 480px) { .os-form { grid-template-columns: 1fr; } }
Binary file
@@ -0,0 +1,95 @@
1
+ // #2585 (the user): settings-like editors docked on the right. On a screen wide enough for the usage rail (RAIL_MEDIA,
2
+ // #1673) an editor opens in the rail's place, beside the page — not modal: the page stays in view and in use (the
3
+ // Office re-fits beside it). While one is open the rail steps aside; it comes back as it was when the panel closes (its
4
+ // shown/hidden choice is never touched). Narrower, the same editor is the shared Dialog (centred; a phone's sheet).
5
+ //
6
+ // <${SidePanel} title=… onClose=… actions=… busy=…>…body…</${SidePanel}>: rendered = open, the owner unmounts it to
7
+ // close (like Dialog). Docked, the owner renders nothing itself: it publishes its title, body and actions to the shell's
8
+ // host (sidePanelStore) on every render, so the body keeps the owner's state and closures; the owner going (a close, a
9
+ // navigation) takes the panel with it. Esc in the panel and its × close it (not while `busy`); focus moves into it when
10
+ // it opens and goes back to what opened it when it closes. Docked, the body renders under the shell, not under its
11
+ // owner: a context provided above the owner does not reach it (pass what it needs as props, or read a store).
12
+ // `dirty`: the editor holds unsaved changes. Docked, the page beside it is in use (its links, search): leaving the page
13
+ // then closes the panel as Cancel does — and with changes, asks first (the app's leave guard; one at a time).
14
+ // `dock` false keeps it from docking (the Office in fullscreen, where the shell is out of view); `fallback`, when given,
15
+ // is what it is below the rail's width (or not docked) instead of the Dialog — Edit layout's bar over the canvas.
16
+ import { html, useEffect, useLayoutEffect, useRef } from "./app-html.js";
17
+ import { createStore, useStore } from "./app-store.js";
18
+ import { t } from "./app-i18n.js";
19
+ import { Icon } from "./ui-icons.js";
20
+ import { Dialog } from "./ui-dialog.js";
21
+ import { confirmDialog } from "./ui-confirm.js";
22
+ import { navigate, setLeaveGuard } from "./app-nav.js";
23
+ import { UsageRail, useRailWidth } from "./header-tools.js";
24
+
25
+ /** { panel: { id, title, label, body, actions, busy, onClose } | null } — the one docked panel. */
26
+ export const sidePanelStore = createStore({ panel: null });
27
+ let seq = 0;
28
+ const clear = (id) => { if (sidePanelStore.get().panel?.id === id) sidePanelStore.set({ panel: null }); };
29
+
30
+ /** An editor: docked on a wide screen, the shared Dialog (`wide`) elsewhere. */
31
+ export function SidePanel({ title, label, onClose, actions, busy = false, dirty = false, dock = true, fallback, children }) {
32
+ const docked = useRailWidth() && dock;
33
+ const id = useRef(0);
34
+ if (!id.current) id.current = ++seq;
35
+ const opener = useRef(undefined);
36
+ if (opener.current === undefined) opener.current = typeof document !== "undefined" ? document.activeElement : null;
37
+ useLayoutEffect(() => {
38
+ if (docked) sidePanelStore.set({ panel: { id: id.current, title, label, body: children, actions, busy, onClose } });
39
+ else clear(id.current);
40
+ });
41
+ useLayoutEffect(() => () => {
42
+ const host = typeof document !== "undefined" ? document.querySelector(".side-panel") : null;
43
+ const inside = !!host && (host.contains(document.activeElement) || document.activeElement === document.body);
44
+ clear(id.current);
45
+ // Focus back to what opened it — when it was in the panel (or nowhere): a person who moved on keeps their place.
46
+ const back = opener.current;
47
+ if ((inside || !host) && back && typeof back.focus === "function" && document.contains?.(back)) back.focus();
48
+ }, []);
49
+ // Leaving the page with changes in the docked panel: asked in the app's dialog; yes = Cancel, then on to there.
50
+ const live = useRef(null);
51
+ live.current = { title, onClose };
52
+ useEffect(() => {
53
+ if (!docked || !dirty) return undefined;
54
+ let asking = false;
55
+ return setLeaveGuard((_to, path) => {
56
+ if (!asking) {
57
+ asking = true;
58
+ confirmDialog({ message: t("app.sidePanelLeave", live.current.title), confirmLabel: t("app.sidePanelDiscard"), danger: true }).then((yes) => {
59
+ asking = false;
60
+ if (!yes) return;
61
+ live.current.onClose();
62
+ navigate(path, { force: true });
63
+ });
64
+ }
65
+ return false;
66
+ });
67
+ }, [docked, dirty]);
68
+ if (docked) return null;
69
+ if (fallback !== undefined) return fallback;
70
+ return html`<${Dialog} title=${title} label=${label} onClose=${onClose} actions=${actions} busy=${busy} wide=${true}>${children}</${Dialog}>`;
71
+ }
72
+
73
+ /** The docked panel itself: a non-modal dialog in the rail's place. */
74
+ function SidePanelHost({ panel }) {
75
+ const box = useRef(null);
76
+ // Into the panel as it opens (its box: no ring; Tab moves on), once per panel.
77
+ useLayoutEffect(() => { if (box.current && typeof box.current.focus === "function") box.current.focus({ preventScroll: true }); }, [panel.id]);
78
+ const close = () => { if (!panel.busy) panel.onClose(); };
79
+ const onKeyDown = (e) => { if (e.key === "Escape" && !e.defaultPrevented) { e.preventDefault(); close(); } };
80
+ return html`<dialog open data-trust="dialog" class="dlg side-panel" aria-label=${panel.label || panel.title} onKeyDown=${onKeyDown}>
81
+ <div class="dlg-box" tabindex="-1" ref=${box}>
82
+ <div class="dlg-head"><h2 class="dlg-title">${panel.title}</h2>
83
+ <button type="button" class="icon-btn dlg-x" aria-label=${t("app.close")} title=${t("app.close")} onClick=${close}><${Icon} name="close" /></button></div>
84
+ <div class="dlg-body">${panel.body}</div>
85
+ ${panel.actions ? html`<div class="dlg-actions">${panel.actions}</div>` : null}
86
+ </div>
87
+ </dialog>`;
88
+ }
89
+
90
+ /** The shell's right-hand column: a docked panel when one is open (wide screens), else the usage rail. */
91
+ export function RightRail({ route }) {
92
+ const { panel } = useStore(sidePanelStore);
93
+ const wide = useRailWidth();
94
+ return panel && wide ? html`<${SidePanelHost} panel=${panel} />` : html`<${UsageRail} route=${route} />`;
95
+ }
@@ -0,0 +1,115 @@
1
+ /* Shared browser controls. No terminal relay, provider output or secrets in diagnostics. */
2
+ const NAMES = { "kiro-cli": "Kiro", "claude-code": "Claude Code", codex: "Codex", antigravity: "Antigravity (console fallback)" };
3
+ const CHOICES = { builder_id: "Builder ID", google: "Google", github: "GitHub", organization: "Your organization", subscription: "Claude subscription", console: "Anthropic Console" };
4
+ const ERRORS = {
5
+ unsupported_prompt: "This CLI screen is not supported. Cancel and use the console fallback.",
6
+ cleanup_failed: "The login process could not be confirmed closed. Close it on the host and restart setup before retrying.",
7
+ signin_cleanup_failed: "The login process could not be confirmed closed. Close it on the host and restart setup before retrying.",
8
+ signin_action_stale: "That prompt changed or was already answered. Refresh its status; do not submit it again.",
9
+ signin_console_only: "This backend uses the explicit console fallback.",
10
+ backend_signin_local_only: "Backend sign-in is available only on this computer. Use the local setup page, or sign in later.",
11
+ signin_session_expired: "This setup session has ended. Reopen setup on the host.",
12
+ expired: "This login attempt expired. Start a new attempt.",
13
+ auth_unconfirmed: "The CLI exited, but credentials could not be verified. Retry or sign in later.",
14
+ provider_rejected: "The provider rejected this sign-in. Retry or sign in later.",
15
+ };
16
+
17
+ export function mountStructuredSignIns(root, { endpoint, fetcher = fetch, changed = () => {} } = {}) {
18
+ const doc = root.ownerDocument;
19
+ let view = null, busy = false, disposed = false, timer = null, generation = 0, message = "", rendered = "";
20
+ const el = (tag, text, parent = root) => { const node = doc.createElement(tag); if (text) node.textContent = text; parent.append(node); return node; };
21
+ const errorText = code => ERRORS[code] || "Could not complete sign-in. Check status, retry, or sign in later.";
22
+ const button = (parent, label, action) => { const b = el("button", label, parent); b.type = "button"; b.disabled = busy; b.onclick = action; return b; };
23
+ const request = async data => {
24
+ const response = await fetcher(endpoint, { method: data ? "POST" : "GET", cache: "no-store", headers: { "Content-Type": "application/json" }, ...(data ? { body: JSON.stringify(data) } : {}) });
25
+ const body = await response.json().catch(() => null);
26
+ if (!response.ok) throw new Error(body?.error || "signin_failed");
27
+ return body;
28
+ };
29
+ const accept = next => { view = next; root.dataset.ready = String(view.ready); changed(view); };
30
+ async function perform(data) {
31
+ if (busy || disposed) return;
32
+ busy = true; generation++; message = ""; render();
33
+ try { const next = await request(data); if (!disposed) accept(next); }
34
+ catch (error) { if (!disposed) message = errorText(error?.message); }
35
+ finally { busy = false; if (!disposed) render(); }
36
+ }
37
+ function render() {
38
+ const signature = JSON.stringify([view, busy, message]);
39
+ if (disposed || rendered === signature) return;
40
+ rendered = signature; root.replaceChildren();
41
+ el("h2", "Sign in your AI backends");
42
+ el("p", "Use the controls here; no terminal typing is needed for Kiro, Claude Code or Codex. You can sign in later from the dashboard.");
43
+ if (message) { const alert = el("p", message); alert.setAttribute("role", "alert"); }
44
+ if (!view) { button(root, "Check sign-ins", () => perform({ operation: "refresh" })); return; }
45
+ for (const backend of view.selected) {
46
+ const card = el("section"); card.dataset.backend = backend;
47
+ el("h3", NAMES[backend] || backend, card);
48
+ const state = view.states[backend], pending = view.pending.includes(backend), skipped = view.skipped.includes(backend);
49
+ el("p", skipped ? "Sign in later · remains in Needs you" : state === "valid" ? "Signed in ✓" : state === "not_installed" ? "Not installed on this host" : state === "console_only" ? "Use the explicit console fallback, or sign in later." : "Sign-in needed", card);
50
+ const login = view.login?.backend === backend ? view.login : null;
51
+ const active = login && login.phase !== "done" && !(login.phase === "failed" && login.reason);
52
+ if (active) {
53
+ el("p", login.phase === "waiting" || login.phase === "starting" ? "Waiting for the CLI…" : "Login: " + login.phase.replaceAll("_", " "), card);
54
+ if (login.phase === "method" && login.canAct) for (const choice of login.choices || []) {
55
+ if (CHOICES[choice]) button(card, CHOICES[choice], () => perform({ operation: "input", id: login.id, revision: login.revision, action: { kind: "method", choice } }));
56
+ }
57
+ if (login.url) {
58
+ // The host already allowlists these; still never turn a non-HTTPS URL into a link.
59
+ let url; try { url = new URL(login.url); } catch {}
60
+ if (url?.protocol === "https:" && !url.username && !url.password) {
61
+ const link = el("a", "Open provider sign-in", card); link.href = url.href; link.target = "_blank"; link.rel = "noopener noreferrer";
62
+ }
63
+ }
64
+ if (login.code) { el("p", "Device code:", card); el("code", login.code, card); }
65
+ if (login.canAct && ["code", "start_url", "region"].includes(login.phase)) {
66
+ const label = el("label", { code: "Paste the returned code", start_url: "Organization Start URL", region: "AWS region" }[login.phase], card);
67
+ const input = el("input", null, label); input.type = login.phase === "code" ? "password" : "text"; input.autocomplete = "off"; input.spellcheck = false;
68
+ input.setAttribute("aria-label", label.textContent); input.disabled = busy;
69
+ button(card, "Submit " + login.phase.replaceAll("_", " "), () => { const value = input.value; input.value = ""; void perform({ operation: "input", id: login.id, revision: login.revision, action: { kind: login.phase, value } }); });
70
+ }
71
+ if (["method", "code", "start_url", "region"].includes(login.phase) && !login.canAct) el("p", "Answer sent. Waiting for the next screen; it will not be sent twice.", card);
72
+ button(card, "Cancel sign-in", () => perform({ operation: "cancel" }));
73
+ } else {
74
+ if (login?.reason && login.reason !== "cancelled") el("p", errorText(login.reason), card);
75
+ const usable = !["antigravity"].includes(backend) && state !== "not_installed";
76
+ if (usable) button(card, state === "valid" ? "Sign in again" : "Sign in", () => {
77
+ const relogin = state === "valid";
78
+ if (relogin && !globalThis.confirm("Sign in again? Kiro will sign out its existing account first.")) return;
79
+ void perform({ operation: "start", backend, relogin });
80
+ });
81
+ }
82
+ if (pending && !skipped) button(card, "Sign in later", () => perform({ operation: "skip", backend }));
83
+ }
84
+ button(root, "Check sign-ins", () => perform({ operation: "refresh" }));
85
+ if (view.ready) el("p", "You can continue setup.");
86
+ }
87
+ async function poll() {
88
+ if (disposed) return;
89
+ const before = generation;
90
+ if (!busy) try { const next = await request(); if (!disposed && before === generation) { accept(next); render(); } }
91
+ catch (error) { if (!disposed && before === generation) { message = errorText(error?.message); render(); } }
92
+ if (!disposed) timer = setTimeout(poll, 1000);
93
+ }
94
+ render(); void poll();
95
+ return {
96
+ addBackends(backends) {
97
+ if (view && Array.isArray(backends) && backends.every(b => NAMES[b]) && backends.some(b => !view.selected.includes(b)))
98
+ return perform({ operation: "select", backends: [...new Set([...view.selected, ...backends])] });
99
+ },
100
+ addBackend(backend) { if (view && NAMES[backend] && !view.selected.includes(backend)) return perform({ operation: "select", backends: [...view.selected, backend] }); },
101
+ dispose() { disposed = true; generation++; clearTimeout(timer); root.replaceChildren(); },
102
+ };
103
+ }
104
+
105
+ const setupRoot = typeof document === "undefined" ? null : document.getElementById("setupSignins");
106
+ if (setupRoot) {
107
+ const cards = mountStructuredSignIns(setupRoot, { endpoint: "setup/signins" });
108
+ const backend = document.getElementById("backend");
109
+ const select = () => cards.addBackend(backend?.value);
110
+ backend?.addEventListener("change", select);
111
+ const observer = new MutationObserver(select); if (backend) observer.observe(backend, { childList: true });
112
+ // Initial GET may finish after the environment list, so also observe our readiness result.
113
+ const ready = new MutationObserver(select); ready.observe(setupRoot, { attributes: true, attributeFilter: ["data-ready"] });
114
+ addEventListener("pagehide", () => { observer.disconnect(); ready.disconnect(); cards.dispose(); }, { once: true });
115
+ }
@@ -7,6 +7,9 @@
7
7
  * Honest by construction: an amount is "API-equivalent at list price (checked <date>) — not money spent"; an unpriced
8
8
  * model's tokens are shown and its amount is not; Codex's cache writes are "not reported" (every CLI seen records 0),
9
9
  * never priced as uncached; an instance whose backend AgEnD cannot measure is listed with its reason, never as $0.
10
+ * Kiro records no token counts: its rows are ESTIMATED from text size (kiro-usage.ts, design §10.2) and say so — on the
11
+ * row (`estimated`), on any total that includes one (`includes_estimates`, with the estimated part of the amount), in
12
+ * the API, on the page and in the CSV. An estimate never mixes silently into a measured number.
10
13
  */
11
14
  import type { Ledger } from "./cache-ledger.js";
12
15
  import { type UsageDays } from "./usage-rollup.js";
@@ -27,6 +30,12 @@ export declare const usageBasis: (checked?: string) => string;
27
30
  * latest whole days and says from which date (`dailyFrom`); the rows and totals always cover the whole range.
28
31
  */
29
32
  export declare const USAGE_DAILY_MAX = 20000;
33
+ /** The backends whose usage is estimated, not measured (#2122 §10.2: Kiro records no token counts). */
34
+ export declare const ESTIMATED_BACKENDS: ReadonlySet<string>;
35
+ /** The backends whose transcripts report reasoning tokens (#2122 §10.3); elsewhere a 0 means not reported. */
36
+ export declare const REPORTS_REASONING: ReadonlySet<string>;
37
+ /** The backends whose transcripts carry a cache-write field that stays 0: "not reported" until a real write is seen. */
38
+ export declare const WRITES_UNREPORTED: ReadonlySet<string>;
30
39
  export interface UsageInstanceInput {
31
40
  name: string;
32
41
  /** Its backend now: what it is collected with from here on (each slice of its history carries its own). */
@@ -63,7 +72,10 @@ export interface UsageInput {
63
72
  export interface UsageTotals {
64
73
  /** Uncached input. */
65
74
  input: number;
75
+ /** Output without its reasoning part — the units of tokscale's daily rows (design §10.3). */
66
76
  output: number;
77
+ /** The part of the output a CLI reports as reasoning (Codex); 0 where none is reported (Claude). */
78
+ reasoning: number;
67
79
  cacheRead: number;
68
80
  cacheWrite5m: number;
69
81
  cacheWrite1h: number;
@@ -73,6 +85,12 @@ export interface UsageTotals {
73
85
  }
74
86
  export interface UsageRow extends UsageTotals {
75
87
  key: string;
88
+ /** Whether any of it comes from a CLI that reports reasoning (REPORTS_REASONING): false → its 0 is "not reported". */
89
+ reasoningReported: boolean;
90
+ /** Some of it is estimated (ESTIMATED_BACKENDS); flagged "estimated" (a row) or "includes_estimates" (the totals). */
91
+ estimated: boolean;
92
+ /** The estimated part of `amount` (null: nothing estimated is priced). */
93
+ amountEstimated: number | null;
76
94
  total: number;
77
95
  /** Delivered messages; null where a message has no such key (by model, by profile). */
78
96
  messages: number | null;
@@ -92,6 +110,8 @@ export interface UsageRow extends UsageTotals {
92
110
  }
93
111
  export interface UsageDaily extends UsageTotals {
94
112
  date: string;
113
+ reasoningReported: boolean;
114
+ estimated: boolean;
95
115
  zone: string;
96
116
  instance: string;
97
117
  backend: string;
@@ -10,6 +10,12 @@ export const usageBasis = (checked = PRICES_CHECKED) => `API-equivalent at list
10
10
  */
11
11
  export const USAGE_DAILY_MAX = 20_000;
12
12
  const M = 1_000_000;
13
+ /** The backends whose usage is estimated, not measured (#2122 §10.2: Kiro records no token counts). */
14
+ export const ESTIMATED_BACKENDS = new Set(["kiro-cli"]);
15
+ /** The backends whose transcripts report reasoning tokens (#2122 §10.3); elsewhere a 0 means not reported. */
16
+ export const REPORTS_REASONING = new Set(["codex", "grok", "muse", "opencode", "antigravity"]);
17
+ /** The backends whose transcripts carry a cache-write field that stays 0: "not reported" until a real write is seen. */
18
+ export const WRITES_UNREPORTED = new Set(["codex", "muse"]);
13
19
  const DATE = /^\d{4}-\d{2}-\d{2}$/;
14
20
  /** A range as asked (query string or tool argument); anything else is the last 7 days. */
15
21
  export function usageRange(kind, from, to) {
@@ -31,19 +37,23 @@ export function rangeDates(range, now, zone) {
31
37
  case "custom": return { from: range.from, to: range.to };
32
38
  }
33
39
  }
34
- const zero = () => ({ input: 0, output: 0, cacheRead: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheWrite: 0, requests: 0 });
35
- const addTotals = (into, [n, uncached, read, w5, w1h, w, out]) => {
40
+ const zero = () => ({ input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheWrite: 0, requests: 0 });
41
+ /** One slice into a row: `reasoning` is the part of the slice's output reported as reasoning (shown beside the rest). */
42
+ const addTotals = (into, [n, uncached, read, w5, w1h, w, out], reasoning) => {
43
+ const r = Math.min(Math.max(0, reasoning), out);
36
44
  into.requests += n;
37
45
  into.input += uncached;
38
46
  into.cacheRead += read;
39
47
  into.cacheWrite5m += w5;
40
48
  into.cacheWrite1h += w1h;
41
49
  into.cacheWrite += w;
42
- into.output += out;
50
+ into.output += out - r;
51
+ into.reasoning += r;
43
52
  };
44
53
  const splitKey = (mk) => { const bar = mk.lastIndexOf("|"); return { model: mk.slice(0, bar), long: mk.slice(bar + 1) === "1" }; };
45
54
  /**
46
- * The list-price amount of one slice, or null when its model is not in the table. Codex writes not reported are 0 in the
55
+ * The list-price amount of one slice, or null when its model is not in the table. Reasoning is a part of the slice's
56
+ * output, so the whole output is priced at the output rate once. Codex writes not reported are 0 in the
47
57
  * ledger (it marks writesSeen on the first one above 0), so they add nothing and are never priced as uncached input.
48
58
  */
49
59
  function amountOf(model, long, [, uncached, read, w5, w1h, w, out]) {
@@ -52,6 +62,7 @@ function amountOf(model, long, [, uncached, read, w5, w1h, w, out]) {
52
62
  return null;
53
63
  return (uncached * r.input + read * r.read + w5 * r.write5m + w1h * r.write1h + w * r.write + out * r.output) / M;
54
64
  }
65
+ const estimatedSlice = (s) => ESTIMATED_BACKENDS.has(s.backend);
55
66
  const unknownSource = (s) => s.backend === "unknown" || s.profile === "unknown";
56
67
  export function buildUsageReport(input) {
57
68
  const { from, to } = rangeDates(input.range, input.now, input.zone);
@@ -70,14 +81,14 @@ export function buildUsageReport(input) {
70
81
  if (!i.ledger || !i.days)
71
82
  continue;
72
83
  coverage.push({ name: i.name, intervals: i.days.coverage.map(([a, b]) => [a, b]) });
73
- // Codex: a write is reported only once a real one has been seen (every CLI so far records 0).
84
+ // Codex and Muse: a write is reported only once a real one has been seen (every CLI so far records 0).
74
85
  const writesSeen = i.ledger.writesSeen === true;
75
86
  const base = { instance: i.name, team: i.team };
76
87
  for (const row of i.days.rows) {
77
88
  if (!inRange(row.date))
78
89
  continue;
79
90
  for (const [mk, tot] of Object.entries(row.m))
80
- slices.push({ ...base, backend: row.be, writesReported: row.be !== "codex" || writesSeen, date: row.date, zone: row.zone, profile: row.pf, ...splitKey(mk), tot });
91
+ slices.push({ ...base, backend: row.be, writesReported: !WRITES_UNREPORTED.has(row.be) || writesSeen, date: row.date, zone: row.zone, profile: row.pf, ...splitKey(mk), tot, reasoning: row.r?.[mk] ?? 0, cost: row.c?.[mk] ?? 0 });
81
92
  }
82
93
  for (const [hk, hour] of Object.entries(i.ledger.hours)) {
83
94
  const h = Number(hk);
@@ -88,7 +99,7 @@ export function buildUsageReport(input) {
88
99
  continue;
89
100
  for (const src of hourSources(hour)) {
90
101
  for (const [mk, tot] of Object.entries(src.m))
91
- slices.push({ ...base, backend: src.backend, writesReported: src.backend !== "codex" || writesSeen, date, zone: input.zone, profile: src.profile, ...splitKey(mk), tot });
102
+ slices.push({ ...base, backend: src.backend, writesReported: !WRITES_UNREPORTED.has(src.backend) || writesSeen, date, zone: input.zone, profile: src.profile, ...splitKey(mk), tot, reasoning: src.r[mk] ?? 0, cost: src.c[mk] ?? 0 });
92
103
  }
93
104
  }
94
105
  }
@@ -102,7 +113,7 @@ export function buildUsageReport(input) {
102
113
  }
103
114
  };
104
115
  const rows = new Map();
105
- const newRow = (key) => ({ key, ...zero(), total: 0, messages: null, amount: null, priced: "none", flags: [], unpriced: [], instances: [], backends: [], models: [], profiles: [], _amount: 0, _pricedAny: false, _unpricedAny: false });
116
+ const newRow = (key) => ({ key, ...zero(), reasoningReported: false, estimated: false, amountEstimated: null, _estAmount: 0, _estPriced: false, total: 0, messages: null, amount: null, priced: "none", flags: [], unpriced: [], instances: [], backends: [], models: [], profiles: [], _amount: 0, _pricedAny: false, _unpricedAny: false });
106
117
  const uniq = (list, v) => { if (!list.includes(v))
107
118
  list.push(v); };
108
119
  // Daily rows: one per day, zone, agent, backend, model, tier and profile (an hour's slices summed into their day).
@@ -110,12 +121,18 @@ export function buildUsageReport(input) {
110
121
  for (const s of slices) {
111
122
  const key = keyOf(s);
112
123
  const row = rows.get(key) ?? (rows.set(key, newRow(key)), rows.get(key));
113
- addTotals(row, s.tot);
124
+ addTotals(row, s.tot, s.reasoning);
125
+ if (REPORTS_REASONING.has(s.backend))
126
+ row.reasoningReported = true;
114
127
  uniq(row.instances, s.instance);
115
128
  uniq(row.backends, s.backend);
116
129
  uniq(row.models, s.model);
117
130
  uniq(row.profiles, s.profile);
118
- const amount = amountOf(s.model, s.long, s.tot);
131
+ // List price first; a model the table lacks takes the cost its CLI reported (Grok), said so (design §10.2).
132
+ const listed = amountOf(s.model, s.long, s.tot), reported = listed === null && s.cost > 0;
133
+ const amount = listed ?? (reported ? s.cost : null);
134
+ if (reported)
135
+ uniq(row.flags, "cost_reported");
119
136
  if (amount === null) {
120
137
  row._unpricedAny = true;
121
138
  uniq(row.unpriced, s.model);
@@ -124,6 +141,14 @@ export function buildUsageReport(input) {
124
141
  row._pricedAny = true;
125
142
  row._amount += amount;
126
143
  }
144
+ if (estimatedSlice(s)) {
145
+ row.estimated = true;
146
+ uniq(row.flags, "estimated");
147
+ if (amount !== null) {
148
+ row._estPriced = true;
149
+ row._estAmount += amount;
150
+ }
151
+ }
127
152
  if (!s.writesReported)
128
153
  uniq(row.flags, "cache_write_not_reported");
129
154
  if (unknownSource(s))
@@ -132,11 +157,13 @@ export function buildUsageReport(input) {
132
157
  const dk = [s.date, s.zone, s.instance, s.backend, s.model, tier, s.profile].join("\u0000");
133
158
  let d = dailyBy.get(dk);
134
159
  if (!d) {
135
- d = { date: s.date, zone: s.zone, instance: s.instance, backend: s.backend, model: s.model, tier, profile: s.profile, ...zero(), amount: amount === null ? null : 0,
136
- flags: [...(s.writesReported ? [] : ["cache_write_not_reported"]), ...(amount === null ? ["not_priced"] : []), ...(unknownSource(s) ? ["source_unknown"] : [])] };
160
+ d = { date: s.date, zone: s.zone, instance: s.instance, backend: s.backend, model: s.model, tier, profile: s.profile, ...zero(), reasoningReported: REPORTS_REASONING.has(s.backend), estimated: estimatedSlice(s), amount: amount === null ? null : 0,
161
+ flags: [...(estimatedSlice(s) ? ["estimated"] : []), ...(s.writesReported ? [] : ["cache_write_not_reported"]), ...(amount === null ? ["not_priced"] : []), ...(unknownSource(s) ? ["source_unknown"] : [])] };
137
162
  dailyBy.set(dk, d);
138
163
  }
139
- addTotals(d, s.tot);
164
+ addTotals(d, s.tot, s.reasoning);
165
+ if (reported && !d.flags.includes("cost_reported"))
166
+ d.flags.push("cost_reported");
140
167
  if (amount !== null && d.amount !== null)
141
168
  d.amount += amount;
142
169
  }
@@ -188,8 +215,9 @@ export function buildUsageReport(input) {
188
215
  row.messages ??= 0;
189
216
  }
190
217
  const finish = (row) => {
191
- const { _amount, _pricedAny, _unpricedAny, ...rest } = row;
192
- rest.total = rest.input + rest.output + rest.cacheRead + rest.cacheWrite5m + rest.cacheWrite1h + rest.cacheWrite;
218
+ const { _amount, _pricedAny, _unpricedAny, _estAmount, _estPriced, ...rest } = row;
219
+ rest.amountEstimated = _estPriced ? _estAmount : null;
220
+ rest.total = rest.input + rest.output + rest.reasoning + rest.cacheRead + rest.cacheWrite5m + rest.cacheWrite1h + rest.cacheWrite;
193
221
  rest.amount = _pricedAny ? _amount : null;
194
222
  rest.priced = _pricedAny ? (_unpricedAny ? "partial" : "all") : "none";
195
223
  if (_unpricedAny)
@@ -200,15 +228,24 @@ export function buildUsageReport(input) {
200
228
  .sort((a, b) => (b.amount ?? -1) - (a.amount ?? -1) || b.total - a.total || a.key.localeCompare(b.key));
201
229
  const totals = finish(newRow("all"));
202
230
  for (const r of out) {
203
- for (const k of ["input", "output", "cacheRead", "cacheWrite5m", "cacheWrite1h", "cacheWrite", "requests"])
231
+ for (const k of ["input", "output", "reasoning", "cacheRead", "cacheWrite5m", "cacheWrite1h", "cacheWrite", "requests"])
204
232
  totals[k] += r[k];
205
233
  totals.total += r.total;
206
234
  if (r.messages !== null)
207
235
  totals.messages = (totals.messages ?? 0) + r.messages;
208
236
  if (r.amount !== null)
209
237
  totals.amount = (totals.amount ?? 0) + r.amount;
238
+ if (r.reasoningReported)
239
+ totals.reasoningReported = true;
240
+ if (r.estimated) {
241
+ totals.estimated = true;
242
+ uniq(totals.flags, "includes_estimates");
243
+ }
244
+ if (r.amountEstimated !== null)
245
+ totals.amountEstimated = (totals.amountEstimated ?? 0) + r.amountEstimated;
210
246
  for (const f of r.flags)
211
- uniq(totals.flags, f);
247
+ if (f !== "estimated")
248
+ uniq(totals.flags, f);
212
249
  for (const m of r.unpriced)
213
250
  uniq(totals.unpriced, m);
214
251
  }