@zq-silk/yui 2.3.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.
- package/README.md +15 -1
- package/dist/artifacts/artifactCapability.js +39 -10
- package/dist/artifacts/imageMetadata.js +120 -0
- package/dist/artifacts/managedGit.js +7 -4
- package/dist/artifacts/taskArtifactRepository.js +6 -0
- package/dist/cli/commandCatalog.js +17 -8
- package/dist/cli/invocationAuthority.js +1 -1
- package/dist/commands/executionAuditCommands.js +5 -0
- package/dist/commands/globalRoleCommands.js +41 -3
- package/dist/commands/projectCommands.js +89 -94
- package/dist/commands/roleSkillValidation.js +4 -8
- package/dist/commands/taskCommands.js +25 -2
- package/dist/commands/taskUsageCommand.js +38 -0
- package/dist/context/roleSessionContext.js +18 -2
- package/dist/context/runContextPack.js +5 -1
- package/dist/context/sessionBootstrapManifest.js +10 -1
- package/dist/context/skillCatalog.js +108 -0
- package/dist/context/skillPackage.js +161 -0
- package/dist/context/taskContext.js +3 -2
- package/dist/context/taskSearch.js +127 -0
- package/dist/controlPlaneCli.js +25 -20
- package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
- package/dist/controller/jobControl.js +2 -0
- package/dist/controller/nativeHostInput.js +37 -0
- package/dist/controller/runtime.js +93 -2
- package/dist/controller/runtimeEventInbox.js +2 -2
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/doctor/doctor.js +10 -0
- package/dist/execution/reviewMainRun.js +1 -0
- package/dist/execution/workItemMainRun.js +1 -0
- package/dist/executor/codexConfigConflict.js +6 -4
- package/dist/executor/effectiveLaunch.js +24 -1
- package/dist/executor/fileRoleLaunchPlanner.js +13 -5
- package/dist/integration/gitIntegrationService.js +7 -12
- package/dist/integration/integrationSourceApplication.js +4 -2
- package/dist/job/httpPreview.js +61 -0
- package/dist/job/jobRunner.js +12 -2
- package/dist/kernel/builtinCapabilities.js +3 -1
- package/dist/message/message.js +15 -1
- package/dist/output/rolePresentation.js +4 -1
- package/dist/repository/executionLaneGitSnapshot.js +2 -1
- package/dist/repository/gitCompatibility.js +62 -0
- package/dist/repository/gitWorkspace.js +48 -71
- package/dist/repository/gitWorktreeInventory.js +121 -0
- package/dist/repository/project.js +74 -25
- package/dist/resources/resourceDiscovery.js +2 -2
- package/dist/resources/resourceGc.js +7 -4
- package/dist/resources/resourceRegistrar.js +2 -1
- package/dist/runtime/agentEndpoint.js +8 -0
- package/dist/runtime/agentHost.js +170 -3
- package/dist/runtime/codexAppServerRuntime.js +1 -1
- package/dist/runtime/codexNativeAccess.js +175 -0
- package/dist/runtime/controlledCodexTui.js +116 -0
- package/dist/runtime/publicReply.js +23 -0
- package/dist/runtime/runtimeObservation.js +48 -0
- package/dist/runtime/structuredProviderHost.js +16 -7
- package/dist/runtime/taskCostMetrics.js +133 -0
- package/dist/runtime/taskUsageMetrics.js +11 -2
- package/dist/runtime/taskUsageQuery.js +38 -0
- package/dist/scheduler/taskExecutionProjection.js +2 -0
- package/dist/scheduler/taskObservabilityProjection.js +9 -1
- package/dist/storage/homeLayout.js +13 -0
- package/dist/storage/sqliteSchema.js +44 -0
- package/dist/storage/sqliteStore.js +53 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskSearch.js +35 -0
- package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
- package/dist/task/ordinaryArchive.js +42 -0
- package/dist/tmux/tmuxManager.js +8 -8
- package/dist/web/assets/assetManifest.js +11 -51
- package/dist/web/assets/client/app/catalog.js +215 -0
- package/dist/web/assets/client/app/main.js +148 -0
- package/dist/web/assets/client/app/selection.js +139 -0
- package/dist/web/assets/client/app/settings.js +326 -0
- package/dist/web/assets/client/app/taskView.js +280 -0
- package/dist/web/assets/client/domain/context.js +96 -0
- package/dist/web/assets/client/domain/records.js +126 -0
- package/dist/web/assets/client/domain/runs.js +119 -0
- package/dist/web/assets/client/domain/taskForms.js +197 -0
- package/dist/web/assets/client/domain/vocab.js +69 -0
- package/dist/web/assets/client/domain/work.js +116 -0
- package/dist/web/assets/client/i18n/en.js +900 -0
- package/dist/web/assets/client/i18n/zh-CN.js +898 -0
- package/dist/web/assets/client/index.js +114 -0
- package/dist/web/assets/client/layout/resizer.js +37 -0
- package/dist/web/assets/client/layout/router.js +35 -0
- package/dist/web/assets/client/layout/shortcuts.js +45 -0
- package/dist/web/assets/client/layout/sizing.js +95 -0
- package/dist/web/assets/client/layout/workspace.js +247 -0
- package/dist/web/assets/client/{api.js → lib/api.js} +20 -4
- package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
- package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
- package/dist/web/assets/client/lib/i18n.js +54 -0
- package/dist/web/assets/client/lib/markdown.js +167 -0
- package/dist/web/assets/client/lib/prefs.js +28 -0
- package/dist/web/assets/client/lib/theme.js +68 -0
- package/dist/web/assets/client/ui/containers.js +101 -0
- package/dist/web/assets/client/ui/controls.js +51 -0
- package/dist/web/assets/client/ui/forms.js +48 -0
- package/dist/web/assets/client/ui/metrics.js +137 -0
- package/dist/web/assets/client/ui/primitives.js +92 -0
- package/dist/web/assets/client/ui/text.js +33 -0
- package/dist/web/assets/client/ui/toast.js +12 -0
- package/dist/web/assets/client/views/bodySearch.js +172 -0
- package/dist/web/assets/client/views/createTask.js +85 -0
- package/dist/web/assets/client/views/dock/conversation.js +612 -0
- package/dist/web/assets/client/views/dock/discussion.js +173 -0
- package/dist/web/assets/client/views/dock/preview.js +129 -0
- package/dist/web/assets/client/views/dock/terminal.js +199 -0
- package/dist/web/assets/client/views/globalInput.js +67 -0
- package/dist/web/assets/client/{overview.js → views/overview.js} +61 -51
- package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
- package/dist/web/assets/client/views/skills.js +152 -0
- package/dist/web/assets/client/views/task/actions.js +36 -0
- package/dist/web/assets/client/views/task/delivery.js +185 -0
- package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
- package/dist/web/assets/client/views/task/files.js +272 -0
- package/dist/web/assets/client/views/task/observation.js +153 -0
- package/dist/web/assets/client/views/task/overview.js +161 -0
- package/dist/web/assets/client/views/task/page.js +131 -0
- package/dist/web/assets/client/views/task/records.js +163 -0
- package/dist/web/assets/client/views/task/runtime.js +130 -0
- package/dist/web/assets/client/views/task/timeline.js +190 -0
- package/dist/web/assets/shared/geometry.js +19 -0
- package/dist/web/assets/shared/taskTabs.js +8 -0
- package/dist/web/assets/shared/themes.js +93 -0
- package/dist/web/assets/shell/dialogs.js +63 -0
- package/dist/web/assets/shell/dock.js +40 -0
- package/dist/web/assets/shell/index.js +40 -0
- package/dist/web/assets/shell/settings.js +34 -0
- package/dist/web/assets/shell/sidebar.js +45 -0
- package/dist/web/assets/styles/index.js +41 -0
- package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +10 -10
- package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
- package/dist/web/assets/styles/markdown.js +22 -3
- package/dist/web/assets/styles/tokens.js +10 -63
- package/dist/web/assets/styles/ui/controls.js +76 -0
- package/dist/web/assets/styles/ui/forms.js +46 -0
- package/dist/web/assets/styles/ui/surfaces.js +122 -0
- package/dist/web/assets/styles/views/delivery.js +29 -0
- package/dist/web/assets/styles/views/dock.js +71 -0
- package/dist/web/assets/styles/views/page.js +32 -0
- package/dist/web/assets/styles/views/records.js +70 -0
- package/dist/web/assets/styles/views/settings.js +26 -0
- package/dist/web/assets/styles/views/sidebar.js +74 -0
- package/dist/web/assets/styles/views/task.js +80 -0
- package/dist/web/assets/styles/views/terminal.js +38 -0
- package/dist/web/taskPreviews.js +205 -0
- package/dist/web/tmuxWebTerminal.js +16 -9
- package/dist/web/webConversation.js +373 -0
- package/dist/web/webMaterials.js +155 -0
- package/dist/web/webServer.js +295 -18
- package/dist/web/webSettings.js +331 -0
- package/dist/web/webSkills.js +86 -0
- package/dist/web/webSnapshot.js +40 -8
- package/dist/web/webTaskSurface.js +106 -8
- package/dist/workspace/gitChangeSetCapture.js +3 -1
- package/docs/managed-turn-and-session-runtime.md +9 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
- package/docs/release-workflow.md +28 -0
- package/docs/release-workflow.zh-CN.md +21 -0
- package/docs/storage-baseline.md +38 -1
- package/docs/storage-baseline.zh-CN.md +22 -0
- package/docs/task-discovery.md +28 -0
- package/docs/testing/verification-levels.md +2 -2
- package/i18n/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/dist/web/assets/client/app.js +0 -808
- package/dist/web/assets/client/components.js +0 -477
- package/dist/web/assets/client/detail.js +0 -379
- package/dist/web/assets/client/dock.js +0 -309
- package/dist/web/assets/client/evidence.js +0 -364
- package/dist/web/assets/client/forms.js +0 -230
- package/dist/web/assets/client/i18n.js +0 -1277
- package/dist/web/assets/client/markdown.js +0 -114
- package/dist/web/assets/client/records.js +0 -198
- package/dist/web/assets/client/sections.js +0 -349
- package/dist/web/assets/client/theme.js +0 -71
- package/dist/web/assets/shell.js +0 -161
- package/dist/web/assets/styles/components.js +0 -216
- package/dist/web/assets/styles/views.js +0 -309
- /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
|
@@ -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
|
+
`;
|