@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
@@ -0,0 +1,215 @@
1
+ export const CATALOG_SCRIPT = String.raw `
2
+ // The Task catalog: the refresh loop over the dashboard read, the sidebar
3
+ // (attention, status tabs, search, paged list), its filters from the URL and
4
+ // the explicit Session read of the current catalog page. This module is the
5
+ // only writer of the catalog fields of state. Only the newest request may
6
+ // apply its result.
7
+ import { fill, formatClock } from "/assets/js/lib/format.js";
8
+ import { h } from "/assets/js/lib/dom.js";
9
+ import { loadingBlock } from "/assets/js/ui/primitives.js";
10
+ import { renderAttentionBar, renderFilters, renderTasks, STATUS_FILTERS, ATTENTION_KINDS } from "/assets/js/views/sidebar.js";
11
+ import { syncUrl } from "/assets/js/layout/router.js";
12
+
13
+ // deps: { el, state, t, locale, api, toast, selectTask, showOverview, reloadSelected }
14
+ // el the sidebar elements, the sync state and the refresh button
15
+ // state writes the catalog fields; reads state.selected
16
+ // selectTask(taskId) a task row was chosen (app/selection)
17
+ // showOverview() redraw the center overview while nothing is selected
18
+ // reloadSelected() re-read the selected Task; rejects when the read fails
19
+ export function createCatalog(deps) {
20
+ // progress: whether a refresh is in flight and the newest request number.
21
+ const progress = { refreshing: false, request: 0 };
22
+ const catalog = {
23
+ refresh: function (options) { return refresh(deps, progress, options); },
24
+ reset: function () { reset(deps, progress); },
25
+ renderSidebar: function () { renderSidebar(deps, progress); },
26
+ renderTaskList: function () { renderTaskList(deps); },
27
+ pickAttention: function (kind) { pickAttention(deps, progress, kind); },
28
+ readPageSessions: function () { return readPageSessions(deps); },
29
+ applyFilters: function (params) { applyFilters(deps, params); }
30
+ };
31
+ bindCatalogControls(deps.el, deps.state, catalog);
32
+ return catalog;
33
+ }
34
+
35
+ function reset(deps, progress) {
36
+ deps.state.catalogCursor = null;
37
+ deps.state.nextCursor = null;
38
+ refresh(deps, progress);
39
+ }
40
+
41
+ async function refresh(deps, progress, options) {
42
+ const el = deps.el;
43
+ const state = deps.state;
44
+ const quiet = options && options.quiet;
45
+ if (progress.refreshing && quiet) return;
46
+ const request = ++progress.request;
47
+ progress.refreshing = true;
48
+ beginRefresh(el, state, deps.t, quiet);
49
+ const previousInputs = state.counts ? state.counts.openInputs : null;
50
+ try {
51
+ const query = catalogQuery(state);
52
+ const dashboard = await deps.api.dashboard(query);
53
+ if (request !== progress.request) return;
54
+ applyDashboard(state, dashboard, query);
55
+ showDashboard(deps, progress, previousInputs);
56
+ // A selected Task may live on another page or outside current filters;
57
+ // only its own read can establish that it no longer exists.
58
+ if (state.selected) {
59
+ try { await deps.reloadSelected(); }
60
+ catch { deps.toast(deps.t("errors.disconnected")); }
61
+ }
62
+ } catch {
63
+ if (request !== progress.request) return;
64
+ dashboardFailed(deps, quiet);
65
+ } finally {
66
+ if (request === progress.request) {
67
+ progress.refreshing = false;
68
+ el.refresh.disabled = false;
69
+ el.catalogNext.disabled = !state.nextCursor;
70
+ }
71
+ }
72
+ }
73
+
74
+ function showDashboard(deps, progress, previousInputs) {
75
+ deps.el.sync.dataset.state = "ok";
76
+ renderSidebar(deps, progress);
77
+ if (!deps.state.selected) deps.showOverview();
78
+ if (previousInputs !== null && deps.state.counts.openInputs > previousInputs) deps.toast(deps.t("input.new"));
79
+ }
80
+
81
+ function dashboardFailed(deps, quiet) {
82
+ deps.el.sync.dataset.state = "error";
83
+ if (quiet) return;
84
+ deps.el.tasks.replaceChildren(h("div.list-empty.is-error", null, h("p", null, deps.t("errors.dashboard"))));
85
+ deps.toast(deps.t("errors.dashboard"));
86
+ }
87
+
88
+ function renderSidebar(deps, progress) {
89
+ const el = deps.el;
90
+ const state = deps.state;
91
+ const t = deps.t;
92
+ renderAttentionBar(el.attention, state, t, function (kind) { pickAttention(deps, progress, kind); });
93
+ renderFilters(el.filters, state, t, function (filter) {
94
+ state.filter = filter;
95
+ state.attentionFilter = null;
96
+ state.catalogAll = false;
97
+ syncUrl(state, false);
98
+ reset(deps, progress);
99
+ });
100
+ el.catalogCount.textContent = fill(t("catalog.count"), { shown: state.tasks.length, total: state.catalogTotal });
101
+ el.catalogReset.hidden = !state.catalogCursor;
102
+ el.catalogNext.hidden = !state.nextCursor && !state.catalogCursor;
103
+ el.catalogAttentionReset.hidden = !state.attentionFilter;
104
+ renderTaskList(deps);
105
+ el.lastSync.textContent = state.generatedAt ? formatClock(state.generatedAt, deps.locale()) : "—";
106
+ }
107
+
108
+ // Redraw the list in place, keeping its scroll position.
109
+ function renderTaskList(deps) {
110
+ const scroll = deps.el.tasks.scrollTop;
111
+ renderTasks(deps.el.tasks, deps.state, deps.t, deps.locale(), deps.selectTask);
112
+ deps.el.tasks.scrollTop = scroll;
113
+ }
114
+
115
+ function pickAttention(deps, progress, kind) {
116
+ const state = deps.state;
117
+ state.attentionFilter = kind;
118
+ state.catalogAll = !!kind && !!state.catalogScope && state.catalogScope.archived === "included";
119
+ state.filter = "all";
120
+ state.query = "";
121
+ deps.el.search.value = "";
122
+ syncUrl(state, false);
123
+ reset(deps, progress);
124
+ }
125
+
126
+ function beginRefresh(el, state, t, quiet) {
127
+ el.catalogNext.disabled = true;
128
+ el.sync.dataset.state = "syncing";
129
+ if (quiet) return;
130
+ el.refresh.disabled = true;
131
+ if (!state.tasks.length) el.tasks.replaceChildren(loadingBlock(t("loading.dashboard")));
132
+ }
133
+
134
+ function catalogQuery(state) {
135
+ const query = new URLSearchParams();
136
+ if (state.catalogAll) query.set("all", "true");
137
+ if (state.filter !== "all") query.set("status", state.filter);
138
+ if (state.query.trim()) query.set("search", state.query.trim());
139
+ if (state.attentionFilter) query.set("attention", state.attentionFilter);
140
+ if (state.catalogCursor) query.set("cursor", state.catalogCursor);
141
+ return query.toString();
142
+ }
143
+
144
+ function taskIds(tasks, key) {
145
+ return JSON.stringify(tasks.map(function (task) { return task[key]; }));
146
+ }
147
+
148
+ function applyDashboard(state, dashboard, query) {
149
+ state.tasks = dashboard.tasks;
150
+ // A Session read describes exactly one catalog page.
151
+ if (state.catalogQuery !== query || (state.sessionOverview
152
+ && taskIds(state.sessionOverview.tasks, "taskId") !== taskIds(dashboard.tasks, "id"))) {
153
+ state.sessionOverview = null;
154
+ }
155
+ state.catalogQuery = query;
156
+ state.counts = { ...dashboard.counts, openInputs: dashboard.attention.openInputs.count };
157
+ state.catalogAttention = dashboard.attention;
158
+ state.catalogScope = dashboard.scope;
159
+ state.catalogTotal = dashboard.total;
160
+ state.nextCursor = dashboard.nextCursor;
161
+ state.attention = dashboard.attention.openInputs.refs.map(function (ref) {
162
+ const task = state.tasks.find(function (entry) { return entry.id === ref.taskId; });
163
+ return { taskId: ref.taskId, taskTitle: task ? task.title : ref.taskId };
164
+ });
165
+ state.generatedAt = new Date().toISOString();
166
+ }
167
+
168
+ async function readPageSessions(deps) {
169
+ const state = deps.state;
170
+ if (state.sessionLoading) return;
171
+ const query = state.catalogQuery;
172
+ state.sessionLoading = true;
173
+ deps.showOverview();
174
+ try {
175
+ const result = await deps.api.pageSessions(query);
176
+ if (query !== state.catalogQuery) return;
177
+ if (taskIds(result.tasks, "taskId") !== taskIds(state.tasks, "id")) throw new Error(deps.t("overview.sessionsPageChanged"));
178
+ state.sessionOverview = result;
179
+ state.sessionError = null;
180
+ } catch (error) { state.sessionError = error.message; }
181
+ finally {
182
+ state.sessionLoading = false;
183
+ if (!state.selected) deps.showOverview();
184
+ }
185
+ }
186
+
187
+ // Filters from the URL, on load and on back/forward.
188
+ function applyFilters(deps, params) {
189
+ const state = deps.state;
190
+ const filter = params.get("filter");
191
+ state.filter = STATUS_FILTERS.includes(filter) ? filter : "all";
192
+ state.query = params.get("q") || "";
193
+ state.attentionFilter = ATTENTION_KINDS.includes(params.get("attention")) ? params.get("attention") : null;
194
+ state.catalogAll = params.get("all") === "true";
195
+ deps.el.search.value = state.query;
196
+ }
197
+
198
+ function bindCatalogControls(el, state, catalog) {
199
+ let searchTimer = null;
200
+ el.search.addEventListener("input", function () {
201
+ state.query = el.search.value;
202
+ syncUrl(state, true);
203
+ window.clearTimeout(searchTimer);
204
+ searchTimer = window.setTimeout(catalog.reset, 200);
205
+ });
206
+ el.catalogNext.addEventListener("click", function () {
207
+ if (!state.nextCursor) return;
208
+ state.catalogCursor = state.nextCursor;
209
+ catalog.refresh();
210
+ });
211
+ el.catalogReset.addEventListener("click", catalog.reset);
212
+ el.catalogAttentionReset.addEventListener("click", function () { catalog.pickAttention(null); });
213
+ el.refresh.addEventListener("click", function () { catalog.refresh(); });
214
+ }
215
+ `;
@@ -0,0 +1,148 @@
1
+ export const APP_SCRIPT = String.raw `
2
+ // Composition root (served as /assets/app.js). It binds the static shell,
3
+ // owns the view state object and is the only place where controllers are
4
+ // wired to each other. Each controller receives one explicit deps object,
5
+ // documented at its factory: services, controllers created before it, and
6
+ // named callbacks into controllers created after it (bound here, late).
7
+ // Everything kept in the browser is view state; Task facts always come from
8
+ // the loopback API.
9
+ //
10
+ // layout/workspace sidebar | center | dock columns, dock modes, sizing
11
+ // app/taskView draws the selected Task, the dock discussion, the overview
12
+ // app/selection selected Task, its detail reads, URL-driven navigation
13
+ // app/catalog refresh loop, sidebar, catalog paging and filters
14
+ // layout/shortcuts keyboard shortcuts
15
+ //
16
+ // Flows:
17
+ // refresh catalog.refresh → sidebar, then the overview (nothing selected)
18
+ // or selection.reload → taskView.render
19
+ // select sidebar row / overview row / URL → selection.selectTask
20
+ // leave back button or Escape → selection.clearSelection
21
+ // write Task page or discussion afterWrite → quiet catalog refresh
22
+ // URL load and back/forward → catalog.applyFilters + selection.followUrl
23
+ import { createI18n } from "/assets/js/lib/i18n.js";
24
+ import { createThemeController } from "/assets/js/lib/theme.js";
25
+ import { api } from "/assets/js/lib/api.js";
26
+ import { createToast } from "/assets/js/ui/toast.js";
27
+ import { createTerminalController } from "/assets/js/views/dock/terminal.js";
28
+ import { createConversationController } from "/assets/js/views/dock/conversation.js";
29
+ import { createPreviewController } from "/assets/js/views/dock/preview.js";
30
+ import { bindGlobalInput } from "/assets/js/views/globalInput.js";
31
+ import { bindCreateTask } from "/assets/js/views/createTask.js";
32
+ import { bindBodySearch } from "/assets/js/views/bodySearch.js";
33
+ import { readQuery } from "/assets/js/layout/router.js";
34
+ import { createWorkspace } from "/assets/js/layout/workspace.js";
35
+ import { bindShortcuts } from "/assets/js/layout/shortcuts.js";
36
+ import { createTaskView } from "/assets/js/app/taskView.js";
37
+ import { createSelection } from "/assets/js/app/selection.js";
38
+ import { createCatalog } from "/assets/js/app/catalog.js";
39
+
40
+ const $ = function (selector) { return document.querySelector(selector); };
41
+ const el = {
42
+ sidebar: $(".sidebar"), sidebarDivider: $("#sidebar-divider"), center: $("#center"), detail: $("#detail"),
43
+ search: $("#search"), filters: $("#status-filters"), attention: $("#attention-bar"), tasks: $("#task-list"),
44
+ catalogNext: $("#catalog-next"), catalogReset: $("#catalog-reset"), catalogCount: $("#catalog-count"),
45
+ catalogAttentionReset: $("#catalog-attention-reset"),
46
+ refresh: $("#refresh"), sync: $("#sync-state"), lastSync: $("#last-sync"), toast: $("#toast"),
47
+ operator: $("#operator-terminal"), settingsOpen: $("#settings-open"), settings: $("#settings-dialog"),
48
+ locale: $("#locale-select"), themeOptions: $("#theme-options"),
49
+ dock: $("#dock"), divider: $("#dock-divider"), dockSwap: $("#dock-swap"), dockClose: $("#dock-close"),
50
+ dockTabDiscussion: $("#dock-tab-discussion"), dockTabSession: $("#dock-tab-session"),
51
+ dockTabConversation: $("#dock-tab-conversation"), conversation: $("#dock-conversation"),
52
+ dockTabPreview: $("#dock-tab-preview"), preview: $("#dock-preview"),
53
+ discussion: $("#dock-discussion"), session: $("#dock-session"),
54
+ terminalHost: $("#terminal-host"), terminalEmpty: $("#terminal-empty"), terminalState: $("#terminal-state"),
55
+ sessionTargets: $("#session-targets"), terminalCli: $("#terminal-cli")
56
+ };
57
+
58
+ // View state. Each group has exactly one writer.
59
+ const state = {
60
+ // app/catalog: the dashboard read, its filters and paging, the Session read.
61
+ tasks: [], counts: null, attention: [], catalogAttention: null, catalogScope: null, catalogQuery: "",
62
+ catalogAll: false, catalogTotal: 0, catalogCursor: null, nextCursor: null,
63
+ sessionOverview: null, sessionLoading: false, sessionError: null,
64
+ attentionFilter: null, generatedAt: null, filter: "all", query: "",
65
+ // app/selection: the selected Task and its loaded detail.
66
+ selected: null, detail: null, detailKey: null,
67
+ // app/taskView: the tab the reader is on.
68
+ activeTab: "overview"
69
+ };
70
+
71
+ const i18n = createI18n(el.locale);
72
+ const t = i18n.t;
73
+ const locale = i18n.getLocale;
74
+ const theme = createThemeController(el.themeOptions, t);
75
+ const toast = createToast(el.toast);
76
+ const conversation = createConversationController(el.conversation, t, locale);
77
+ const preview = createPreviewController(el.preview, t);
78
+ const terminal = createTerminalController({
79
+ host: el.terminalHost, empty: el.terminalEmpty, state: el.terminalState, targets: el.sessionTargets, cli: el.terminalCli
80
+ }, t, locale, toast);
81
+ theme.subscribe(function () { terminal.retheme(); });
82
+
83
+ // Controllers are created in dependency order; the arrow callbacks below are
84
+ // the only edges pointing at controllers created later.
85
+ let taskView = null;
86
+ let selection = null;
87
+ let catalog = null;
88
+ const workspace = createWorkspace({
89
+ el: el, state: state, terminal: terminal, conversation: conversation, preview: preview,
90
+ sessionOpened: function () { taskView.updateSessionTargets(); }
91
+ });
92
+ taskView = createTaskView({
93
+ el: el, state: state, t: t, locale: locale, api: api, toast: toast, terminal: terminal, workspace: workspace,
94
+ selectTask: function (taskId) { return selection.selectTask(taskId); },
95
+ clearSelection: function () { selection.clearSelection(); },
96
+ pickAttention: function (kind) { catalog.pickAttention(kind); },
97
+ readPageSessions: function () { return catalog.readPageSessions(); },
98
+ refreshCatalog: function () { return catalog.refresh({ quiet: true }); }
99
+ });
100
+ selection = createSelection({
101
+ state: state, t: t, api: api, toast: toast, workspace: workspace, taskView: taskView,
102
+ renderTaskList: function () { catalog.renderTaskList(); }
103
+ });
104
+ catalog = createCatalog({
105
+ el: el, state: state, t: t, locale: locale, api: api, toast: toast,
106
+ selectTask: selection.selectTask,
107
+ showOverview: taskView.showOverview,
108
+ reloadSelected: selection.reload
109
+ });
110
+ const globalInput = bindGlobalInput({ t: t, api: api });
111
+ bindCreateTask({ t: t, api: api, selectTask: selection.selectTask,
112
+ afterWrite: function () { catalog.refresh({ quiet: true }); } });
113
+ bindBodySearch({ t: t, api: api, selectTask: selection.selectTask,
114
+ currentTask: function () { return state.selected; },
115
+ captureDraftTarget: conversation.captureDraftTarget, insertReference: conversation.insertReference });
116
+ bindShortcuts({
117
+ el: el, state: state, globalInput: globalInput, workspace: workspace,
118
+ selection: selection, catalog: catalog, taskView: taskView
119
+ });
120
+ el.settingsOpen.addEventListener("click", function () { window.location.assign("/settings"); });
121
+
122
+ // The URL names the catalog filters and the selected Task (with its section).
123
+ function applyStateFromUrl() {
124
+ const params = readQuery();
125
+ catalog.applyFilters(params);
126
+ selection.followUrl(params);
127
+ }
128
+ window.addEventListener("popstate", function () {
129
+ applyStateFromUrl();
130
+ catalog.reset();
131
+ });
132
+
133
+ i18n.subscribe(function () {
134
+ theme.render();
135
+ terminal.relabel();
136
+ catalog.renderSidebar();
137
+ taskView.relabel();
138
+ workspace.updateLayout();
139
+ });
140
+
141
+ workspace.updateLayout();
142
+ taskView.showOverview();
143
+ conversation.open({ scope: "global", roleName: "operator" });
144
+ applyStateFromUrl();
145
+ workspace.restoreDefaultMode();
146
+ catalog.refresh();
147
+ window.setInterval(function () { catalog.refresh({ quiet: true }); }, 5000);
148
+ `;
@@ -0,0 +1,139 @@
1
+ export const SELECTION_SCRIPT = String.raw `
2
+ // Which Task is selected and its loaded detail: selecting one, leaving it,
3
+ // following the URL, and reading the Context snapshot (the only input to core
4
+ // rendering) plus the optional runtime observation. This module is the only
5
+ // writer of state.selected, state.detail and state.detailKey; app/taskView
6
+ // draws the result. Leaving a Task with an unsent draft asks first; the page
7
+ // itself warns before unload.
8
+ import { applyUrl, readQuery, syncUrl, urlTaskId } from "/assets/js/layout/router.js";
9
+
10
+ // Fingerprint of the rendered payload. Quiet polling skips identical reads so
11
+ // local view state (open blocks, list pages, drafts) survives.
12
+ function fingerprint(value) {
13
+ const text = JSON.stringify(value);
14
+ let hash = 0;
15
+ for (let i = 0; i < text.length; i += 1) hash = (hash * 31 + text.charCodeAt(i)) | 0;
16
+ return text.length + ":" + hash;
17
+ }
18
+
19
+ // deps: { state, t, api, toast, workspace, taskView, renderTaskList }
20
+ // state writes selected, detail, detailKey
21
+ // api context, inspect and observation reads
22
+ // workspace layout/workspace: layout and the dock on entering/leaving
23
+ // taskView app/taskView: draws the selected Task and the overview
24
+ // renderTaskList() redraw the sidebar list's selection (app/catalog)
25
+ export function createSelection(deps) {
26
+ window.addEventListener("beforeunload", function (event) {
27
+ if (!deps.taskView.hasUnsent()) return;
28
+ event.preventDefault();
29
+ event.returnValue = "";
30
+ });
31
+ return {
32
+ selectTask: function (taskId) { return selectTask(deps, taskId); },
33
+ clearSelection: function () { clearSelection(deps); },
34
+ followUrl: function (params) { followUrl(deps, params); },
35
+ // Quietly re-read the selected Task; rejects when the read fails.
36
+ reload: function () { return load(deps, deps.state.selected, false); }
37
+ };
38
+ }
39
+
40
+ function canLeave(deps) {
41
+ return !deps.taskView.hasUnsent() || window.confirm(deps.t("confirm.leave"));
42
+ }
43
+
44
+ async function selectTask(deps, taskId) {
45
+ const state = deps.state;
46
+ // Re-selecting the visible Task is not navigation.
47
+ if (state.selected === taskId && state.detail) return;
48
+ if (state.selected !== taskId && !canLeave(deps)) { syncUrl(state, true); return; }
49
+ if (urlTaskId() !== taskId) {
50
+ const params = readQuery();
51
+ params.delete("section");
52
+ applyUrl(params, true);
53
+ }
54
+ // Show taskId as selected before its detail arrives.
55
+ state.selected = taskId;
56
+ state.detail = null;
57
+ deps.workspace.enterTask();
58
+ deps.taskView.enterTask();
59
+ syncUrl(state, state.selected === urlTaskId());
60
+ deps.workspace.updateLayout();
61
+ deps.renderTaskList();
62
+ try {
63
+ await load(deps, taskId, true);
64
+ } catch {
65
+ if (state.selected !== taskId) return;
66
+ deps.taskView.showDetailError();
67
+ deps.toast(deps.t("errors.detail"));
68
+ }
69
+ }
70
+
71
+ function clearSelection(deps) {
72
+ if (!canLeave(deps)) return;
73
+ dropSelection(deps);
74
+ syncUrl(deps.state, !urlTaskId());
75
+ deps.workspace.updateLayout();
76
+ deps.taskView.showOverview();
77
+ deps.renderTaskList();
78
+ deps.taskView.updateSessionTargets();
79
+ }
80
+
81
+ // Drop the selected Task and its dock discussion; callers redraw.
82
+ function dropSelection(deps) {
83
+ deps.state.selected = null;
84
+ deps.state.detail = null;
85
+ deps.taskView.leaveTask();
86
+ deps.workspace.leaveTask();
87
+ }
88
+
89
+ // The Task and section named by the URL, on load and on back/forward.
90
+ function followUrl(deps, params) {
91
+ const state = deps.state;
92
+ const taskId = params.get("task");
93
+ if (taskId) {
94
+ if (taskId === state.selected && state.detail) deps.taskView.applySection(params.get("section"));
95
+ else selectTask(deps, taskId);
96
+ return;
97
+ }
98
+ if (state.selected && !canLeave(deps)) { syncUrl(state, true); return; }
99
+ dropSelection(deps);
100
+ deps.workspace.updateLayout();
101
+ deps.taskView.showOverview();
102
+ }
103
+
104
+ async function load(deps, taskId, navigate) {
105
+ const state = deps.state;
106
+ if (navigate) deps.taskView.showLoading();
107
+ // Rendering consumes the current Context snapshot, not event pages.
108
+ const snapshot = await deps.api.workbench(taskId);
109
+ const core = snapshot.core;
110
+ const task = snapshot.task;
111
+ if (state.selected !== taskId) return;
112
+ const previous = state.detail && state.detail.task.id === taskId ? state.detail : null;
113
+ const detail = {
114
+ task: task, core: core, briefValue: snapshot.brief, evidenceSummary: snapshot.evidence,
115
+ viewState: previous ? previous.viewState : {},
116
+ runtime: previous ? previous.runtime : null,
117
+ runtimeStatus: previous ? previous.runtimeStatus : "waiting",
118
+ runtimeObservedAt: previous ? previous.runtimeObservedAt : new Date().toISOString()
119
+ };
120
+ state.detail = detail;
121
+ state.detailKey = fingerprint(core);
122
+ deps.taskView.render(navigate);
123
+ if (navigate) deps.taskView.applySection(readQuery().get("section"));
124
+ observe(deps, taskId, detail);
125
+ }
126
+
127
+ // The optional observation never participates in core readiness.
128
+ function observe(deps, taskId, detail) {
129
+ function settle(runtime, status) {
130
+ if (deps.state.detail !== detail) return;
131
+ detail.runtime = runtime;
132
+ detail.runtimeStatus = status;
133
+ detail.runtimeObservedAt = new Date().toISOString();
134
+ deps.taskView.observationArrived(detail);
135
+ }
136
+ deps.api.observation(taskId).then(function (runtime) { settle(runtime, "available"); })
137
+ .catch(function () { settle(null, "unavailable"); });
138
+ }
139
+ `;