@songsid/agend 2.3.2-beta.4 → 2.3.2-beta.6

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 (197) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/dist/antigravity-usage-scan.d.ts +58 -0
  3. package/dist/antigravity-usage-scan.js +257 -0
  4. package/dist/antigravity-usage-scan.js.map +1 -0
  5. package/dist/antigravity-usage-worker.d.ts +1 -0
  6. package/dist/antigravity-usage-worker.js +10 -0
  7. package/dist/antigravity-usage-worker.js.map +1 -0
  8. package/dist/backend/kiro-identity.d.ts +45 -0
  9. package/dist/backend/kiro-identity.js +74 -0
  10. package/dist/backend/kiro-identity.js.map +1 -1
  11. package/dist/backend/kiro.d.ts +15 -0
  12. package/dist/backend/kiro.js +42 -5
  13. package/dist/backend/kiro.js.map +1 -1
  14. package/dist/backend/types.d.ts +6 -0
  15. package/dist/backend/types.js.map +1 -1
  16. package/dist/backend-signin-guide.d.ts +1 -0
  17. package/dist/backend-signin-guide.js +9 -0
  18. package/dist/backend-signin-guide.js.map +1 -0
  19. package/dist/backend-signin-pending.d.ts +10 -0
  20. package/dist/backend-signin-pending.js +44 -0
  21. package/dist/backend-signin-pending.js.map +1 -0
  22. package/dist/browser-backend-signins.d.ts +56 -0
  23. package/dist/browser-backend-signins.js +189 -0
  24. package/dist/browser-backend-signins.js.map +1 -0
  25. package/dist/cache-ledger.d.ts +38 -2
  26. package/dist/cache-ledger.js +133 -8
  27. package/dist/cache-ledger.js.map +1 -1
  28. package/dist/cache-prices.d.ts +2 -1
  29. package/dist/cache-prices.js +5 -2
  30. package/dist/cache-prices.js.map +1 -1
  31. package/dist/cache-service.d.ts +68 -0
  32. package/dist/cache-service.js +355 -13
  33. package/dist/cache-service.js.map +1 -1
  34. package/dist/channel/adapters/discord.d.ts +11 -0
  35. package/dist/channel/adapters/discord.js +50 -8
  36. package/dist/channel/adapters/discord.js.map +1 -1
  37. package/dist/channel/mcp-tools.js +3 -1
  38. package/dist/channel/mcp-tools.js.map +1 -1
  39. package/dist/cli.js +21 -2
  40. package/dist/cli.js.map +1 -1
  41. package/dist/config-validator.js +25 -3
  42. package/dist/config-validator.js.map +1 -1
  43. package/dist/config.d.ts +19 -0
  44. package/dist/config.js +25 -0
  45. package/dist/config.js.map +1 -1
  46. package/dist/cpu-quota.d.ts +23 -0
  47. package/dist/cpu-quota.js +86 -0
  48. package/dist/cpu-quota.js.map +1 -0
  49. package/dist/daemon.js +6 -3
  50. package/dist/daemon.js.map +1 -1
  51. package/dist/fleet-manager.d.ts +56 -5
  52. package/dist/fleet-manager.js +387 -144
  53. package/dist/fleet-manager.js.map +1 -1
  54. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
  55. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +14 -1
  56. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +20 -4
  57. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +20 -4
  58. package/dist/instance-lifecycle.d.ts +9 -0
  59. package/dist/instance-lifecycle.js +27 -1
  60. package/dist/instance-lifecycle.js.map +1 -1
  61. package/dist/kiro-db-reader.d.ts +5 -0
  62. package/dist/kiro-db-reader.js +18 -0
  63. package/dist/kiro-db-reader.js.map +1 -1
  64. package/dist/kiro-engine-status.d.ts +2 -0
  65. package/dist/kiro-engine-status.js +9 -0
  66. package/dist/kiro-engine-status.js.map +1 -1
  67. package/dist/kiro-migrate.d.ts +49 -0
  68. package/dist/kiro-migrate.js +105 -0
  69. package/dist/kiro-migrate.js.map +1 -0
  70. package/dist/kiro-transcript-lane.d.ts +2 -0
  71. package/dist/kiro-transcript-lane.js.map +1 -1
  72. package/dist/kiro-transcript-worker.js +1 -0
  73. package/dist/kiro-transcript-worker.js.map +1 -1
  74. package/dist/kiro-usage-run.d.ts +7 -0
  75. package/dist/kiro-usage-run.js +39 -0
  76. package/dist/kiro-usage-run.js.map +1 -0
  77. package/dist/kiro-usage-scan.d.ts +49 -0
  78. package/dist/kiro-usage-scan.js +223 -0
  79. package/dist/kiro-usage-scan.js.map +1 -0
  80. package/dist/kiro-usage-worker.d.ts +1 -0
  81. package/dist/kiro-usage-worker.js +10 -0
  82. package/dist/kiro-usage-worker.js.map +1 -0
  83. package/dist/kiro-usage.d.ts +58 -0
  84. package/dist/kiro-usage.js +310 -0
  85. package/dist/kiro-usage.js.map +1 -0
  86. package/dist/locale.js +78 -2
  87. package/dist/locale.js.map +1 -1
  88. package/dist/log-service.js +1 -1
  89. package/dist/log-service.js.map +1 -1
  90. package/dist/login-url.d.ts +4 -0
  91. package/dist/login-url.js +22 -0
  92. package/dist/login-url.js.map +1 -0
  93. package/dist/needs-you-hub.d.ts +2 -1
  94. package/dist/needs-you-hub.js +1 -1
  95. package/dist/needs-you-hub.js.map +1 -1
  96. package/dist/needs-you.d.ts +8 -2
  97. package/dist/needs-you.js +6 -0
  98. package/dist/needs-you.js.map +1 -1
  99. package/dist/notice-delivery.d.ts +7 -6
  100. package/dist/notice-delivery.js +16 -18
  101. package/dist/notice-delivery.js.map +1 -1
  102. package/dist/office-share-page.d.ts +13 -0
  103. package/dist/office-share-page.js +82 -0
  104. package/dist/office-share-page.js.map +1 -0
  105. package/dist/office-share-service.d.ts +89 -0
  106. package/dist/office-share-service.js +172 -0
  107. package/dist/office-share-service.js.map +1 -0
  108. package/dist/office-share.d.ts +228 -0
  109. package/dist/office-share.js +489 -0
  110. package/dist/office-share.js.map +1 -0
  111. package/dist/opencode-usage-scan.d.ts +28 -0
  112. package/dist/opencode-usage-scan.js +92 -0
  113. package/dist/opencode-usage-scan.js.map +1 -0
  114. package/dist/opencode-usage-worker.d.ts +1 -0
  115. package/dist/opencode-usage-worker.js +10 -0
  116. package/dist/opencode-usage-worker.js.map +1 -0
  117. package/dist/outbound-handlers.js +22 -2
  118. package/dist/outbound-handlers.js.map +1 -1
  119. package/dist/outbound-schemas.d.ts +4 -0
  120. package/dist/outbound-schemas.js +4 -0
  121. package/dist/outbound-schemas.js.map +1 -1
  122. package/dist/public-web-gateway.js +6 -0
  123. package/dist/public-web-gateway.js.map +1 -1
  124. package/dist/quickstart-api.js +13 -2
  125. package/dist/quickstart-api.js.map +1 -1
  126. package/dist/quickstart.js +2 -3
  127. package/dist/quickstart.js.map +1 -1
  128. package/dist/settings-api.js +1 -1
  129. package/dist/settings-api.js.map +1 -1
  130. package/dist/setup-form.d.ts +1 -1
  131. package/dist/setup-form.js +7 -1
  132. package/dist/setup-form.js.map +1 -1
  133. package/dist/setup-host.d.ts +9 -0
  134. package/dist/setup-host.js +125 -7
  135. package/dist/setup-host.js.map +1 -1
  136. package/dist/setup-signin-assets.d.ts +2 -0
  137. package/dist/setup-signin-assets.js +6 -0
  138. package/dist/setup-signin-assets.js.map +1 -0
  139. package/dist/setup-wizard.js +2 -2
  140. package/dist/setup-wizard.js.map +1 -1
  141. package/dist/structured-signin-transport.d.ts +8 -0
  142. package/dist/structured-signin-transport.js +79 -0
  143. package/dist/structured-signin-transport.js.map +1 -0
  144. package/dist/structured-signin.d.ts +131 -0
  145. package/dist/structured-signin.js +388 -0
  146. package/dist/structured-signin.js.map +1 -0
  147. package/dist/tool-permissions.js +2 -0
  148. package/dist/tool-permissions.js.map +1 -1
  149. package/dist/topic-commands.js +4 -1
  150. package/dist/topic-commands.js.map +1 -1
  151. package/dist/transcript-sources.d.ts +26 -2
  152. package/dist/transcript-sources.js +87 -40
  153. package/dist/transcript-sources.js.map +1 -1
  154. package/dist/types.d.ts +15 -5
  155. package/dist/ui/backend-signin.js +63 -10
  156. package/dist/ui/fleet-office-pack.js +7 -2
  157. package/dist/ui/fleet-office-skin.js +5 -4
  158. package/dist/ui/fleet-office-zones.js +17 -6
  159. package/dist/ui/fleet-office.js +20 -10
  160. package/dist/ui/fleet-usage.js +19 -15
  161. package/dist/ui/office-share-dialog.js +145 -0
  162. package/dist/ui/office-share-page.js +116 -0
  163. package/dist/ui/panel-log.js +2 -2
  164. package/dist/ui/panel-needs.js +4 -4
  165. package/dist/ui/panel-settings.js +12 -8
  166. package/dist/ui/settings-registry.js +2 -2
  167. package/dist/ui/settings-strings.js +6 -6
  168. package/dist/ui/shared/app-i18n.js +2 -2
  169. package/dist/ui/shared/app.css +29 -0
  170. package/dist/ui/structured-signin.js +115 -0
  171. package/dist/usage-report.d.ts +20 -0
  172. package/dist/usage-report.js +54 -17
  173. package/dist/usage-report.js.map +1 -1
  174. package/dist/usage-rollup.d.ts +9 -1
  175. package/dist/usage-rollup.js +20 -4
  176. package/dist/usage-rollup.js.map +1 -1
  177. package/dist/web-api.d.ts +15 -0
  178. package/dist/web-api.js +190 -0
  179. package/dist/web-api.js.map +1 -1
  180. package/dist/web-auth.js +1 -1
  181. package/dist/web-auth.js.map +1 -1
  182. package/dist/web-backend-login.d.ts +1 -7
  183. package/dist/web-backend-login.js +2 -24
  184. package/dist/web-backend-login.js.map +1 -1
  185. package/dist/web-office-skins.d.ts +2 -1
  186. package/dist/web-office-skins.js +2 -1
  187. package/dist/web-office-skins.js.map +1 -1
  188. package/dist/web-settings-patch.js +1 -1
  189. package/dist/web-settings-patch.js.map +1 -1
  190. package/dist/working-recovery.d.ts +2 -2
  191. package/dist/working-recovery.js +43 -8
  192. package/dist/working-recovery.js.map +1 -1
  193. package/dist/working-state-journal.d.ts +2 -0
  194. package/dist/working-state-journal.js +16 -1
  195. package/dist/working-state-journal.js.map +1 -1
  196. package/package.json +2 -1
  197. package/templates/windows-shortcuts.ps1 +7 -5
@@ -62,7 +62,7 @@ register("log", {
62
62
  ev_backend_unreachable: "CLI unreachable", ev_startup_backend_unreachable: "CLI unreachable at start", ev_backend_launch_warning: "Launch warning",
63
63
  ev_clear_action: "Conversation cleared", ev_normal_exit_action: "Exited", ev_reply_drop_detected: "A reply went missing",
64
64
  ev_reply_drop_recovered: "Missing reply recovered", ev_reply_drop_unrecovered: "Missing reply not recovered",
65
- ev_supervision_ended: "Supervision ended", ev_tmux_server_crash: "tmux crashed", ev_malformed_tool_call: "Malformed tool call", ev_health_check_error: "Health check error", ev_working_state: "Working-state journal",
65
+ ev_supervision_ended: "Supervision ended", ev_tmux_server_crash: "tmux crashed", ev_malformed_tool_call: "Malformed tool call", ev_health_check_error: "Health check error", ev_context_lost: "Started fresh: context lost", ev_working_state: "Working-state journal",
66
66
  },
67
67
  "zh-TW": {
68
68
  offTitle: "紀錄未開啟",
@@ -100,7 +100,7 @@ register("log", {
100
100
  ev_backend_unreachable: "連不到 CLI", ev_startup_backend_unreachable: "啟動時連不到 CLI", ev_backend_launch_warning: "啟動警告",
101
101
  ev_clear_action: "對話已清除", ev_normal_exit_action: "已結束", ev_reply_drop_detected: "有回覆遺失",
102
102
  ev_reply_drop_recovered: "遺失的回覆已補回", ev_reply_drop_unrecovered: "遺失的回覆沒有補回",
103
- ev_supervision_ended: "監督結束", ev_tmux_server_crash: "tmux 當掉", ev_malformed_tool_call: "格式錯誤的工具呼叫", ev_health_check_error: "健康檢查錯誤", ev_working_state: "工作狀態紀錄",
103
+ ev_supervision_ended: "監督結束", ev_tmux_server_crash: "tmux 當掉", ev_malformed_tool_call: "格式錯誤的工具呼叫", ev_health_check_error: "健康檢查錯誤", ev_context_lost: "改用全新對話:context 遺失", ev_working_state: "工作狀態紀錄",
104
104
  },
105
105
  });
106
106
  const tl = (key, ...a) => t(`log.${key}`, ...a);
@@ -25,7 +25,7 @@ import { openSignIn, signInOffered } from "./backend-signin.js";
25
25
  register("needs", {
26
26
  en: {
27
27
  title: "Needs you", empty: "Nothing needs you right now", emptyHint: "When an agent waits for you, it shows here — and in your chat app's General.", emptyHintWeb: "When an agent waits for you, it shows here.",
28
- open: "Open chat", openCard: "Open the card", noAssignee: "Task board (nobody assigned)", signIn: "Sign in",
28
+ open: "Open chat", openCard: "Open the card", noAssignee: "Task board (nobody assigned)", fleetSignIns: "Fleet sign-ins", signIn: "Sign in",
29
29
  gap: "not wrapped up: {0}", gapUnchecked: "{0} checklist item(s) unchecked", gapNoResult: "no result", gapJoin: ", ", acknowledge: "Acknowledge", acknowledging: "Acknowledging…", answering: "Answering…",
30
30
  ageNow: "just now", ageMin: "{0} min", ageHour: "{0} h", ageDay: "{0} d",
31
31
  notifyTitle: "Notify me on this device", notifyOn: "Notifications are on for this browser while a tab is open.",
@@ -38,7 +38,7 @@ register("needs", {
38
38
  },
39
39
  "zh-TW": {
40
40
  title: "等你處理", empty: "目前沒有需要你處理的事", emptyHint: "agent 在等你時,會出現在這裡——也會出現在聊天 app 的 General。", emptyHintWeb: "agent 在等你時,會出現在這裡。",
41
- open: "開啟聊天", openCard: "打開卡片", noAssignee: "任務看板(沒有負責人)", signIn: "登入",
41
+ open: "開啟聊天", openCard: "打開卡片", noAssignee: "任務看板(沒有負責人)", fleetSignIns: "Fleet 登入", signIn: "登入",
42
42
  gap: "還沒補齊:{0}", gapUnchecked: "還有 {0} 項沒勾", gapNoResult: "沒有產出總結", gapJoin: "、", acknowledge: "確認", acknowledging: "確認中…", answering: "回覆中…",
43
43
  ageNow: "剛剛", ageMin: "{0} 分鐘", ageHour: "{0} 小時", ageDay: "{0} 天",
44
44
  notifyTitle: "在這台裝置通知我", notifyOn: "這個瀏覽器的通知已開啟(需要開著分頁)。",
@@ -143,7 +143,7 @@ export function NeedsPanel({ navKey }) {
143
143
  <div data-trust="panel-content" class="panel-body"><div class="col">
144
144
  ${!items.length ? html`<${Empty} icon="check" title=${tn("empty")} hint=${tn(chatApp ? "emptyHint" : "emptyHintWeb")} />`
145
145
  : groups.map((g) => html`<section key=${g.instance} class="n-group" aria-label=${g.instance}>
146
- <h2 class="n-group-head">${g.instance ? html`<a href=${chatPath(g.instance)} title=${g.instance}>${nameFor(g.instance)}</a>` : tn("noAssignee")}</h2>
146
+ <h2 class="n-group-head">${g.instance ? html`<a href=${chatPath(g.instance)} title=${g.instance}>${nameFor(g.instance)}</a>` : tn(g.items.some(i => i.type === "backend") ? "fleetSignIns" : "noAssignee")}</h2>
147
147
  ${g.items.map((item) => html`<${NeedsItem} key=${item.id} item=${item} nowMs=${nowMs} owner=${owner} />`)}
148
148
  </section>`)}
149
149
  <${NotifyToggle} />
@@ -200,7 +200,7 @@ function NeedsItem({ item, nowMs, owner }) {
200
200
  disabled=${promptBusy} onClick=${() => answer(a.id)}>${(p ? p.busy : fallback === "busy") ? tn("answering") : a.label}</button>`) : null}
201
201
  ${item.type === "delivery" ? html`<button type="button" class="btn btn-sm" disabled=${!!ack} onClick=${acknowledge}>
202
202
  <${Icon} name="check" size=${14} />${ack === "busy" ? tn("acknowledging") : tn("acknowledge")}</button>` : null}
203
- ${item.reason === "auth_paused" && item.signIn && signInOffered() ? html`<button type="button" class="btn btn-sm btn-primary" onClick=${() => openSignIn(item.signIn)}>${tn("signIn")}</button>` : null}
203
+ ${(item.reason === "auth_paused" || item.reason === "backend_signin") && item.signIn && signInOffered() ? html`<button type="button" class="btn btn-sm btn-primary" onClick=${() => openSignIn(item.signIn)}>${tn("signIn")}</button>` : null}
204
204
  ${item.type === "task" && item.card ? html`<a class="btn btn-sm" href=${`/ui/fleet?card=${encodeURIComponent(item.card.code)}`}>${tn("openCard")}</a>` : null}
205
205
  ${item.instance ? html`<a class="btn btn-sm btn-ghost" href=${chatPath(item.instance)}>${tn("open")}</a>` : null}
206
206
  </div>
@@ -46,6 +46,7 @@ import { startTour } from "./chat-tour.js";
46
46
  import "./chat-strings.js";
47
47
  import { AppearanceSection } from "./settings-appearance.js";
48
48
  import { GuestsCard } from "./settings-guests.js";
49
+ import { OfficeSharesCard } from "./office-share-dialog.js";
49
50
  import { syncStage, useDraft } from "./settings-draft.js";
50
51
  import { WebAuthorityCard } from "./settings-web-authority.js";
51
52
  import { SignInList } from "./backend-signin.js";
@@ -952,10 +953,11 @@ function Classic({ ctx, search, openDialog }) {
952
953
  })}</div>`;
953
954
  }
954
955
 
955
- const configuredNoticeMode = (value, route) => value === false ? false : value === "dm" || route === "dm" ? "dm" : true;
956
- const noticeMode = (v) => v === "dm" ? "dm" : v === "false" || v === false ? false : true;
957
- const noticeChoices = () => [{ value: "true", label: tn("noticeTopic") }, { value: "dm", label: tn("noticeDm") }, { value: "false", label: tn("noticeOff") }];
958
- const noticeLabel = (v) => tn(v === "dm" ? "noticeDm" : v === false ? "noticeOff" : "noticeTopic");
956
+ // Sign-in notices and daily tips post in the topic or not at all: the DM choice was withdrawn (hmsg-d1809f2a,
957
+ // hmsg-3b8ac2af), and an old "dm" reads as on.
958
+ const noticeMode = (v) => v !== "false" && v !== false;
959
+ const noticeChoices = () => [{ value: "true", label: tn("noticeTopic") }, { value: "false", label: tn("noticeOff") }];
960
+ const noticeLabel = (v) => tn(v ? "noticeTopic" : "noticeOff");
959
961
 
960
962
  /** General's form, from the saved defaults (`d`) and ClassicBot's (`c`). */
961
963
  function generalForm(d, c, fleet = {}, schema = {}) {
@@ -971,7 +973,7 @@ function generalForm(d, c, fleet = {}, schema = {}) {
971
973
  // #2347 §10 review: unset startup concurrency is automatic (deriveSpawnConcurrency: 2–10 by free memory and cores),
972
974
  // not 10 — an empty field, and a save that leaves it unset (the defaults merge replaces `startup` whole).
973
975
  concurrency: startup.concurrency == null ? "" : String(startup.concurrency), stagger: String(startup.stagger_delay_ms ?? 500),
974
- tips: String(configuredNoticeMode(d.tips, fleet.web?.notice_delivery?.tips)),
976
+ tips: String(noticeMode(d.tips)),
975
977
  replyTriggers: Boolean(d.reply_triggers ?? schema.reply_triggers_default),
976
978
  classicReplyTriggers: Boolean(c.reply_triggers ?? d.reply_triggers ?? schema.reply_triggers_default), classicReplyTriggersInherit: !hasOwn(c, "reply_triggers"),
977
979
  classicBackend: c.backend || d.backend || "claude-code",
@@ -1029,7 +1031,7 @@ function General({ ctx, part = "all" }) {
1029
1031
  }, {
1030
1032
  backend: d.backend || "claude-code", model: d.model || undefined, locale: d.locale || null, auto_pause_after: d.auto_pause_after ?? 0,
1031
1033
  agent_mode: d.agent_mode || "mcp", tool_set: TOOL_SETS.includes(d.tool_set) ? d.tool_set : null, tool_progress: d.tool_progress ?? "off",
1032
- tips: configuredNoticeMode(d.tips, ctx.fleet.web?.notice_delivery?.tips), cross_instance_visibility: visibilityDefault(d), reply_triggers: Boolean(d.reply_triggers ?? schema.reply_triggers_default), reply_completion_guard: d.reply_completion_guard ?? true, log_level: d.log_level || "info",
1034
+ tips: d.tips ?? true, cross_instance_visibility: visibilityDefault(d), reply_triggers: Boolean(d.reply_triggers ?? schema.reply_triggers_default), reply_completion_guard: d.reply_completion_guard ?? true, log_level: d.log_level || "info",
1033
1035
  dashboard: Object.fromEntries(DASHBOARD_FEATURES.filter((k) => k !== "office").map((k) => [k, dashboardDefault(d, k)])),
1034
1036
  office_access: dashboardDefault(d, "office"),
1035
1037
  hang_detector: { enabled: hang.enabled !== false, timeout_minutes: hang.timeout_minutes ?? HANG_TIMEOUT_DEFAULT_MINUTES },
@@ -1190,7 +1192,7 @@ function WebSection({ ctx, staged, part = "all" }) {
1190
1192
  // serves their reads there).
1191
1193
  const overPublicLink = globalThis.document?.body?.dataset?.publicLink === "1";
1192
1194
  const original = useMemo(() => ({
1193
- notify: configuredNoticeMode(web.notify_login, web.notice_delivery?.login),
1195
+ notify: noticeMode(web.notify_login),
1194
1196
  echo: web.echo_to_channel ?? true,
1195
1197
  log: web.log_tab === true, // #1218: off by default
1196
1198
  usagePub: web.usage_report_on_public_link === true, // #2122: off by default
@@ -1353,7 +1355,9 @@ function WebSection({ ctx, staged, part = "all" }) {
1353
1355
  ${named || f.autoOpen ? html`<label class=${fc("check", "autoOpen")}><input id="publicAutoOpen" type="checkbox" checked=${f.autoOpen} onChange=${(e) => set("autoOpen")(e.target.checked)} /><span>${tn("publicAutoOpen")}</span><${Impact} schema=${schema} field="web.public_link.auto_open" /></label>
1354
1356
  <p class="note">${tn("publicAutoOpenHint")}</p><${Feedback} error=${errors.autoOpen} />` : null}
1355
1357
  </section>`) : null}
1356
- ${has("access") && !overPublicLink ? html`<${PublicTunnelToken} /><${GuestsCard} ctx=${ctx} staged=${staged} /><${WebAuthorityCard} ctx=${ctx} staged=${staged} />` : null}
1358
+ ${has("access") && !overPublicLink ? html`<${PublicTunnelToken} />` : null}
1359
+ ${has("access") ? html`<${OfficeSharesCard} />` : null}
1360
+ ${has("access") && !overPublicLink ? html`<${GuestsCard} ctx=${ctx} staged=${staged} /><${WebAuthorityCard} ctx=${ctx} staged=${staged} />` : null}
1357
1361
  ${has("chat") ? lock(html`<section class="card"><h3>${tn("webChat")}</h3>
1358
1362
  <label class=${fc("check", "echo")}><input id="webEchoToChannel" type="checkbox" checked=${f.echo} onChange=${(e) => set("echo")(e.target.checked)} /><span>${tn("webEcho")}</span><${Impact} schema=${schema} field="web.echo_to_channel" /></label>
1359
1363
  <p class="note">${tn("webEchoHint")}</p>
@@ -33,8 +33,8 @@ const ROWS = [
33
33
  // Fleet · Agents
34
34
  ["agents.new", "agents", "#newAgentBtn", "settings.newAgent", null, ["new agent", "create", "add agent", "instance"], ["新增 agent", "建立", "新 agent"]],
35
35
  ["agents.provider-keys", "agents", ".s-provider-keys", "settings.providerKeys", "web.provider_secrets", ["api key", "provider", "openai", "anthropic", "token", "secret"], ["API 金鑰", "金鑰", "供應商", "token"]],
36
- ["defaults.tips", "agent-defaults", "#g-tips", "settings.dailyTips", "defaults.tips", ["daily tips", "dm", "notices"], ["每日提示", "私訊"]],
37
- ["web.notify-login", "access", "#webNotifyLogin", "settings.webNotifyLogin", "web.notify_login", ["sign in", "login", "dm", "notices"], ["登入通知", "私訊"]],
36
+ ["defaults.tips", "agent-defaults", "#g-tips", "settings.dailyTips", "defaults.tips", ["daily tips", "notices"], ["每日提示"]],
37
+ ["web.notify-login", "access", "#webNotifyLogin", "settings.webNotifyLogin", "web.notify_login", ["sign in", "login", "notices"], ["登入通知"]],
38
38
  // Fleet · Agent defaults
39
39
  ["defaults.locale", "agent-defaults", "#g-locale", "settings.language", "defaults.locale", ["language", "bot language", "reply language", "locale"], ["語言", "回覆語言", "bot 語言"]],
40
40
  ["defaults.backend", "agent-defaults", "#g-be", "settings.defaultBackend", "defaults.backend", ["backend", "cli", "claude", "codex", "kiro"], ["後端", "CLI"]],
@@ -4,10 +4,10 @@ import { register } from "/assets/app-i18n.js";
4
4
 
5
5
  register("settings", {
6
6
  en: {
7
- noticeTopic: "Topic", noticeDm: "Direct message (topic fallback)", noticeOff: "Off",
7
+ noticeTopic: "Topic", noticeOff: "Off",
8
8
  webNotifyLogin: "Web sign-in notices", dailyTips: "Daily tips",
9
- webNotifyLoginHint: "DM the signing-in operator or public-link owner; local guest notices go to the owning connection’s first configured admin. Missing account or failed DM falls back to the topic. Off suppresses operator notices only; guest and public-link security notices remain in the topic. Existing default: Topic; new fleets: DM.",
10
- dailyTipsHint: "DM the General connection’s first configured admin, with topic fallback. Default: Topic. /tips still posts where invoked.",
9
+ webNotifyLoginHint: "Posted in the topic. Off suppresses operator notices only; guest and public-link security notices still post. Default: Topic.",
10
+ dailyTipsHint: "Posted in the General topic. Default: Topic. /tips still posts where invoked.",
11
11
  title: "Settings",
12
12
  myAgents: "My Agents",
13
13
  bots: "Bots",
@@ -679,10 +679,10 @@ register("settings", {
679
679
  se_externalEmojiHint: "使用其他伺服器的 emoji,bot 在要加反應的頻道需有「使用外部表情符號」權限。",
680
680
  se_unavailable: "無法使用",
681
681
  se_hint: "留空即使用預設值。預覽由 AgEnD 依 bot 實際 react 的方式解析。進度前綴不是反應,而是進度訊息開頭的 emoji。",
682
- noticeTopic: "頻道", noticeDm: "私訊(失敗時改送頻道)", noticeOff: "關閉",
682
+ noticeTopic: "頻道", noticeOff: "關閉",
683
683
  webNotifyLogin: "網頁登入通知", dailyTips: "每日提示",
684
- webNotifyLoginHint: "私訊登入的管理者或公開連結擁有者;本機訪客登入通知送給所屬連線的第一位設定管理員。帳號不明或私訊失敗時改送原頻道。關閉只停用管理者登入通知;訪客與公開連結安全通知仍送頻道。既有預設:頻道;新 fleet:私訊。",
685
- dailyTipsHint: "私訊 General 所屬連線的第一位設定管理員,失敗時改送頻道。預設:頻道。/tips 仍送至呼叫的聊天室。",
684
+ webNotifyLoginHint: "發在頻道。關閉只停用管理者登入通知;訪客與公開連結安全通知仍會發出。預設:頻道。",
685
+ dailyTipsHint: "發在 General 頻道。預設:頻道。/tips 仍送至呼叫的聊天室。",
686
686
  title: "設定",
687
687
  myAgents: "我的 Agent",
688
688
  bots: "機器人",
@@ -105,7 +105,7 @@ register("app", {
105
105
  confirmTitle: "Please confirm", confirmOk: "Continue", cancel: "Cancel",
106
106
  sidePanelLeave: "Leave {0} without saving? Your changes there will be lost.", sidePanelDiscard: "Discard changes",
107
107
  // The server's own wording (src/locale.ts needs.reason.*), so the web says what Discord says.
108
- needs_reason_hang: "Not responding", needs_reason_exited: "Exited", needs_reason_assist: "Waiting at its terminal", needs_reason_permission: "Permission needed", needs_reason_dangerous_command: "Dangerous command waiting", needs_reason_login: "Sign-in needed", needs_reason_dialog: "Waiting at a dialog", needs_reason_terminal_input: "May be waiting for input", needs_reason_auth_paused: "Paused: sign-in needed", needs_reason_crashed: "Crashed", needs_reason_delivery_uncertain: "Could not confirm delivery", needs_reason_delivery_failed: "Delivery failed", needs_reason_task_review: "Waiting for your confirmation", needs_reason_task_blocked: "Blocked: asks you",
108
+ needs_reason_hang: "Not responding", needs_reason_exited: "Exited", needs_reason_assist: "Waiting at its terminal", needs_reason_permission: "Permission needed", needs_reason_dangerous_command: "Dangerous command waiting", needs_reason_login: "Sign-in needed", needs_reason_dialog: "Waiting at a dialog", needs_reason_terminal_input: "May be waiting for input", needs_reason_auth_paused: "Paused: sign-in needed", needs_reason_backend_signin: "Backend sign-in needed", needs_reason_crashed: "Crashed", needs_reason_delivery_uncertain: "Could not confirm delivery", needs_reason_delivery_failed: "Delivery failed", needs_reason_task_review: "Waiting for your confirmation", needs_reason_task_blocked: "Blocked: asks you",
109
109
  },
110
110
  "zh-TW": {
111
111
  brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet 概覽", view: "檢視", settings: "設定", instances: "Instances", newInstance: "新增 instance", newAgent: "新增 Agent",
@@ -145,6 +145,6 @@ register("app", {
145
145
  search: "搜尋", searchTitle: "搜尋聊天、agent 之間的訊息與任務({0})",
146
146
  confirmTitle: "請確認", confirmOk: "繼續", cancel: "取消",
147
147
  sidePanelLeave: "不儲存就離開「{0}」?你在那裡的修改會遺失。", sidePanelDiscard: "捨棄修改",
148
- needs_reason_hang: "沒有回應", needs_reason_exited: "已結束", needs_reason_assist: "在終端機上等待", needs_reason_permission: "需要權限", needs_reason_dangerous_command: "危險指令待確認", needs_reason_login: "需要登入", needs_reason_dialog: "停在對話框", needs_reason_terminal_input: "可能在等輸入", needs_reason_auth_paused: "已暫停:需要登入", needs_reason_crashed: "已崩潰", needs_reason_delivery_uncertain: "無法確認是否送達", needs_reason_delivery_failed: "傳送失敗", needs_reason_task_review: "等你確認", needs_reason_task_blocked: "卡住,在問你",
148
+ needs_reason_hang: "沒有回應", needs_reason_exited: "已結束", needs_reason_assist: "在終端機上等待", needs_reason_permission: "需要權限", needs_reason_dangerous_command: "危險指令待確認", needs_reason_login: "需要登入", needs_reason_dialog: "停在對話框", needs_reason_terminal_input: "可能在等輸入", needs_reason_auth_paused: "已暫停:需要登入", needs_reason_backend_signin: "後端需要登入", needs_reason_crashed: "已崩潰", needs_reason_delivery_uncertain: "無法確認是否送達", needs_reason_delivery_failed: "傳送失敗", needs_reason_task_review: "等你確認", needs_reason_task_blocked: "卡住,在問你",
149
149
  },
150
150
  });
@@ -1936,6 +1936,7 @@ html.oo-immersive .p-fleet > .panel-body { z-index: 50; }
1936
1936
  .us-bar { display: block; width: 100%; height: 6px; margin: 4px 0; }
1937
1937
  .us-bar-bg { fill: var(--surface-2); } .us-bar-fg { fill: var(--us-0); }
1938
1938
  .us-meta { display: block; font-size: var(--fs-xs); color: var(--text-3); overflow-wrap: anywhere; }
1939
+ .us-est { display: block; font-size: var(--fs-xs); font-weight: var(--fw-regular); color: var(--text-3); }
1939
1940
  .us-flags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
1940
1941
  .us-trend, .us-heat { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
1941
1942
  .us-trend h3, .us-heat h3, .us-rest h3 { font-size: var(--fs-sm); font-weight: var(--fw-semibold); }
@@ -2503,3 +2504,31 @@ html.oo-immersive .p-fleet > .panel-body { z-index: 50; }
2503
2504
  .msg.user .msg-reactions { justify-content: flex-end; }
2504
2505
  .msg-reactions .reaction { display: inline-flex; align-items: center; justify-content: center; min-width: 28px; height: 24px; padding: 0 6px;
2505
2506
  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; }
2507
+
2508
+ /* #2636: an Office share link's page (office-share-page.js) — the Office alone, filling the window under a one-line head. */
2509
+ .share-page { display: flex; flex-direction: column; min-height: 100dvh; background: var(--bg); color: var(--text); }
2510
+ .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); }
2511
+ .share-head .share-note { margin-left: auto; }
2512
+ .share-stage { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; padding: var(--s-2); }
2513
+ .share-loading, .share-page > .note { padding: var(--s-4); }
2514
+ .share-ended { align-items: center; justify-content: center; padding: var(--s-6); text-align: center; }
2515
+ @media (max-width: 640px) { .share-head .share-note { margin-left: 0; flex-basis: 100%; } }
2516
+
2517
+ /* #2636: Office share links — the Share… dialog and the open links (office-share-dialog.js; Settings → Access & security). */
2518
+ .os { display: flex; flex-direction: column; gap: var(--s-3); }
2519
+ .os > p { margin: 0; }
2520
+ .os-form { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); align-items: end; }
2521
+ .os-form .check, .os-form .note { grid-column: 1 / -1; margin: 0; }
2522
+ .os-made { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
2523
+ .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);
2524
+ background: var(--bg); color: var(--text); font-size: var(--fs-sm); outline: none; }
2525
+ .os-url:focus { border-color: var(--focus); box-shadow: 0 0 0 3px var(--accent-dim); }
2526
+ .os-made .note { flex-basis: 100%; margin: 0; }
2527
+ .os-head { margin: var(--s-2) 0 0; font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-2); }
2528
+ .os-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-2); }
2529
+ .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); }
2530
+ .os-what { flex: 1; min-width: 0; overflow-wrap: anywhere; font-size: var(--fs-sm); }
2531
+ .os-all { margin-top: var(--s-2); }
2532
+ .os-none { margin: 0; }
2533
+ .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); }
2534
+ @media (max-width: 480px) { .os-form { grid-template-columns: 1fr; } }
@@ -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
  }