@zq-silk/yui 2.2.0 → 2.4.0

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 (206) hide show
  1. package/README.md +19 -3
  2. package/dist/artifacts/artifactCapability.js +39 -10
  3. package/dist/artifacts/imageMetadata.js +120 -0
  4. package/dist/artifacts/managedGit.js +7 -4
  5. package/dist/artifacts/taskArtifactRepository.js +6 -0
  6. package/dist/cli/commandCatalog.js +25 -11
  7. package/dist/cli/interactionPolicy.js +4 -0
  8. package/dist/cli/invocationAuthority.js +1 -1
  9. package/dist/commands/executionAuditCommands.js +5 -0
  10. package/dist/commands/globalRoleCommands.js +41 -3
  11. package/dist/commands/projectCommands.js +89 -94
  12. package/dist/commands/roleSkillValidation.js +4 -8
  13. package/dist/commands/taskCommands.js +30 -9
  14. package/dist/commands/taskIntegrationCommands.js +5 -16
  15. package/dist/commands/taskUsageCommand.js +38 -0
  16. package/dist/context/roleSessionContext.js +18 -2
  17. package/dist/context/runContextPack.js +5 -1
  18. package/dist/context/sessionBootstrapManifest.js +10 -1
  19. package/dist/context/skillCatalog.js +108 -0
  20. package/dist/context/skillPackage.js +161 -0
  21. package/dist/context/taskContext.js +3 -2
  22. package/dist/context/taskSearch.js +127 -0
  23. package/dist/controlPlaneCli.js +51 -39
  24. package/dist/controller/clientRuntime.js +3 -2
  25. package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
  26. package/dist/controller/jobControl.js +2 -0
  27. package/dist/controller/nativeHostInput.js +37 -0
  28. package/dist/controller/runtime.js +93 -2
  29. package/dist/controller/runtimeEventInbox.js +2 -2
  30. package/dist/controller/runtimeEventProcessor.js +3 -3
  31. package/dist/doctor/doctor.js +10 -0
  32. package/dist/execution/reviewMainRun.js +1 -0
  33. package/dist/execution/workItemMainRun.js +1 -0
  34. package/dist/executor/codexConfigConflict.js +6 -4
  35. package/dist/executor/effectiveLaunch.js +24 -1
  36. package/dist/executor/fileRoleLaunchPlanner.js +13 -5
  37. package/dist/integration/gitIntegrationService.js +15 -79
  38. package/dist/integration/integrationSourceApplication.js +4 -2
  39. package/dist/job/httpPreview.js +61 -0
  40. package/dist/job/jobCleanupInspection.js +67 -0
  41. package/dist/job/jobRunner.js +12 -2
  42. package/dist/kernel/builtinCapabilities.js +3 -1
  43. package/dist/message/message.js +15 -1
  44. package/dist/output/rolePresentation.js +4 -1
  45. package/dist/repository/executionLaneGitSnapshot.js +2 -1
  46. package/dist/repository/gitCompatibility.js +62 -0
  47. package/dist/repository/gitWorkspace.js +86 -80
  48. package/dist/repository/gitWorktreeInventory.js +121 -0
  49. package/dist/repository/project.js +74 -25
  50. package/dist/repository/taskWorkspaceCoordinator.js +72 -145
  51. package/dist/repository/taskWorkspacePreparer.js +39 -43
  52. package/dist/repository/workspaceCleanupInspection.js +88 -7
  53. package/dist/resources/resourceDiscovery.js +2 -2
  54. package/dist/resources/resourceGc.js +7 -4
  55. package/dist/resources/resourceRegistrar.js +18 -11
  56. package/dist/runtime/agentEndpoint.js +8 -0
  57. package/dist/runtime/agentHost.js +170 -3
  58. package/dist/runtime/codexAppServerRuntime.js +1 -1
  59. package/dist/runtime/codexNativeAccess.js +175 -0
  60. package/dist/runtime/controlledCodexTui.js +116 -0
  61. package/dist/runtime/integrationRuntimeIsolation.js +21 -0
  62. package/dist/runtime/publicReply.js +23 -0
  63. package/dist/runtime/runtimeObservation.js +48 -0
  64. package/dist/runtime/structuredProviderHost.js +16 -7
  65. package/dist/runtime/taskCostMetrics.js +133 -0
  66. package/dist/runtime/taskRuntimeIsolation.js +91 -21
  67. package/dist/runtime/taskUsageMetrics.js +11 -2
  68. package/dist/runtime/taskUsageQuery.js +38 -0
  69. package/dist/scheduler/taskExecutionProjection.js +2 -0
  70. package/dist/scheduler/taskObservabilityProjection.js +9 -1
  71. package/dist/storage/homeLayout.js +13 -0
  72. package/dist/storage/sqliteSchema.js +44 -0
  73. package/dist/storage/sqliteStore.js +53 -0
  74. package/dist/storage/storageVersions.js +1 -1
  75. package/dist/storage/storeRpc.js +2 -0
  76. package/dist/storage/taskSearch.js +35 -0
  77. package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
  78. package/dist/task/archiveDiagnostics.js +26 -8
  79. package/dist/task/archivePreflight.js +26 -9
  80. package/dist/task/leaderArchive.js +9 -15
  81. package/dist/task/ordinaryArchive.js +42 -0
  82. package/dist/tmux/tmuxManager.js +8 -8
  83. package/dist/web/assets/assetManifest.js +11 -51
  84. package/dist/web/assets/client/app/catalog.js +215 -0
  85. package/dist/web/assets/client/app/main.js +148 -0
  86. package/dist/web/assets/client/app/selection.js +139 -0
  87. package/dist/web/assets/client/app/settings.js +326 -0
  88. package/dist/web/assets/client/app/taskView.js +280 -0
  89. package/dist/web/assets/client/domain/context.js +96 -0
  90. package/dist/web/assets/client/domain/records.js +126 -0
  91. package/dist/web/assets/client/domain/runs.js +119 -0
  92. package/dist/web/assets/client/domain/taskForms.js +197 -0
  93. package/dist/web/assets/client/domain/vocab.js +69 -0
  94. package/dist/web/assets/client/domain/work.js +116 -0
  95. package/dist/web/assets/client/i18n/en.js +900 -0
  96. package/dist/web/assets/client/i18n/zh-CN.js +898 -0
  97. package/dist/web/assets/client/index.js +114 -0
  98. package/dist/web/assets/client/layout/resizer.js +37 -0
  99. package/dist/web/assets/client/layout/router.js +35 -0
  100. package/dist/web/assets/client/layout/shortcuts.js +45 -0
  101. package/dist/web/assets/client/layout/sizing.js +95 -0
  102. package/dist/web/assets/client/layout/workspace.js +247 -0
  103. package/dist/web/assets/client/{api.js → lib/api.js} +28 -4
  104. package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
  105. package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
  106. package/dist/web/assets/client/lib/i18n.js +54 -0
  107. package/dist/web/assets/client/lib/markdown.js +167 -0
  108. package/dist/web/assets/client/lib/prefs.js +28 -0
  109. package/dist/web/assets/client/lib/theme.js +68 -0
  110. package/dist/web/assets/client/ui/containers.js +101 -0
  111. package/dist/web/assets/client/ui/controls.js +51 -0
  112. package/dist/web/assets/client/ui/forms.js +48 -0
  113. package/dist/web/assets/client/ui/metrics.js +137 -0
  114. package/dist/web/assets/client/ui/primitives.js +92 -0
  115. package/dist/web/assets/client/ui/text.js +33 -0
  116. package/dist/web/assets/client/ui/toast.js +12 -0
  117. package/dist/web/assets/client/views/bodySearch.js +172 -0
  118. package/dist/web/assets/client/views/createTask.js +85 -0
  119. package/dist/web/assets/client/views/dock/conversation.js +612 -0
  120. package/dist/web/assets/client/views/dock/discussion.js +173 -0
  121. package/dist/web/assets/client/views/dock/preview.js +129 -0
  122. package/dist/web/assets/client/views/dock/terminal.js +199 -0
  123. package/dist/web/assets/client/views/globalInput.js +67 -0
  124. package/dist/web/assets/client/{overview.js → views/overview.js} +62 -56
  125. package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
  126. package/dist/web/assets/client/views/skills.js +152 -0
  127. package/dist/web/assets/client/views/task/actions.js +36 -0
  128. package/dist/web/assets/client/views/task/delivery.js +185 -0
  129. package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
  130. package/dist/web/assets/client/views/task/files.js +272 -0
  131. package/dist/web/assets/client/views/task/observation.js +153 -0
  132. package/dist/web/assets/client/views/task/overview.js +161 -0
  133. package/dist/web/assets/client/views/task/page.js +131 -0
  134. package/dist/web/assets/client/views/task/records.js +163 -0
  135. package/dist/web/assets/client/views/task/runtime.js +130 -0
  136. package/dist/web/assets/client/views/task/timeline.js +190 -0
  137. package/dist/web/assets/shared/geometry.js +19 -0
  138. package/dist/web/assets/{icons.js → shared/icons.js} +4 -0
  139. package/dist/web/assets/shared/taskTabs.js +8 -0
  140. package/dist/web/assets/shared/themes.js +93 -0
  141. package/dist/web/assets/shell/dialogs.js +63 -0
  142. package/dist/web/assets/shell/dock.js +40 -0
  143. package/dist/web/assets/shell/index.js +40 -0
  144. package/dist/web/assets/shell/settings.js +34 -0
  145. package/dist/web/assets/shell/sidebar.js +45 -0
  146. package/dist/web/assets/styles/index.js +41 -0
  147. package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +15 -13
  148. package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
  149. package/dist/web/assets/styles/markdown.js +22 -3
  150. package/dist/web/assets/styles/tokens.js +10 -63
  151. package/dist/web/assets/styles/ui/controls.js +76 -0
  152. package/dist/web/assets/styles/ui/forms.js +46 -0
  153. package/dist/web/assets/styles/ui/surfaces.js +122 -0
  154. package/dist/web/assets/styles/views/delivery.js +29 -0
  155. package/dist/web/assets/styles/views/dock.js +71 -0
  156. package/dist/web/assets/styles/views/page.js +32 -0
  157. package/dist/web/assets/styles/views/records.js +70 -0
  158. package/dist/web/assets/styles/views/settings.js +26 -0
  159. package/dist/web/assets/styles/views/sidebar.js +74 -0
  160. package/dist/web/assets/styles/views/task.js +80 -0
  161. package/dist/web/assets/styles/views/terminal.js +38 -0
  162. package/dist/web/taskPreviews.js +205 -0
  163. package/dist/web/tmuxWebTerminal.js +16 -9
  164. package/dist/web/webConversation.js +373 -0
  165. package/dist/web/webMaterials.js +155 -0
  166. package/dist/web/webServer.js +306 -18
  167. package/dist/web/webSettings.js +331 -0
  168. package/dist/web/webSkills.js +86 -0
  169. package/dist/web/webSnapshot.js +40 -8
  170. package/dist/web/webTaskSurface.js +114 -9
  171. package/dist/workspace/gitChangeSetCapture.js +3 -1
  172. package/docs/managed-turn-and-session-runtime.md +9 -0
  173. package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
  174. package/docs/observability/README.md +13 -3
  175. package/docs/observability/README.zh-CN.md +10 -3
  176. package/docs/release-workflow.md +53 -0
  177. package/docs/release-workflow.zh-CN.md +40 -0
  178. package/docs/storage-baseline.md +38 -1
  179. package/docs/storage-baseline.zh-CN.md +22 -0
  180. package/docs/task-delivery.md +36 -8
  181. package/docs/task-delivery.zh-CN.md +24 -7
  182. package/docs/task-discovery.md +28 -0
  183. package/docs/testing/verification-levels.md +2 -2
  184. package/i18n/README.zh-CN.md +4 -3
  185. package/package.json +1 -1
  186. package/skills/yui-leader/references/execution.md +2 -1
  187. package/skills/yui-leader/references/integration.md +20 -0
  188. package/skills/yui-operator/SKILL.md +4 -0
  189. package/skills/yui-reviewer/SKILL.md +3 -0
  190. package/skills/yui-runtime/SKILL.md +4 -0
  191. package/skills/yui-runtime/references/publication.md +20 -4
  192. package/skills/yui-worker/SKILL.md +3 -0
  193. package/dist/web/assets/client/app.js +0 -804
  194. package/dist/web/assets/client/components.js +0 -418
  195. package/dist/web/assets/client/detail.js +0 -333
  196. package/dist/web/assets/client/dock.js +0 -225
  197. package/dist/web/assets/client/evidence.js +0 -172
  198. package/dist/web/assets/client/forms.js +0 -230
  199. package/dist/web/assets/client/i18n.js +0 -1161
  200. package/dist/web/assets/client/markdown.js +0 -114
  201. package/dist/web/assets/client/records.js +0 -63
  202. package/dist/web/assets/client/sections.js +0 -313
  203. package/dist/web/assets/client/theme.js +0 -71
  204. package/dist/web/assets/shell.js +0 -161
  205. package/dist/web/assets/styles/components.js +0 -169
  206. package/dist/web/assets/styles/views.js +0 -292
@@ -1,804 +0,0 @@
1
- export const APP_SCRIPT = String.raw `
2
- // Application controller: view state, URL routing, the refresh loop, the
3
- // sidebar | center | dock layout, keyboard shortcuts and dialogs. Everything
4
- // kept here is view state; Task facts always come from the loopback API.
5
- import { createI18n } from "/assets/js/i18n.js";
6
- import { createThemeController } from "/assets/js/theme.js";
7
- import { api, releaseMutation } from "/assets/js/api.js";
8
- import { formatClock } from "/assets/js/format.js";
9
- import { h } from "/assets/js/dom.js";
10
- import { renderAttentionBar, renderFilters, renderTasks, STATUS_FILTERS, ATTENTION_KINDS } from "/assets/js/sidebar.js";
11
- import { renderOverview } from "/assets/js/overview.js";
12
- import { renderTaskDetail, selectTab, updateObservation, TABS } from "/assets/js/detail.js";
13
- import { renderDiscussion, discussionHasUnsent, createTerminalController, sameTarget } from "/assets/js/dock.js";
14
- import { entriesOf } from "/assets/js/records.js";
15
-
16
- const $ = function (selector) { return document.querySelector(selector); };
17
- const el = {
18
- sidebar: $(".sidebar"), sidebarDivider: $("#sidebar-divider"), center: $("#center"), detail: $("#detail"),
19
- search: $("#search"), filters: $("#status-filters"), attention: $("#attention-bar"), tasks: $("#task-list"),
20
- catalogNext: $("#catalog-next"), catalogReset: $("#catalog-reset"), catalogCount: $("#catalog-count"),
21
- catalogAttentionReset: $("#catalog-attention-reset"),
22
- refresh: $("#refresh"), sync: $("#sync-state"), lastSync: $("#last-sync"), toast: $("#toast"),
23
- operator: $("#operator-terminal"), settingsOpen: $("#settings-open"), settings: $("#settings-dialog"),
24
- locale: $("#locale-select"), themeOptions: $("#theme-options"),
25
- dock: $("#dock"), divider: $("#dock-divider"), dockSwap: $("#dock-swap"), dockClose: $("#dock-close"),
26
- dockTabDiscussion: $("#dock-tab-discussion"), dockTabSession: $("#dock-tab-session"),
27
- discussion: $("#dock-discussion"), session: $("#dock-session"),
28
- terminalHost: $("#terminal-host"), terminalEmpty: $("#terminal-empty"), terminalState: $("#terminal-state"),
29
- sessionTargets: $("#session-targets"), terminalCli: $("#terminal-cli")
30
- };
31
-
32
- const state = {
33
- tasks: [], counts: null, attention: [], catalogAttention: null, catalogScope: null, catalogQuery: "",
34
- catalogAll: false, catalogTotal: 0, catalogCursor: null, nextCursor: null,
35
- sessionOverview: null, sessionLoading: false, sessionError: null,
36
- attentionFilter: null, generatedAt: null, filter: "all", query: "",
37
- selected: null, detail: null, detailKey: null, activeTab: "overview"
38
- };
39
-
40
- function readPreference(key, fallback) {
41
- try { return localStorage.getItem(key) || fallback; } catch { return fallback; }
42
- }
43
- function writePreference(key, value) {
44
- try { localStorage.setItem(key, value); } catch {}
45
- }
46
- function clearPreference(key) {
47
- try { localStorage.removeItem(key); } catch {}
48
- }
49
- const dock = {
50
- open: readPreference("yui.dock.open", "true") !== "false",
51
- mode: "discussion",
52
- center: readPreference("yui.dock.side", "end") === "center",
53
- width: Number(readPreference("yui.dock.width", "440")) || 440,
54
- // On narrow screens the dock is a full-screen sheet opened explicitly per
55
- // Task, never restored from the desktop preference.
56
- sheet: false
57
- };
58
- // Task list width: null keeps the CSS default (--sidebar-w); otherwise the width
59
- // the user dragged to. It is a preference; the width actually applied is clamped
60
- // to the current window.
61
- const sidebar = { width: Number(readPreference("yui.sidebar.width", "")) || null, resizing: false };
62
- const narrow = window.matchMedia("(max-width: 900px)");
63
-
64
- const i18n = createI18n(el.locale);
65
- const t = i18n.t;
66
- const locale = i18n.getLocale;
67
- const theme = createThemeController(el.themeOptions, t);
68
- const terminal = createTerminalController({
69
- host: el.terminalHost, empty: el.terminalEmpty, state: el.terminalState, targets: el.sessionTargets, cli: el.terminalCli
70
- }, t, locale, showToast);
71
- theme.subscribe(function () { terminal.retheme(); });
72
-
73
- let toastTimer = null;
74
- function showToast(message) {
75
- el.toast.textContent = message;
76
- el.toast.classList.add("show");
77
- window.clearTimeout(toastTimer);
78
- toastTimer = window.setTimeout(function () { el.toast.classList.remove("show"); }, 3200);
79
- }
80
-
81
- // --- URL / history ---------------------------------------------------------------
82
- // The selected Task, section, status filter, search and attention filter live
83
- // in the query string so back/forward, refresh and deep links keep context.
84
- function readQuery() { return new URLSearchParams(window.location.search); }
85
- function applyUrl(params, replace) {
86
- const entries = Array.from(params.entries()).filter(function (entry) { return entry[1] !== ""; });
87
- const search = entries.length ? "?" + entries.map(function (entry) {
88
- return encodeURIComponent(entry[0]) + "=" + encodeURIComponent(entry[1]);
89
- }).join("&") : "";
90
- const url = window.location.pathname + search + window.location.hash;
91
- history[replace ? "replaceState" : "pushState"]({ task: params.get("task") || null }, "", url);
92
- }
93
- function urlTaskId() { return readQuery().get("task") || null; }
94
- function syncUrl(replace) {
95
- const params = readQuery();
96
- if (state.selected) params.set("task", state.selected);
97
- else { params.delete("task"); params.delete("section"); }
98
- if (state.filter !== "all") params.set("filter", state.filter); else params.delete("filter");
99
- if (state.query) params.set("q", state.query); else params.delete("q");
100
- if (state.attentionFilter) params.set("attention", state.attentionFilter); else params.delete("attention");
101
- if (state.catalogAll) params.set("all", "true"); else params.delete("all");
102
- applyUrl(params, replace);
103
- }
104
- function setSectionParam(section) {
105
- const params = readQuery();
106
- if (!state.selected || params.get("section") === section) return;
107
- if (section === "overview") params.delete("section"); else params.set("section", section);
108
- applyUrl(params, true);
109
- }
110
-
111
- // --- Detail rendering -------------------------------------------------------------
112
- function detailContext() {
113
- return {
114
- activeTab: state.activeTab,
115
- onTab: switchTab,
116
- onBack: clearSelection,
117
- showDock: toggleDock,
118
- openSession: function (roleName) { openSession({ scope: "task", taskId: state.selected, roleName: roleName }); },
119
- answerInput: answerInput,
120
- inspect: api.inspect,
121
- artifacts: api.artifacts,
122
- readArtifact: api.artifact,
123
- evidence: api.evidence,
124
- panels: api.panels,
125
- readPanel: api.readPanel,
126
- control: api.control,
127
- updateTask: api.updateTask,
128
- afterWrite: function () { refreshDashboard({ quiet: true }); }
129
- };
130
- }
131
- function dockActions() {
132
- return {
133
- sendMessage: api.sendMessage,
134
- inspect: api.inspect,
135
- afterWrite: function () { refreshDashboard({ quiet: true }); }
136
- };
137
- }
138
-
139
- // Fingerprint of the rendered payload. Quiet polling skips identical reads so
140
- // local view state (open blocks, list pages, drafts) survives.
141
- function fingerprint(value) {
142
- const text = JSON.stringify(value);
143
- let hash = 0;
144
- for (let i = 0; i < text.length; i += 1) hash = (hash * 31 + text.charCodeAt(i)) | 0;
145
- return text.length + ":" + hash;
146
- }
147
- // Only the observed identity participates: observation timestamps advance on
148
- // every poll and would otherwise re-render continuously.
149
- function runtimeSignature(detail) {
150
- const runtime = detail.runtime;
151
- return detail.runtimeStatus + "|" + JSON.stringify(runtime ? [
152
- runtime.sessions && runtime.sessions.sessions, runtime.remoteDelivery,
153
- runtime.execution && runtime.execution.attention, runtime.execution && runtime.execution.blockers,
154
- runtime.execution && runtime.execution.next, runtime.execution && runtime.execution.status
155
- ] : null) + "|" + ((runtime && runtime.roles) || []).map(function (role) {
156
- return role.name + ":" + (role.runtimeSession ? role.runtimeSession.nativeSessionId + "/" + role.runtimeSession.status : "-") + ":" + role.status;
157
- }).join(",");
158
- }
159
-
160
- let renderedDetailKey = null;
161
- let renderedDiscussionKey = null;
162
- function centerIsBusy() {
163
- const active = document.activeElement;
164
- const editing = active && (["INPUT", "SELECT", "TEXTAREA"].includes(active.tagName) || active.isContentEditable);
165
- return el.detail.dataset.taskId === (state.detail && state.detail.task.id)
166
- && (el.detail.querySelector('[data-unsent="true"]') || el.detail.querySelector('[data-reading="true"]')
167
- || editing && el.center.contains(active));
168
- }
169
- function renderCurrentDetail(force) {
170
- if (!state.detail) {
171
- if (!state.selected) { renderedDetailKey = null; showOverview(); }
172
- return;
173
- }
174
- renderDock();
175
- // Polling must not replace an unsent draft or an in-flight form, including
176
- // after focus moved. This is view state, never Task state.
177
- if (centerIsBusy()) return;
178
- const key = locale() + "|" + state.detailKey + "|" + runtimeSignature(state.detail);
179
- if (!force && key === renderedDetailKey) return;
180
- const openSections = Array.from(el.detail.querySelectorAll("details[data-view-key][open]")).map(function (d) { return d.dataset.viewKey; });
181
- const focusedTab = el.detail.querySelector(".tab:focus");
182
- const focusedTabId = focusedTab && focusedTab.id;
183
- const scroll = el.detail.dataset.taskId === state.detail.task.id ? el.center.scrollTop : 0;
184
- renderTaskDetail(el.detail, state.detail, t, locale(), detailContext());
185
- el.detail.querySelectorAll("details[data-view-key]").forEach(function (d) { d.open = openSections.includes(d.dataset.viewKey); });
186
- if (focusedTabId) el.detail.querySelector("#" + CSS.escape(focusedTabId)).focus({ preventScroll: true });
187
- el.center.scrollTop = scroll;
188
- renderedDetailKey = key;
189
- syncDockButtons();
190
- }
191
- function renderDock() {
192
- if (!state.detail) return;
193
- const key = locale() + "|" + state.detail.task.id + "|" + state.detailKey + "|" + state.detail.task.status;
194
- if (key !== renderedDiscussionKey || el.discussion.dataset.taskId !== state.detail.task.id) {
195
- renderDiscussion(el.discussion, state.detail, t, locale(), dockActions());
196
- renderedDiscussionKey = key;
197
- }
198
- updateSessionTargets();
199
- }
200
- function updateSessionTargets() {
201
- const targets = [];
202
- if (state.detail) {
203
- const runtimeRoles = (state.detail.runtime && state.detail.runtime.roles) || [];
204
- entriesOf(state.detail.core, "role").forEach(function (entry) {
205
- const runtimeRole = runtimeRoles.find(function (role) { return role.name === entry.ref.refId; });
206
- targets.push({ scope: "task", taskId: state.detail.task.id, roleName: entry.ref.refId,
207
- live: !!(runtimeRole && runtimeRole.runtimeSession && runtimeRole.runtimeSession.nativeSessionId) });
208
- });
209
- }
210
- targets.push({ scope: "global", roleName: "operator" });
211
- const current = terminal.current();
212
- if (current && !targets.some(function (target) { return sameTarget(target, current); })) targets.unshift(current);
213
- terminal.setTargets(targets);
214
- }
215
-
216
- function showOverview() {
217
- delete el.detail.dataset.taskId;
218
- renderOverview(el.detail, state, t, locale(), {
219
- select: selectTask,
220
- filterAttention: pickAttention,
221
- readSessions: readPageSessions,
222
- openOperator: openOperator
223
- });
224
- }
225
-
226
- function switchTab(tab, options) {
227
- if (!TABS.includes(tab)) return;
228
- state.activeTab = tab;
229
- selectTab(el.detail, tab);
230
- if (!(options && options.keepScroll)) el.center.scrollTop = 0;
231
- setSectionParam(tab);
232
- }
233
- // A section may name a tab or any anchored block inside one (for example
234
- // "reviews" → the Work tab, scrolled to #detail-reviews).
235
- function applySection(section) {
236
- if (!section || TABS.includes(section)) { switchTab(section || "overview"); return; }
237
- const anchor = el.detail.querySelector("#detail-" + CSS.escape(section));
238
- const panel = anchor && anchor.closest(".tab-panel");
239
- if (!panel) { switchTab("overview"); return; }
240
- state.activeTab = panel.id.replace(/^panel-/, "");
241
- selectTab(el.detail, state.activeTab);
242
- anchor.scrollIntoView({ block: "start" });
243
- }
244
-
245
- // --- Loading ----------------------------------------------------------------------
246
- async function loadTaskDetail(taskId, navigate) {
247
- if (navigate) {
248
- el.detail.replaceChildren(h("div.page", null, h("div.loading-block", null, h("span.spinner"), h("span", null, t("loading.detail")))));
249
- delete el.detail.dataset.taskId;
250
- el.center.scrollTop = 0;
251
- }
252
- // Rendering consumes the current Context snapshot, not event pages.
253
- const core = await api.context(taskId);
254
- const taskEntry = core.records.find(function (entry) { return entry.ref.store === "task"; });
255
- if (!taskEntry) throw new Error("Task reference unavailable.");
256
- const task = taskEntry.omitted ? (await api.inspect(taskId, taskEntry.ref)).value : taskEntry.value;
257
- if (state.selected !== taskId) return;
258
- const previous = state.detail && state.detail.task.id === taskId ? state.detail : null;
259
- const detail = {
260
- task: task, core: core,
261
- viewState: previous ? previous.viewState : {},
262
- runtime: previous ? previous.runtime : null,
263
- runtimeStatus: previous ? previous.runtimeStatus : "waiting",
264
- runtimeObservedAt: previous ? previous.runtimeObservedAt : new Date().toISOString()
265
- };
266
- state.detail = detail;
267
- state.detailKey = fingerprint(core);
268
- renderCurrentDetail(navigate);
269
- if (navigate) applySection(readQuery().get("section"));
270
- // The optional observation never participates in core readiness.
271
- api.observation(taskId).then(function (runtime) {
272
- if (state.detail !== detail) return;
273
- detail.runtime = runtime;
274
- detail.runtimeStatus = "available";
275
- detail.runtimeObservedAt = new Date().toISOString();
276
- observationArrived(detail);
277
- }).catch(function () {
278
- if (state.detail !== detail) return;
279
- detail.runtime = null;
280
- detail.runtimeStatus = "unavailable";
281
- detail.runtimeObservedAt = new Date().toISOString();
282
- observationArrived(detail);
283
- });
284
- }
285
- function observationArrived(detail) {
286
- if (el.detail.dataset.taskId === detail.task.id) updateObservation(el.detail, detail, t, locale(), detailContext());
287
- // Patching slots is not enough when the observed identity changed facts the
288
- // detail drew earlier; the render key decides whether a re-render is due.
289
- if (state.detail === detail) renderCurrentDetail();
290
- }
291
-
292
- let refreshing = false;
293
- let catalogRequest = 0;
294
- function resetCatalog() {
295
- state.catalogCursor = null;
296
- state.nextCursor = null;
297
- refreshDashboard();
298
- }
299
- async function refreshDashboard(options) {
300
- const quiet = options && options.quiet;
301
- if (refreshing && quiet) return;
302
- const request = ++catalogRequest;
303
- refreshing = true;
304
- el.catalogNext.disabled = true;
305
- el.sync.dataset.state = "syncing";
306
- if (!quiet) {
307
- el.refresh.disabled = true;
308
- if (!state.tasks.length) el.tasks.replaceChildren(h("div.loading-block", null, h("span.spinner"), h("span", null, t("loading.dashboard"))));
309
- }
310
- const previousInputs = state.counts ? state.counts.openInputs : null;
311
- try {
312
- const query = new URLSearchParams();
313
- if (state.catalogAll) query.set("all", "true");
314
- if (state.filter !== "all") query.set("status", state.filter);
315
- if (state.query.trim()) query.set("search", state.query.trim());
316
- if (state.attentionFilter) query.set("attention", state.attentionFilter);
317
- if (state.catalogCursor) query.set("cursor", state.catalogCursor);
318
- const dashboard = await api.dashboard(query.toString());
319
- if (request !== catalogRequest) return;
320
- state.tasks = dashboard.tasks;
321
- if (state.catalogQuery !== query.toString() || (state.sessionOverview && JSON.stringify(
322
- state.sessionOverview.tasks.map(function (task) { return task.taskId; })) !== JSON.stringify(dashboard.tasks.map(function (task) { return task.id; })))) {
323
- state.sessionOverview = null;
324
- }
325
- state.catalogQuery = query.toString();
326
- state.counts = { ...dashboard.counts, openInputs: dashboard.attention.openInputs.count };
327
- state.catalogAttention = dashboard.attention;
328
- state.catalogScope = dashboard.scope;
329
- state.catalogTotal = dashboard.total;
330
- state.nextCursor = dashboard.nextCursor;
331
- state.attention = dashboard.attention.openInputs.refs.map(function (ref) {
332
- const task = state.tasks.find(function (entry) { return entry.id === ref.taskId; });
333
- return { taskId: ref.taskId, taskTitle: task ? task.title : ref.taskId };
334
- });
335
- state.generatedAt = new Date().toISOString();
336
- el.sync.dataset.state = "ok";
337
- renderSidebar();
338
- if (!state.selected) showOverview();
339
- if (previousInputs !== null && state.counts.openInputs > previousInputs) showToast(t("input.new"));
340
- // A selected Task may live on another page or outside current filters;
341
- // only its own read can establish that it no longer exists.
342
- if (state.selected) {
343
- try { await loadTaskDetail(state.selected, false); }
344
- catch { showToast(t("errors.disconnected")); }
345
- }
346
- } catch {
347
- if (request !== catalogRequest) return;
348
- el.sync.dataset.state = "error";
349
- if (!quiet) {
350
- el.tasks.replaceChildren(h("div.list-empty.is-error", null, h("p", null, t("errors.dashboard"))));
351
- showToast(t("errors.dashboard"));
352
- }
353
- } finally {
354
- if (request === catalogRequest) {
355
- refreshing = false;
356
- el.refresh.disabled = false;
357
- el.catalogNext.disabled = !state.nextCursor;
358
- }
359
- }
360
- }
361
-
362
- function renderSidebar() {
363
- renderAttentionBar(el.attention, state, t, pickAttention);
364
- renderFilters(el.filters, state, t, function (filter) {
365
- state.filter = filter;
366
- state.attentionFilter = null;
367
- state.catalogAll = false;
368
- syncUrl(false);
369
- resetCatalog();
370
- });
371
- el.catalogCount.textContent = t("catalog.count").replace("{shown}", String(state.tasks.length)).replace("{total}", String(state.catalogTotal));
372
- el.catalogReset.hidden = !state.catalogCursor;
373
- el.catalogNext.hidden = !state.nextCursor && !state.catalogCursor;
374
- el.catalogAttentionReset.hidden = !state.attentionFilter;
375
- const scroll = el.tasks.scrollTop;
376
- renderTasks(el.tasks, state, t, locale(), selectTask);
377
- el.tasks.scrollTop = scroll;
378
- el.lastSync.textContent = state.generatedAt ? formatClock(state.generatedAt, locale()) : "—";
379
- }
380
-
381
- async function readPageSessions() {
382
- if (state.sessionLoading) return;
383
- const query = state.catalogQuery;
384
- state.sessionLoading = true;
385
- showOverview();
386
- try {
387
- const result = await api.pageSessions(query);
388
- if (query !== state.catalogQuery) return;
389
- if (JSON.stringify(result.tasks.map(function (task) { return task.taskId; })) !== JSON.stringify(state.tasks.map(function (task) { return task.id; }))) {
390
- throw new Error(t("overview.sessionsPageChanged"));
391
- }
392
- state.sessionOverview = result;
393
- state.sessionError = null;
394
- } catch (error) { state.sessionError = error.message; }
395
- finally {
396
- state.sessionLoading = false;
397
- if (!state.selected) showOverview();
398
- }
399
- }
400
-
401
- function pickAttention(kind) {
402
- state.attentionFilter = kind;
403
- state.catalogAll = !!kind && !!state.catalogScope && state.catalogScope.archived === "included";
404
- state.filter = "all";
405
- state.query = "";
406
- el.search.value = "";
407
- syncUrl(false);
408
- resetCatalog();
409
- }
410
-
411
- // --- Selection --------------------------------------------------------------------
412
- function hasUnsent() {
413
- return !!(el.detail.querySelector('[data-unsent="true"]') || discussionHasUnsent(el.discussion));
414
- }
415
- function canLeaveDetail() {
416
- return !hasUnsent() || window.confirm(t("confirm.leave"));
417
- }
418
- window.addEventListener("beforeunload", function (event) {
419
- if (!hasUnsent()) return;
420
- event.preventDefault();
421
- event.returnValue = "";
422
- });
423
-
424
- async function selectTask(taskId) {
425
- // Re-selecting the visible Task is not navigation.
426
- if (state.selected === taskId && state.detail) return;
427
- if (state.selected !== taskId && !canLeaveDetail()) { syncUrl(true); return; }
428
- if (urlTaskId() !== taskId) {
429
- const params = readQuery();
430
- params.delete("section");
431
- applyUrl(params, true);
432
- }
433
- state.selected = taskId;
434
- state.detail = null;
435
- state.activeTab = "overview";
436
- dock.sheet = false;
437
- renderedDetailKey = null;
438
- renderedDiscussionKey = null;
439
- el.discussion.replaceChildren();
440
- delete el.discussion.dataset.taskId;
441
- syncUrl(state.selected === urlTaskId());
442
- updateLayout();
443
- const scroll = el.tasks.scrollTop;
444
- renderTasks(el.tasks, state, t, locale(), selectTask);
445
- el.tasks.scrollTop = scroll;
446
- try {
447
- await loadTaskDetail(taskId, true);
448
- } catch {
449
- if (state.selected !== taskId) return;
450
- el.detail.replaceChildren(h("div.page", null, h("div.list-empty.is-error", null, h("p", null, t("errors.detail")))));
451
- showToast(t("errors.detail"));
452
- }
453
- }
454
-
455
- function clearSelection() {
456
- if (!canLeaveDetail()) return;
457
- state.selected = null;
458
- state.detail = null;
459
- el.discussion.replaceChildren();
460
- delete el.discussion.dataset.taskId;
461
- if (dock.mode === "discussion" || (terminal.current() && terminal.current().scope === "task")) {
462
- if (!terminal.connected()) dock.mode = "discussion";
463
- }
464
- syncUrl(!urlTaskId());
465
- updateLayout();
466
- showOverview();
467
- const scroll = el.tasks.scrollTop;
468
- renderTasks(el.tasks, state, t, locale(), selectTask);
469
- el.tasks.scrollTop = scroll;
470
- updateSessionTargets();
471
- }
472
-
473
- async function answerInput(input, answer, control) {
474
- if (!state.detail) return;
475
- const taskId = state.detail.task.id;
476
- if (control) control.disabled = true;
477
- try {
478
- await api.answerInput(taskId, input.id, answer);
479
- releaseMutation(taskId + "/input/" + input.id);
480
- showToast(t("input.answered"));
481
- const form = control && control.closest("form");
482
- if (form) form.dataset.unsent = "false";
483
- await refreshDashboard({ quiet: true });
484
- } catch (error) {
485
- if (control) control.disabled = error.disposition !== "not-submitted";
486
- showToast(error.disposition === "not-submitted" ? error.message : t("input.unknown"));
487
- }
488
- }
489
-
490
- // --- Dock layout ------------------------------------------------------------------
491
- function dockVisible() {
492
- if (narrow.matches ? !dock.sheet : !dock.open) return false;
493
- if (state.selected) return true;
494
- return dock.mode === "session" && terminal.connected();
495
- }
496
- function maxDockWidth() {
497
- const sidebar = window.innerWidth > 900 ? el.sidebar.offsetWidth : 0;
498
- return Math.max(320, Math.min(760, window.innerWidth - sidebar - 420));
499
- }
500
- function setDockWidth(width, persist) {
501
- const effective = Math.max(320, Math.min(maxDockWidth(), width));
502
- document.documentElement.style.setProperty("--dock-w", effective + "px");
503
- el.divider.setAttribute("aria-valuemax", String(maxDockWidth()));
504
- el.divider.setAttribute("aria-valuenow", String(effective));
505
- if (persist) { dock.width = effective; writePreference("yui.dock.width", String(effective)); }
506
- }
507
- function updateLayout() {
508
- const visible = dockVisible();
509
- document.body.classList.toggle("task-open", !!state.selected);
510
- document.body.classList.toggle("dock-open", visible);
511
- document.body.classList.toggle("dock-center", dock.center);
512
- el.dock.hidden = !visible;
513
- el.divider.hidden = !visible;
514
- el.discussion.hidden = dock.mode !== "discussion";
515
- el.session.hidden = dock.mode !== "session";
516
- el.dockTabDiscussion.setAttribute("aria-selected", String(dock.mode === "discussion"));
517
- el.dockTabSession.setAttribute("aria-selected", String(dock.mode === "session"));
518
- el.dockTabDiscussion.disabled = !state.selected;
519
- el.dockSwap.setAttribute("aria-pressed", String(dock.center));
520
- applySidebarWidth();
521
- syncDockButtons();
522
- }
523
- function syncDockButtons() {
524
- const visible = dockVisible();
525
- el.detail.querySelectorAll("[data-dock-toggle]").forEach(function (button) {
526
- button.setAttribute("aria-pressed", String(visible && dock.mode === button.dataset.dockToggle));
527
- });
528
- el.operator.setAttribute("aria-pressed", String(visible && dock.mode === "session"
529
- && !!terminal.current() && terminal.current().scope === "global"));
530
- }
531
- function setDockOpen(open) {
532
- if (narrow.matches) dock.sheet = open;
533
- else {
534
- dock.open = open;
535
- writePreference("yui.dock.open", String(open));
536
- }
537
- if (!open && terminal.connected()) terminal.close();
538
- updateLayout();
539
- }
540
- function setDockMode(mode) {
541
- dock.mode = mode === "session" || !state.selected ? "session" : "discussion";
542
- updateLayout();
543
- }
544
- function toggleDock(mode) {
545
- if (dockVisible() && (!mode || dock.mode === mode)) { setDockOpen(false); return; }
546
- if (mode) dock.mode = mode === "session" || !state.selected ? mode : "discussion";
547
- if (!state.selected && dock.mode === "discussion") dock.mode = "session";
548
- setDockOpen(true);
549
- }
550
- function openSession(target) {
551
- dock.mode = "session";
552
- if (narrow.matches) dock.sheet = true;
553
- else {
554
- dock.open = true;
555
- writePreference("yui.dock.open", "true");
556
- }
557
- terminal.open(target);
558
- updateLayout();
559
- updateSessionTargets();
560
- }
561
- function openOperator() { openSession({ scope: "global", roleName: "operator" }); }
562
-
563
- el.dockTabDiscussion.addEventListener("click", function () { setDockMode("discussion"); });
564
- el.dockTabSession.addEventListener("click", function () { setDockMode("session"); });
565
- el.dockClose.addEventListener("click", function () { setDockOpen(false); });
566
- el.dockSwap.addEventListener("click", function () {
567
- dock.center = !dock.center;
568
- writePreference("yui.dock.side", dock.center ? "center" : "end");
569
- updateLayout();
570
- });
571
- el.operator.addEventListener("click", function () {
572
- const current = terminal.current();
573
- if (dockVisible() && dock.mode === "session" && current && current.scope === "global") { setDockOpen(false); return; }
574
- openOperator();
575
- });
576
-
577
- let resizing = false;
578
- el.divider.addEventListener("pointerdown", function (event) {
579
- if (event.button !== 0 || window.innerWidth <= 900) return;
580
- resizing = true;
581
- el.divider.setPointerCapture(event.pointerId);
582
- document.body.classList.add("dock-resizing");
583
- event.preventDefault();
584
- });
585
- el.divider.addEventListener("pointermove", function (event) {
586
- if (!resizing) return;
587
- const rect = el.dock.getBoundingClientRect();
588
- const width = dock.center ? event.clientX - rect.left : rect.right - event.clientX;
589
- setDockWidth(width, false);
590
- dock.width = Math.max(320, Math.min(maxDockWidth(), width));
591
- });
592
- function finishResize() {
593
- if (!resizing) return;
594
- resizing = false;
595
- document.body.classList.remove("dock-resizing");
596
- writePreference("yui.dock.width", String(dock.width));
597
- }
598
- el.divider.addEventListener("pointerup", finishResize);
599
- el.divider.addEventListener("pointercancel", finishResize);
600
- el.divider.addEventListener("keydown", function (event) {
601
- const grow = dock.center ? "ArrowRight" : "ArrowLeft";
602
- const shrink = dock.center ? "ArrowLeft" : "ArrowRight";
603
- const delta = event.key === grow ? 24 : event.key === shrink ? -24 : 0;
604
- if (!delta && event.key !== "Home" && event.key !== "End") return;
605
- event.preventDefault();
606
- setDockWidth(event.key === "Home" ? 320 : event.key === "End" ? maxDockWidth() : dock.width + delta, true);
607
- });
608
- narrow.addEventListener("change", updateLayout);
609
-
610
- // --- Sidebar width ----------------------------------------------------------------
611
- const SIDEBAR_MIN = 240;
612
- const SIDEBAR_MAX = 560;
613
- function maxSidebarWidth() {
614
- // An open dock can shrink to its minimum, so it reserves only that much.
615
- const reserve = dockVisible() ? 320 + 9 : 0;
616
- return Math.max(SIDEBAR_MIN, Math.min(SIDEBAR_MAX, window.innerWidth - reserve - 420));
617
- }
618
- function applySidebarWidth() {
619
- const root = document.documentElement.style;
620
- if (sidebar.width === null) root.removeProperty("--sidebar-w");
621
- else root.setProperty("--sidebar-w", Math.max(SIDEBAR_MIN, Math.min(maxSidebarWidth(), sidebar.width)) + "px");
622
- // A wider task list leaves the dock less room; the dock keeps its stored width.
623
- setDockWidth(dock.width, false);
624
- el.sidebarDivider.setAttribute("aria-valuemax", String(maxSidebarWidth()));
625
- el.sidebarDivider.setAttribute("aria-valuenow", String(el.sidebar.offsetWidth));
626
- }
627
- function setSidebarWidth(width, persist) {
628
- sidebar.width = width === null ? null : Math.max(SIDEBAR_MIN, Math.min(maxSidebarWidth(), Math.round(width)));
629
- if (persist) {
630
- if (sidebar.width === null) clearPreference("yui.sidebar.width");
631
- else writePreference("yui.sidebar.width", String(sidebar.width));
632
- }
633
- applySidebarWidth();
634
- }
635
- el.sidebarDivider.addEventListener("pointerdown", function (event) {
636
- if (event.button !== 0 || window.innerWidth <= 900) return;
637
- sidebar.resizing = true;
638
- el.sidebarDivider.setPointerCapture(event.pointerId);
639
- document.body.classList.add("sidebar-resizing");
640
- event.preventDefault();
641
- });
642
- el.sidebarDivider.addEventListener("pointermove", function (event) {
643
- if (!sidebar.resizing) return;
644
- setSidebarWidth(event.clientX - el.sidebar.getBoundingClientRect().left, false);
645
- });
646
- function finishSidebarResize() {
647
- if (!sidebar.resizing) return;
648
- sidebar.resizing = false;
649
- document.body.classList.remove("sidebar-resizing");
650
- if (sidebar.width !== null) writePreference("yui.sidebar.width", String(sidebar.width));
651
- }
652
- el.sidebarDivider.addEventListener("pointerup", finishSidebarResize);
653
- el.sidebarDivider.addEventListener("pointercancel", finishSidebarResize);
654
- el.sidebarDivider.addEventListener("dblclick", function () { setSidebarWidth(null, true); });
655
- el.sidebarDivider.addEventListener("keydown", function (event) {
656
- const current = el.sidebar.offsetWidth;
657
- const next = event.key === "ArrowRight" ? current + 16 : event.key === "ArrowLeft" ? current - 16
658
- : event.key === "Home" ? SIDEBAR_MIN : event.key === "End" ? maxSidebarWidth() : null;
659
- if (next === null) return;
660
- event.preventDefault();
661
- setSidebarWidth(next, true);
662
- });
663
- window.addEventListener("resize", applySidebarWidth);
664
-
665
- // --- Sidebar controls -------------------------------------------------------------
666
- let searchTimer = null;
667
- el.search.addEventListener("input", function () {
668
- state.query = el.search.value;
669
- syncUrl(true);
670
- window.clearTimeout(searchTimer);
671
- searchTimer = window.setTimeout(resetCatalog, 200);
672
- });
673
- el.catalogNext.addEventListener("click", function () {
674
- if (!state.nextCursor) return;
675
- state.catalogCursor = state.nextCursor;
676
- refreshDashboard();
677
- });
678
- el.catalogReset.addEventListener("click", resetCatalog);
679
- el.catalogAttentionReset.addEventListener("click", function () { pickAttention(null); });
680
- el.refresh.addEventListener("click", function () { refreshDashboard(); });
681
- el.settingsOpen.addEventListener("click", function () { el.settings.showModal(); });
682
-
683
- // --- Global Role input ------------------------------------------------------------
684
- const globalDialog = $("#global-input-dialog");
685
- const globalForm = $("#global-input-form");
686
- const globalRole = $("#global-input-role");
687
- const globalAction = $("#global-input-action");
688
- const globalBody = $("#global-input-body");
689
- const globalTarget = $("#global-input-target");
690
- const globalThen = $("#global-input-then");
691
- const globalSubmit = $("#global-input-submit");
692
- const globalReceipt = $("#global-input-receipt");
693
- const globalState = $("#global-input-state");
694
- $("#global-input-open").addEventListener("click", function () { globalDialog.showModal(); });
695
- $("#global-input-close").addEventListener("click", function () { globalDialog.close(); });
696
- globalAction.addEventListener("change", function () {
697
- $("#global-input-body-label").hidden = globalAction.value === "interrupt";
698
- $("#global-input-target-label").hidden = globalAction.value === "queue";
699
- $("#global-input-then-label").hidden = globalAction.value !== "interrupt";
700
- globalBody.required = globalAction.value !== "interrupt";
701
- globalTarget.required = globalAction.value !== "queue";
702
- });
703
- $("#global-input-inspect").addEventListener("click", async function () {
704
- globalState.hidden = false;
705
- try {
706
- const facts = await api.globalState(globalRole.value.trim());
707
- globalState.textContent = JSON.stringify(facts, null, 2);
708
- globalTarget.value = (facts.turn && (facts.turn.nativeTurnId || facts.turn.attemptId)) || "";
709
- } catch (error) { globalState.textContent = error.message; }
710
- });
711
- globalForm.addEventListener("submit", async function (event) {
712
- event.preventDefault();
713
- if (globalSubmit.disabled) return;
714
- globalSubmit.disabled = true;
715
- const role = globalRole.value.trim();
716
- const action = globalAction.value;
717
- const requestId = crypto.randomUUID();
718
- const input = { action: action, requestId: requestId };
719
- if (action !== "interrupt") input.body = globalBody.value;
720
- if (action !== "queue") input.expectedTarget = globalTarget.value.trim();
721
- if (action === "interrupt" && globalThen.value.trim()) input.thenMessage = globalThen.value.trim();
722
- globalReceipt.dataset.state = "pending";
723
- globalReceipt.textContent = t("receipt.waiting") + " · " + requestId;
724
- try {
725
- const receipt = await api.globalControl(role, input);
726
- const status = receipt.steer || receipt.interrupt || receipt.delivery || {};
727
- globalReceipt.dataset.state = "ok";
728
- globalReceipt.textContent = JSON.stringify(receipt);
729
- const unknown = ["pending", "delivery-unknown", "steer-unknown", "interrupt-unknown"].includes(status.state)
730
- || status.code === "DELIVERY_UNKNOWN";
731
- globalSubmit.disabled = unknown;
732
- if (!unknown && !status.code) globalBody.value = "";
733
- } catch (error) {
734
- globalReceipt.dataset.state = error.disposition === "not-submitted" ? "bad" : "warn";
735
- globalReceipt.textContent = error.disposition === "not-submitted" ? error.message : t("receipt.unknownControl") + " · " + requestId;
736
- globalSubmit.disabled = error.disposition !== "not-submitted";
737
- }
738
- });
739
-
740
- // --- Keyboard ---------------------------------------------------------------------
741
- document.addEventListener("keydown", function (event) {
742
- if (globalDialog.open || el.settings.open) return;
743
- const active = document.activeElement;
744
- const typing = active && (["INPUT", "SELECT", "TEXTAREA"].includes(active.tagName) || active.isContentEditable);
745
- const inTerminal = el.terminalHost.contains(active);
746
- if (event.key === "Escape") {
747
- // A native Session needs its own Escape key.
748
- if (inTerminal) return;
749
- if (typing) { active.blur(); return; }
750
- if (dockVisible()) { setDockOpen(false); return; }
751
- if (state.selected) clearSelection();
752
- return;
753
- }
754
- if (typing || inTerminal || event.metaKey || event.ctrlKey || event.altKey) return;
755
- const key = event.key.toLowerCase();
756
- if (key === "/") { event.preventDefault(); el.search.focus(); el.search.select(); }
757
- else if (key === "r") refreshDashboard();
758
- else if (key === "o") openOperator();
759
- else if (key === "d") toggleDock();
760
- else if (/^[1-5]$/.test(key) && state.detail) switchTab(TABS[Number(key) - 1]);
761
- });
762
-
763
- // --- Boot -------------------------------------------------------------------------
764
- function applyStateFromUrl() {
765
- const params = readQuery();
766
- const filter = params.get("filter");
767
- state.filter = STATUS_FILTERS.includes(filter) ? filter : "all";
768
- state.query = params.get("q") || "";
769
- state.attentionFilter = ATTENTION_KINDS.includes(params.get("attention")) ? params.get("attention") : null;
770
- state.catalogAll = params.get("all") === "true";
771
- el.search.value = state.query;
772
- const taskId = params.get("task");
773
- if (taskId) {
774
- if (taskId === state.selected && state.detail) applySection(params.get("section"));
775
- else selectTask(taskId);
776
- return;
777
- }
778
- if (state.selected && !canLeaveDetail()) { syncUrl(true); return; }
779
- state.selected = null;
780
- state.detail = null;
781
- el.discussion.replaceChildren();
782
- delete el.discussion.dataset.taskId;
783
- updateLayout();
784
- showOverview();
785
- }
786
- window.addEventListener("popstate", function () {
787
- applyStateFromUrl();
788
- resetCatalog();
789
- });
790
- i18n.subscribe(function () {
791
- theme.render();
792
- terminal.relabel();
793
- renderSidebar();
794
- renderedDiscussionKey = null;
795
- if (state.detail) renderCurrentDetail(true); else showOverview();
796
- updateLayout();
797
- });
798
-
799
- updateLayout();
800
- showOverview();
801
- applyStateFromUrl();
802
- refreshDashboard();
803
- window.setInterval(function () { refreshDashboard({ quiet: true }); }, 5000);
804
- `;