@zq-silk/yui 2.3.0 → 2.5.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 (197) hide show
  1. package/README.md +15 -1
  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 +18 -9
  7. package/dist/cli/invocationAuthority.js +1 -1
  8. package/dist/commands/executionAuditCommands.js +5 -0
  9. package/dist/commands/globalRoleCommands.js +41 -3
  10. package/dist/commands/projectCommands.js +89 -94
  11. package/dist/commands/roleSkillValidation.js +4 -8
  12. package/dist/commands/taskCommands.js +65 -14
  13. package/dist/commands/taskUsageCommand.js +38 -0
  14. package/dist/context/roleSessionContext.js +18 -2
  15. package/dist/context/runContextPack.js +5 -1
  16. package/dist/context/sessionBootstrapManifest.js +18 -3
  17. package/dist/context/skillCatalog.js +108 -0
  18. package/dist/context/skillPackage.js +161 -0
  19. package/dist/context/taskContext.js +3 -2
  20. package/dist/context/taskSearch.js +127 -0
  21. package/dist/controlPlaneCli.js +25 -20
  22. package/dist/controller/fileSchedulerStoreAdapter.js +39 -8
  23. package/dist/controller/globalInputDelivery.js +2 -4
  24. package/dist/controller/jobControl.js +2 -0
  25. package/dist/controller/nativeHostInput.js +37 -0
  26. package/dist/controller/providerRetryDelivery.js +4 -2
  27. package/dist/controller/runtime.js +93 -2
  28. package/dist/controller/runtimeEventInbox.js +2 -2
  29. package/dist/controller/runtimeEventProcessor.js +3 -3
  30. package/dist/doctor/doctor.js +10 -0
  31. package/dist/execution/reviewMainRun.js +1 -0
  32. package/dist/execution/workItemMainRun.js +1 -0
  33. package/dist/executor/agentAdapter.js +3 -5
  34. package/dist/executor/codexConfigConflict.js +6 -4
  35. package/dist/executor/effectiveLaunch.js +24 -1
  36. package/dist/executor/fileRoleLaunchPlanner.js +15 -7
  37. package/dist/integration/gitIntegrationService.js +7 -12
  38. package/dist/integration/integrationSourceApplication.js +4 -2
  39. package/dist/interaction/notificationPresentation.js +85 -0
  40. package/dist/interaction/operatorPresentation.js +1 -2
  41. package/dist/job/httpPreview.js +61 -0
  42. package/dist/job/jobRunner.js +12 -2
  43. package/dist/kernel/builtinCapabilities.js +3 -1
  44. package/dist/message/message.js +15 -1
  45. package/dist/output/rolePresentation.js +4 -1
  46. package/dist/repository/executionLaneGitSnapshot.js +2 -1
  47. package/dist/repository/gitCompatibility.js +62 -0
  48. package/dist/repository/gitWorkspace.js +48 -71
  49. package/dist/repository/gitWorktreeInventory.js +121 -0
  50. package/dist/repository/project.js +74 -25
  51. package/dist/resources/resourceDiscovery.js +2 -2
  52. package/dist/resources/resourceGc.js +7 -4
  53. package/dist/resources/resourceRegistrar.js +2 -1
  54. package/dist/runtime/agentEndpoint.js +8 -0
  55. package/dist/runtime/agentHost.js +170 -3
  56. package/dist/runtime/codexAppServerRuntime.js +1 -1
  57. package/dist/runtime/codexNativeAccess.js +175 -0
  58. package/dist/runtime/controlledCodexTui.js +116 -0
  59. package/dist/runtime/publicReply.js +23 -0
  60. package/dist/runtime/runtimeObservation.js +48 -0
  61. package/dist/runtime/structuredProviderHost.js +16 -7
  62. package/dist/runtime/taskCostMetrics.js +133 -0
  63. package/dist/runtime/taskUsageMetrics.js +11 -2
  64. package/dist/runtime/taskUsageQuery.js +38 -0
  65. package/dist/scheduler/leaderWakeupProcessor.js +1 -9
  66. package/dist/scheduler/taskExecutionProjection.js +2 -0
  67. package/dist/scheduler/taskObservabilityProjection.js +9 -1
  68. package/dist/storage/homeLayout.js +13 -0
  69. package/dist/storage/sqliteSchema.js +44 -0
  70. package/dist/storage/sqliteStore.js +53 -0
  71. package/dist/storage/storageVersions.js +1 -1
  72. package/dist/storage/storeRpc.js +2 -0
  73. package/dist/storage/taskSearch.js +35 -0
  74. package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
  75. package/dist/task/ordinaryArchive.js +42 -0
  76. package/dist/tmux/tmuxManager.js +8 -8
  77. package/dist/web/assets/assetManifest.js +11 -51
  78. package/dist/web/assets/client/app/catalog.js +215 -0
  79. package/dist/web/assets/client/app/main.js +148 -0
  80. package/dist/web/assets/client/app/selection.js +139 -0
  81. package/dist/web/assets/client/app/settings.js +326 -0
  82. package/dist/web/assets/client/app/taskView.js +280 -0
  83. package/dist/web/assets/client/domain/context.js +96 -0
  84. package/dist/web/assets/client/domain/records.js +126 -0
  85. package/dist/web/assets/client/domain/runs.js +119 -0
  86. package/dist/web/assets/client/domain/taskForms.js +197 -0
  87. package/dist/web/assets/client/domain/vocab.js +69 -0
  88. package/dist/web/assets/client/domain/work.js +116 -0
  89. package/dist/web/assets/client/i18n/en.js +900 -0
  90. package/dist/web/assets/client/i18n/zh-CN.js +898 -0
  91. package/dist/web/assets/client/index.js +114 -0
  92. package/dist/web/assets/client/layout/resizer.js +37 -0
  93. package/dist/web/assets/client/layout/router.js +35 -0
  94. package/dist/web/assets/client/layout/shortcuts.js +45 -0
  95. package/dist/web/assets/client/layout/sizing.js +95 -0
  96. package/dist/web/assets/client/layout/workspace.js +247 -0
  97. package/dist/web/assets/client/{api.js → lib/api.js} +20 -4
  98. package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
  99. package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
  100. package/dist/web/assets/client/lib/i18n.js +54 -0
  101. package/dist/web/assets/client/lib/markdown.js +167 -0
  102. package/dist/web/assets/client/lib/prefs.js +28 -0
  103. package/dist/web/assets/client/lib/theme.js +68 -0
  104. package/dist/web/assets/client/ui/containers.js +101 -0
  105. package/dist/web/assets/client/ui/controls.js +51 -0
  106. package/dist/web/assets/client/ui/forms.js +48 -0
  107. package/dist/web/assets/client/ui/metrics.js +137 -0
  108. package/dist/web/assets/client/ui/primitives.js +92 -0
  109. package/dist/web/assets/client/ui/text.js +33 -0
  110. package/dist/web/assets/client/ui/toast.js +12 -0
  111. package/dist/web/assets/client/views/bodySearch.js +172 -0
  112. package/dist/web/assets/client/views/createTask.js +85 -0
  113. package/dist/web/assets/client/views/dock/conversation.js +612 -0
  114. package/dist/web/assets/client/views/dock/discussion.js +173 -0
  115. package/dist/web/assets/client/views/dock/preview.js +129 -0
  116. package/dist/web/assets/client/views/dock/terminal.js +199 -0
  117. package/dist/web/assets/client/views/globalInput.js +67 -0
  118. package/dist/web/assets/client/{overview.js → views/overview.js} +61 -51
  119. package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
  120. package/dist/web/assets/client/views/skills.js +152 -0
  121. package/dist/web/assets/client/views/task/actions.js +36 -0
  122. package/dist/web/assets/client/views/task/delivery.js +185 -0
  123. package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
  124. package/dist/web/assets/client/views/task/files.js +272 -0
  125. package/dist/web/assets/client/views/task/observation.js +153 -0
  126. package/dist/web/assets/client/views/task/overview.js +161 -0
  127. package/dist/web/assets/client/views/task/page.js +131 -0
  128. package/dist/web/assets/client/views/task/records.js +163 -0
  129. package/dist/web/assets/client/views/task/runtime.js +130 -0
  130. package/dist/web/assets/client/views/task/timeline.js +190 -0
  131. package/dist/web/assets/shared/geometry.js +19 -0
  132. package/dist/web/assets/shared/taskTabs.js +8 -0
  133. package/dist/web/assets/shared/themes.js +93 -0
  134. package/dist/web/assets/shell/dialogs.js +63 -0
  135. package/dist/web/assets/shell/dock.js +40 -0
  136. package/dist/web/assets/shell/index.js +40 -0
  137. package/dist/web/assets/shell/settings.js +34 -0
  138. package/dist/web/assets/shell/sidebar.js +45 -0
  139. package/dist/web/assets/styles/index.js +41 -0
  140. package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +10 -10
  141. package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
  142. package/dist/web/assets/styles/markdown.js +22 -3
  143. package/dist/web/assets/styles/tokens.js +10 -63
  144. package/dist/web/assets/styles/ui/controls.js +76 -0
  145. package/dist/web/assets/styles/ui/forms.js +46 -0
  146. package/dist/web/assets/styles/ui/surfaces.js +122 -0
  147. package/dist/web/assets/styles/views/delivery.js +29 -0
  148. package/dist/web/assets/styles/views/dock.js +71 -0
  149. package/dist/web/assets/styles/views/page.js +32 -0
  150. package/dist/web/assets/styles/views/records.js +70 -0
  151. package/dist/web/assets/styles/views/settings.js +26 -0
  152. package/dist/web/assets/styles/views/sidebar.js +74 -0
  153. package/dist/web/assets/styles/views/task.js +80 -0
  154. package/dist/web/assets/styles/views/terminal.js +38 -0
  155. package/dist/web/taskPreviews.js +205 -0
  156. package/dist/web/tmuxWebTerminal.js +16 -9
  157. package/dist/web/webConversation.js +373 -0
  158. package/dist/web/webMaterials.js +155 -0
  159. package/dist/web/webServer.js +295 -18
  160. package/dist/web/webSettings.js +331 -0
  161. package/dist/web/webSkills.js +86 -0
  162. package/dist/web/webSnapshot.js +40 -8
  163. package/dist/web/webTaskSurface.js +106 -8
  164. package/dist/workspace/gitChangeSetCapture.js +3 -1
  165. package/docs/cli-information-contract.md +1 -1
  166. package/docs/cli-information-contract.zh-CN.md +1 -1
  167. package/docs/managed-turn-and-session-runtime.md +31 -0
  168. package/docs/managed-turn-and-session-runtime.zh-CN.md +22 -0
  169. package/docs/release-workflow.md +54 -0
  170. package/docs/release-workflow.zh-CN.md +39 -0
  171. package/docs/storage-baseline.md +38 -1
  172. package/docs/storage-baseline.zh-CN.md +22 -0
  173. package/docs/task-discovery.md +28 -0
  174. package/docs/testing/verification-levels.md +2 -2
  175. package/i18n/README.zh-CN.md +1 -1
  176. package/package.json +1 -1
  177. package/skills/yui-leader/SKILL.md +5 -4
  178. package/skills/yui-leader/references/execution.md +2 -1
  179. package/skills/yui-operator/SKILL.md +3 -0
  180. package/skills/yui-reviewer/SKILL.md +4 -2
  181. package/skills/yui-runtime/SKILL.md +43 -2
  182. package/skills/yui-worker/SKILL.md +3 -2
  183. package/dist/web/assets/client/app.js +0 -808
  184. package/dist/web/assets/client/components.js +0 -477
  185. package/dist/web/assets/client/detail.js +0 -379
  186. package/dist/web/assets/client/dock.js +0 -309
  187. package/dist/web/assets/client/evidence.js +0 -364
  188. package/dist/web/assets/client/forms.js +0 -230
  189. package/dist/web/assets/client/i18n.js +0 -1277
  190. package/dist/web/assets/client/markdown.js +0 -114
  191. package/dist/web/assets/client/records.js +0 -198
  192. package/dist/web/assets/client/sections.js +0 -349
  193. package/dist/web/assets/client/theme.js +0 -71
  194. package/dist/web/assets/shell.js +0 -161
  195. package/dist/web/assets/styles/components.js +0 -216
  196. package/dist/web/assets/styles/views.js +0 -309
  197. /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
@@ -1,75 +1,87 @@
1
1
  export const OVERVIEW_SCRIPT = String.raw `
2
- // Workspace overview, shown in the center while no Task is selected.
3
- import { h, icon, clear } from "/assets/js/dom.js";
4
- import { byNewest, formatDateTime, relativeTime } from "/assets/js/format.js";
5
- import { card, dot, tone, label, metricTile, emptyState, button, badge, note } from "/assets/js/components.js";
6
- import { ATTENTION_KINDS } from "/assets/js/sidebar.js";
7
-
8
- const SESSION_GROUPS = ["active", "waiting", "background", "quiet", "diagnostic", "stopped", "unknown", "idle"];
2
+ // Workspace overview, shown in the center while no Task is selected: one
3
+ // reading column with the work path first (answer, in progress, recent),
4
+ // then catalog-wide signals and the explicit Session read.
5
+ import { h, icon, clear } from "/assets/js/lib/dom.js";
6
+ import { byNewest, fill, formatDateTime, relativeTime } from "/assets/js/lib/format.js";
7
+ import { badge, button, dot, emptyState, note } from "/assets/js/ui/primitives.js";
8
+ import { card } from "/assets/js/ui/containers.js";
9
+ import { metricTile } from "/assets/js/ui/metrics.js";
10
+ import { label, sessionBadges, tone } from "/assets/js/domain/vocab.js";
11
+ import { ATTENTION_KINDS } from "/assets/js/views/sidebar.js";
9
12
 
10
13
  export function renderOverview(container, state, t, locale, actions) {
11
14
  clear(container);
12
- const counts = state.counts;
15
+ const tasks = state.tasks || [];
16
+ const active = tasks.filter(function (task) { return task.status === "active"; });
17
+ const recent = tasks.filter(function (task) { return task.status !== "archived"; }).slice().sort(byNewest).slice(0, 8);
13
18
  const page = h("div.page.overview");
14
- page.append(h("header.page-head", null,
19
+ page.append(overviewHead(state.counts, t, actions), metricStrip(state.counts, t));
20
+ page.append(h("div.card-stack", null,
21
+ inboxCard(state, t, actions),
22
+ taskListCard(t("overview.activeNow"), "pulse", active, t("overview.activeEmpty"), t, locale, actions),
23
+ taskListCard(t("overview.recent"), "history", recent, t("sidebar.empty"), t, locale, actions),
24
+ catalogCard(state, t, actions),
25
+ sessionCard(state, t, locale, actions)));
26
+ page.append(h("p.page-foot", null, t("overview.pageScope")));
27
+ container.append(page);
28
+ }
29
+
30
+ function overviewHead(counts, t, actions) {
31
+ return h("header.page-head", null,
15
32
  h("div.page-head-main", null,
16
33
  h("p.kicker", null, t("overview.kicker")),
17
34
  h("h1.page-title", null, t("overview.title")),
18
35
  h("p.page-sub", null, counts
19
- ? t("overview.sub").replace("{total}", String(counts.total)).replace("{active}", String(counts.active || 0))
36
+ ? fill(t("overview.sub"), { total: counts.total, active: counts.active || 0 })
20
37
  : t("loading.dashboard"))),
21
38
  h("div.page-head-actions", null,
22
- button(t("actions.operator"), { icon: "terminal", variant: "ghost", onClick: actions.openOperator }))));
39
+ button(t("actions.operator"), { icon: "terminal", variant: "ghost", onClick: actions.openOperator })));
40
+ }
23
41
 
24
- page.append(h("div.metric-strip", null,
42
+ function metricStrip(counts, t) {
43
+ return h("div.metric-strip", null,
25
44
  metricTile(t("metrics.inputs"), counts ? counts.openInputs : "—", { tone: counts && counts.openInputs ? "warn" : "" }),
26
45
  metricTile(t("metrics.active"), counts ? counts.active : "—", { tone: "info" }),
27
46
  metricTile(t("metrics.draft"), counts ? counts.draft : "—"),
28
47
  metricTile(t("metrics.completed"), counts ? counts.completed : "—", { tone: "ok" }),
29
- metricTile(t("metrics.total"), counts ? counts.total : "—")));
48
+ metricTile(t("metrics.total"), counts ? counts.total : "—"));
49
+ }
30
50
 
31
- // One reading column: the work path first (answer, in progress, recent),
32
- // then catalog-wide signals and the explicit Session read.
33
- // Open questions: the catalog names the affected Tasks, never the questions.
51
+ // Open questions: the catalog names the affected Tasks, never the questions.
52
+ function inboxCard(state, t, actions) {
53
+ const counts = state.counts;
34
54
  const inbox = card({ title: t("overview.inbox"), icon: "inbox", count: counts ? counts.openInputs : null });
35
55
  const items = state.attention || [];
36
56
  if (!items.length) {
37
57
  inbox.body.append(emptyState(counts && counts.openInputs > 0 ? t("overview.inboxElsewhere") : t("overview.inboxEmpty"), "check"));
38
- } else {
39
- inbox.body.append(h("div.row-list", null, items.slice(0, 8).map(function (item) {
40
- return h("button.list-row", { type: "button", onclick: function () { actions.select(item.taskId); } },
41
- dot("warn"), h("span.list-row-title", null, item.taskTitle), h("code.id", null, item.taskId),
42
- h("span.list-row-go", null, t("overview.answer"), icon("chevron", "icon-sm")));
43
- })));
58
+ return inbox;
44
59
  }
60
+ inbox.body.append(h("div.row-list", null, items.slice(0, 8).map(function (item) {
61
+ return h("button.list-row", { type: "button", onclick: function () { actions.select(item.taskId); } },
62
+ dot("warn"), h("span.list-row-title", null, item.taskTitle), h("code.id", null, item.taskId),
63
+ h("span.list-row-go", null, t("overview.answer"), icon("chevron", "icon-sm")));
64
+ })));
65
+ return inbox;
66
+ }
45
67
 
68
+ function catalogCard(state, t, actions) {
46
69
  const catalog = card({ title: t("overview.catalog"), icon: "broadcast", hint: t("overview.catalogHint") });
47
- if (state.catalogAttention) {
48
- catalog.body.append(h("div.row-list", null, ATTENTION_KINDS.map(function (kind) {
49
- const value = state.catalogAttention[kind];
50
- if (!value) return null;
51
- return h("button.list-row", {
52
- type: "button", disabled: value.taskCount === 0,
53
- "aria-pressed": String(state.attentionFilter === kind),
54
- onclick: function () { actions.filterAttention(kind); }
55
- },
56
- h("span.list-row-title", null, t("catalog." + kind)),
57
- h("span.list-row-count", null, String(value.count)),
58
- h("span.faint", null, value.taskCount + " " + t("overview.tasks")));
59
- })));
60
- catalog.body.append(note(t("overview.signalsHelp")));
61
- } else catalog.body.append(emptyState(t("loading.dashboard")));
62
-
63
- const active = (state.tasks || []).filter(function (task) { return task.status === "active"; });
64
- const recent = (state.tasks || []).filter(function (task) { return task.status !== "archived"; }).slice().sort(byNewest).slice(0, 8);
65
- page.append(h("div.card-stack", null,
66
- inbox,
67
- taskListCard(t("overview.activeNow"), "pulse", active, t("overview.activeEmpty"), t, locale, actions),
68
- taskListCard(t("overview.recent"), "history", recent, t("sidebar.empty"), t, locale, actions),
69
- catalog,
70
- sessionCard(state, t, locale, actions)));
71
- page.append(h("p.page-foot", null, t("overview.pageScope")));
72
- container.append(page);
70
+ if (!state.catalogAttention) { catalog.body.append(emptyState(t("loading.dashboard"))); return catalog; }
71
+ catalog.body.append(h("div.row-list", null, ATTENTION_KINDS.map(function (kind) {
72
+ const value = state.catalogAttention[kind];
73
+ if (!value) return null;
74
+ return h("button.list-row", {
75
+ type: "button", disabled: value.taskCount === 0,
76
+ "aria-pressed": String(state.attentionFilter === kind),
77
+ onclick: function () { actions.filterAttention(kind); }
78
+ },
79
+ h("span.list-row-title", null, t("catalog." + kind)),
80
+ h("span.list-row-count", null, String(value.count)),
81
+ h("span.faint", null, value.taskCount + " " + t("overview.tasks")));
82
+ })));
83
+ catalog.body.append(note(t("overview.signalsHelp")));
84
+ return catalog;
73
85
  }
74
86
 
75
87
  function taskListCard(title, iconName, tasks, empty, t, locale, actions) {
@@ -102,9 +114,7 @@ function sessionCard(state, t, locale, actions) {
102
114
  }
103
115
  element.body.append(h("p.faint.small", null, t("overview.readAt") + " " + formatDateTime(overview.readAt, locale)));
104
116
  element.body.append(h("div.row-list", null, overview.tasks.map(function (task) {
105
- const chips = SESSION_GROUPS.filter(function (group) { return task.counts && task.counts[group]; }).map(function (group) {
106
- return badge(task.counts[group] + " " + t("session." + group), tone("session", group), { dot: true });
107
- });
117
+ const chips = sessionBadges(task.counts, t);
108
118
  return h("button.list-row.list-row-wrap", { type: "button", onclick: function () { actions.select(task.taskId); } },
109
119
  h("span.list-row-title", null, task.title), h("code.id", null, task.taskId),
110
120
  h("span.chip-row", null, chips.length ? chips : h("span.faint", null, t("session.noneRecorded"))));
@@ -1,8 +1,10 @@
1
1
  export const SIDEBAR_SCRIPT = String.raw `
2
2
  // Task index: attention shortcuts, status tabs and the grouped task list.
3
- import { h, icon, clear } from "/assets/js/dom.js";
4
- import { relativeTime } from "/assets/js/format.js";
5
- import { dot, tone, label } from "/assets/js/components.js";
3
+ import { h, icon, clear } from "/assets/js/lib/dom.js";
4
+ import { relativeTime } from "/assets/js/lib/format.js";
5
+ import { dot } from "/assets/js/ui/primitives.js";
6
+ import { revealTab, scrollableTabRow, syncTabsOverflow } from "/assets/js/ui/controls.js";
7
+ import { label, tone } from "/assets/js/domain/vocab.js";
6
8
 
7
9
  export const STATUS_FILTERS = ["all", "active", "draft", "completed", "cancelled", "archived"];
8
10
  export const ATTENTION_KINDS = ["openInputs", "unknownOperations", "pendingOperations", "executionSignals"];
@@ -29,56 +31,13 @@ export function renderAttentionBar(container, state, t, onPick) {
29
31
  })));
30
32
  }
31
33
 
34
+ // --- Status tabs -------------------------------------------------------------
32
35
  // The tab row is created once and updated in place so its horizontal scroll
33
- // position survives every refresh. When the sidebar is too narrow for every
34
- // tab, the clipped edge fades out, the wheel scrolls the row sideways and a
35
- // newly selected tab is scrolled into view.
36
- function syncTabsOverflow(row) {
37
- const max = row.scrollWidth - row.clientWidth;
38
- const before = row.scrollLeft > 1;
39
- const after = row.scrollLeft < max - 1;
40
- row.dataset.fade = before && after ? "both" : after ? "end" : before ? "start" : "none";
41
- }
42
-
43
- function revealTab(row, tab) {
44
- const margin = 28;
45
- const left = tab.offsetLeft - margin;
46
- const right = tab.offsetLeft + tab.offsetWidth + margin;
47
- if (left < row.scrollLeft) row.scrollLeft = Math.max(0, left);
48
- else if (right > row.scrollLeft + row.clientWidth) row.scrollLeft = right - row.clientWidth;
49
- }
50
-
36
+ // position survives every refresh; a newly selected tab scrolls into view.
51
37
  export function renderFilters(container, state, t, onFilter) {
52
- const counts = state.counts || {};
53
- let row = container.querySelector(":scope > .tabs-row");
54
- if (!row) {
55
- row = h("div.tabs-row");
56
- STATUS_FILTERS.forEach(function (status) {
57
- row.append(h("button.status-tab", { type: "button", dataset: { status }, onclick: function () { onFilter(status); } },
58
- h("span.status-tab-label"), h("span.status-tab-count")));
59
- });
60
- container.append(row);
61
- row.addEventListener("scroll", function () { syncTabsOverflow(row); }, { passive: true });
62
- row.addEventListener("wheel", function (event) {
63
- if (Math.abs(event.deltaY) <= Math.abs(event.deltaX) || row.scrollWidth <= row.clientWidth) return;
64
- event.preventDefault();
65
- row.scrollLeft += event.deltaY;
66
- }, { passive: false });
67
- new ResizeObserver(function () { syncTabsOverflow(row); }).observe(row);
68
- }
38
+ const row = container.querySelector(":scope > .tabs-row") || statusRow(container, onFilter);
69
39
  STATUS_FILTERS.forEach(function (status) {
70
- const tab = row.querySelector('[data-status="' + status + '"]');
71
- tab.setAttribute("aria-pressed", String(state.filter === status));
72
- tab.querySelector(".status-tab-label").textContent = status === "all"
73
- ? t(state.catalogAll ? "filter.everything" : "filter.unarchived") : label(t, "status", status);
74
- const count = status === "all"
75
- ? (counts.total === undefined ? undefined : counts.total - (state.catalogAll ? 0 : counts.archived || 0))
76
- : counts[status];
77
- const badge = tab.querySelector(".status-tab-count");
78
- const hide = count === undefined || count === null
79
- || (status === "archived" && state.catalogScope && state.catalogScope.archived === "excluded");
80
- badge.hidden = hide;
81
- if (!hide) badge.textContent = String(count);
40
+ updateStatusTab(row.querySelector('[data-status="' + status + '"]'), status, state, t);
82
41
  });
83
42
  if (row.dataset.selected !== state.filter) {
84
43
  row.dataset.selected = state.filter;
@@ -87,6 +46,34 @@ export function renderFilters(container, state, t, onFilter) {
87
46
  syncTabsOverflow(row);
88
47
  }
89
48
 
49
+ function statusRow(container, onFilter) {
50
+ const row = h("div.tabs-row");
51
+ STATUS_FILTERS.forEach(function (status) {
52
+ row.append(h("button.status-tab", { type: "button", dataset: { status }, onclick: function () { onFilter(status); } },
53
+ h("span.status-tab-label"), h("span.status-tab-count")));
54
+ });
55
+ container.append(row);
56
+ return scrollableTabRow(row);
57
+ }
58
+
59
+ function updateStatusTab(tab, status, state, t) {
60
+ const counts = state.counts || {};
61
+ tab.setAttribute("aria-pressed", String(state.filter === status));
62
+ tab.querySelector(".status-tab-label").textContent = status === "all"
63
+ ? t(state.catalogAll ? "filter.everything" : "filter.unarchived") : label(t, "status", status);
64
+ const count = status === "all"
65
+ ? (counts.total === undefined ? undefined : counts.total - (state.catalogAll ? 0 : counts.archived || 0))
66
+ : counts[status];
67
+ const badge = tab.querySelector(".status-tab-count");
68
+ const hide = count === undefined || count === null
69
+ || (status === "archived" && state.catalogScope && state.catalogScope.archived === "excluded");
70
+ badge.hidden = hide;
71
+ if (!hide) badge.textContent = String(count);
72
+ }
73
+
74
+ // --- Task list ---------------------------------------------------------------
75
+ const GROUP_ORDER = ["attention", "active", "draft", "completed", "cancelled", "archived"];
76
+
90
77
  function groupOf(task) {
91
78
  const attention = task.attention || {};
92
79
  if (attention.openInputs > 0 || attention.unknownOperations > 0) return "attention";
@@ -94,10 +81,7 @@ function groupOf(task) {
94
81
  return task.status;
95
82
  }
96
83
 
97
- const GROUP_ORDER = ["attention", "active", "draft", "completed", "cancelled", "archived"];
98
-
99
- export function renderTasks(container, state, t, locale, onSelect) {
100
- clear(container);
84
+ function groupTasks(state) {
101
85
  const groups = {};
102
86
  (state.tasks || []).forEach(function (task) {
103
87
  if (state.filter === "all" && !state.catalogAll && task.status === "archived") return;
@@ -105,6 +89,12 @@ export function renderTasks(container, state, t, locale, onSelect) {
105
89
  const group = groupOf(task);
106
90
  (groups[group] = groups[group] || []).push(task);
107
91
  });
92
+ return groups;
93
+ }
94
+
95
+ export function renderTasks(container, state, t, locale, onSelect) {
96
+ clear(container);
97
+ const groups = groupTasks(state);
108
98
  const present = GROUP_ORDER.filter(function (group) { return groups[group] && groups[group].length; });
109
99
  if (!present.length) {
110
100
  container.append(h("div.list-empty", null, icon("search"), h("p", null, t(state.query || state.attentionFilter ? "sidebar.noMatch" : "sidebar.empty"))));
@@ -118,13 +108,8 @@ export function renderTasks(container, state, t, locale, onSelect) {
118
108
  }
119
109
 
120
110
  function taskRow(task, state, t, locale, onSelect) {
121
- const attention = task.attention || {};
122
111
  const counts = task.counts || {};
123
- const signals = h("span.task-signals");
124
- if (attention.openInputs > 0) signals.append(signal("inbox", attention.openInputs, "warn", t("catalog.openInputs")));
125
- if (attention.unknownOperations > 0) signals.append(signal("alert", attention.unknownOperations, "bad", t("catalog.unknownOperations")));
126
- if (counts.activeRuns > 0) signals.append(signal("pulse", counts.activeRuns, "info", t("sidebar.activeRuns")));
127
- const row = h("button.task-row", {
112
+ return h("button.task-row", {
128
113
  type: "button",
129
114
  dataset: { id: task.id },
130
115
  "aria-current": String(state.selected === task.id),
@@ -136,8 +121,17 @@ function taskRow(task, state, t, locale, onSelect) {
136
121
  h("span.task-line", null, h("span.task-name", null, task.title), h("time.task-time", { dateTime: task.updatedAt }, relativeTime(task.updatedAt, locale, t))),
137
122
  h("span.task-line.task-sub", null, h("code.task-id", null, task.id),
138
123
  counts.workItems > 0 ? h("span.task-count", { title: t("sidebar.workItems") }, icon("layers", "icon-sm"), String(counts.workItems)) : null,
139
- signals)));
140
- return row;
124
+ taskSignals(task, t))));
125
+ }
126
+
127
+ function taskSignals(task, t) {
128
+ const attention = task.attention || {};
129
+ const counts = task.counts || {};
130
+ const signals = h("span.task-signals");
131
+ if (attention.openInputs > 0) signals.append(signal("inbox", attention.openInputs, "warn", t("catalog.openInputs")));
132
+ if (attention.unknownOperations > 0) signals.append(signal("alert", attention.unknownOperations, "bad", t("catalog.unknownOperations")));
133
+ if (counts.activeRuns > 0) signals.append(signal("pulse", counts.activeRuns, "info", t("sidebar.activeRuns")));
134
+ return signals;
141
135
  }
142
136
 
143
137
  function signal(iconName, count, toneName, title) {
@@ -0,0 +1,152 @@
1
+ export const SKILLS_SCRIPT = String.raw `
2
+ import { h } from "/assets/js/lib/dom.js";
3
+ import { requestJson, submitMutation } from "/assets/js/lib/api.js";
4
+
5
+ export function mountSkills(root, t) {
6
+ if (!root) return;
7
+ const tr = (key, fallback) => t("skills.browser." + key, fallback);
8
+ const scope = h("select", null, h("option", { value: "global" }, tr("global", "Global Role")),
9
+ h("option", { value: "task" }, tr("task", "Task Role")));
10
+ const task = h("input", { placeholder: "task-123", disabled: true });
11
+ const roles = h("select");
12
+ const status = h("p", { role: "status", "aria-live": "polite" });
13
+ const bindings = h("div.settings-stack");
14
+ const frozen = h("div.settings-stack");
15
+ const query = h("input", { type: "search" });
16
+ const catalog = h("div.settings-stack");
17
+ const reader = h("div.settings-stack", { tabindex: "-1" });
18
+ const acknowledge = h("input", { type: "checkbox" });
19
+ const acknowledgement = h("label.settings-reset", null, acknowledge, tr("ack", "Keep any live global Session on its existing version."));
20
+ const save = h("button.btn.btn-primary", { type: "button", disabled: true }, tr("save", "Save bindings"));
21
+ const reload = h("button.btn", { type: "button" }, tr("reload", "Load / reload Role"));
22
+ const more = h("button.btn", { type: "button", hidden: true }, tr("more", "More Skills"));
23
+ const search = h("button.btn", { type: "button" }, tr("search", "Search"));
24
+ let state = null, draft = [], busy = false, uncertain = false, generation = 0, catalogGeneration = 0, readerGeneration = 0;
25
+ const selected = () => ({ scope: scope.value, role: roles.value, ...(scope.value === "task" ? { task: task.value.trim() } : {}) });
26
+ const params = object => new URLSearchParams(object).toString();
27
+ const label = (text, control) => h("label.field", null, h("span", null, text), control);
28
+ function dirty() { return !!state && JSON.stringify(draft) !== JSON.stringify(state.skills); }
29
+ function invalidate() {
30
+ generation++; state = null; draft = []; bindings.replaceChildren(); frozen.replaceChildren(); save.disabled = true;
31
+ scope.disabled = roles.disabled = false; task.disabled = scope.value !== "task";
32
+ acknowledgement.hidden = scope.value !== "global";
33
+ }
34
+ function renderBindings() {
35
+ // A draft remains attached to the exact Role baseline until saved or explicitly reloaded.
36
+ [scope, roles].forEach(c => { c.disabled = busy || dirty(); });
37
+ task.disabled = busy || dirty() || scope.value !== "task";
38
+ bindings.replaceChildren(h("h3", null, tr("configured", "Configured bindings")),
39
+ ...draft.map(id => h("div.settings-actions", null, h("code", null, id),
40
+ h("button.btn", { type: "button", disabled: busy || uncertain, onclick: () => { draft = draft.filter(s => s !== id); renderBindings(); } }, tr("remove", "Remove binding")),
41
+ h("button.btn", { type: "button", onclick: () => read({ id }) }, tr("readSource", "Read current source")))));
42
+ if (!draft.length) bindings.append(h("p", null, tr("none", "No additional bindings. Built-in Role Skills are loaded automatically.")));
43
+ save.disabled = busy || uncertain || !dirty();
44
+ }
45
+ function renderEvidence(data) {
46
+ frozen.replaceChildren(h("h3", null, tr("frozen", "Current Session · frozen evidence")));
47
+ if (!data.session) frozen.append(h("p", null, tr("noSession", "No current Session. No loaded version can be claimed.")));
48
+ else {
49
+ frozen.append(h("p.mono-line", null, data.session.nativeSessionId + " · " + data.session.status +
50
+ " · " + tr("revision", "launch / desired revision") + " " + data.session.sourceDesiredRevision + " / " + data.launchRevision));
51
+ if (!data.session.packages) frozen.append(h("p", null, tr("unrecorded", "This Session has no frozen package evidence. Current source is not a substitute.")));
52
+ else data.session.packages.forEach(pkg => frozen.append(h("div.settings-stack", null,
53
+ h("strong", null, pkg.id), h("code.mono-line", null, pkg.digest),
54
+ h("span.mono-line", null, pkg.source.kind + " · " + pkg.source.path),
55
+ h("button.btn", { type: "button", onclick: () => read({ ...data.target, id: pkg.id, digest: pkg.digest, frozen: "true" }) }, tr("readFrozen", "Read frozen package")))));
56
+ }
57
+ }
58
+ async function read(input) {
59
+ const current = ++readerGeneration;
60
+ reader.textContent = tr("loading", "Loading…");
61
+ try {
62
+ const data = await requestJson("/api/skills/file?" + params(input));
63
+ if (current !== readerGeneration) return;
64
+ const files = h("select", null, data.files.map(path => h("option", { value: path }, path)));
65
+ files.value = data.resource;
66
+ files.addEventListener("change", () => read({ ...input, resource: files.value }));
67
+ reader.replaceChildren(h("h3", null, data.id + " · " + data.resource),
68
+ h("p.mono-line", null, data.source.kind + " · " + data.source.path),
69
+ h("p.mono-line", null, tr("version", "Package version") + ": " + data.version),
70
+ h("p.mono-line", null, "SHA-256 (" + tr("file", "file") + "): " + data.fileDigest),
71
+ label(tr("resource", "Read a resource (never executed)"), files),
72
+ h("pre.settings-observation", { tabindex: "0" }, data.content));
73
+ reader.focus({ preventScroll: true }); reader.scrollIntoView({ block: "start" });
74
+ } catch (error) {
75
+ if (current === readerGeneration) { reader.textContent = error.message; reader.scrollIntoView({ block: "nearest" }); }
76
+ }
77
+ }
78
+ async function loadRole() {
79
+ if (busy) return;
80
+ if (dirty() && !window.confirm(tr("discard", "Discard unsaved Skill bindings and reload?"))) return;
81
+ const current = ++generation;
82
+ status.textContent = tr("loading", "Loading…");
83
+ try {
84
+ const roleNames = await requestJson("/api/skills/roles?" + params({ scope: scope.value, ...(scope.value === "task" ? { task: task.value.trim() } : {}) }));
85
+ if (current !== generation) return;
86
+ const previous = roles.value;
87
+ roles.replaceChildren(...roleNames.map(r => h("option", { value: r.name }, r.name)));
88
+ if (roleNames.some(r => r.name === previous)) roles.value = previous;
89
+ if (!roles.value) { invalidate(); status.textContent = tr("noRoles", "No Roles in this scope."); return; }
90
+ const data = await requestJson("/api/skills/role?" + params(selected()));
91
+ if (current !== generation) return;
92
+ state = data; draft = [...data.skills]; uncertain = false; renderBindings();
93
+ status.textContent = tr("nextLaunch", "Bindings apply to a subsequent launch; saving does not load a Skill or restart a Session. Removing a binding never deletes its package.");
94
+ renderEvidence(data);
95
+ } catch (error) { if (current === generation) { invalidate(); status.textContent = error.message; } }
96
+ }
97
+ async function loadCatalog(cursor) {
98
+ const current = ++catalogGeneration;
99
+ more.disabled = search.disabled = true;
100
+ try {
101
+ const data = await requestJson("/api/skills/catalog?" + params({ q: query.value.trim(), cursor: cursor || "0" }));
102
+ if (current !== catalogGeneration) return;
103
+ if (!cursor) catalog.replaceChildren();
104
+ if (!data.items.length) catalog.append(h("p", null, tr("noMatches", "No matching Skills.")));
105
+ data.items.forEach(item => catalog.append(h("article.settings-card", null,
106
+ h("h3", null, item.id), h("p", null, item.description || item.error),
107
+ h("small.mono-line", null, item.source.kind + " · " + item.source.path),
108
+ h("div.settings-actions", null,
109
+ h("button.btn", { type: "button", onclick: () => read({ id: item.id }) }, tr("readSource", "Read current source")),
110
+ h("button.btn", { type: "button", disabled: !!item.error, onclick: () => {
111
+ if (!state || busy || uncertain) { status.textContent = tr("choose", "Load an explicit Role before editing bindings."); return; }
112
+ if (!draft.includes(item.id)) draft.push(item.id);
113
+ renderBindings(); status.textContent = tr("unsaved", "Unsaved bindings. Review the selected Role and save.");
114
+ bindings.scrollIntoView({ block: "nearest" });
115
+ } }, tr("add", "Add to selected Role"))))));
116
+ more.hidden = !data.nextCursor; more.onclick = () => loadCatalog(data.nextCursor);
117
+ } catch (error) { catalog.replaceChildren(h("p", null, error.message)); }
118
+ finally { if (current === catalogGeneration) more.disabled = search.disabled = false; }
119
+ }
120
+ save.addEventListener("click", async () => {
121
+ if (!state || busy || uncertain || !dirty()) return;
122
+ busy = true; renderBindings();
123
+ [scope, task, roles, reload].forEach(c => { c.disabled = true; });
124
+ try {
125
+ const result = await submitMutation("skills/" + JSON.stringify(state.target), "/api/skills/role",
126
+ { target: state.target, revision: state.revision, skills: draft, acknowledgeLive: acknowledge.checked });
127
+ state = result.role; draft = [...state.skills];
128
+ renderEvidence(state);
129
+ status.textContent = tr("saved", "Bindings saved. Existing frozen Sessions and Runs are unchanged; no package was deleted.");
130
+ } catch (error) {
131
+ uncertain = error.disposition !== "not-submitted";
132
+ status.textContent = error.message + (uncertain ? "\n" + tr("unknown", "Save outcome unknown. Reload and compare before making another change.") : "");
133
+ } finally {
134
+ busy = false; [scope, roles, reload].forEach(c => { c.disabled = false; }); task.disabled = scope.value !== "task"; renderBindings();
135
+ }
136
+ });
137
+ scope.addEventListener("change", () => { invalidate(); task.disabled = scope.value !== "task"; });
138
+ task.addEventListener("input", invalidate);
139
+ roles.addEventListener("change", () => { invalidate(); void loadRole(); });
140
+ reload.addEventListener("click", loadRole);
141
+ search.addEventListener("click", () => loadCatalog());
142
+ query.addEventListener("keydown", event => { if (event.key === "Enter") { event.preventDefault(); void loadCatalog(); } });
143
+ window.addEventListener("beforeunload", event => { if (dirty()) { event.preventDefault(); event.returnValue = ""; } });
144
+ root.replaceChildren(h("h2", null, tr("title", "Skills · browse & bind")),
145
+ h("p", null, tr("intro", "Read existing packages and choose additional bindings for one explicit Role. Scripts are displayed as data, never run.")),
146
+ h("div.settings-grid", null, label(tr("scope", "Scope"), scope), label(tr("taskId", "Task ID"), task), label("Role", roles)),
147
+ h("div.settings-actions", null, reload), status, bindings, acknowledgement,
148
+ h("div.settings-actions", null, save), frozen, label(tr("query", "Find an existing Skill by name or purpose"), query),
149
+ h("div.settings-actions", null, search), catalog, more, reader);
150
+ void loadRole(); void loadCatalog();
151
+ }
152
+ `;
@@ -0,0 +1,36 @@
1
+ export const TASK_ACTIONS_SCRIPT = String.raw `
2
+ import { h } from "/assets/js/lib/dom.js";
3
+ import { button, note } from "/assets/js/ui/primitives.js";
4
+ import { card } from "/assets/js/ui/containers.js";
5
+ import { receiptLine, setReceipt, submitWrite } from "/assets/js/ui/forms.js";
6
+
7
+ export function taskActions(task, t, ctx) {
8
+ const element = card({ title: t("workbench.next"), icon: "chat" });
9
+ element.body.append(button(t("workbench.continue"), { variant: "primary", onClick: function () { ctx.openConversation(); } }));
10
+ const action = task.status === "draft" ? "activate"
11
+ : ["completed", "cancelled"].includes(task.status) ? "archive" : null;
12
+ if (!action) return element;
13
+ const activation = task.activationRequest;
14
+ const blocked = action === "activate" && (task.executionGate.state !== "enabled"
15
+ || activation && ["pending", "failed"].includes(activation.disposition));
16
+ const receipt = receiptLine(blocked ? (activation && activation.outcome || t("workbench.activationBlocked")) : t("receipt.notSubmitted"));
17
+ const control = button(t("workbench." + action), { disabled: !!blocked, onClick: function () {
18
+ if (!window.confirm(t("workbench." + action + "Confirm"))) return;
19
+ submitWrite({
20
+ control: control, receipt: receipt, form: element.body, t: t,
21
+ send: function (requestId) { return ctx.taskAction(task.id, action, requestId); },
22
+ saved: function (value) {
23
+ const result = value.result;
24
+ const text = action === "activate" ? result.operationRef + " · " + result.request.disposition
25
+ : result.task.id + " · " + result.task.status;
26
+ setReceipt(receipt, text, "ok");
27
+ return true;
28
+ },
29
+ rejectedKey: "receipt.notSubmittedBecause", unknownKey: "workbench.unknown",
30
+ afterWrite: ctx.afterWrite
31
+ });
32
+ } });
33
+ element.body.append(control, note(t("workbench." + action + "Hint")), receipt);
34
+ return element;
35
+ }
36
+ `;