@songsid/agend 2.3.2-beta.8 → 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.
- package/README.md +6 -0
- package/README.zh-TW.md +6 -0
- package/dist/agent-cli.js +48 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +6 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.js +3 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.js +2 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/claude-code.js +23 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-session-lookup.d.ts +15 -0
- package/dist/backend/codex-session-lookup.js +58 -0
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.js +15 -4
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/kiro-classic-archive.d.ts +57 -0
- package/dist/backend/kiro-classic-archive.js +622 -0
- package/dist/backend/kiro-classic-archive.js.map +1 -0
- package/dist/backend/kiro-classic-links.d.ts +24 -0
- package/dist/backend/kiro-classic-links.js +135 -0
- package/dist/backend/kiro-classic-links.js.map +1 -0
- package/dist/backend/kiro-identity.d.ts +100 -1
- package/dist/backend/kiro-identity.js +262 -31
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro-migration-verify.d.ts +10 -0
- package/dist/backend/kiro-migration-verify.js +52 -0
- package/dist/backend/kiro-migration-verify.js.map +1 -0
- package/dist/backend/kiro-reconcile-store.d.ts +27 -0
- package/dist/backend/kiro-reconcile-store.js +344 -0
- package/dist/backend/kiro-reconcile-store.js.map +1 -0
- package/dist/backend/kiro-v2-store-worker.js +35 -1
- package/dist/backend/kiro-v2-store-worker.js.map +1 -1
- package/dist/backend/kiro-v2-store.d.ts +31 -1
- package/dist/backend/kiro-v2-store.js +22 -12
- package/dist/backend/kiro-v2-store.js.map +1 -1
- package/dist/backend/kiro.d.ts +124 -1
- package/dist/backend/kiro.js +553 -29
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +102 -0
- package/dist/backend/types.js +9 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/cache-service.d.ts +13 -1
- package/dist/cache-service.js +45 -2
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/ipc-timeouts.js +3 -0
- package/dist/channel/ipc-timeouts.js.map +1 -1
- package/dist/channel/mcp-tools.js +40 -5
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.js +2 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +6 -0
- package/dist/classic-channel-manager.js +25 -0
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +50 -4
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +7 -1
- package/dist/config-validator.js +17 -8
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +66 -0
- package/dist/daemon.js +421 -19
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +15 -4
- package/dist/dashboard-permissions.js +49 -23
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/doctor.js +2 -1
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-control-audit.d.ts +1 -1
- package/dist/fleet-control-audit.js +3 -0
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-manager.d.ts +90 -3
- package/dist/fleet-manager.js +569 -41
- package/dist/fleet-manager.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +9 -0
- package/dist/forged-envelope-scan.js +30 -5
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/general-instance.d.ts +24 -0
- package/dist/general-instance.js +53 -0
- package/dist/general-instance.js.map +1 -1
- package/dist/general-knowledge/skills/docker-install/SKILL.md +25 -0
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +5 -3
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +34 -0
- package/dist/general-knowledge/skills/session-management/SKILL.md +1 -1
- package/dist/general-knowledge/skills/tui-effort/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +32 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +32 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +60 -18
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +60 -18
- package/dist/instance-config-impact.js +3 -2
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +4 -0
- package/dist/instance-lifecycle.js +7 -0
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/kiro-migrate.d.ts +19 -2
- package/dist/kiro-migrate.js +133 -35
- package/dist/kiro-migrate.js.map +1 -1
- package/dist/kiro-reconcile.d.ts +21 -0
- package/dist/kiro-reconcile.js +88 -0
- package/dist/kiro-reconcile.js.map +1 -0
- package/dist/kiro-session-proof.d.ts +41 -0
- package/dist/kiro-session-proof.js +89 -0
- package/dist/kiro-session-proof.js.map +1 -0
- package/dist/kiro-tui-forensic.d.ts +19 -0
- package/dist/kiro-tui-forensic.js +124 -0
- package/dist/kiro-tui-forensic.js.map +1 -0
- package/dist/locale.js +198 -0
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +2 -2
- package/dist/log-service.js.map +1 -1
- package/dist/needs-you.d.ts +3 -1
- package/dist/needs-you.js +4 -2
- package/dist/needs-you.js.map +1 -1
- package/dist/office-levels.d.ts +64 -0
- package/dist/office-levels.js +66 -0
- package/dist/office-levels.js.map +1 -0
- package/dist/office-share-page.d.ts +3 -1
- package/dist/office-share-page.js +8 -3
- package/dist/office-share-page.js.map +1 -1
- package/dist/office-share-service.d.ts +6 -3
- package/dist/office-share-service.js +12 -8
- package/dist/office-share-service.js.map +1 -1
- package/dist/office-share.d.ts +80 -13
- package/dist/office-share.js +145 -25
- package/dist/office-share.js.map +1 -1
- package/dist/outbound-handlers.js +20 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +47 -2
- package/dist/outbound-schemas.js +41 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +1 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.d.ts +39 -1
- package/dist/quickstart-api.js +115 -17
- package/dist/quickstart-api.js.map +1 -1
- package/dist/remote-card-answer.d.ts +83 -0
- package/dist/remote-card-answer.js +208 -0
- package/dist/remote-card-answer.js.map +1 -0
- package/dist/remote-card-prompt.d.ts +38 -0
- package/dist/remote-card-prompt.js +86 -0
- package/dist/remote-card-prompt.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +28 -6
- package/dist/scheduler/task-board.js +66 -11
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/service-installer.d.ts +13 -0
- package/dist/service-installer.js +15 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/service-owner.d.ts +25 -0
- package/dist/service-owner.js +43 -2
- package/dist/service-owner.js.map +1 -1
- package/dist/settings-api.d.ts +2 -0
- package/dist/settings-api.js +18 -12
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +10 -5
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +7 -4
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-instance-patch.js +1 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/setup-form.d.ts +2 -2
- package/dist/setup-form.js +139 -93
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-i18n.d.ts +25 -0
- package/dist/setup-i18n.js +226 -0
- package/dist/setup-i18n.js.map +1 -0
- package/dist/task-board-waker.js +7 -2
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tool-permissions.js +18 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/transcript-sources.d.ts +1 -0
- package/dist/transcript-sources.js +20 -3
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/ui/backend-signin.js +4 -3
- package/dist/ui/chat-store.js +3 -2
- package/dist/ui/chat-strings.js +2 -2
- package/dist/ui/fleet-office-card.js +114 -0
- package/dist/ui/fleet-office-editor.js +81 -9
- package/dist/ui/fleet-office-layout.js +87 -10
- package/dist/ui/fleet-office-levels.js +105 -0
- package/dist/ui/fleet-office-model.js +9 -1
- package/dist/ui/fleet-office-pack.d.ts +18 -3
- package/dist/ui/fleet-office-pack.js +217 -23
- package/dist/ui/fleet-office-skin.js +39 -3
- package/dist/ui/fleet-office-store.js +74 -3
- package/dist/ui/fleet-office-view.js +8 -0
- package/dist/ui/fleet-office-zones.js +102 -19
- package/dist/ui/fleet-office.js +323 -50
- package/dist/ui/fleet-org.js +2 -0
- package/dist/ui/fleet-task-card.js +2 -2
- package/dist/ui/fleet-task-common.js +30 -7
- package/dist/ui/fleet-task-work.js +2 -2
- package/dist/ui/fleet-tasks.js +10 -11
- package/dist/ui/office-share-dialog.js +31 -7
- package/dist/ui/office-share-page.js +50 -1
- package/dist/ui/panel-chat.js +14 -2
- package/dist/ui/panel-log.js +4 -4
- package/dist/ui/panel-needs.js +1 -1
- package/dist/ui/panel-settings.js +16 -8
- package/dist/ui/settings-discord.js +3 -2
- package/dist/ui/settings-guests.js +17 -7
- package/dist/ui/settings-model.js +17 -9
- package/dist/ui/settings-registry.js +1 -0
- package/dist/ui/settings-strings.js +16 -4
- package/dist/ui/settings-token.js +3 -2
- package/dist/ui/settings-wizard.js +25 -9
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-stream.js +1 -1
- package/dist/ui/shared/app.css +74 -2
- package/dist/ui/shared/setup-host-text.js +107 -0
- package/dist/ui/structured-signin.js +89 -38
- package/dist/usage-rollup.d.ts +34 -3
- package/dist/usage-rollup.js +56 -3
- package/dist/usage-rollup.js.map +1 -1
- package/dist/web-api.d.ts +10 -1
- package/dist/web-api.js +207 -17
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +1 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-office-acts.d.ts +5 -2
- package/dist/web-office-acts.js +9 -4
- package/dist/web-office-acts.js.map +1 -1
- package/dist/web-office-pack-read.d.ts +35 -0
- package/dist/web-office-pack-read.js +252 -0
- package/dist/web-office-pack-read.js.map +1 -0
- package/dist/web-office-pack-tools.d.ts +241 -0
- package/dist/web-office-pack-tools.js +785 -0
- package/dist/web-office-pack-tools.js.map +1 -0
- package/dist/web-office-skins.d.ts +68 -4
- package/dist/web-office-skins.js +324 -33
- package/dist/web-office-skins.js.map +1 -1
- package/dist/web-office-store.d.ts +47 -4
- package/dist/web-office-store.js +149 -15
- package/dist/web-office-store.js.map +1 -1
- package/dist/web-settings-patch.d.ts +3 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +2 -2
- package/templates/windows-shortcuts.ps1 +18 -17
package/dist/ui/fleet-org.js
CHANGED
|
@@ -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: "
|
|
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: "
|
|
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
|
-
|
|
458
|
-
|
|
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>`;
|
package/dist/ui/fleet-tasks.js
CHANGED
|
@@ -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
|
|
192
|
-
*
|
|
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"))}>${
|
|
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",
|
|
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: "所有辦公室",
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
|
|
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
|
}
|
|
@@ -46,6 +47,54 @@ export function shareLease(base, timers = globalThis) {
|
|
|
46
47
|
});
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
/** #2662: how many times a picture refused for the share's minute (429) is asked for, in all. */
|
|
51
|
+
export const IMAGE_ATTEMPTS = 4;
|
|
52
|
+
/**
|
|
53
|
+
* #2662: pictures read at once. All offices may name a thousand; asked all together they would open more connections
|
|
54
|
+
* than the share's gateway keeps (32, for everyone watching) — a browser on HTTP/2 to the tunnel does not hold back.
|
|
55
|
+
*/
|
|
56
|
+
export const IMAGE_PARALLEL = 6;
|
|
57
|
+
let reading = 0;
|
|
58
|
+
const queued = [];
|
|
59
|
+
async function inTurn(job) {
|
|
60
|
+
while (reading >= IMAGE_PARALLEL) await new Promise((go) => queued.push(go));
|
|
61
|
+
reading++;
|
|
62
|
+
try { return await job(); } finally { reading--; queued.shift()?.(); }
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* #2662: a skin pack's picture on the share (loadPackSkin's `image`): read with fetch, IMAGE_PARALLEL at a time, so a
|
|
66
|
+
* 429 (the share's minute of pictures, shared by everyone watching) is seen and waited out — its Retry-After (1–60 s),
|
|
67
|
+
* up to IMAGE_ATTEMPTS in all — instead of drawn as missing. The picture, or null (not found, or still refused after the
|
|
68
|
+
* last attempt).
|
|
69
|
+
*/
|
|
70
|
+
export async function shareImage(url, { fetch: get = globalThis.fetch, timers = globalThis, decode = blobImage } = {}) {
|
|
71
|
+
for (let attempt = 1; ; attempt++) {
|
|
72
|
+
let r, blob = null;
|
|
73
|
+
try {
|
|
74
|
+
r = await inTurn(async () => {
|
|
75
|
+
const answer = await get(url, { method: "GET" });
|
|
76
|
+
if (answer.ok) blob = await answer.blob();
|
|
77
|
+
return answer;
|
|
78
|
+
});
|
|
79
|
+
} catch { return null; }
|
|
80
|
+
if (r.ok) { try { return await decode(blob); } catch { return null; } }
|
|
81
|
+
if (r.status !== 429 || attempt >= IMAGE_ATTEMPTS) return null;
|
|
82
|
+
const after = Number.parseInt(r.headers?.get?.("Retry-After") ?? "", 10);
|
|
83
|
+
const seconds = Number.isFinite(after) ? Math.min(60, Math.max(1, after)) : 60;
|
|
84
|
+
await new Promise((resolve) => timers.setTimeout(resolve, seconds * 1000));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** A PNG's bytes as an image to draw (the page's CSP allows blob: pictures). */
|
|
88
|
+
function blobImage(blob) {
|
|
89
|
+
return new Promise((resolve) => {
|
|
90
|
+
if (typeof Image !== "function") { resolve(null); return; }
|
|
91
|
+
const src = URL.createObjectURL(blob), im = new Image();
|
|
92
|
+
im.onload = () => { URL.revokeObjectURL(src); resolve(im); };
|
|
93
|
+
im.onerror = () => { URL.revokeObjectURL(src); resolve(null); };
|
|
94
|
+
im.src = src;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
49
98
|
/** The share's /state into the app store's shape (the status frame's, as app-store.js takes it). */
|
|
50
99
|
export function appFromState(d) {
|
|
51
100
|
const instances = Array.isArray(d && d.instances) ? d.instances : [];
|
|
@@ -100,7 +149,7 @@ export function SharePage({ base, timers = globalThis, pollMs = POLL_MS }) {
|
|
|
100
149
|
</header>
|
|
101
150
|
${state.phase === "offline" ? html`<p class="note share-note" role="status">${to("offline")}</p>` : null}
|
|
102
151
|
${view ? html`<div class="share-stage"><${Office} view=${view} traffic=${items} lease=${lease} assetUrl=${(p) => shareUrl(base, p) || p}
|
|
103
|
-
opening=${state.office === "all" ? ALL_ID : state.office || undefined} /></div>`
|
|
152
|
+
assetImage=${(url) => shareImage(url, { fetch: (...a) => timers.fetch(...a), timers })} opening=${state.office === "all" ? ALL_ID : state.office || undefined} /></div>`
|
|
104
153
|
: html`<p class="muted share-loading" role="status">${to("loading")}</p>`}
|
|
105
154
|
</main>`;
|
|
106
155
|
}
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -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>`}
|
|
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. */
|
package/dist/ui/panel-log.js
CHANGED
|
@@ -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);
|
package/dist/ui/panel-needs.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
1028
|
-
|
|
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
|
|
1036
|
-
|
|
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
|
}
|