@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,185 @@
1
+ export const TASK_DELIVERY_SCRIPT = String.raw `
2
+ // Task › Delivery: work items, reviews, integration and remote delivery,
3
+ // saved files and retained workspaces. Opening the tab reads the original
4
+ // evidence once per Context cursor; reading never accepts, publishes,
5
+ // executes or cleans anything.
6
+ import { h, clear } from "/assets/js/lib/dom.js";
7
+ import { button, dot, emptyState, note } from "/assets/js/ui/primitives.js";
8
+ import { listCard, loadedNote, moreButton, setCardCount } from "/assets/js/ui/containers.js";
9
+ import { richText } from "/assets/js/ui/text.js";
10
+ import { statusBadge, tone } from "/assets/js/domain/vocab.js";
11
+ import { entriesOf, exactCache, familyState, loadFamily, summaryFields, totalOf, valuesOf } from "/assets/js/domain/context.js";
12
+ import { exactRow, lazyRow } from "/assets/js/domain/records.js";
13
+ import { workItemBody, workItemCard } from "/assets/js/domain/work.js";
14
+ import { reviewCard } from "/assets/js/domain/runs.js";
15
+ import { newest, readRemoteDelivery, resultsSection, workspacesSection } from "/assets/js/views/task/deliveryResults.js";
16
+ import { filesCard } from "/assets/js/views/task/files.js";
17
+
18
+ export function renderDelivery(panel, data, t, locale, ctx) {
19
+ const view = data.viewState;
20
+ exactCache(view);
21
+ view.openRows = view.openRows || {};
22
+ view.reports = view.reports || {};
23
+ view.panels = view.panels || {};
24
+ view.panels.delivery = panel;
25
+ const scope = { data: data, view: view, core: data.core, task: data.task, t: t, locale: locale, ctx: ctx, refresh: refresh };
26
+ const work = workSection(scope);
27
+ const reviews = reviewsSection(scope);
28
+ const results = resultsSection(scope);
29
+ const workspaces = workspacesSection(scope);
30
+ panel.append(work.element, reviews.element, results.element, filesCard(data, t, locale, ctx), workspaces.element);
31
+ panel.redraw = function () { work.draw(); reviews.draw(); results.draw(); workspaces.draw(); };
32
+ panel.redraw();
33
+
34
+ // Reads outlive one render: they redraw whichever Delivery panel is current
35
+ // and re-read when the Context cursor moved while they were in flight.
36
+ function refresh() {
37
+ const current = view.panels.delivery;
38
+ if (!current || !current.isConnected) return;
39
+ current.redraw();
40
+ if (!current.hidden) current.onShow();
41
+ }
42
+ panel.onShow = function () {
43
+ readEvidence(scope);
44
+ readRemoteDelivery(scope);
45
+ const key = scope.core.coreCursor;
46
+ const family = familyState(view, "work-item");
47
+ if (family.key !== key && family.errorKey !== key && !family.pending) loadFamily(view, data.task.id, "work-item", key, ctx, { limit: 40 }).then(refresh);
48
+ };
49
+ }
50
+
51
+ // Original evidence (reviews, integrations, workspaces), once per cursor.
52
+ function readEvidence(scope) {
53
+ const view = scope.view;
54
+ const key = scope.core.coreCursor;
55
+ const previous = view.evidence || {};
56
+ if (previous.key === key || previous.errorKey === key || previous.pending) return;
57
+ view.evidence = { key: previous.key, value: previous.value, readAt: previous.readAt, error: null, pending: true };
58
+ scope.ctx.evidence(scope.task.id).then(function (value) {
59
+ view.evidence = { key: key, value: value, readAt: new Date().toISOString(), error: null, pending: false };
60
+ }, function (error) {
61
+ view.evidence = { key: previous.key, value: previous.value, readAt: previous.readAt, error: error.message, errorKey: key, pending: false };
62
+ }).then(scope.refresh);
63
+ }
64
+
65
+ // --- Work items ------------------------------------------------------------
66
+ function workSection(scope) {
67
+ const t = scope.t;
68
+ const work = listCard({ id: "detail-work", title: t("work.title"), icon: "layers", count: totalOf(scope.core, "work-item") || null, hint: t("work.openHint") });
69
+ return { element: work, draw: function () { drawWork(work, scope); } };
70
+ }
71
+
72
+ // Open items from the snapshot first, then open items only the family pages
73
+ // list, then the settled ones.
74
+ function drawWork(work, scope) {
75
+ const t = scope.t;
76
+ clear(work.list);
77
+ const family = familyState(scope.view, "work-item");
78
+ const entries = entriesOf(scope.core, "work-item");
79
+ const titles = workTitles(family, scope.core);
80
+ const shown = new Set(entries.map(function (entry) { return entry.ref.refId; }));
81
+ const extraOpen = family.items.filter(function (item) { return item.status === "open" && !shown.has(item.ref.refId); });
82
+ entries.forEach(function (entry) {
83
+ work.list.append(exactRow(entry, scope.task.id, t, scope.ctx, scope.view.exact, function (value) { return workItemCard(value, t, scope.locale, titles); }));
84
+ });
85
+ extraOpen.forEach(function (item) { work.list.append(workRow(scope, item, titles)); });
86
+ const loaded = family.key && family.pages;
87
+ if (loaded) setCardCount(work, family.total || null);
88
+ if (!entries.length && !extraOpen.length) {
89
+ work.list.append(emptyState(loaded && family.total === 0 ? t("work.none") : t("work.noneOpen"), "layers"));
90
+ }
91
+ if (family.error) work.list.append(note(t("records.unavailable") + " " + family.error, "bad"));
92
+ if (!loaded) {
93
+ if (family.pending) work.list.append(h("p.row-label", null, t("work.reading")));
94
+ return;
95
+ }
96
+ drawFinished(work, scope, family, titles);
97
+ }
98
+
99
+ function workTitles(family, core) {
100
+ const titles = {};
101
+ family.items.forEach(function (item) { const fields = summaryFields(item.summary); if (fields.title) titles[item.ref.refId] = fields.title; });
102
+ valuesOf(core, "work-item").forEach(function (item) { titles[item.id] = item.title; });
103
+ return titles;
104
+ }
105
+
106
+ function drawFinished(work, scope, family, titles) {
107
+ const finished = family.items.filter(function (item) { return item.status !== "open"; });
108
+ const finishedTotal = Math.max(finished.length, (family.total || 0) - totalOf(scope.core, "work-item"));
109
+ if (!finishedTotal) return;
110
+ const group = h("div.row-stack.is-finished", null, h("p.row-label", null, scope.t("work.finished") + " · " + finishedTotal));
111
+ finished.forEach(function (item) { group.append(workRow(scope, item, titles)); });
112
+ work.list.append(group);
113
+ if (!family.nextCursor) return;
114
+ work.list.append(h("div.list-foot", null, loadedNote(scope.t, family.items.length, family.total), moreButton(scope.t, function () {
115
+ loadFamily(scope.view, scope.task.id, "work-item", family.key, scope.ctx, { more: true, limit: 40 }).then(scope.refresh);
116
+ })));
117
+ }
118
+
119
+ function workRow(scope, item, titles) {
120
+ const t = scope.t;
121
+ const fields = summaryFields(item.summary);
122
+ return lazyRow(item, scope.task.id, t, scope.ctx, {
123
+ cache: scope.view.exact, openRows: scope.view.openRows,
124
+ head: [dot(tone("work", item.status)), h("span.lazy-title", null, fields.title || item.ref.refId),
125
+ item.assignee ? h("span.faint.small.lazy-meta", null, item.assignee) : null,
126
+ statusBadge(t, "work", "work", item.status)],
127
+ render: function (value) { return workItemBody(value, t, scope.locale, titles); }
128
+ });
129
+ }
130
+
131
+ // --- Reviews ---------------------------------------------------------------
132
+ function reviewsSection(scope) {
133
+ const reviews = listCard({ id: "detail-reviews", title: scope.t("reviews.title"), icon: "eye", hint: scope.t("reviews.hint") });
134
+ return { element: reviews, draw: function () { drawReviews(reviews, scope); } };
135
+ }
136
+
137
+ function drawReviews(reviews, scope) {
138
+ const view = scope.view;
139
+ clear(reviews.list);
140
+ const evidence = view.evidence && view.evidence.key && view.evidence.value;
141
+ if (evidence) {
142
+ const rounds = evidence.reviews.slice().sort(newest);
143
+ setCardCount(reviews, rounds.length || null);
144
+ if (!rounds.length) reviews.list.append(emptyState(scope.t("reviews.none"), "eye"));
145
+ rounds.forEach(function (round) { reviews.list.append(reviewRow(scope, round)); });
146
+ return;
147
+ }
148
+ const entries = entriesOf(scope.core, "review-round");
149
+ setCardCount(reviews, entries.length || null);
150
+ entries.forEach(function (entry) {
151
+ reviews.list.append(exactRow(entry, scope.task.id, scope.t, scope.ctx, view.exact, function (value) { return reviewRow(scope, value); }));
152
+ });
153
+ if (view.evidence && view.evidence.error) reviews.list.append(note(scope.t("records.unavailable") + " " + view.evidence.error, "bad"));
154
+ else reviews.list.append(h("p.row-label", null, scope.t("evidence.loading")));
155
+ }
156
+
157
+ function reviewRow(scope, round) {
158
+ const t = scope.t;
159
+ const element = reviewCard(round, t, scope.locale);
160
+ if (!round.reviewerRunId) return element;
161
+ const holder = h("div.stack");
162
+ const cached = scope.view.reports[round.id];
163
+ if (cached) { holder.append(richText(t("evidence.report"), cached, t, { threshold: 480 })); element.append(holder); return element; }
164
+ element.append(h("div.record-actions", null, reportButton(scope, round, holder)), holder);
165
+ return element;
166
+ }
167
+
168
+ // The reviewer's report is read from its execution record on request and
169
+ // kept in view state for later renders.
170
+ function reportButton(scope, round, holder) {
171
+ const t = scope.t;
172
+ const report = button(t("evidence.readReport"), { icon: "eye", variant: "link" });
173
+ report.addEventListener("click", async function () {
174
+ report.disabled = true;
175
+ try {
176
+ const source = await scope.ctx.inspect(scope.task.id, { store: "run", refId: round.reviewerRunId });
177
+ const output = (source.result && source.result.output) || (source.value && source.value.result && source.value.result.output) || t("evidence.noReport");
178
+ scope.view.reports[round.id] = output;
179
+ holder.append(richText(t("evidence.report"), output, t, { threshold: 480 }));
180
+ report.remove();
181
+ } catch (error) { holder.append(note(error.message, "bad")); report.disabled = false; }
182
+ });
183
+ return report;
184
+ }
185
+ `;
@@ -0,0 +1,159 @@
1
+ export const TASK_DELIVERY_RESULTS_SCRIPT = String.raw `
2
+ // Task › Delivery results: remote delivery per project, integration attempts
3
+ // with their checks, and retained workspaces with the cleanup command. The
4
+ // original evidence (view.evidence) replaces snapshot samples once read.
5
+ import { h, icon, clear } from "/assets/js/lib/dom.js";
6
+ import { fill, formatClock } from "/assets/js/lib/format.js";
7
+ import { badge, chip, codeBlock, dot, externalLink, mono, note, safeUrl, timeTag } from "/assets/js/ui/primitives.js";
8
+ import { card, cardDisclosure } from "/assets/js/ui/containers.js";
9
+ import { label, statusBadge, tone } from "/assets/js/domain/vocab.js";
10
+ import { cachedExact, entriesOf, readExact, totalOf, valuesOf } from "/assets/js/domain/context.js";
11
+
12
+ const DELIVERY_TONES = { merged: "ok", partial: "warn", unverified: "warn", uncovered: "warn", pending: "info", none: "idle", unavailable: "idle" };
13
+
14
+ export function newest(left, right) {
15
+ return Date.parse(right.createdAt || 0) - Date.parse(left.createdAt || 0);
16
+ }
17
+
18
+ // --- Integration and remote delivery ------------------------------------------
19
+ export function resultsSection(scope) {
20
+ const t = scope.t;
21
+ const delivery = card({ id: "detail-results", title: t("delivery.title"), icon: "merge", hint: t("delivery.hint") });
22
+ const body = h("div.stack.delivery-stack");
23
+ delivery.body.append(body);
24
+ return { element: delivery, draw: function () { drawResults(body, scope); } };
25
+ }
26
+
27
+ // The remote delivery fact: the snapshot value, its exact read, or else the
28
+ // runtime observation.
29
+ function remoteDelivery(scope) {
30
+ const entry = entriesOf(scope.core, "remote-delivery")[0];
31
+ if (entry && !entry.omitted) return entry.value;
32
+ const hit = entry && cachedExact(scope.view.exact, entry);
33
+ if (hit) return hit.value;
34
+ return (scope.data.runtime && scope.data.runtime.remoteDelivery) || null;
35
+ }
36
+
37
+ // Read a withheld remote delivery value once per digest, unless the
38
+ // observation already reports it.
39
+ export function readRemoteDelivery(scope) {
40
+ const entry = entriesOf(scope.core, "remote-delivery")[0];
41
+ if (!entry || !entry.omitted || (scope.data.runtime && scope.data.runtime.remoteDelivery)) return;
42
+ if (!cachedExact(scope.view.exact, entry)) readExact(entry, scope.task.id, scope.ctx, scope.view.exact).then(scope.refresh, function () {});
43
+ }
44
+
45
+ function drawResults(body, scope) {
46
+ clear(body);
47
+ const remote = remoteDelivery(scope);
48
+ if (remote) drawRemote(body, remote, scope.t);
49
+ else body.append(note(scope.t("evidence.observationUnavailable")));
50
+ drawIntegrations(body, scope);
51
+ }
52
+
53
+ function drawRemote(body, remote, t) {
54
+ body.append(h("h4.sub-head", null, icon("package", "icon-sm"), h("span", null, t("evidence.remote")),
55
+ badge(t("remoteDelivery." + remote.status, remote.status), DELIVERY_TONES[remote.status] || "idle", { dot: true })));
56
+ if (remote.codeProjectCount) {
57
+ body.append(h("p.faint.small", null, fill(t("delivery.counts"), {
58
+ merged: remote.mergedProjectCount, verified: remote.verifiedProjectCount, total: remote.codeProjectCount
59
+ })));
60
+ }
61
+ (remote.projects || []).forEach(function (project) { body.append(projectRow(project, t)); });
62
+ if (!(remote.projects || []).length) body.append(note(t("delivery.noProjects")));
63
+ }
64
+
65
+ function drawIntegrations(body, scope) {
66
+ const t = scope.t;
67
+ const state = scope.view.evidence || {};
68
+ const evidence = state.key && state.value;
69
+ body.append(h("h4.sub-head", null, icon("merge", "icon-sm"), h("span", null, t("evidence.integrations")),
70
+ evidence ? h("span.count", null, String(evidence.integrations.length)) : null));
71
+ if (!evidence) {
72
+ body.append(state.error ? note(t("evidence.originalUnavailable") + " " + state.error, "bad") : h("p.faint.small", null, t("evidence.loading")));
73
+ return;
74
+ }
75
+ if (!evidence.integrations.length) body.append(note(t("evidence.noIntegrations")));
76
+ const rows = h("div.row-stack.boxed");
77
+ evidence.integrations.slice().sort(newest).forEach(function (attempt) { rows.append(integrationRow(attempt, t, scope.locale)); });
78
+ if (rows.childNodes.length) body.append(rows);
79
+ body.append(h("p.faint.small", null, t("delivery.readAt") + " " + formatClock(state.readAt, scope.locale)
80
+ + (state.pending ? " · " + t("evidence.loading") : "")));
81
+ }
82
+
83
+ function projectRow(project, t) {
84
+ const publication = project.publication;
85
+ const href = publication && publication.externalUrl && safeUrl(publication.externalUrl);
86
+ const element = h("article.record.delivery-project");
87
+ element.append(h("header.record-head", null, icon("folder", "icon-sm"), h("strong", null, project.directory || project.projectId),
88
+ project.coverage ? chip(label(t, "coverage", project.coverage)) : null,
89
+ h("span.spacer"),
90
+ publication ? (href
91
+ ? externalLink(href, label(t, "publication", publication.externalKind) + " #" + publication.externalId)
92
+ : mono(publication.externalKind + " " + publication.externalId)) : null));
93
+ if (publication && publication.title) element.append(h("p.small", null, publication.title));
94
+ if (project.reason) element.append(h("p.faint.small", null, project.reason));
95
+ element.append(h("div.meta-line", null,
96
+ h("span", null, t("evidence.expectedCommit") + " "), mono(project.expectedLocalCommit ? project.expectedLocalCommit.slice(0, 12) : t("common.unknown")),
97
+ project.state ? chip(label(t, "publicationState", project.state)) : null,
98
+ project.verification ? chip(t("evidence.verification") + " · " + label(t, "verification", project.verification)) : null,
99
+ project.adoption ? h("span", null, t("evidence.adoption") + " ", mono(project.adoption.id)) : null));
100
+ return element;
101
+ }
102
+
103
+ function integrationRow(attempt, t, locale) {
104
+ const source = attempt.source || {};
105
+ const element = h("article.record.integration-row");
106
+ element.dataset.status = attempt.status;
107
+ element.append(h("header.record-head", null, dot(tone("integration", attempt.status)), mono(attempt.id),
108
+ chip(label(t, "integration.source", source.kind) + (source.workItemId ? " · " + source.workItemId : "")),
109
+ source.strategy ? chip(source.strategy) : null,
110
+ h("span.spacer"),
111
+ statusBadge(t, "integration", "integration", attempt.status)));
112
+ if (attempt.summary) element.append(h("p.small", null, attempt.summary));
113
+ element.append(h("div.meta-line", null,
114
+ attempt.projectId ? h("span", null, attempt.projectId) : null,
115
+ attempt.targetRef ? mono(attempt.targetRef) : null,
116
+ h("span", null, t("evidence.candidate") + " "), mono(attempt.candidateCommit ? attempt.candidateCommit.slice(0, 12) : t("evidence.noCandidate")),
117
+ timeTag(attempt.createdAt, locale, t)));
118
+ element.append(attempt.checks && attempt.checks.length ? checkList(attempt.checks, t) : h("p.faint.small", null, t("evidence.noChecks")));
119
+ return element;
120
+ }
121
+
122
+ function checkList(checks, t) {
123
+ return h("ul.check-list", null, checks.map(function (check) {
124
+ return h("li", null, dot(check.outcome === "passed" ? "ok" : check.outcome === "failed" ? "bad" : "idle"),
125
+ h("span.mono-line", null, check.name), h("span.faint", null, label(t, "check", check.outcome)),
126
+ check.details ? h("span.faint.small.check-details", null, check.details) : null);
127
+ }));
128
+ }
129
+
130
+ // --- Workspaces and cleanup --------------------------------------------------
131
+ export function workspacesSection(scope) {
132
+ const workspaces = cardDisclosure("detail-workspaces", scope.t("workspaces.title"), "workspaces", String(totalOf(scope.core, "managed-workspace")));
133
+ return { element: workspaces, draw: function () { drawWorkspaces(workspaces, scope); } };
134
+ }
135
+
136
+ function drawWorkspaces(workspaces, scope) {
137
+ const t = scope.t;
138
+ clear(workspaces.body);
139
+ const evidence = scope.view.evidence && scope.view.evidence.key && scope.view.evidence.value;
140
+ const list = evidence ? evidence.workspaces : valuesOf(scope.core, "managed-workspace");
141
+ if (!list.length) workspaces.body.append(note(t("evidence.noWorkspaces")));
142
+ const rows = h("div.row-stack.boxed");
143
+ list.forEach(function (workspace) { rows.append(workspaceRow(workspace, t, scope.locale)); });
144
+ if (rows.childNodes.length) workspaces.body.append(rows);
145
+ workspaces.body.append(note(t("evidence.cleanupNote")), codeBlock("yui task archive-preflight " + scope.task.id + " --integrated"));
146
+ }
147
+
148
+ function workspaceRow(workspace, t, locale) {
149
+ const owner = workspace.owner || {};
150
+ const ownerId = owner.workItemId || owner.reviewRoundId || owner.integrationAttemptId
151
+ || (owner.executionGroupId ? owner.executionGroupId + (owner.executionLaneId ? "/" + owner.executionLaneId : "") : "") || owner.taskId || "";
152
+ return h("article.record.is-compact", null,
153
+ h("header.record-head", null, chip(label(t, "workspace.owner", owner.type)), mono(ownerId), h("span.spacer"), timeTag(workspace.updatedAt, locale, t)),
154
+ h("p.small.mono-line", null, workspace.root),
155
+ (workspace.entries || []).length ? h("div.meta-line", null, workspace.entries.map(function (entry) {
156
+ return h("span", null, entry.directory + " · ", mono(entry.branch || entry.baseRef || ""));
157
+ })) : null);
158
+ }
159
+ `;
@@ -0,0 +1,272 @@
1
+ export const TASK_FILES_SCRIPT = String.raw `
2
+ // Task › Delivery › Saved files. Files are read on request at a fixed
3
+ // revision and the selected revision survives refreshes; explicit frozen
4
+ // references (git:<commit>:<path>) stay attached to their original revision.
5
+ import { h, icon, clear } from "/assets/js/lib/dom.js";
6
+ import { formatBytes } from "/assets/js/lib/format.js";
7
+ import { badge, button, emptyState, mono, note } from "/assets/js/ui/primitives.js";
8
+ import { card } from "/assets/js/ui/containers.js";
9
+ import { renderMarkdown, resolveMarkdownImage, escapeHtml } from "/assets/js/lib/markdown.js";
10
+
11
+ export function filesCard(data, t, locale, actions) {
12
+ const state = data.viewState;
13
+ const load = button(state.artifactList ? t("evidence.reload") : t("evidence.load"), { icon: "refresh", variant: "ghost" });
14
+ const files = card({ id: "detail-files", title: t("evidence.files"), icon: "file", hint: t("evidence.filesHint"), actions: load });
15
+ const list = h("div.file-list");
16
+ const viewer = artifactViewer(h("div.file-viewer"), data, t, actions);
17
+ const referenced = referencedFiles(data, t, viewer.read);
18
+ const pathInput = h("input", { placeholder: t("materials.path"), "aria-label": t("materials.path"), maxLength: 512 });
19
+ const commitInput = h("input", { placeholder: t("materials.commit"), "aria-label": t("materials.commit"), maxLength: 64 });
20
+ const open = button(t("materials.open"), { variant: "ghost" });
21
+ open.addEventListener("click", function () { viewer.read(pathInput.value, commitInput.value); });
22
+ const more = button(t("materials.moreFiles"), { variant: "ghost" });
23
+ more.hidden = !state.artifactList?.nextOffset;
24
+ more.addEventListener("click", async function () {
25
+ more.disabled = true; load.disabled = true;
26
+ try {
27
+ const previous = state.artifactList;
28
+ const page = await actions.artifacts(data.task.id, previous.commit, previous.nextOffset);
29
+ state.artifactList = { ...page, entries: previous.entries.concat(page.entries) };
30
+ drawFileList(list, state, t, viewer.read); more.hidden = page.nextOffset === null;
31
+ } catch (error) { list.append(note(error.message, "bad")); }
32
+ finally { more.disabled = false; load.disabled = false; }
33
+ });
34
+ let placeholder = !state.artifactList && !referenced.childNodes.length ? emptyState(t("evidence.notLoaded"), "file") : null;
35
+ load.addEventListener("click", async function () {
36
+ load.disabled = true; more.disabled = true;
37
+ list.dataset.reading = "true";
38
+ try {
39
+ state.artifactList = await actions.artifacts(data.task.id);
40
+ if (placeholder) { placeholder.remove(); placeholder = null; }
41
+ drawFileList(list, state, t, viewer.read);
42
+ more.hidden = state.artifactList.nextOffset === null;
43
+ } catch (error) {
44
+ clear(list);
45
+ list.append(note(t("evidence.listUnavailable") + " " + error.message, "bad"));
46
+ } finally { load.disabled = false; more.disabled = false; list.dataset.reading = "false"; }
47
+ });
48
+ if (placeholder) files.body.append(placeholder);
49
+ files.body.append(h("div.record-actions", null, pathInput, commitInput, open), referenced, list, more, viewer.element);
50
+ drawFileList(list, state, t, viewer.read);
51
+ viewer.draw();
52
+ return files;
53
+ }
54
+
55
+ // One viewer per card; a newer read supersedes one still in flight.
56
+ function artifactViewer(viewer, data, t, actions) {
57
+ const state = data.viewState;
58
+ let readSequence = 0;
59
+ function draw() { drawArtifact(viewer, state.artifact, data.task.id, t, actions, read, state); }
60
+ async function read(path, commit, offset, before) {
61
+ const sequence = ++readSequence;
62
+ viewer.dataset.reading = "true";
63
+ clear(viewer);
64
+ viewer.append(h("p.faint", null, t("evidence.reading")));
65
+ try {
66
+ const result = await actions.readArtifact(data.task.id, path, commit, offset, before);
67
+ if (sequence !== readSequence) return;
68
+ state.artifact = result;
69
+ draw();
70
+ } catch (error) {
71
+ if (sequence === readSequence) { clear(viewer); viewer.append(note(t("evidence.unavailable") + " " + error.message, "bad")); }
72
+ } finally { if (sequence === readSequence) viewer.dataset.reading = "false"; }
73
+ }
74
+ return { element: viewer, read: read, draw: draw };
75
+ }
76
+
77
+ function drawArtifact(viewer, value, taskId, t, actions, read, state = {}) {
78
+ clear(viewer);
79
+ if (!value) return;
80
+ const selected = value.after || value;
81
+ const previewKey = JSON.stringify([taskId, selected.commit, selected.relativePath, selected.digest, value.before?.commit]);
82
+ if (state.artifactPreview?.key !== previewKey) {
83
+ // Only the selected fixed version owns cached bytes; changing it releases the old bounded cache.
84
+ state.artifactPreview = { key: previewKey, images: [], pending: 0, totalBytes: 0, totalPixels: 0 };
85
+ }
86
+ const preview = state.artifactPreview;
87
+ const ref = "git:" + selected.commit + ":" + selected.relativePath;
88
+ const copy = button(t("evidence.copy"), { icon: "copy", variant: "ghost" });
89
+ copy.addEventListener("click", async function () {
90
+ try { await navigator.clipboard.writeText(taskId + " " + ref); copy.lastChild.textContent = t("evidence.copied"); }
91
+ catch { copy.lastChild.textContent = t("evidence.copyManual"); }
92
+ });
93
+ const copyText = button(t("materials.copyPage"), { variant: "ghost" });
94
+ copyText.addEventListener("click", async function () {
95
+ try { await navigator.clipboard.writeText(value.content); copyText.textContent = t("evidence.copied"); }
96
+ catch { copyText.textContent = t("evidence.copyManual"); }
97
+ });
98
+ const feedback = button(t("materials.feedback"), { variant: "ghost" });
99
+ feedback.addEventListener("click", function () {
100
+ actions.openConversation((value.before ? [value.before, value.after] : [selected]).map(artifactRef));
101
+ });
102
+ const download = button(t("materials.download"), { variant: "ghost" });
103
+ download.addEventListener("click", async function () {
104
+ download.disabled = true;
105
+ try {
106
+ if (value.kind === "image") { downloadImage(value); return; }
107
+ const parts = [];
108
+ let offset = 0, page;
109
+ do {
110
+ page = await actions.readArtifact(taskId, selected.relativePath, selected.commit, offset, value.before?.commit);
111
+ parts.push(page.content); offset = page.nextOffset;
112
+ } while (offset !== null);
113
+ const url = URL.createObjectURL(new Blob(parts, { type: "text/plain;charset=utf-8" }));
114
+ const link = h("a", { href: url, download: selected.relativePath.split("/").pop() + (value.before ? ".diff" : "") });
115
+ link.click(); window.setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
116
+ } catch (error) { viewer.append(note(error.message, "bad")); }
117
+ finally { download.disabled = false; }
118
+ });
119
+ viewer.append(h("header.viewer-head", null, icon("file"), h("strong", null, selected.relativePath), h("span.spacer"), copy));
120
+ viewer.append(h("p.viewer-ref", null, mono(ref)));
121
+ viewer.append(h("p.viewer-ref", null, mono("sha256:" + selected.digest)));
122
+ if (value.before) viewer.append(h("p.viewer-ref", null, mono("← git:" + value.before.commit + ":" + value.before.relativePath + " · sha256:" + value.before.digest)));
123
+ viewer.append(h("p.faint.small", null, t("evidence.fixedNote")));
124
+ viewer.append(h("div.record-actions", null, value.kind === "image" ? null : copyText, download, feedback));
125
+ if (value.kind === "image") { viewer.append(imagePreview(value, t, undefined, preview)); return; }
126
+ const before = h("input", { placeholder: t("materials.before"), "aria-label": t("materials.before"), maxLength: 64 });
127
+ const compare = button(t("materials.compare"), { variant: "ghost" });
128
+ compare.addEventListener("click", function () { read(selected.relativePath, selected.commit, 0, before.value); });
129
+ viewer.append(h("div.record-actions", null, before, compare));
130
+ const source = h("pre.code-block.artifact-text", { tabIndex: 0, "aria-label": t("evidence.fixedText") }, value.content);
131
+ if (!value.before && /\.md$|\.markdown$/i.test(selected.relativePath)) {
132
+ viewer.append(markdownPreview(value, taskId, t, actions, preview));
133
+ viewer.append(h("details", null, h("summary", null, t("materials.source")), source));
134
+ } else viewer.append(source);
135
+ viewer.append(note((value.content.length ? value.offset + 1 : 0) + "–" + (value.offset + value.content.length) + " / " + value.totalCharacters));
136
+ const first = button(t("materials.firstPage"), { variant: "ghost" });
137
+ first.disabled = value.offset === 0;
138
+ first.addEventListener("click", function () { read(selected.relativePath, selected.commit, 0, value.before?.commit); });
139
+ const next = button(t("materials.nextPage"), { variant: "ghost" });
140
+ next.disabled = value.nextOffset === null;
141
+ next.addEventListener("click", function () { read(selected.relativePath, selected.commit, value.nextOffset, value.before?.commit); });
142
+ viewer.append(h("div.record-actions", null, first, next));
143
+ }
144
+
145
+ function artifactRef(value) {
146
+ return { taskId: value.taskId, commit: value.commit, relativePath: value.relativePath, digest: value.digest };
147
+ }
148
+
149
+ function downloadImage(value) {
150
+ const bytes = Uint8Array.from(atob(value.base64), function (char) { return char.charCodeAt(0); });
151
+ const url = URL.createObjectURL(new Blob([bytes], { type: value.mime }));
152
+ h("a", { href: url, download: value.relativePath.split("/").pop() }).click();
153
+ window.setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
154
+ }
155
+
156
+ function imagePreview(value, t, alt, state = {}) {
157
+ const image = h("img.artifact-image", { alt: alt || value.relativePath, decoding: "async" });
158
+ const viewport = h("span.image-viewport", { tabIndex: 0, "aria-label": t("materials.image") }, image);
159
+ const status = h("span.feed-note", { role: "status" }, value.width + " × " + value.height + " · " + formatBytes(value.byteSize));
160
+ const scale = h("select", { "aria-label": t("materials.zoom") },
161
+ h("option", { value: "fit" }, t("materials.fit")),
162
+ ...[50, 100, 200, 400].map(function (percent) { return h("option", { value: String(percent) }, percent + "%"); }));
163
+ function applyScale() {
164
+ image.classList.toggle("is-zoomed", scale.value !== "fit");
165
+ if (scale.value === "fit") image.removeAttribute("width");
166
+ else image.width = Math.round((image.naturalWidth || value.width) * Number(scale.value) / 100);
167
+ }
168
+ scale.value = state.zoom || "fit";
169
+ applyScale();
170
+ scale.addEventListener("change", function () {
171
+ state.zoom = scale.value;
172
+ applyScale();
173
+ });
174
+ image.addEventListener("error", function () {
175
+ image.removeAttribute("src"); image.hidden = true; scale.disabled = true;
176
+ status.textContent = t("materials.imageError");
177
+ }, { once: true });
178
+ image.src = "data:" + value.mime + ";base64," + value.base64;
179
+ return h("span.image-preview", null, h("span.record-actions", null, scale, status), viewport);
180
+ }
181
+
182
+ function markdownPreview(value, taskId, t, actions, preview) {
183
+ const body = h("div.md.artifact-markdown");
184
+ const images = [];
185
+ body.innerHTML = renderMarkdown(value.content, { image: function (alt, target) {
186
+ if (images.length >= 12) return '<span class="feed-note">' + escapeHtml(t("materials.imageLimit")) + "</span>";
187
+ const index = images.push({ alt, target }) - 1;
188
+ return '<span class="md-image-slot" data-image-index="' + index + '"></span>';
189
+ } });
190
+ body.querySelectorAll("[data-image-index]").forEach(function (slot) {
191
+ const index = Number(slot.dataset.imageIndex);
192
+ const entry = images[index];
193
+ const saved = preview.images[index] || (preview.images[index] = {});
194
+ let path;
195
+ try { path = resolveMarkdownImage(value.relativePath, entry.target); }
196
+ catch (error) { slot.append(note(entry.alt + ": " + error.message, "bad")); return; }
197
+ const load = button(t("materials.loadImage") + " · " + (entry.alt || path), { variant: "ghost" });
198
+ const status = h("span.feed-note", { role: "status" });
199
+ slot.append(load, status);
200
+ function showImage() {
201
+ if (slot.isConnected === false) return;
202
+ const image = saved.image;
203
+ clear(slot);
204
+ const download = button(t("materials.download"), { variant: "ghost" });
205
+ download.addEventListener("click", function () { downloadImage(image); });
206
+ const feedback = button(t("materials.feedback"), { variant: "ghost" });
207
+ feedback.addEventListener("click", function () { actions.openConversation([artifactRef(value), artifactRef(image)]); });
208
+ slot.append(imagePreview(image, t, entry.alt, saved), h("span.viewer-ref", null,
209
+ path + " · git:" + image.commit + " · sha256:" + image.digest),
210
+ h("span.record-actions", null, download, feedback));
211
+ }
212
+ async function observeRead() {
213
+ load.disabled = true; body.dataset.reading = "true"; status.textContent = t("evidence.reading");
214
+ try { await saved.pending; showImage(); }
215
+ catch (error) { status.textContent = error.message; load.disabled = false; }
216
+ finally { body.dataset.reading = String(preview.pending > 0); }
217
+ }
218
+ load.addEventListener("click", function () {
219
+ if (saved.pending) return saved.pending;
220
+ if (preview.pending >= 2) { status.textContent = t("materials.imageBusy"); return; }
221
+ if (preview.totalBytes >= 16 * 1024 * 1024 || preview.totalPixels >= 32_000_000) { status.textContent = t("materials.imageLimit"); return; }
222
+ preview.pending++;
223
+ saved.pending = (async function () {
224
+ const image = await actions.readArtifact(taskId, path, value.commit, 0);
225
+ if (image.kind !== "image") throw new Error(t("materials.imageUnsupported"));
226
+ if (preview.totalBytes + image.byteSize > 16 * 1024 * 1024 || preview.totalPixels + image.width * image.height > 32_000_000) {
227
+ throw new Error(t("materials.imageLimit"));
228
+ }
229
+ preview.totalBytes += image.byteSize; preview.totalPixels += image.width * image.height;
230
+ saved.image = image;
231
+ })().finally(function () { preview.pending--; delete saved.pending; });
232
+ return observeRead();
233
+ });
234
+ if (saved.image) showImage();
235
+ else if (saved.pending) void observeRead();
236
+ });
237
+ return body;
238
+ }
239
+
240
+ function drawFileList(list, state, t, read) {
241
+ clear(list);
242
+ const current = state.artifactList;
243
+ if (!current) return;
244
+ const selected = state.artifact && (state.artifact.after || state.artifact);
245
+ if (selected && current.commit !== selected.commit) list.append(note(t("evidence.differentRevision")));
246
+ if (!current.entries.length) { list.append(emptyState(t("evidence.noFiles"), "file")); return; }
247
+ current.entries.forEach(function (entry) {
248
+ list.append(h("button.file-row", {
249
+ type: "button",
250
+ "aria-current": String(!!selected && selected.relativePath === entry.relativePath && selected.commit === current.commit),
251
+ onclick: function () { read(entry.relativePath, current.commit); }
252
+ }, icon("file", "icon-sm"), h("span.file-name", null, entry.relativePath), h("span.faint.small", null, formatBytes(entry.size))));
253
+ });
254
+ }
255
+
256
+ // Frozen references named by the completion, the Brief or a decision.
257
+ function referencedFiles(data, t, read) {
258
+ const sources = [data.task.completionArtifactRefs || [],
259
+ data.core.records.filter(function (entry) { return !entry.omitted && ["task-brief", "task-decision"].includes(entry.ref.store); })
260
+ .map(function (entry) { return JSON.stringify(entry.value); })].flat().join(" ");
261
+ const refs = Array.from(sources.matchAll(/git:([0-9a-f]{40}):([^\s"\\]+?)(?=[\s"\\]|$)/g));
262
+ const seen = new Set();
263
+ const referenced = h("div.file-list");
264
+ refs.forEach(function (match) {
265
+ if (seen.has(match[0])) return;
266
+ seen.add(match[0]);
267
+ referenced.append(h("button.file-row", { type: "button", onclick: function () { read(match[2], match[1]); } },
268
+ icon("flag", "icon-sm"), h("span.file-name", null, match[2]), badge(t("evidence.referenced"), "accent")));
269
+ });
270
+ return referenced;
271
+ }
272
+ `;