@songsid/agend 2.3.2-beta.9 → 2.3.2

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 (243) hide show
  1. package/README.md +6 -0
  2. package/README.zh-TW.md +6 -0
  3. package/dist/agent-cli.js +48 -0
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.js +6 -0
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/agent-whats-new.js +3 -0
  8. package/dist/agent-whats-new.js.map +1 -1
  9. package/dist/auth-api.js +2 -0
  10. package/dist/auth-api.js.map +1 -1
  11. package/dist/backend/claude-code.js +23 -1
  12. package/dist/backend/claude-code.js.map +1 -1
  13. package/dist/backend/codex-session-lookup.d.ts +15 -0
  14. package/dist/backend/codex-session-lookup.js +58 -0
  15. package/dist/backend/codex-session-lookup.js.map +1 -1
  16. package/dist/backend/codex.js +15 -4
  17. package/dist/backend/codex.js.map +1 -1
  18. package/dist/backend/kiro-classic-archive.d.ts +57 -0
  19. package/dist/backend/kiro-classic-archive.js +622 -0
  20. package/dist/backend/kiro-classic-archive.js.map +1 -0
  21. package/dist/backend/kiro-classic-links.d.ts +24 -0
  22. package/dist/backend/kiro-classic-links.js +135 -0
  23. package/dist/backend/kiro-classic-links.js.map +1 -0
  24. package/dist/backend/kiro-identity.d.ts +100 -1
  25. package/dist/backend/kiro-identity.js +262 -31
  26. package/dist/backend/kiro-identity.js.map +1 -1
  27. package/dist/backend/kiro-migration-verify.d.ts +10 -0
  28. package/dist/backend/kiro-migration-verify.js +52 -0
  29. package/dist/backend/kiro-migration-verify.js.map +1 -0
  30. package/dist/backend/kiro-reconcile-store.d.ts +27 -0
  31. package/dist/backend/kiro-reconcile-store.js +344 -0
  32. package/dist/backend/kiro-reconcile-store.js.map +1 -0
  33. package/dist/backend/kiro-v2-store-worker.js +35 -1
  34. package/dist/backend/kiro-v2-store-worker.js.map +1 -1
  35. package/dist/backend/kiro-v2-store.d.ts +31 -1
  36. package/dist/backend/kiro-v2-store.js +22 -12
  37. package/dist/backend/kiro-v2-store.js.map +1 -1
  38. package/dist/backend/kiro.d.ts +124 -1
  39. package/dist/backend/kiro.js +553 -29
  40. package/dist/backend/kiro.js.map +1 -1
  41. package/dist/backend/types.d.ts +102 -0
  42. package/dist/backend/types.js +9 -0
  43. package/dist/backend/types.js.map +1 -1
  44. package/dist/cache-service.d.ts +13 -1
  45. package/dist/cache-service.js +45 -2
  46. package/dist/cache-service.js.map +1 -1
  47. package/dist/channel/ipc-timeouts.js +3 -0
  48. package/dist/channel/ipc-timeouts.js.map +1 -1
  49. package/dist/channel/mcp-tools.js +40 -5
  50. package/dist/channel/mcp-tools.js.map +1 -1
  51. package/dist/channel/tool-router.js +2 -1
  52. package/dist/channel/tool-router.js.map +1 -1
  53. package/dist/classic-channel-manager.d.ts +6 -0
  54. package/dist/classic-channel-manager.js +25 -0
  55. package/dist/classic-channel-manager.js.map +1 -1
  56. package/dist/cli.js +50 -4
  57. package/dist/cli.js.map +1 -1
  58. package/dist/config-validator.d.ts +7 -1
  59. package/dist/config-validator.js +17 -8
  60. package/dist/config-validator.js.map +1 -1
  61. package/dist/config.js +2 -0
  62. package/dist/config.js.map +1 -1
  63. package/dist/daemon.d.ts +66 -0
  64. package/dist/daemon.js +421 -19
  65. package/dist/daemon.js.map +1 -1
  66. package/dist/dashboard-permissions.d.ts +15 -4
  67. package/dist/dashboard-permissions.js +49 -23
  68. package/dist/dashboard-permissions.js.map +1 -1
  69. package/dist/doctor.js +2 -1
  70. package/dist/doctor.js.map +1 -1
  71. package/dist/fleet-control-audit.d.ts +1 -1
  72. package/dist/fleet-control-audit.js +3 -0
  73. package/dist/fleet-control-audit.js.map +1 -1
  74. package/dist/fleet-manager.d.ts +90 -3
  75. package/dist/fleet-manager.js +569 -41
  76. package/dist/fleet-manager.js.map +1 -1
  77. package/dist/forged-envelope-scan.d.ts +9 -0
  78. package/dist/forged-envelope-scan.js +30 -5
  79. package/dist/forged-envelope-scan.js.map +1 -1
  80. package/dist/general-instance.d.ts +24 -0
  81. package/dist/general-instance.js +53 -0
  82. package/dist/general-instance.js.map +1 -1
  83. package/dist/general-knowledge/skills/docker-install/SKILL.md +25 -0
  84. package/dist/general-knowledge/skills/fleet-config/SKILL.md +5 -3
  85. package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
  86. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +34 -0
  87. package/dist/general-knowledge/skills/session-management/SKILL.md +1 -1
  88. package/dist/general-knowledge/skills/tui-effort/SKILL.md +1 -1
  89. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +32 -0
  90. package/dist/general-knowledge/skills/web-appearance/SKILL.md +32 -0
  91. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +57 -17
  92. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +57 -17
  93. package/dist/instance-config-impact.js +3 -2
  94. package/dist/instance-config-impact.js.map +1 -1
  95. package/dist/instance-lifecycle.d.ts +4 -0
  96. package/dist/instance-lifecycle.js +7 -0
  97. package/dist/instance-lifecycle.js.map +1 -1
  98. package/dist/kiro-migrate.d.ts +19 -2
  99. package/dist/kiro-migrate.js +133 -35
  100. package/dist/kiro-migrate.js.map +1 -1
  101. package/dist/kiro-reconcile.d.ts +21 -0
  102. package/dist/kiro-reconcile.js +88 -0
  103. package/dist/kiro-reconcile.js.map +1 -0
  104. package/dist/kiro-session-proof.d.ts +41 -0
  105. package/dist/kiro-session-proof.js +89 -0
  106. package/dist/kiro-session-proof.js.map +1 -0
  107. package/dist/kiro-tui-forensic.d.ts +19 -0
  108. package/dist/kiro-tui-forensic.js +124 -0
  109. package/dist/kiro-tui-forensic.js.map +1 -0
  110. package/dist/locale.js +198 -0
  111. package/dist/locale.js.map +1 -1
  112. package/dist/log-service.js +2 -2
  113. package/dist/log-service.js.map +1 -1
  114. package/dist/needs-you.d.ts +3 -1
  115. package/dist/needs-you.js +4 -2
  116. package/dist/needs-you.js.map +1 -1
  117. package/dist/office-levels.d.ts +64 -0
  118. package/dist/office-levels.js +66 -0
  119. package/dist/office-levels.js.map +1 -0
  120. package/dist/office-share-page.d.ts +3 -1
  121. package/dist/office-share-page.js +8 -3
  122. package/dist/office-share-page.js.map +1 -1
  123. package/dist/office-share-service.d.ts +6 -3
  124. package/dist/office-share-service.js +12 -8
  125. package/dist/office-share-service.js.map +1 -1
  126. package/dist/office-share.d.ts +45 -8
  127. package/dist/office-share.js +75 -15
  128. package/dist/office-share.js.map +1 -1
  129. package/dist/outbound-handlers.js +20 -1
  130. package/dist/outbound-handlers.js.map +1 -1
  131. package/dist/outbound-schemas.d.ts +47 -2
  132. package/dist/outbound-schemas.js +41 -2
  133. package/dist/outbound-schemas.js.map +1 -1
  134. package/dist/public-web-gateway.js +1 -1
  135. package/dist/public-web-gateway.js.map +1 -1
  136. package/dist/quickstart-api.d.ts +39 -1
  137. package/dist/quickstart-api.js +115 -17
  138. package/dist/quickstart-api.js.map +1 -1
  139. package/dist/remote-card-answer.d.ts +83 -0
  140. package/dist/remote-card-answer.js +208 -0
  141. package/dist/remote-card-answer.js.map +1 -0
  142. package/dist/remote-card-prompt.d.ts +38 -0
  143. package/dist/remote-card-prompt.js +86 -0
  144. package/dist/remote-card-prompt.js.map +1 -0
  145. package/dist/scheduler/task-board.d.ts +28 -6
  146. package/dist/scheduler/task-board.js +66 -11
  147. package/dist/scheduler/task-board.js.map +1 -1
  148. package/dist/service-installer.d.ts +13 -0
  149. package/dist/service-installer.js +15 -0
  150. package/dist/service-installer.js.map +1 -1
  151. package/dist/service-owner.d.ts +25 -0
  152. package/dist/service-owner.js +43 -2
  153. package/dist/service-owner.js.map +1 -1
  154. package/dist/settings-api.d.ts +2 -0
  155. package/dist/settings-api.js +18 -12
  156. package/dist/settings-api.js.map +1 -1
  157. package/dist/settings-change.js +10 -5
  158. package/dist/settings-change.js.map +1 -1
  159. package/dist/settings-effect.js +7 -4
  160. package/dist/settings-effect.js.map +1 -1
  161. package/dist/settings-instance-patch.js +1 -1
  162. package/dist/settings-instance-patch.js.map +1 -1
  163. package/dist/setup-form.d.ts +2 -2
  164. package/dist/setup-form.js +139 -93
  165. package/dist/setup-form.js.map +1 -1
  166. package/dist/setup-i18n.d.ts +25 -0
  167. package/dist/setup-i18n.js +226 -0
  168. package/dist/setup-i18n.js.map +1 -0
  169. package/dist/task-board-waker.js +7 -2
  170. package/dist/task-board-waker.js.map +1 -1
  171. package/dist/tool-permissions.js +18 -0
  172. package/dist/tool-permissions.js.map +1 -1
  173. package/dist/transcript-sources.d.ts +1 -0
  174. package/dist/transcript-sources.js +20 -3
  175. package/dist/transcript-sources.js.map +1 -1
  176. package/dist/types.d.ts +10 -0
  177. package/dist/ui/backend-signin.js +4 -3
  178. package/dist/ui/chat-store.js +3 -2
  179. package/dist/ui/chat-strings.js +2 -2
  180. package/dist/ui/fleet-office-card.js +114 -0
  181. package/dist/ui/fleet-office-editor.js +81 -9
  182. package/dist/ui/fleet-office-layout.js +56 -11
  183. package/dist/ui/fleet-office-levels.js +105 -0
  184. package/dist/ui/fleet-office-model.js +9 -1
  185. package/dist/ui/fleet-office-pack.d.ts +15 -3
  186. package/dist/ui/fleet-office-pack.js +177 -23
  187. package/dist/ui/fleet-office-skin.js +39 -3
  188. package/dist/ui/fleet-office-store.js +74 -3
  189. package/dist/ui/fleet-office-view.js +8 -0
  190. package/dist/ui/fleet-office-zones.js +102 -19
  191. package/dist/ui/fleet-office.js +308 -44
  192. package/dist/ui/fleet-org.js +2 -0
  193. package/dist/ui/fleet-task-card.js +2 -2
  194. package/dist/ui/fleet-task-common.js +30 -7
  195. package/dist/ui/fleet-task-work.js +2 -2
  196. package/dist/ui/fleet-tasks.js +10 -11
  197. package/dist/ui/office-share-dialog.js +31 -7
  198. package/dist/ui/office-share-page.js +1 -0
  199. package/dist/ui/panel-chat.js +14 -2
  200. package/dist/ui/panel-log.js +4 -4
  201. package/dist/ui/panel-needs.js +1 -1
  202. package/dist/ui/panel-settings.js +16 -8
  203. package/dist/ui/settings-discord.js +3 -2
  204. package/dist/ui/settings-guests.js +17 -7
  205. package/dist/ui/settings-model.js +17 -9
  206. package/dist/ui/settings-registry.js +1 -0
  207. package/dist/ui/settings-strings.js +16 -4
  208. package/dist/ui/settings-token.js +3 -2
  209. package/dist/ui/settings-wizard.js +25 -9
  210. package/dist/ui/shared/app-i18n.js +2 -2
  211. package/dist/ui/shared/app-stream.js +1 -1
  212. package/dist/ui/shared/app.css +70 -2
  213. package/dist/ui/shared/setup-host-text.js +107 -0
  214. package/dist/ui/structured-signin.js +89 -38
  215. package/dist/usage-rollup.d.ts +34 -3
  216. package/dist/usage-rollup.js +56 -3
  217. package/dist/usage-rollup.js.map +1 -1
  218. package/dist/web-api.d.ts +10 -1
  219. package/dist/web-api.js +207 -17
  220. package/dist/web-api.js.map +1 -1
  221. package/dist/web-auth.js +1 -1
  222. package/dist/web-auth.js.map +1 -1
  223. package/dist/web-office-acts.d.ts +5 -2
  224. package/dist/web-office-acts.js +9 -4
  225. package/dist/web-office-acts.js.map +1 -1
  226. package/dist/web-office-pack-read.d.ts +35 -0
  227. package/dist/web-office-pack-read.js +252 -0
  228. package/dist/web-office-pack-read.js.map +1 -0
  229. package/dist/web-office-pack-tools.d.ts +241 -0
  230. package/dist/web-office-pack-tools.js +785 -0
  231. package/dist/web-office-pack-tools.js.map +1 -0
  232. package/dist/web-office-skins.d.ts +68 -4
  233. package/dist/web-office-skins.js +324 -33
  234. package/dist/web-office-skins.js.map +1 -1
  235. package/dist/web-office-store.d.ts +47 -4
  236. package/dist/web-office-store.js +124 -23
  237. package/dist/web-office-store.js.map +1 -1
  238. package/dist/web-settings-patch.d.ts +3 -1
  239. package/dist/web-settings-patch.js +4 -1
  240. package/dist/web-settings-patch.js.map +1 -1
  241. package/dist/workflow-templates/default.md +1 -1
  242. package/package.json +2 -2
  243. package/templates/windows-shortcuts.ps1 +18 -17
@@ -60,6 +60,8 @@ export function orgState(i, exec, awaiting, needs) {
60
60
  * What a node's card says (fleet-canvas.js; the Office view's desk too, fleet-office.js): its names, its state and
61
61
  * backend (`sub`), and all of it in one line (`full`, the card's title and label).
62
62
  */
63
+ /** #2661: a node's state in words (as its card and tooltip say it). */
64
+ export const stateWords = (n) => to(n.state);
63
65
  export function nodeText(n) {
64
66
  const it = n.i;
65
67
  const names = it ? namesOf(it) : { primary: n.name, secondary: "" };
@@ -175,7 +175,7 @@ export function CardDialog({ id, tick, projects, writable = true, place = null,
175
175
  </div>
176
176
 
177
177
  <div class="tc-actions">
178
- <${StatusPill} status=${card.status} />
178
+ <${StatusPill} status=${card.status} card=${card} />
179
179
  ${writable ? html`
180
180
  ${card.status === "review" ? html`
181
181
  <button type="button" class="btn btn-primary btn-sm" disabled=${busy} onClick=${() => moved(() => moveCard(card, "done"))}><${Icon} name="check" size=${14} />${isMain ? t("taskcard.confirmMain") : t("tasks.confirm")}</button>
@@ -260,7 +260,7 @@ export function CardDialog({ id, tick, projects, writable = true, place = null,
260
260
 
261
261
  ${isMain ? html`<${Field} title=${t("taskcard.subs", children.length)}>
262
262
  ${children.length ? html`<ul class="tc-children">${children.map((c) => html`<li key=${c.id}><button type="button" onClick=${() => onOpen(c.id)}>
263
- <${StatusPill} status=${c.status} /><span class="tb-code">${c.id}</span><span class="tc-child-title">${c.title}</span><span class="tb-who">${c.assignee || ""}</span></button></li>`)}</ul>` : null}
263
+ <${StatusPill} status=${c.status} card=${c} /><span class="tb-code">${c.id}</span><span class="tc-child-title">${c.title}</span><span class="tb-who">${c.assignee || ""}</span></button></li>`)}</ul>` : null}
264
264
  ${finished || !writable ? null : html`<${QuickAdd} placeholder=${t("taskcard.quickAdd")} onAdd=${(title) => run(() => boardApi.create({ parent: card.id, title }))} />`}
265
265
  </${Field}>` : null}
266
266
 
@@ -10,7 +10,8 @@ import { toast } from "/assets/ui-toast.js";
10
10
 
11
11
  register("tasks", {
12
12
  en: {
13
- open: "To do", claimed: "In progress", blocked: "Blocked", review: "Waiting for you", done: "Done", cancelled: "Cancelled",
13
+ open: "To do", claimed: "In progress", blocked: "Blocked", review: "To confirm", done: "Done", cancelled: "Cancelled",
14
+ reviewYou: "Waiting for you", reviewBy: "Waiting for {0}", reviewByTip: "{0} confirms it, not you",
14
15
  main: "Main task", sub: "Sub task", urgent: "Urgent", high: "High", normal: "Normal", low: "Low",
15
16
  sizeS: "Small", sizeM: "Medium", sizeL: "Large", sizeNone: "Not sized",
16
17
  projects: "Projects", noProject: "No project", noProjectDesc: "Tasks the fleet opened outside any project",
@@ -53,7 +54,6 @@ register("tasks", {
53
54
  nowDoing: "Working on", nowBlocked: "Blocked", nowNext: "Next", ownerTip: "Main task owner: {0}", moreCards: "+{0}",
54
55
  wrapTag: "To fill in", wrapLine: "Not wrapped up yet: {0}", gapUnchecked: "{0} checklist items unchecked", gapUncheckedOne: "1 checklist item unchecked",
55
56
  gapNoResult: "no result", confirmGapTitle: "Confirm {0}?", confirmAnyway: "Confirm anyway",
56
- askedFirst: "{0} handed it out and has been asked; you can answer it here too",
57
57
  confirmGapLabel: "{0}. You can send it back to have it filled in — or confirm anyway, with a note if you like",
58
58
  "err.too_long": "{0} is too long: {1} characters, at most {2}", "err.state_lines": "State is at most {0} lines (it has {1})",
59
59
  "err.acceptance_empty": "An acceptance item cannot be empty", "err.acceptance_max": "At most {0} acceptance items",
@@ -76,7 +76,7 @@ register("tasks", {
76
76
  "note.deps_unmet": "Note: {0} not done yet",
77
77
  // #2246 step 3: who works on what (fleet-task-work.js)
78
78
  instances: "Instances", instancesTip: "The fleet's instances and what each works on", instancesHead: "{0} instance(s) · {1} working",
79
- workSentBack: "Sent back", workDoing: "In progress", workBlocked: "Blocked", workReview: "Waiting for you", workMain: "Owns",
79
+ workSentBack: "Sent back", workDoing: "In progress", workBlocked: "Blocked", workReview: "To confirm", workMain: "Owns",
80
80
  workReady: "Can start", workWaiting: "Waiting on", workSubs: "sub tasks {0}/{1}", workClaimable: "nobody's: can be taken",
81
81
  workWaitsFor: "waits for {0}", workNone: "Nothing on the board", workNewComments: "Comments it has not read yet",
82
82
  workOutside: "Not in the fleet", workFailed: "Could not read who works on what: {0}", workJoin: ", ",
@@ -84,6 +84,7 @@ register("tasks", {
84
84
  },
85
85
  "zh-TW": {
86
86
  open: "待辦", claimed: "進行中", blocked: "卡住", review: "待確認", done: "完成", cancelled: "取消",
87
+ reviewYou: "等你確認", reviewBy: "等 {0} 確認", reviewByTip: "由 {0} 確認,不是你",
87
88
  main: "主任務", sub: "子任務", urgent: "緊急", high: "高", normal: "一般", low: "低",
88
89
  sizeS: "小", sizeM: "中", sizeL: "大", sizeNone: "未定",
89
90
  projects: "專案", noProject: "未分類", noProjectDesc: "fleet 在專案以外開的 task",
@@ -126,7 +127,6 @@ register("tasks", {
126
127
  nowDoing: "執行中", nowBlocked: "卡住", nowNext: "下一步", ownerTip: "主任務負責人:{0}", moreCards: "+{0}",
127
128
  wrapTag: "待補齊", wrapLine: "負責人還沒補齊:{0}", gapUnchecked: "還有 {0} 項沒勾", gapUncheckedOne: "還有 1 項沒勾",
128
129
  gapNoResult: "沒有產出總結", confirmGapTitle: "確認 {0}?", confirmAnyway: "還是確認",
129
- askedFirst: "已先通知派工者 {0};你也可以直接在這裡回覆",
130
130
  confirmGapLabel: "{0}。可以先退回請負責人補齊;還是要確認的話,可以留一句確認意見",
131
131
  "err.too_long": "{0}太長:{1} 字,上限 {2} 字", "err.state_lines": "目前狀態最多 {0} 行(現在 {1} 行)",
132
132
  "err.acceptance_empty": "驗收條件不能是空的", "err.acceptance_max": "驗收條件最多 {0} 項",
@@ -190,6 +190,17 @@ export const percentDone = (units) => (units.total ? Math.round((units.done / un
190
190
  /** The key a card's project is filed under: its id, or NO_PROJECT. */
191
191
  export const projectKey = (card) => (card.projectId === null || card.projectId === undefined ? NO_PROJECT : card.projectId);
192
192
 
193
+ /**
194
+ * #2080: whether a card waits for the person — Waiting for you, and its count: the host's word (`waitsForPerson`, the
195
+ * same rule as Needs you: a card the person handed out, in review needing their confirmation or blocked on a question,
196
+ * or one whose dispatcher was deleted); a card read without it, by the person's own hand-out alone. An agent's card in
197
+ * review is its confirmer's (`confirmer`), never the person's.
198
+ */
199
+ export function waitsForYou(card) {
200
+ if (typeof card.waitsForPerson === "boolean") return card.waitsForPerson;
201
+ return !!card.dispatcher && card.dispatcher.kind === "person" && ((card.status === "review" && card.needsReview) || card.status === "blocked");
202
+ }
203
+
193
204
  /**
194
205
  * One project's numbers for its card on Home and its header, from the cards at hand (`key`: a project id or
195
206
  * NO_PROJECT). The board's read carries the server's (over every card, finished ones too); this is the fallback.
@@ -201,7 +212,7 @@ export function projectStats(key, cards) {
201
212
  if (projectKey(c) !== key) continue;
202
213
  if (!lastActivity || c.updatedAt > lastActivity) lastActivity = c.updatedAt;
203
214
  if (c.status === "cancelled") continue;
204
- if (c.status === "review") waiting++;
215
+ if (c.status === "review" && waitsForYou(c)) waiting++;
205
216
  if (c.status === "blocked") blocked++;
206
217
  if (c.kind !== "main") continue;
207
218
  mains++;
@@ -454,8 +465,20 @@ export function ProgressBar({ progress, wide = false }) {
454
465
  ${barSegments(progress).filter((s) => s.k === "done").map((s) => html`<rect key=${s.k} class=${`tb-seg st-${s.k}`} x=${s.x} width=${s.w} height="6" />`)}</svg>`;
455
466
  }
456
467
 
457
- export function StatusPill({ status }) {
458
- return html`<span class=${`tb-pill st-${status}`}>${t(`tasks.${status}`)}</span>`;
468
+ /** #2080: whom a card in review waits for, in words: you, the agent that confirms it, or (neither known) "To confirm". */
469
+ export function reviewLabel(card) {
470
+ if (waitsForYou(card)) return t("tasks.reviewYou");
471
+ return card.confirmer ? t("tasks.reviewBy", card.confirmer) : t("tasks.review");
472
+ }
473
+
474
+ /**
475
+ * A card's status. #2080: with the card, one in review says whom it waits for — the person ("Waiting for you") or the
476
+ * agent that confirms it ("Waiting for atlas").
477
+ */
478
+ export function StatusPill({ status, card = null }) {
479
+ const by = card && status === "review" && !waitsForYou(card) && card.confirmer ? card.confirmer : null;
480
+ const label = !card || status !== "review" ? t(`tasks.${status}`) : reviewLabel(card);
481
+ return html`<span class=${`tb-pill st-${status}${by ? " by-agent" : ""}`} title=${by ? t("tasks.reviewByTip", by) : null}>${label}</span>`;
459
482
  }
460
483
 
461
484
  /**
@@ -8,7 +8,7 @@ import { Dialog } from "/assets/ui-dialog.js";
8
8
  import { Icon } from "/assets/ui-icons.js";
9
9
  import { Skeleton } from "/assets/ui-states.js";
10
10
  import { GroupMark, InstanceAvatar, dotLabel, dotState, groupKind, groupLabel, groupOf, namesOf, namesStore } from "/assets/instance-nav.js";
11
- import { Who, boardApi, errorText } from "./fleet-task-common.js";
11
+ import { Who, boardApi, errorText, reviewLabel } from "./fleet-task-common.js";
12
12
 
13
13
  const LABEL = { back: "tasks.workSentBack", doing: "tasks.workDoing", blocked: "tasks.workBlocked", review: "tasks.workReview", main: "tasks.workMain", ready: "tasks.workReady", waiting: "tasks.workWaiting" };
14
14
 
@@ -33,7 +33,7 @@ export function WorkLines({ lines, onOpen }) {
33
33
  if (!lines.length) return html`<p class="muted tw-none">${t("tasks.workNone")}</p>`;
34
34
  return html`<ul class="tw-lines">${lines.map((l) => html`<li key=${`${l.kind}:${l.card.id}`}>
35
35
  <button type="button" class="tw-line" title=${`${l.card.id} ${l.card.title}`} onClick=${() => onOpen(l.card.id)}>
36
- <span class=${`tw-kind k-${l.kind}`}>${t(LABEL[l.kind])}</span><span class="tb-code">${l.card.id}</span><span class="tw-title">${l.card.title}</span>
36
+ <span class=${`tw-kind k-${l.kind}`}>${l.kind === "review" ? reviewLabel(l.card) : t(LABEL[l.kind])}</span><span class="tb-code">${l.card.id}</span><span class="tw-title">${l.card.title}</span>
37
37
  ${l.note ? html`<span class="muted tw-note">${l.note}</span>` : null}
38
38
  ${l.unread ? html`<span class="tw-unread" title=${t("tasks.workNewComments")} aria-label=${t("tasks.workNewComments")}><${Icon} name="chat" size=${13} /></span>` : null}
39
39
  </button></li>`)}</ul>`;
@@ -15,7 +15,7 @@ import { Empty, ErrorState, Skeleton } from "/assets/ui-states.js";
15
15
  import { Icon } from "/assets/ui-icons.js";
16
16
  import { Menu } from "/assets/ui-menu.js";
17
17
  import { toast } from "/assets/ui-toast.js";
18
- import { AskHost, COLUMNS, NO_PROJECT, ProgressBar, StatusPill, UNASSIGNED, Who, answerBlocked, boardApi, currentWork, dropNeighbours, errorText, followsSubs, moveCard, onPublicLink, projectKey, projectStats, readStored, recentlyDone, relTime, workersOf, wrapUpText, writeStored } from "./fleet-task-common.js";
18
+ import { AskHost, COLUMNS, NO_PROJECT, ProgressBar, StatusPill, UNASSIGNED, Who, answerBlocked, boardApi, currentWork, dropNeighbours, errorText, followsSubs, moveCard, onPublicLink, projectKey, projectStats, readStored, recentlyDone, relTime, reviewLabel, waitsForYou, workersOf, wrapUpText, writeStored } from "./fleet-task-common.js";
19
19
  import { CardDialog } from "./fleet-task-card.js";
20
20
  import { InstancesDrawer } from "./fleet-task-work.js";
21
21
 
@@ -188,16 +188,16 @@ function ProjectTile({ project, stats, onOpen, onDialog, reload }) {
188
188
  }
189
189
 
190
190
  /**
191
- * Cards waiting for the person: to confirm or send back, and blocked ones to answer. A card's buttons take one answer
192
- * at a time: claimed before the first await, so a double click sends one move.
191
+ * Cards waiting for the person: to confirm or send back, and blocked ones to answer — #2080: only those the person
192
+ * handed out (waitsForYou, Needs you's rule); an agent's card in review is its confirmer's, and a blocked one asks its
193
+ * dispatcher, not the person. A card's buttons take one answer at a time: claimed before the first await, so a double
194
+ * click sends one move.
193
195
  */
194
196
  function Waiting({ cards, writable, onOpen, reload }) {
195
- const { instances } = useStore(appStore);
196
- const fleetNames = new Set(instances.map((i) => i.name));
197
197
  const pending = useRef(new Set());
198
198
  const [busy, setBusy] = useState(() => new Set());
199
- const review = cards.filter((c) => c.status === "review");
200
- const blocked = cards.filter((c) => c.status === "blocked");
199
+ const review = cards.filter((c) => c.status === "review" && waitsForYou(c));
200
+ const blocked = cards.filter((c) => c.status === "blocked" && waitsForYou(c));
201
201
  if (!review.length && !blocked.length) return null;
202
202
  const act = async (fn, done, card) => {
203
203
  if (pending.current.has(card.id)) return;
@@ -213,8 +213,6 @@ function Waiting({ cards, writable, onOpen, reload }) {
213
213
  <${StatusPill} status=${c.status} />
214
214
  <button type="button" class="tb-wait-title" onClick=${() => onOpen(c.id)}><span class="tb-code">${c.id}</span> ${c.title}
215
215
  ${c.status === "blocked" && c.blockedReason ? html`<span class="tb-wait-reason">${c.blockedReason}</span>` : null}
216
- ${c.status === "blocked" && c.dispatcher && c.dispatcher.kind === "instance" && c.dispatcher.name !== c.assignee && fleetNames.has(c.dispatcher.name)
217
- ? html`<span class="tb-wait-asked muted">${t("tasks.askedFirst", c.dispatcher.name)}</span>` : null}
218
216
  ${c.status === "review" && wrapUpText(c) ? html`<span class="tb-wait-reason wrapup">${t("tasks.wrapLine", wrapUpText(c))}</span>` : null}</button>
219
217
  </div>
220
218
  <div class="tb-wait-actions">
@@ -257,7 +255,7 @@ function SearchBox({ onOpen }) {
257
255
  ${results ? html`<ul class="tb-search-results" role="listbox">
258
256
  ${results.length ? results.map((r, i) => html`<li key=${r.card.id} role="option" aria-selected=${i === active ? "true" : "false"} class=${i === active ? "active" : ""}>
259
257
  <button type="button" onClick=${() => pick(r.card.id)}>
260
- <span class="tb-code">${r.card.id}</span><span class="tb-search-title">${r.card.title}</span><${StatusPill} status=${r.card.status} />
258
+ <span class="tb-code">${r.card.id}</span><span class="tb-search-title">${r.card.title}</span><${StatusPill} status=${r.card.status} card=${r.card} />
261
259
  <span class="tb-search-where muted">${[r.project, r.parentTitle].filter(Boolean).join(" › ")}${r.match ? ` · ${t("tasks.inField", t(`tasks.field${r.match.field}`))}: ${r.match.text}` : ""}</span>
262
260
  </button></li>`) : html`<li class="muted tb-search-none">${t("tasks.searchEmpty")}</li>`}
263
261
  </ul>` : null}
@@ -457,9 +455,10 @@ function Tile({ card, dragging, flash, subs = [], expanded, writable, onToggleSu
457
455
  ${now ? html`<div class="tb-tile-now" title=${now.cards.map((c) => `${c.id} ${c.title}`).join("\n")}>
458
456
  <span class=${`tb-now-label st-${now.tone}`}>${t(now.label)}</span><span class="tb-code">${now.cards[0].id}</span>
459
457
  <span class="tb-now-title">${now.cards[0].title}</span>${now.cards.length > 1 ? html`<span class="muted">${t("tasks.moreCards", now.cards.length - 1)}</span>` : null}</div>` : null}
458
+ ${card.status === "review" && !hasSubs ? html`<div class=${`tb-tile-waits${waitsForYou(card) ? " you" : ""}`}>${reviewLabel(card)}</div>` : null}
460
459
  <div class="tb-tile-foot">
461
460
  ${now ? html`<span class="tb-people">${workers.map((w) => html`<${Who} key=${w} name=${w} empty=${t("tasks.nobody")} title=${t("tasks.ownerTip", card.assignee || t("tasks.nobody"))} />`)}</span>`
462
- : hasSubs && card.status === "review" ? html`<span class="tb-who tb-now-label st-review" title=${t("tasks.ownerTip", card.assignee || t("tasks.nobody"))}>${t("tasks.review")}</span>`
461
+ : hasSubs && card.status === "review" ? html`<span class="tb-who tb-now-label st-review" title=${t("tasks.ownerTip", card.assignee || t("tasks.nobody"))}>${reviewLabel(card)}</span>`
463
462
  : html`<${Who} name=${card.assignee} empty=${t("tasks.nobody")} />`}
464
463
  <span class="tb-tile-stats">
465
464
  ${card.acceptance.length ? html`<span class=${accepted === card.acceptance.length ? "all" : ""} title=${t(card.kind === "main" ? "tasks.definition" : "tasks.acceptance")}><${Icon} name="check" size=${13} />${accepted}/${card.acceptance.length}</span>` : null}
@@ -1,6 +1,9 @@
1
1
  // #2636: Office share links on the dashboard — the Office's Share… dialog (make one: which office, how long, whether
2
2
  // agents' own lines show; then its URL to copy) and the list of open ones (Settings → Access & security, and under the
3
3
  // dialog's form): each with its office, when it ends, whether lines show and who made it, Revoke and Revoke all.
4
+ // #2716: the whole-fleet office can be shared too ("auto", the default while it is the one shown); #2715: the link's
5
+ // look — light or dark as this dashboard shows it now (the default, the device's when it follows the device), or the
6
+ // viewer's device's.
4
7
  // GET / POST /ui/office-share, DELETE /ui/office-share/<id|all> (web-api.ts): an admin session on the dashboard reached
5
8
  // directly only; the public link never offers these.
6
9
  import { html, useEffect, useState } from "/assets/app-html.js";
@@ -11,7 +14,8 @@ import { toast } from "/assets/ui-toast.js";
11
14
  register("oshare", {
12
15
  en: {
13
16
  share: "Share…", title: "Share the office", lead: "Anyone with the link can watch this office live — names and states, nothing else — until it ends. No sign-in.",
14
- office: "Office", allOffices: "All offices", how: "For", text: "Include agents' own status lines", create: "Create link", creating: "Starting the link…",
17
+ office: "Office", allOffices: "All offices", wholeFleet: "Whole fleet", how: "For",
18
+ look: "Look", lookAuto: "The viewer's device (light or dark)", lookLight: "Light", lookDark: "Dark", text: "Include agents' own status lines", create: "Create link", creating: "Starting the link…",
15
19
  made: "Anyone with this link can watch until {0}.", copy: "Copy link", copied: "Link copied", open: "Open links", none: "No share links are open.",
16
20
  ends: "until {0}", withText: "with status lines", by: "by {0}", revoke: "Revoke", revokeAll: "Revoke all", close: "Close", failed: "The link could not be made.",
17
21
  m15: "15 minutes", m30: "30 minutes", h1: "1 hour", h2: "2 hours", h4: "4 hours", h8: "8 hours", h24: "24 hours",
@@ -22,7 +26,8 @@ register("oshare", {
22
26
  },
23
27
  "zh-TW": {
24
28
  share: "分享…", title: "分享辦公室", lead: "拿到連結的人都能即時觀看這間辦公室(只有名稱與狀態),直到連結結束,不需要登入。",
25
- office: "辦公室", allOffices: "所有辦公室", how: "時間", text: "包含 agent 自己的狀態文字", create: "建立連結", creating: "正在啟動連結…",
29
+ office: "辦公室", allOffices: "所有辦公室", wholeFleet: "整個 fleet", how: "時間",
30
+ look: "外觀", lookAuto: "依觀看者的裝置(淺色或深色)", lookLight: "淺色", lookDark: "深色", text: "包含 agent 自己的狀態文字", create: "建立連結", creating: "正在啟動連結…",
26
31
  made: "拿到這個連結的人可以觀看到 {0}。", copy: "複製連結", copied: "已複製連結", open: "開啟中的連結", none: "目前沒有開啟中的分享連結。",
27
32
  ends: "到 {0} 為止", withText: "含狀態文字", by: "由 {0} 建立", revoke: "撤銷", revokeAll: "全部撤銷", close: "關閉", failed: "無法建立連結。",
28
33
  m15: "15 分鐘", m30: "30 分鐘", h1: "1 小時", h2: "2 小時", h4: "4 小時", h8: "8 小時", h24: "24 小時",
@@ -44,6 +49,16 @@ export function refusalText(d) {
44
49
  return ["disabled", "office", "duration", "too_many"].includes(e) ? to(`e_${e}`) : to("failed");
45
50
  }
46
51
 
52
+ /**
53
+ * #2715: the look a new link takes by default — what this dashboard shows now: its chosen light or dark, or (following
54
+ * the device) the device's; unknown, the viewer's own.
55
+ */
56
+ export function defaultLook(theme = globalThis.AgendTheme && typeof globalThis.AgendTheme.get === "function" ? globalThis.AgendTheme.get() : null,
57
+ media = typeof globalThis.matchMedia === "function" ? globalThis.matchMedia : null) {
58
+ if (theme === "light" || theme === "dark") return theme;
59
+ try { return media ? (media("(prefers-color-scheme: dark)").matches ? "dark" : "light") : "auto"; } catch { return "auto"; }
60
+ }
61
+
47
62
  /** The open shares, read (and re-read after a change): { enabled, shares } or null while unread / unavailable. */
48
63
  export function useShares(get = globalThis.fetch, rev = 0) {
49
64
  const [d, setD] = useState(null);
@@ -67,7 +82,7 @@ export function ShareList({ shares, names = {}, get = globalThis.fetch, onChange
67
82
  if (!shares || !shares.length) return html`<p class="muted os-none">${to("none")}</p>`;
68
83
  return html`<div class="os-list">
69
84
  <ul class="os-rows">${shares.map((s) => html`<li key=${s.id} class="os-row">
70
- <span class="os-what"><strong>${s.office === "all" ? to("allOffices") : names[s.office] || s.office_name || s.office}</strong>
85
+ <span class="os-what"><strong>${s.office === "all" ? to("allOffices") : s.office === "auto" ? to("wholeFleet") : names[s.office] || s.office_name || s.office}</strong>
71
86
  <span class="muted"> · ${to("ends", when(s.expiresAt))}${s.text ? ` · ${to("withText")}` : ""}</span></span>
72
87
  <button type="button" class="btn btn-sm" disabled=${busy} onClick=${() => revoke(s.id)}>${to("revoke")}</button>
73
88
  </li>`)}</ul>
@@ -75,9 +90,13 @@ export function ShareList({ shares, names = {}, get = globalThis.fetch, onChange
75
90
  </div>`;
76
91
  }
77
92
 
78
- /** Share…: make a link for one office (the one shown, by default) or all of them. `offices`: the stored ones. */
93
+ /**
94
+ * Share…: make a link for one office (the one shown, by default), all of them, or (#2716) the whole-fleet office
95
+ * (`current` null: the one shown). `offices`: the stored ones.
96
+ */
79
97
  export function ShareDialog({ offices = [], current = null, get = globalThis.fetch, onClose }) {
80
- const [office, setOffice] = useState(current && offices.some((o) => o.id === current) ? current : "all");
98
+ const [office, setOffice] = useState(current === null ? "auto" : current === "all" || offices.some((o) => o.id === current) ? current : "all");
99
+ const [theme, setTheme] = useState(() => defaultLook());
81
100
  const [minutes, setMinutes] = useState(60);
82
101
  const [text, setText] = useState(false);
83
102
  const [busy, setBusy] = useState(false);
@@ -89,7 +108,7 @@ export function ShareDialog({ offices = [], current = null, get = globalThis.fet
89
108
  const create = async () => {
90
109
  setBusy(true); setError("");
91
110
  try {
92
- const r = await get("/ui/office-share", { method: "POST", headers: JSON_HEADERS, body: JSON.stringify({ office, minutes, text }) });
111
+ const r = await get("/ui/office-share", { method: "POST", headers: JSON_HEADERS, body: JSON.stringify({ office, minutes, text, theme }) });
93
112
  const d = await r.json().catch(() => ({}));
94
113
  if (r.ok && d && typeof d.url === "string") setMade(d); else setError(refusalText(d));
95
114
  } catch { setError(to("failed")); }
@@ -110,13 +129,18 @@ export function ShareDialog({ offices = [], current = null, get = globalThis.fet
110
129
  </div>` : html`<div class="os-form">
111
130
  <label class="field"><span>${to("office")}</span>
112
131
  <select class="tb-select" value=${office} onChange=${(e) => setOffice(e.target.value)} disabled=${busy}>
113
- ${offices.length > 1 || !offices.length ? html`<option value="all">${to("allOffices")}</option>` : null}
132
+ <option value="auto">${to("wholeFleet")}</option>
133
+ ${offices.length > 1 || office === "all" ? html`<option value="all">${to("allOffices")}</option>` : null}
114
134
  ${offices.map((o) => html`<option key=${o.id} value=${o.id}>${o.name}</option>`)}
115
135
  </select></label>
116
136
  <label class="field"><span>${to("how")}</span>
117
137
  <select class="tb-select" value=${String(minutes)} onChange=${(e) => setMinutes(Number(e.target.value))} disabled=${busy}>
118
138
  ${DURATIONS.map(([k, m]) => html`<option key=${k} value=${String(m)}>${to(k)}</option>`)}
119
139
  </select></label>
140
+ <label class="field"><span>${to("look")}</span>
141
+ <select class="tb-select os-look" value=${theme} onChange=${(e) => setTheme(e.target.value)} disabled=${busy}>
142
+ ${[["auto", "lookAuto"], ["light", "lookLight"], ["dark", "lookDark"]].map(([v, k]) => html`<option key=${v} value=${v}>${to(k)}</option>`)}
143
+ </select></label>
120
144
  <label class="check"><input type="checkbox" checked=${text} onChange=${(e) => setText(e.target.checked)} disabled=${busy} /> ${to("text")}</label>
121
145
  ${error ? html`<p class="err os-err" role="alert">${error}</p>` : null}
122
146
  </div>`}
@@ -30,6 +30,7 @@ export function shareUrl(base, path) {
30
30
  const p = String(path);
31
31
  if (p === "/ui/offices") return `${base}/offices`;
32
32
  if (p === "/ui/office-acts") return `${base}/office-acts`;
33
+ if (p === "/ui/office-levels") return `${base}/office-levels`;
33
34
  if (p === "/ui/office-skins" || p.startsWith("/ui/office-skins/") || p.startsWith("/ui/office-skins?")) return `${base}${p.slice(3)}`;
34
35
  return null;
35
36
  }
@@ -618,8 +618,20 @@ function Prompts({ name }) {
618
618
  if (!list.length) return null;
619
619
  return html`<div data-trust="permission" class="prompts" aria-live="polite">${list.map(p => html`<div key=${p.nonce} class=${`prompt${p.resolved ? " done" : ""}`} role="group" aria-label=${p.text}>
620
620
  <div class="txt">${p.resolved ? (p.outcome || t("chat.promptGone")) : textLinkSegments(p.text, p.links).map(sg => sg.href ? html`<a data-trust="link" class="st-link" href=${sg.href}>${sg.text}</a>` : sg.text)}</div>
621
- ${p.resolved ? null : html`<div class="acts">${p.actions.map(a => html`<button key=${a.id} type="button" class="btn" disabled=${!!p.busy} onClick=${() => store.answerPrompt(p, a.id)}>${a.label}</button>`)}
622
- ${p.busy ? html`<span class="wait">${t("chat.promptAnswering")}</span>` : null}</div>`}</div>`)}</div>`;
621
+ ${p.resolved ? null : html`<div class="acts">${p.actions.filter(a => !a.input).map(a => html`<button key=${a.id} type="button" class="btn" disabled=${!!p.busy} onClick=${() => store.answerPrompt(p, a.id)}>${a.label}</button>`)}
622
+ ${p.busy ? html`<span class="wait">${t("chat.promptAnswering")}</span>` : null}</div>`}
623
+ ${p.resolved ? null : p.actions.filter(a => a.input).map(a => html`<${PromptText} key=${a.id} p=${p} action=${a} />`)}</div>`)}</div>`;
624
+ }
625
+
626
+ /** #2675: a prompt's typed answer (a kiro question card's "answer in words"): a box and its send button. */
627
+ function PromptText({ p, action }) {
628
+ const [text, setText] = useState("");
629
+ const send = (e) => { e.preventDefault(); if (text.trim() && !p.busy) store.answerPrompt(p, action.id, text); };
630
+ return html`<form class="prompt-text" onSubmit=${send}>
631
+ <input type="text" maxlength="4000" aria-label=${action.label} placeholder=${t("chat.promptTextPlaceholder")} value=${text} disabled=${!!p.busy}
632
+ onInput=${(e) => setText(e.currentTarget.value)} />
633
+ <button type="submit" class="btn" disabled=${!!p.busy || !text.trim()}>${t("chat.promptTextSend")}</button>
634
+ </form>`;
623
635
  }
624
636
 
625
637
  /** "<name> is working… 0:42", "looks stuck", "Stopping…", or what it waits on at its terminal. Text only. */
@@ -55,14 +55,14 @@ register("log", {
55
55
  cat_read: "Read", cat_edit: "Edit", cat_search: "Search", cat_run: "Run", cat_web: "Web", cat_mcp: "MCP", cat_agent: "Sub-agent",
56
56
  cat_plan: "Plan", cat_message: "Message", cat_other: "Tool", plan_items: "{0} items",
57
57
  ev_crash_respawn: "Crashed and restarted", ev_crash_loop: "Keeps crashing", ev_hang_detected: "Looks stuck", ev_hang_action: "Stuck: acted on",
58
- ev_instance_paused: "Paused", ev_instance_resumed: "Resumed", ev_interactive_prompt: "Waiting at a prompt", ev_interactive_prompt_assist: "Prompt: asked for help",
58
+ ev_instance_paused: "Paused", ev_instance_resumed: "Resumed", ev_interactive_prompt: "Waiting at a prompt", ev_interactive_prompt_assist: "Prompt: asked for help", ev_remote_card: "Asked a question (posted to chat)", ev_remote_card_answer: "Question answered from chat",
59
59
  ev_dialog_parked: "A dialog waits", ev_model_failover: "Switched to another model", ev_model_recovered: "Model back",
60
60
  ev_mcp_died: "Tool server stopped", ev_mcp_recovered: "Tool server back", ev_mcp_auto_restart: "Tool server restarted",
61
61
  ev_message_failed: "A message was not delivered", ev_pty_error: "Terminal error", ev_pty_recovered: "Terminal recovered",
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_context_lost: "Started fresh: context lost", 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", ev_kiro_reconcile: "Kiro conversation reconciliation",
66
66
  },
67
67
  "zh-TW": {
68
68
  offTitle: "紀錄未開啟",
@@ -93,14 +93,14 @@ register("log", {
93
93
  cat_read: "讀取", cat_edit: "編輯", cat_search: "搜尋", cat_run: "執行", cat_web: "網頁", cat_mcp: "MCP", cat_agent: "子 agent",
94
94
  cat_plan: "計畫", cat_message: "訊息", cat_other: "工具", plan_items: "{0} 項",
95
95
  ev_crash_respawn: "當機後重新啟動", ev_crash_loop: "反覆當機", ev_hang_detected: "看起來卡住了", ev_hang_action: "卡住:已處理",
96
- ev_instance_paused: "已暫停", ev_instance_resumed: "已繼續", ev_interactive_prompt: "停在提示上等待", ev_interactive_prompt_assist: "提示:已請求協助",
96
+ ev_instance_paused: "已暫停", ev_instance_resumed: "已繼續", ev_interactive_prompt: "停在提示上等待", ev_interactive_prompt_assist: "提示:已請求協助", ev_remote_card: "提出問題(已貼到聊天)", ev_remote_card_answer: "問題已從聊天回答",
97
97
  ev_dialog_parked: "有對話框在等待", ev_model_failover: "改用另一個模型", ev_model_recovered: "模型恢復",
98
98
  ev_mcp_died: "工具伺服器停止", ev_mcp_recovered: "工具伺服器恢復", ev_mcp_auto_restart: "工具伺服器已重新啟動",
99
99
  ev_message_failed: "有訊息沒有送達", ev_pty_error: "終端機錯誤", ev_pty_recovered: "終端機恢復",
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_context_lost: "改用全新對話:context 遺失", 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: "工作狀態紀錄", ev_kiro_reconcile: "Kiro 對話接續處理",
104
104
  },
105
105
  });
106
106
  const tl = (key, ...a) => t(`log.${key}`, ...a);
@@ -196,7 +196,7 @@ function NeedsItem({ item, nowMs, owner }) {
196
196
  <div class="n-title"><strong>${reasonText(item)}</strong><span class="n-age">${age(item.since, nowMs)}</span></div>
197
197
  ${detailOf(item) ? html`<p class="n-detail">${detailOf(item)}</p>` : null}
198
198
  <div class="n-actions">
199
- ${item.type === "prompt" && Array.isArray(item.actions) ? item.actions.map((a) => html`<button key=${a.id} type="button" class="btn btn-sm"
199
+ ${item.type === "prompt" && Array.isArray(item.actions) ? item.actions.filter((a) => !a.input).map((a) => html`<button key=${a.id} type="button" class="btn btn-sm"
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}
@@ -9,7 +9,8 @@
9
9
  // - Not the Apply: confirming hands the staged changes to the app's runner (settings-apply.js) before the first write.
10
10
  // This panel renders that operation from the store, and finds it again when it mounts.
11
11
  import { html, useEffect, useMemo, useRef, useState } from "/assets/app-html.js";
12
- import { t } from "/assets/app-i18n.js";
12
+ import { t, lang } from "/assets/app-i18n.js";
13
+ import { hostText } from "/assets/setup-host-text.js";
13
14
  import { appStore, useStore } from "/assets/app-store.js";
14
15
  import { useLease } from "/assets/app-ctx.js";
15
16
  import { DevicePrefs, PanelHeader, PendingChanges, setTitle } from "/assets/app-shell.js";
@@ -27,7 +28,7 @@ import "./settings-strings.js";
27
28
  import { api, attach, confirmedWrite, newKey } from "./settings-confirm.js";
28
29
  import { dismissOperation, operationActive, restartFleet, startOperation, takeLeftover } from "./settings-apply.js";
29
30
  import {
30
- ACCESS_MODES, AGENT_MODES, DASHBOARD_FEATURES, DEFAULT_SCHEMA, ownChoiceImpact, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS, VISIBILITY_MODES, batchImpact,
31
+ ACCESS_MODES, AGENT_MODES, DASHBOARD_FEATURES, SEPARATE_FEATURE_KEYS, DEFAULT_SCHEMA, ownChoiceImpact, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS, VISIBILITY_MODES, batchImpact,
31
32
  changedFields, channelId, channelsOf, chLabel, connectionState, dashboardDefault, effectiveSummary, fleetModel, fromYaml, fullModelRequests, groupAgents,
32
33
  hasOwn, impactOf, localeOptions, defaultsImpact, requestImpact, nonNegative, positive, replyGuardSupported, sameValue, shortName, STATUS_ORDER, toYaml,
33
34
  toolSetDefault, visibilityDefault, worstImpact,
@@ -55,7 +56,8 @@ const tn = (k, ...v) => t(`settings.${k}`, ...v);
55
56
  /** What a refused change says to a person: the server's code in words (the code itself only for one not listed). */
56
57
  const APPLY_ERRORS = new Set(["unsupported_sensitive_effect", "confirmation_diff_too_large", "session_required", "admin_required", "settings_changed",
57
58
  "request_no_longer_current", "too_many_pending_changes", "fleet_unavailable", "invalid_settings_effect", "settings_confirmation_unavailable", "failed"]);
58
- export const applyErrorText = (code) => (APPLY_ERRORS.has(code) ? tn(`applyError_${code}`) : tn("applyErrorOther", String(code)));
59
+ // #2726: a code or words of the setup host (the wizard's commit) in the page's language; anything else as it came.
60
+ export const applyErrorText = (code) => (APPLY_ERRORS.has(code) ? tn(`applyError_${code}`) : tn("applyErrorOther", hostText(String(code), lang())));
59
61
  const ICONS = { preferences: "user", look: "palette", agents: "bot", "agent-defaults": "sliders", chat: "chat", "web-dashboard": "globe", access: "key", advanced: "code" };
60
62
  const RELEASES = "https://github.com/songsid/AgEnD/releases";
61
63
 
@@ -1024,16 +1026,16 @@ function General({ ctx, part = "all" }) {
1024
1026
  tips: noticeMode(f.tips), reply_triggers: f.replyTriggers, reply_completion_guard: f.reply, log_level: f.log_level,
1025
1027
  // The dashboard features every agent may use unless its own setting says otherwise. All of them go, because the write
1026
1028
  // to the fleet's defaults merges top-level keys only (`dashboard` is replaced whole): the untouched ones as saved.
1027
- dashboard: Object.fromEntries(DASHBOARD_FEATURES.filter((k) => k !== "office").map((k) => [k, f[`dash_${k}`]])),
1028
- office_access: f.dash_office,
1029
+ dashboard: Object.fromEntries(DASHBOARD_FEATURES.filter((k) => !Object.hasOwn(SEPARATE_FEATURE_KEYS, k)).map((k) => [k, f[`dash_${k}`]])),
1030
+ ...Object.fromEntries(Object.entries(SEPARATE_FEATURE_KEYS).map(([k, key]) => [key, f[`dash_${k}`]])),
1029
1031
  hang_detector: { enabled: f.hangOn, timeout_minutes: num(f.hang) },
1030
1032
  startup: { ...(f.concurrency.trim() === "" ? {} : { concurrency: num(f.concurrency) }), stagger_delay_ms: num(f.stagger) },
1031
1033
  }, {
1032
1034
  backend: d.backend || "claude-code", model: d.model || undefined, locale: d.locale || null, auto_pause_after: d.auto_pause_after ?? 0,
1033
1035
  agent_mode: d.agent_mode || "mcp", tool_set: TOOL_SETS.includes(d.tool_set) ? d.tool_set : null, tool_progress: d.tool_progress ?? "off",
1034
1036
  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",
1035
- dashboard: Object.fromEntries(DASHBOARD_FEATURES.filter((k) => k !== "office").map((k) => [k, dashboardDefault(d, k)])),
1036
- office_access: dashboardDefault(d, "office"),
1037
+ dashboard: Object.fromEntries(DASHBOARD_FEATURES.filter((k) => !Object.hasOwn(SEPARATE_FEATURE_KEYS, k)).map((k) => [k, dashboardDefault(d, k)])),
1038
+ ...Object.fromEntries(Object.entries(SEPARATE_FEATURE_KEYS).map(([k, key]) => [key, dashboardDefault(d, k)])),
1037
1039
  hang_detector: { enabled: hang.enabled !== false, timeout_minutes: hang.timeout_minutes ?? HANG_TIMEOUT_DEFAULT_MINUTES },
1038
1040
  startup: { ...(startup.concurrency == null ? {} : { concurrency: startup.concurrency }), stagger_delay_ms: startup.stagger_delay_ms ?? 500 },
1039
1041
  });
@@ -1196,13 +1198,14 @@ function WebSection({ ctx, staged, part = "all" }) {
1196
1198
  echo: web.echo_to_channel ?? true,
1197
1199
  log: web.log_tab === true, // #1218: off by default
1198
1200
  usagePub: web.usage_report_on_public_link === true, // #2122: off by default
1201
+ levels: web.office_levels !== false, // #2661: on by default
1199
1202
  pub: { allow_public: (web.public_link && web.public_link.allow_public) ?? true, ttl_minutes: (web.public_link && web.public_link.ttl_minutes) ?? 120, protocol: (web.public_link && web.public_link.protocol) ?? "http2", hostname: (web.public_link && web.public_link.hostname) ?? "", tunnel_token_env: (web.public_link && web.public_link.tunnel_token_env) ?? "", origin_port: (web.public_link && web.public_link.origin_port) ?? 19282, auto_open: (web.public_link && web.public_link.auto_open) ?? false },
1200
1203
  session: { absolute_hours: local.absolute_hours ?? null, idle_hours: local.idle_hours ?? null },
1201
1204
  terminal: { enabled: term.enabled ?? null, ttl_minutes: term.ttl_minutes ?? null },
1202
1205
  }), []);
1203
1206
  const text = (v) => (v === null || v === undefined ? "" : String(v));
1204
1207
  /** #2347: a field's row class: `s-dirty` while it differs from what is saved (the draft may come from before). */
1205
- const savedForm = useMemo(() => ({ notify: String(original.notify), echo: original.echo, log: original.log, usagePub: original.usagePub, pub: original.pub.allow_public,
1208
+ const savedForm = useMemo(() => ({ notify: String(original.notify), echo: original.echo, log: original.log, usagePub: original.usagePub, levels: original.levels, pub: original.pub.allow_public,
1206
1209
  ttl: String(original.pub.ttl_minutes), protocol: original.pub.protocol, hostname: original.pub.hostname, originPort: String(original.pub.origin_port),
1207
1210
  abs: text(original.session.absolute_hours), idle: text(original.session.idle_hours), termOn: original.terminal.enabled !== false,
1208
1211
  termTtl: text(original.terminal.ttl_minutes), autoOpen: original.pub.auto_open }), []);
@@ -1216,6 +1219,7 @@ function WebSection({ ctx, staged, part = "all" }) {
1216
1219
  return {
1217
1220
  notify: String(pick(pending, "notify_login", original.notify)),
1218
1221
  echo: pick(pending, "echo_to_channel", original.echo), log: pick(pending, "log_tab", original.log), usagePub: pick(pending, "usage_report_on_public_link", original.usagePub),
1222
+ levels: pick(pending, "office_levels", original.levels),
1219
1223
  pub: pick(pub, "allow_public", original.pub.allow_public), ttl: String(pick(pub, "ttl_minutes", original.pub.ttl_minutes)), protocol: pick(pub, "protocol", original.pub.protocol),
1220
1224
  hostname: pick(pub, "hostname", original.pub.hostname), originPort: String(pick(pub, "origin_port", original.pub.origin_port)), autoOpen: pick(pub, "auto_open", original.pub.auto_open),
1221
1225
  abs: text(pick(session, "absolute_hours", original.session.absolute_hours)), idle: text(pick(session, "idle_hours", original.session.idle_hours)),
@@ -1267,6 +1271,7 @@ function WebSection({ ctx, staged, part = "all" }) {
1267
1271
  ...(f.echo !== original.echo ? { echo_to_channel: f.echo } : {}),
1268
1272
  ...(f.log !== original.log ? { log_tab: f.log } : {}),
1269
1273
  ...(f.usagePub !== original.usagePub ? { usage_report_on_public_link: f.usagePub } : {}),
1274
+ ...(f.levels !== original.levels ? { office_levels: f.levels } : {}),
1270
1275
  ...(Object.keys(publicPatch).length ? { public_link: publicPatch } : {}),
1271
1276
  ...(Object.keys(sessionPatch).length ? { session: { local: sessionPatch } } : {}),
1272
1277
  ...(Object.keys(terminalPatch).length ? { web_terminal: terminalPatch } : {}),
@@ -1276,6 +1281,7 @@ function WebSection({ ctx, staged, part = "all" }) {
1276
1281
  ...(body.echo_to_channel !== undefined ? ["web.echo_to_channel"] : []),
1277
1282
  ...(body.log_tab !== undefined ? ["web.log_tab"] : []),
1278
1283
  ...(body.usage_report_on_public_link !== undefined ? ["web.usage_report_on_public_link"] : []),
1284
+ ...(body.office_levels !== undefined ? ["web.office_levels"] : []),
1279
1285
  ...Object.keys(publicPatch).map((k) => `web.public_link.${k}`),
1280
1286
  ...Object.keys(sessionPatch).map((k) => `web.session.local.${k}`),
1281
1287
  ...Object.keys(terminalPatch).map((k) => `web_terminal.${k}`),
@@ -1367,6 +1373,8 @@ function WebSection({ ctx, staged, part = "all" }) {
1367
1373
  <p class="note">${tn("webLogTabHint")}</p>
1368
1374
  <label class=${fc("check", "usagePub")}><input id="webUsagePublic" type="checkbox" checked=${f.usagePub} onChange=${(e) => set("usagePub")(e.target.checked)} /><span>${tn("webUsagePublic")}</span><${Impact} schema=${schema} field="web.usage_report_on_public_link" /></label>
1369
1375
  <p class="note">${tn("webUsagePublicHint")}</p>
1376
+ <label class=${fc("check", "levels")}><input id="webOfficeLevels" type="checkbox" checked=${f.levels} onChange=${(e) => set("levels")(e.target.checked)} /><span>${tn("webOfficeLevels")}</span><${Impact} schema=${schema} field="web.office_levels" /></label>
1377
+ <p class="note">${tn("webOfficeLevelsHint")}</p>
1370
1378
  </section>`) : null}
1371
1379
  ${has("readonly") ? html`<section class="card s-web-readonly"><h3>${tn("webReadOnlyTitle")}</h3>
1372
1380
  <p class="note">${tn("webReadOnlyLead")}</p>
@@ -8,7 +8,8 @@
8
8
  // Stop, the 2-minute limit or leaving the page each retire what was asked before — an answer about A never lands after
9
9
  // B, nor after A again (an older A), nor once the page is gone.
10
10
  import { html, useEffect, useRef, useState } from "/assets/app-html.js";
11
- import { t } from "/assets/app-i18n.js";
11
+ import { t, lang } from "/assets/app-i18n.js";
12
+ import { hostText } from "/assets/setup-host-text.js";
12
13
  import { useTypedText } from "/assets/text-undo.js";
13
14
  import { api } from "./settings-confirm.js";
14
15
  import { Select } from "./settings-dialogs.js";
@@ -129,6 +130,6 @@ export function DiscordServerPicker({ idPrefix, token, bot, invite, portal, guil
129
130
  ${channels && channels.list && channels.list.length
130
131
  ? html`<${Select} id=${`${idPrefix}-gen`} value=${channel} onChange=${pickChannel} options=${["", ...channels.list.map((c) => ({ value: c.id, label: `#${c.name}` }))]} />`
131
132
  : html`<input id=${`${idPrefix}-gen`} type="text" value=${channelText} onInput=${(e) => pickChannel(onChannelText(e.target.value))} />`}
132
- ${channels && channels.error ? html`<p class="feedback warning">${channels.error}</p>` : null}</div>
133
+ ${channels && channels.error ? html`<p class="feedback warning">${hostText(channels.error, lang())}</p>` : null}</div>
133
134
  </div>`;
134
135
  }
@@ -60,11 +60,19 @@ function wroteTo(names) {
60
60
  return html`${before}${names.map((n, i) => html`${i ? ", " : ""}<span key=${n} class="g-wrote">${n}</span>`)}${after}`;
61
61
  }
62
62
 
63
- function Names({ id, all, value, wrote = [], onChange }) {
63
+ /** #2667: a granted name as a person reads it — a ClassicBot room by its display name, when it has one. */
64
+ const shownName = (rooms, n) => { const r = (rooms || []).find((x) => x.name === n); return (r && r.display_name) || n; };
65
+
66
+ /** The agents to choose from: fleet.yaml's instances, then (#2667) the ClassicBot rooms, under their own heading. */
67
+ function Names({ id, all, rooms = [], value, wrote = [], onChange }) {
64
68
  const chosen = new Set(value || []);
65
- return html`<div class="guest-names" id=${id} role="group">${all.map((n) => html`<label key=${n} class="check guest-name">
66
- <input type="checkbox" checked=${chosen.has(n)} onChange=${(e) => { const s = new Set(chosen); if (e.target.checked) s.add(n); else s.delete(n); onChange(all.filter((x) => s.has(x))); }} />
67
- <span>${n}${wrote.includes(n) ? html` <span class="note">${tn("grantsWroteHere")}</span>` : null}</span></label>`)}</div>`;
69
+ const order = [...all, ...rooms.map((r) => r.name)];
70
+ const box = (n, label, note) => html`<label key=${n} class="check guest-name" title=${note ? n : undefined}>
71
+ <input type="checkbox" checked=${chosen.has(n)} onChange=${(e) => { const s = new Set(chosen); if (e.target.checked) s.add(n); else s.delete(n); onChange(order.filter((x) => s.has(x))); }} />
72
+ <span>${label}${note ? html` <span class="note">${n}</span>` : null}${wrote.includes(n) ? html` <span class="note">${tn("grantsWroteHere")}</span>` : null}</span></label>`;
73
+ return html`<div class="guest-names" id=${id} role="group">${all.map((n) => box(n, n, false))}
74
+ ${rooms.length ? html`<span class="guest-names-group note" key="--rooms">${tn("grantsClassicRooms")}</span>` : null}
75
+ ${rooms.map((r) => box(r.name, r.display_name || r.name, !!r.display_name))}</div>`;
68
76
  }
69
77
 
70
78
  const clock = (ms) => new Date(ms).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
@@ -147,6 +155,8 @@ export function GuestsSection({ ctx, staged }) {
147
155
  const setGrant = (k, grant) => { setStatus(null); setF((x) => { const g = { ...(x.grants || {}) }; if (grant) g[k] = grant; else delete g[k]; return { ...x, grants: g }; }); };
148
156
  const guests = (overview && overview.guests) || [];
149
157
  const instances = (overview && overview.instances) || [];
158
+ const rooms = (overview && Array.isArray(overview.rooms) && overview.rooms) || [];
159
+ const choosable = [...instances, ...rooms.map((r) => r.name)];
150
160
  const who = (k) => {
151
161
  const g = guests.find((x) => accountKey(x) === k), p = (people || []).find((x) => accountKey(x) === k);
152
162
  const grant = (f.grants && f.grants[k]) || (original.grants && original.grants[k]);
@@ -223,10 +233,10 @@ export function GuestsSection({ ctx, staged }) {
223
233
  <div class="g-head"><span class="g-av" aria-hidden="true">${(person.name || "?").slice(0, 1).toUpperCase()}</span>
224
234
  <div class="g-id"><strong>${person.name}</strong> <span class="note">${PLATFORMS[person.platform] || person.platform}${person.username ? ` · @${person.username}` : ""}</span></div>
225
235
  ${removed ? html`<span class="g-state">${tn("grantsWillBeRemoved")}</span>` : added ? html`<span class="g-state">${tn("grantsNew")}</span>` : pill(person)}</div>
226
- <div class="g-chips">${((draft || saved).instances || []).map((n) => html`<span key=${n} class="g-chip">${n}</span>`)}</div>
236
+ <div class="g-chips">${((draft || saved).instances || []).map((n) => html`<span key=${n} class="g-chip" title=${shownName(rooms, n) !== n ? n : undefined}>${shownName(rooms, n)}</span>`)}</div>
227
237
  ${draft && !draft.instances.length ? html`<p class="note g-error" role="alert">${tn("grantsNoAgents")}</p>` : null}
228
238
  ${editing === k && draft ? html`<div class="g-edit"><p class="note">${tn("grantsMaySee", person.name)}</p>
229
- <${Names} id=${`grant-${k}`} all=${instances} value=${draft.instances} wrote=${person.wrote} onChange=${(list) => setGrant(k, { ...draft, instances: list })} /></div>` : null}
239
+ <${Names} id=${`grant-${k}`} all=${instances} rooms=${rooms} value=${draft.instances} wrote=${person.wrote} onChange=${(list) => setGrant(k, { ...draft, instances: list })} /></div>` : null}
230
240
  ${readOnly ? null : html`<div class="guest-actions">
231
241
  ${draft ? html`<button type="button" class="btn btn-sm" aria-expanded=${editing === k ? "true" : "false"} onClick=${() => setEditing(editing === k ? null : k)}>${tn("grantsEditAgents")}</button>` : null}
232
242
  ${draft ? html`<button type="button" class="btn btn-sm g-send" disabled=${!applied || ss.sending || cooling || !f.enabled || !original.enabled}
@@ -245,7 +255,7 @@ export function GuestsSection({ ctx, staged }) {
245
255
  <span class="g-av" aria-hidden="true">${(p.name || "?").slice(0, 1).toUpperCase()}</span>
246
256
  <div class="g-id"><strong>${p.name}</strong> <span class="note">${PLATFORMS[p.platform] || p.platform}${p.username ? ` · @${p.username}` : ""} · <span class="g-when">${tn("grantsLastSeen", new Date(p.lastSeen).toLocaleString())}</span></span>
247
257
  ${p.instances && p.instances.length ? html`<div class="note">${wroteTo(p.instances)}</div>` : null}</div>
248
- <button type="button" class="btn btn-sm" onClick=${() => { const k = accountKey(p); setGrant(k, { ...(p.name ? { name: p.name } : {}), instances: instances.filter((n) => (p.instances || []).includes(n)) }); setEditing(k); }}>${tn("grantsChoose")}</button></li>`)}</ul>`}`}
258
+ <button type="button" class="btn btn-sm" onClick=${() => { const k = accountKey(p); setGrant(k, { ...(p.name ? { name: p.name } : {}), instances: choosable.filter((n) => (p.instances || []).includes(n)) }); setEditing(k); }}>${tn("grantsChoose")}</button></li>`)}</ul>`}`}
249
259
  </fieldset>
250
260
  ${overview && overview.audit ? html`<h4>${tn("grantsRecent")}</h4>${!overview.audit.length ? html`<p class="note" id="grantsRecentNone">${tn("grantsRecentNone")}</p>`
251
261
  : html`<ul class="g-audit" id="grantsRecentList">${overview.audit.map((e, i) => html`<li key=${`${e.at}-${i}`} class="g-audit-row" data-kind=${e.kind}>