@zq-silk/yui 2.2.0 → 2.3.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 +4 -2
- package/dist/cli/commandCatalog.js +8 -3
- package/dist/cli/interactionPolicy.js +4 -0
- package/dist/commands/taskCommands.js +5 -7
- package/dist/commands/taskIntegrationCommands.js +5 -16
- package/dist/controlPlaneCli.js +28 -21
- package/dist/controller/clientRuntime.js +3 -2
- package/dist/integration/gitIntegrationService.js +8 -67
- package/dist/job/jobCleanupInspection.js +67 -0
- package/dist/repository/gitWorkspace.js +41 -12
- package/dist/repository/taskWorkspaceCoordinator.js +72 -145
- package/dist/repository/taskWorkspacePreparer.js +39 -43
- package/dist/repository/workspaceCleanupInspection.js +88 -7
- package/dist/resources/resourceRegistrar.js +16 -10
- package/dist/runtime/integrationRuntimeIsolation.js +21 -0
- package/dist/runtime/taskRuntimeIsolation.js +91 -21
- package/dist/task/archiveDiagnostics.js +26 -8
- package/dist/task/archivePreflight.js +26 -9
- package/dist/task/leaderArchive.js +9 -15
- package/dist/web/assets/client/api.js +8 -0
- package/dist/web/assets/client/app.js +10 -6
- package/dist/web/assets/client/components.js +95 -36
- package/dist/web/assets/client/detail.js +110 -64
- package/dist/web/assets/client/dock.js +95 -11
- package/dist/web/assets/client/evidence.js +286 -94
- package/dist/web/assets/client/i18n.js +178 -62
- package/dist/web/assets/client/overview.js +9 -13
- package/dist/web/assets/client/records.js +142 -7
- package/dist/web/assets/client/sections.js +234 -198
- package/dist/web/assets/icons.js +4 -0
- package/dist/web/assets/styles/components.js +64 -17
- package/dist/web/assets/styles/responsive.js +7 -5
- package/dist/web/assets/styles/tokens.js +1 -1
- package/dist/web/assets/styles/views.js +48 -31
- package/dist/web/webServer.js +12 -1
- package/dist/web/webTaskSurface.js +8 -1
- package/docs/observability/README.md +13 -3
- package/docs/observability/README.zh-CN.md +10 -3
- package/docs/release-workflow.md +25 -0
- package/docs/release-workflow.zh-CN.md +19 -0
- package/docs/task-delivery.md +36 -8
- package/docs/task-delivery.zh-CN.md +24 -7
- package/i18n/README.zh-CN.md +3 -2
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +2 -1
- package/skills/yui-leader/references/integration.md +20 -0
- package/skills/yui-operator/SKILL.md +4 -0
- package/skills/yui-reviewer/SKILL.md +3 -0
- package/skills/yui-runtime/SKILL.md +4 -0
- package/skills/yui-runtime/references/publication.md +20 -4
- package/skills/yui-worker/SKILL.md +3 -0
|
@@ -8,7 +8,7 @@ import { FitAddon } from "/assets/vendor/addon-fit.mjs";
|
|
|
8
8
|
import { h, icon, clear } from "/assets/js/dom.js";
|
|
9
9
|
import { formatShort } from "/assets/js/format.js";
|
|
10
10
|
import { label, chip, emptyState, richText, dot } from "/assets/js/components.js";
|
|
11
|
-
import { entriesOf,
|
|
11
|
+
import { entriesOf, totalOf, readExact, recordCard } from "/assets/js/records.js";
|
|
12
12
|
import { messageComposer } from "/assets/js/forms.js";
|
|
13
13
|
import { pageToken } from "/assets/js/api.js";
|
|
14
14
|
|
|
@@ -31,6 +31,7 @@ export function renderDiscussion(host, data, t, locale, actions) {
|
|
|
31
31
|
feed = host.querySelector(".feed");
|
|
32
32
|
}
|
|
33
33
|
host.dataset.taskId = task.id;
|
|
34
|
+
latest.set(task.id, { data: data, t: t, locale: locale, actions: actions });
|
|
34
35
|
if (!feed.querySelector('[data-reading="true"]')) drawFeed(feed, data, t, locale, actions);
|
|
35
36
|
feed.scrollTop = atEnd ? feed.scrollHeight : previousScroll;
|
|
36
37
|
if (!keepComposer) {
|
|
@@ -44,21 +45,92 @@ export function discussionHasUnsent(host) {
|
|
|
44
45
|
return !!host.querySelector('[data-unsent="true"]');
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
// The Context read carries the newest messages as summaries; each is read
|
|
49
|
+
// exactly once per digest (kept in the Task's view state) and drawn in full,
|
|
50
|
+
// so a message needs no separate raw-record reader.
|
|
51
|
+
// Older messages are listed on request, newest first, and read the same way.
|
|
52
|
+
const older = new Map();
|
|
53
|
+
const failedReads = new Set();
|
|
54
|
+
const latest = new Map();
|
|
55
|
+
|
|
56
|
+
function readKey(entry) { return entry.ref.refId + "@" + entry.ref.digest; }
|
|
57
|
+
|
|
58
|
+
function exactValue(entry, cache) {
|
|
59
|
+
if (!entry.omitted) return entry.value;
|
|
60
|
+
const hit = cache[entry.ref.store + "/" + entry.ref.refId];
|
|
61
|
+
return hit && hit.digest === entry.ref.digest && hit.result ? hit.result.value : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function loadOlder(data, actions, state, shownIds) {
|
|
65
|
+
state.pending = true;
|
|
66
|
+
state.error = null;
|
|
67
|
+
let rounds = 0;
|
|
68
|
+
function next() {
|
|
69
|
+
return actions.list(data.task.id, "task-message", { limit: 20, cursor: state.cursor }).then(function (page) {
|
|
70
|
+
const known = new Set(state.items.map(function (item) { return item.ref.refId; }));
|
|
71
|
+
const fresh = page.items.filter(function (item) { return !known.has(item.ref.refId) && !shownIds.has(item.ref.refId); });
|
|
72
|
+
state.items = state.items.concat(fresh);
|
|
73
|
+
state.cursor = page.nextCursor;
|
|
74
|
+
state.done = !page.nextCursor;
|
|
75
|
+
rounds += 1;
|
|
76
|
+
if (!fresh.length && page.nextCursor && rounds < 10) return next();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// A listing that moved invalidates its continuation; the next request
|
|
80
|
+
// starts again from the newest page and skips what is already shown.
|
|
81
|
+
return next().catch(function (error) { state.error = error.message; state.cursor = null; })
|
|
82
|
+
.then(function () { state.pending = false; });
|
|
83
|
+
}
|
|
84
|
+
|
|
47
85
|
function drawFeed(feed, data, t, locale, actions) {
|
|
48
86
|
clear(feed);
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
87
|
+
const task = data.task;
|
|
88
|
+
const cache = data.viewState.exact = data.viewState.exact || {};
|
|
89
|
+
const contextEntries = entriesOf(data.core, "task-message");
|
|
90
|
+
const shownIds = new Set(contextEntries.map(function (entry) { return entry.ref.refId; }));
|
|
91
|
+
if (!older.has(task.id)) older.set(task.id, { items: [], cursor: null, done: false, pending: false, error: null });
|
|
92
|
+
const state = older.get(task.id);
|
|
93
|
+
const olderItems = state.items.filter(function (item) { return !shownIds.has(item.ref.refId); })
|
|
94
|
+
.sort(function (a, b) { return Date.parse(a.createdAt || 0) - Date.parse(b.createdAt || 0); });
|
|
95
|
+
// Context lists messages newest first; the feed reads oldest first.
|
|
96
|
+
const rows = olderItems.map(function (item) { return { entry: { ref: item.ref, summary: item.summary, omitted: true }, at: item.createdAt }; })
|
|
97
|
+
.concat(contextEntries.slice().reverse().map(function (entry) { return { entry: entry, at: null }; }));
|
|
98
|
+
const reads = [];
|
|
99
|
+
rows.forEach(function (row) {
|
|
100
|
+
row.value = exactValue(row.entry, cache);
|
|
101
|
+
if (!row.value && !failedReads.has(readKey(row.entry))) {
|
|
102
|
+
row.reading = true;
|
|
103
|
+
reads.push(readExact(row.entry, task.id, actions, cache).catch(function () { failedReads.add(readKey(row.entry)); }));
|
|
104
|
+
}
|
|
105
|
+
row.at = (row.value && row.value.createdAt) || row.at;
|
|
52
106
|
});
|
|
53
|
-
if (
|
|
54
|
-
|
|
107
|
+
if (rows.every(function (row) { return row.at; })) rows.sort(function (a, b) { return Date.parse(a.at) - Date.parse(b.at); });
|
|
108
|
+
|
|
109
|
+
const remaining = totalOf(data.core, "task-message") - shownIds.size - olderItems.length;
|
|
110
|
+
if (actions.list && remaining > 0 && !state.done) {
|
|
111
|
+
const more = h("button.show-more.feed-older", { type: "button", disabled: state.pending },
|
|
112
|
+
state.pending ? t("discussion.olderLoading") : t("discussion.older").replace("{count}", String(remaining)));
|
|
113
|
+
more.addEventListener("click", function () {
|
|
114
|
+
more.disabled = true;
|
|
115
|
+
more.textContent = t("discussion.olderLoading");
|
|
116
|
+
loadOlder(data, actions, state, shownIds).then(function () { redraw(feed, task.id, true); });
|
|
117
|
+
});
|
|
118
|
+
feed.append(more);
|
|
119
|
+
}
|
|
120
|
+
if (state.error) feed.append(h("p.feed-note", null, t("records.unavailable") + " " + state.error));
|
|
121
|
+
if (!rows.length) {
|
|
55
122
|
feed.append(emptyState(t("discussion.empty"), "chat"));
|
|
56
123
|
return;
|
|
57
124
|
}
|
|
58
125
|
let lastDay = "";
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
|
|
126
|
+
rows.forEach(function (row) {
|
|
127
|
+
if (!row.value) {
|
|
128
|
+
const placeholder = recordCard(row.entry, task.id, t, actions, { compact: true });
|
|
129
|
+
if (row.reading) placeholder.dataset.reading = "true";
|
|
130
|
+
feed.append(placeholder);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const value = row.value;
|
|
62
134
|
const day = value.createdAt ? new Intl.DateTimeFormat(locale, { dateStyle: "medium" }).format(new Date(value.createdAt)) : "";
|
|
63
135
|
if (day && day !== lastDay) { feed.append(h("div.feed-day", null, h("span", null, day))); lastDay = day; }
|
|
64
136
|
const type = (value.author && value.author.type) || "system";
|
|
@@ -69,12 +141,24 @@ function drawFeed(feed, data, t, locale, actions) {
|
|
|
69
141
|
h("header.msg-head", null, h("strong", null, author),
|
|
70
142
|
value.intent ? chip(label(t, "intent", value.intent)) : null,
|
|
71
143
|
value.kind === "role-result" ? chip(label(t, "messageKind", value.kind)) : null,
|
|
72
|
-
h("time", { dateTime: value.createdAt || "" }, formatShort(value.createdAt, locale))),
|
|
144
|
+
h("time", { dateTime: value.createdAt || "", title: row.entry.ref.refId }, formatShort(value.createdAt, locale))),
|
|
73
145
|
h("div.msg-bubble", null, richText(null, value.body || "", t, { threshold: 900 })));
|
|
74
|
-
main.append(recordReader(entry, data.task.id, t, actions));
|
|
75
146
|
message.append(main);
|
|
76
147
|
feed.append(message);
|
|
77
148
|
});
|
|
149
|
+
if (reads.length) Promise.all(reads).then(function () { redraw(feed, task.id, false); });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Redraw after a read without losing the reader's place: the bottom stays
|
|
153
|
+
// pinned, and prepended older messages keep the current message in view.
|
|
154
|
+
function redraw(feed, taskId, prepend) {
|
|
155
|
+
const host = feed.closest("[data-task-id]");
|
|
156
|
+
const current = latest.get(taskId);
|
|
157
|
+
if (!feed.isConnected || !host || host.dataset.taskId !== taskId || !current) return;
|
|
158
|
+
const atEnd = feed.scrollHeight - feed.clientHeight - feed.scrollTop < 40;
|
|
159
|
+
const fromBottom = feed.scrollHeight - feed.scrollTop;
|
|
160
|
+
drawFeed(feed, current.data, current.t, current.locale, current.actions);
|
|
161
|
+
feed.scrollTop = atEnd && !prepend ? feed.scrollHeight : feed.scrollHeight - fromBottom;
|
|
78
162
|
}
|
|
79
163
|
|
|
80
164
|
// --- Live native Session ----------------------------------------------------------
|
|
@@ -1,18 +1,290 @@
|
|
|
1
1
|
export const EVIDENCE_SCRIPT = String.raw `
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// The Delivery tab: 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.
|
|
5
6
|
import { h, icon, clear } from "/assets/js/dom.js";
|
|
6
|
-
import { formatBytes } from "/assets/js/format.js";
|
|
7
|
-
import {
|
|
7
|
+
import { formatBytes, formatClock } from "/assets/js/format.js";
|
|
8
|
+
import {
|
|
9
|
+
card, listCard, setCardCount, button, note, mono, chip, emptyState, richText, badge, statusBadge, label, tone, dot,
|
|
10
|
+
timeTag, disclosure, workItemCard, workItemBody, reviewCard
|
|
11
|
+
} from "/assets/js/components.js";
|
|
12
|
+
import { entriesOf, valuesOf, totalOf, summaryFields, readExact, exactRow, lazyRow, familyState, loadFamily } from "/assets/js/records.js";
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
const DELIVERY_TONES = { merged: "ok", partial: "warn", unverified: "warn", uncovered: "warn", pending: "info", none: "idle", unavailable: "idle" };
|
|
15
|
+
|
|
16
|
+
function newest(left, right) {
|
|
17
|
+
return Date.parse(right.createdAt || 0) - Date.parse(left.createdAt || 0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Only http(s) links without credentials leave the page.
|
|
21
|
+
function safeUrl(value) {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(value);
|
|
24
|
+
return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password ? url.href : null;
|
|
25
|
+
} catch { return null; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function renderDelivery(panel, data, t, locale, ctx) {
|
|
29
|
+
const view = data.viewState;
|
|
30
|
+
const core = data.core;
|
|
31
|
+
const task = data.task;
|
|
32
|
+
view.exact = view.exact || {};
|
|
33
|
+
view.openRows = view.openRows || {};
|
|
34
|
+
view.reports = view.reports || {};
|
|
35
|
+
view.panels = view.panels || {};
|
|
36
|
+
view.panels.delivery = panel;
|
|
37
|
+
|
|
38
|
+
// --- Work items ------------------------------------------------------------
|
|
39
|
+
const work = listCard({ id: "detail-work", title: t("work.title"), icon: "layers", count: totalOf(core, "work-item") || null, hint: t("work.openHint") });
|
|
40
|
+
function drawWork() {
|
|
41
|
+
clear(work.list);
|
|
42
|
+
const family = familyState(view, "work-item");
|
|
43
|
+
const entries = entriesOf(core, "work-item");
|
|
44
|
+
const titles = {};
|
|
45
|
+
family.items.forEach(function (item) { const fields = summaryFields(item.summary); if (fields.title) titles[item.ref.refId] = fields.title; });
|
|
46
|
+
valuesOf(core, "work-item").forEach(function (item) { titles[item.id] = item.title; });
|
|
47
|
+
const shown = new Set(entries.map(function (entry) { return entry.ref.refId; }));
|
|
48
|
+
const extraOpen = family.items.filter(function (item) { return item.status === "open" && !shown.has(item.ref.refId); });
|
|
49
|
+
const finished = family.items.filter(function (item) { return item.status !== "open"; });
|
|
50
|
+
entries.forEach(function (entry) {
|
|
51
|
+
work.list.append(exactRow(entry, task.id, t, ctx, view.exact, function (value) { return workItemCard(value, t, locale, titles); }));
|
|
52
|
+
});
|
|
53
|
+
extraOpen.forEach(function (item) { work.list.append(workRow(item, titles)); });
|
|
54
|
+
const loaded = family.key && family.pages;
|
|
55
|
+
if (loaded) setCardCount(work, family.total || null);
|
|
56
|
+
if (!entries.length && !extraOpen.length) {
|
|
57
|
+
work.list.append(emptyState(loaded && family.total === 0 ? t("work.none") : t("work.noneOpen"), "layers"));
|
|
58
|
+
}
|
|
59
|
+
if (family.error) work.list.append(note(t("records.unavailable") + " " + family.error, "bad"));
|
|
60
|
+
if (!loaded) {
|
|
61
|
+
if (family.pending) work.list.append(h("p.row-label", null, t("work.reading")));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const finishedTotal = Math.max(finished.length, (family.total || 0) - totalOf(core, "work-item"));
|
|
65
|
+
if (!finishedTotal) return;
|
|
66
|
+
const group = h("div.row-stack.is-finished", null, h("p.row-label", null, t("work.finished") + " · " + finishedTotal));
|
|
67
|
+
finished.forEach(function (item) { group.append(workRow(item, titles)); });
|
|
68
|
+
work.list.append(group);
|
|
69
|
+
if (family.nextCursor) work.list.append(listFoot(family, function () {
|
|
70
|
+
return loadFamily(view, task.id, "work-item", family.key, ctx, { more: true, limit: 40 });
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
function workRow(item, titles) {
|
|
74
|
+
const fields = summaryFields(item.summary);
|
|
75
|
+
return lazyRow(item, task.id, t, ctx, {
|
|
76
|
+
cache: view.exact, openRows: view.openRows,
|
|
77
|
+
head: [dot(tone("work", item.status)), h("span.lazy-title", null, fields.title || item.ref.refId),
|
|
78
|
+
item.assignee ? h("span.faint.small.lazy-meta", null, item.assignee) : null,
|
|
79
|
+
statusBadge(t, "work", "work", item.status)],
|
|
80
|
+
render: function (value) { return workItemBody(value, t, locale, titles); }
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function listFoot(family, more) {
|
|
84
|
+
const next = button(t("history.more"), { icon: "chevron", variant: "ghost" });
|
|
85
|
+
next.addEventListener("click", function () {
|
|
86
|
+
next.disabled = true;
|
|
87
|
+
more().then(refresh);
|
|
88
|
+
});
|
|
89
|
+
return h("div.list-foot", null,
|
|
90
|
+
h("span.faint.small", null, t("history.loaded").replace("{shown}", String(family.items.length)).replace("{total}", String(family.total))),
|
|
91
|
+
next);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --- Reviews ---------------------------------------------------------------
|
|
95
|
+
const reviews = listCard({ id: "detail-reviews", title: t("reviews.title"), icon: "eye", hint: t("reviews.hint") });
|
|
96
|
+
function drawReviews() {
|
|
97
|
+
clear(reviews.list);
|
|
98
|
+
const evidence = view.evidence && view.evidence.key && view.evidence.value;
|
|
99
|
+
if (evidence) {
|
|
100
|
+
const rounds = evidence.reviews.slice().sort(newest);
|
|
101
|
+
setCardCount(reviews, rounds.length || null);
|
|
102
|
+
if (!rounds.length) reviews.list.append(emptyState(t("reviews.none"), "eye"));
|
|
103
|
+
rounds.forEach(function (round) { reviews.list.append(reviewRow(round)); });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const entries = entriesOf(core, "review-round");
|
|
107
|
+
setCardCount(reviews, entries.length || null);
|
|
108
|
+
entries.forEach(function (entry) {
|
|
109
|
+
reviews.list.append(exactRow(entry, task.id, t, ctx, view.exact, function (value) { return reviewRow(value); }));
|
|
110
|
+
});
|
|
111
|
+
if (view.evidence && view.evidence.error) reviews.list.append(note(t("records.unavailable") + " " + view.evidence.error, "bad"));
|
|
112
|
+
else reviews.list.append(h("p.row-label", null, t("evidence.loading")));
|
|
113
|
+
}
|
|
114
|
+
function reviewRow(round) {
|
|
115
|
+
const element = reviewCard(round, t, locale);
|
|
116
|
+
if (!round.reviewerRunId) return element;
|
|
117
|
+
const holder = h("div.stack");
|
|
118
|
+
const cached = view.reports[round.id];
|
|
119
|
+
if (cached) { holder.append(richText(t("evidence.report"), cached, t, { threshold: 480 })); element.append(holder); return element; }
|
|
120
|
+
const report = button(t("evidence.readReport"), { icon: "eye", variant: "link" });
|
|
121
|
+
report.addEventListener("click", async function () {
|
|
122
|
+
report.disabled = true;
|
|
123
|
+
try {
|
|
124
|
+
const source = await ctx.inspect(task.id, { store: "run", refId: round.reviewerRunId });
|
|
125
|
+
const output = (source.result && source.result.output) || (source.value && source.value.result && source.value.result.output) || t("evidence.noReport");
|
|
126
|
+
view.reports[round.id] = output;
|
|
127
|
+
holder.append(richText(t("evidence.report"), output, t, { threshold: 480 }));
|
|
128
|
+
report.remove();
|
|
129
|
+
} catch (error) { holder.append(note(error.message, "bad")); report.disabled = false; }
|
|
130
|
+
});
|
|
131
|
+
element.append(h("div.record-actions", null, report), holder);
|
|
132
|
+
return element;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- Integration and remote delivery ------------------------------------------
|
|
136
|
+
const delivery = card({ id: "detail-results", title: t("delivery.title"), icon: "merge", hint: t("delivery.hint") });
|
|
137
|
+
const deliveryBody = h("div.stack.delivery-stack");
|
|
138
|
+
delivery.body.append(deliveryBody);
|
|
139
|
+
function remoteDelivery() {
|
|
140
|
+
const entry = entriesOf(core, "remote-delivery")[0];
|
|
141
|
+
if (entry && !entry.omitted) return entry.value;
|
|
142
|
+
const cached = entry && view.exact["remote-delivery/" + entry.ref.refId];
|
|
143
|
+
if (cached && cached.digest === entry.ref.digest && cached.result) return cached.result.value;
|
|
144
|
+
return (data.runtime && data.runtime.remoteDelivery) || null;
|
|
145
|
+
}
|
|
146
|
+
function drawDelivery() {
|
|
147
|
+
clear(deliveryBody);
|
|
148
|
+
const remote = remoteDelivery();
|
|
149
|
+
if (remote) {
|
|
150
|
+
deliveryBody.append(h("h4.sub-head", null, icon("package", "icon-sm"), h("span", null, t("evidence.remote")),
|
|
151
|
+
badge(t("remoteDelivery." + remote.status, remote.status), DELIVERY_TONES[remote.status] || "idle", { dot: true })));
|
|
152
|
+
if (remote.codeProjectCount) {
|
|
153
|
+
deliveryBody.append(h("p.faint.small", null, t("delivery.counts")
|
|
154
|
+
.replace("{merged}", String(remote.mergedProjectCount)).replace("{verified}", String(remote.verifiedProjectCount))
|
|
155
|
+
.split("{total}").join(String(remote.codeProjectCount))));
|
|
156
|
+
}
|
|
157
|
+
(remote.projects || []).forEach(function (project) { deliveryBody.append(projectRow(project)); });
|
|
158
|
+
if (!(remote.projects || []).length) deliveryBody.append(note(t("delivery.noProjects")));
|
|
159
|
+
} else deliveryBody.append(note(t("evidence.observationUnavailable")));
|
|
160
|
+
|
|
161
|
+
const state = view.evidence || {};
|
|
162
|
+
const evidence = state.key && state.value;
|
|
163
|
+
deliveryBody.append(h("h4.sub-head", null, icon("merge", "icon-sm"), h("span", null, t("evidence.integrations")),
|
|
164
|
+
evidence ? h("span.count", null, String(evidence.integrations.length)) : null));
|
|
165
|
+
if (!evidence) {
|
|
166
|
+
deliveryBody.append(state.error ? note(t("evidence.originalUnavailable") + " " + state.error, "bad") : h("p.faint.small", null, t("evidence.loading")));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (!evidence.integrations.length) deliveryBody.append(note(t("evidence.noIntegrations")));
|
|
170
|
+
const rows = h("div.row-stack.boxed");
|
|
171
|
+
evidence.integrations.slice().sort(newest).forEach(function (attempt) { rows.append(integrationRow(attempt)); });
|
|
172
|
+
if (rows.childNodes.length) deliveryBody.append(rows);
|
|
173
|
+
deliveryBody.append(h("p.faint.small", null, t("delivery.readAt") + " " + formatClock(state.readAt, locale)
|
|
174
|
+
+ (state.pending ? " · " + t("evidence.loading") : "")));
|
|
175
|
+
}
|
|
176
|
+
function projectRow(project) {
|
|
177
|
+
const publication = project.publication;
|
|
178
|
+
const href = publication && publication.externalUrl && safeUrl(publication.externalUrl);
|
|
179
|
+
const element = h("article.record.delivery-project");
|
|
180
|
+
element.append(h("header.record-head", null, icon("folder", "icon-sm"), h("strong", null, project.directory || project.projectId),
|
|
181
|
+
project.coverage ? chip(label(t, "coverage", project.coverage)) : null,
|
|
182
|
+
h("span.spacer"),
|
|
183
|
+
publication ? (href
|
|
184
|
+
? h("a.link", { href: href, rel: "noopener noreferrer", target: "_blank" }, label(t, "publication", publication.externalKind) + " #" + publication.externalId)
|
|
185
|
+
: mono(publication.externalKind + " " + publication.externalId)) : null));
|
|
186
|
+
if (publication && publication.title) element.append(h("p.small", null, publication.title));
|
|
187
|
+
if (project.reason) element.append(h("p.faint.small", null, project.reason));
|
|
188
|
+
element.append(h("div.meta-line", null,
|
|
189
|
+
h("span", null, t("evidence.expectedCommit") + " "), mono(project.expectedLocalCommit ? project.expectedLocalCommit.slice(0, 12) : t("common.unknown")),
|
|
190
|
+
project.state ? chip(label(t, "publicationState", project.state)) : null,
|
|
191
|
+
project.verification ? chip(t("evidence.verification") + " · " + label(t, "verification", project.verification)) : null,
|
|
192
|
+
project.adoption ? h("span", null, t("evidence.adoption") + " ", mono(project.adoption.id)) : null));
|
|
193
|
+
return element;
|
|
194
|
+
}
|
|
195
|
+
function integrationRow(attempt) {
|
|
196
|
+
const source = attempt.source || {};
|
|
197
|
+
const element = h("article.record.integration-row");
|
|
198
|
+
element.dataset.status = attempt.status;
|
|
199
|
+
element.append(h("header.record-head", null, dot(tone("integration", attempt.status)), mono(attempt.id),
|
|
200
|
+
chip(label(t, "integration.source", source.kind) + (source.workItemId ? " · " + source.workItemId : "")),
|
|
201
|
+
source.strategy ? chip(source.strategy) : null,
|
|
202
|
+
h("span.spacer"),
|
|
203
|
+
statusBadge(t, "integration", "integration", attempt.status)));
|
|
204
|
+
if (attempt.summary) element.append(h("p.small", null, attempt.summary));
|
|
205
|
+
element.append(h("div.meta-line", null,
|
|
206
|
+
attempt.projectId ? h("span", null, attempt.projectId) : null,
|
|
207
|
+
attempt.targetRef ? mono(attempt.targetRef) : null,
|
|
208
|
+
h("span", null, t("evidence.candidate") + " "), mono(attempt.candidateCommit ? attempt.candidateCommit.slice(0, 12) : t("evidence.noCandidate")),
|
|
209
|
+
timeTag(attempt.createdAt, locale, t)));
|
|
210
|
+
if (attempt.checks && attempt.checks.length) {
|
|
211
|
+
element.append(h("ul.check-list", null, attempt.checks.map(function (check) {
|
|
212
|
+
return h("li", null, dot(check.outcome === "passed" ? "ok" : check.outcome === "failed" ? "bad" : "idle"),
|
|
213
|
+
h("span.mono-line", null, check.name), h("span.faint", null, label(t, "check", check.outcome)),
|
|
214
|
+
check.details ? h("span.faint.small.check-details", null, check.details) : null);
|
|
215
|
+
})));
|
|
216
|
+
} else element.append(h("p.faint.small", null, t("evidence.noChecks")));
|
|
217
|
+
return element;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// --- Workspaces and cleanup --------------------------------------------------
|
|
221
|
+
const workspaces = disclosure(t("workspaces.title"), "workspaces", { className: "card-disclosure", meta: String(totalOf(core, "managed-workspace")) });
|
|
222
|
+
workspaces.id = "detail-workspaces";
|
|
223
|
+
function drawWorkspaces() {
|
|
224
|
+
clear(workspaces.body);
|
|
225
|
+
const evidence = view.evidence && view.evidence.key && view.evidence.value;
|
|
226
|
+
const list = evidence ? evidence.workspaces : valuesOf(core, "managed-workspace");
|
|
227
|
+
if (!list.length) workspaces.body.append(note(t("evidence.noWorkspaces")));
|
|
228
|
+
const rows = h("div.row-stack.boxed");
|
|
229
|
+
list.forEach(function (workspace) {
|
|
230
|
+
const owner = workspace.owner || {};
|
|
231
|
+
const ownerId = owner.workItemId || owner.reviewRoundId || owner.integrationAttemptId
|
|
232
|
+
|| (owner.executionGroupId ? owner.executionGroupId + (owner.executionLaneId ? "/" + owner.executionLaneId : "") : "") || owner.taskId || "";
|
|
233
|
+
rows.append(h("article.record.is-compact", null,
|
|
234
|
+
h("header.record-head", null, chip(label(t, "workspace.owner", owner.type)), mono(ownerId), h("span.spacer"), timeTag(workspace.updatedAt, locale, t)),
|
|
235
|
+
h("p.small.mono-line", null, workspace.root),
|
|
236
|
+
(workspace.entries || []).length ? h("div.meta-line", null, workspace.entries.map(function (entry) {
|
|
237
|
+
return h("span", null, entry.directory + " · ", mono(entry.branch || entry.baseRef || ""));
|
|
238
|
+
})) : null));
|
|
239
|
+
});
|
|
240
|
+
if (rows.childNodes.length) workspaces.body.append(rows);
|
|
241
|
+
workspaces.body.append(note(t("evidence.cleanupNote")), h("pre.code-block", null, "yui task archive-preflight " + task.id + " --integrated"));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
panel.append(work, reviews, delivery, filesCard(data, t, locale, ctx), workspaces);
|
|
245
|
+
panel.redraw = function () { drawWork(); drawReviews(); drawDelivery(); drawWorkspaces(); };
|
|
246
|
+
panel.redraw();
|
|
247
|
+
|
|
248
|
+
// Reads outlive one render: they redraw whichever Delivery panel is current
|
|
249
|
+
// and re-read when the Context cursor moved while they were in flight.
|
|
250
|
+
function refresh() {
|
|
251
|
+
const current = view.panels.delivery;
|
|
252
|
+
if (!current || !current.isConnected) return;
|
|
253
|
+
current.redraw();
|
|
254
|
+
if (!current.hidden) current.onShow();
|
|
255
|
+
}
|
|
256
|
+
panel.onShow = function () {
|
|
257
|
+
const key = core.coreCursor;
|
|
258
|
+
const previous = view.evidence || {};
|
|
259
|
+
if (previous.key !== key && previous.errorKey !== key && !previous.pending) {
|
|
260
|
+
view.evidence = { key: previous.key, value: previous.value, readAt: previous.readAt, error: null, pending: true };
|
|
261
|
+
ctx.evidence(task.id).then(function (value) {
|
|
262
|
+
view.evidence = { key: key, value: value, readAt: new Date().toISOString(), error: null, pending: false };
|
|
263
|
+
}, function (error) {
|
|
264
|
+
view.evidence = { key: previous.key, value: previous.value, readAt: previous.readAt, error: error.message, errorKey: key, pending: false };
|
|
265
|
+
}).then(refresh);
|
|
266
|
+
}
|
|
267
|
+
const entry = entriesOf(core, "remote-delivery")[0];
|
|
268
|
+
if (entry && entry.omitted && !(data.runtime && data.runtime.remoteDelivery)) {
|
|
269
|
+
const hit = view.exact["remote-delivery/" + entry.ref.refId];
|
|
270
|
+
if (!(hit && hit.digest === entry.ref.digest && hit.result)) {
|
|
271
|
+
readExact(entry, task.id, ctx, view.exact).then(refresh, function () {});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const family = familyState(view, "work-item");
|
|
275
|
+
if (family.key !== key && family.errorKey !== key && !family.pending) loadFamily(view, task.id, "work-item", key, ctx, { limit: 40 }).then(refresh);
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- Saved files ------------------------------------------------------------------
|
|
280
|
+
// Files are read on request at a fixed revision; the selected revision
|
|
281
|
+
// survives refreshes.
|
|
282
|
+
function filesCard(data, t, locale, actions) {
|
|
10
283
|
const state = data.viewState;
|
|
11
|
-
const
|
|
284
|
+
const load = button(state.artifactList ? t("evidence.reload") : t("evidence.load"), { icon: "refresh", variant: "ghost" });
|
|
285
|
+
const files = card({ id: "detail-files", title: t("evidence.files"), icon: "file", hint: t("evidence.filesHint"), actions: load });
|
|
12
286
|
const list = h("div.file-list");
|
|
13
287
|
const viewer = h("div.file-viewer");
|
|
14
|
-
const load = button(state.artifactList ? t("evidence.reload") : t("evidence.load"), { icon: "refresh", variant: "ghost" });
|
|
15
|
-
files.querySelector(".card-head").append(h("div.card-actions", null, load));
|
|
16
288
|
let readSequence = 0;
|
|
17
289
|
async function read(path, commit) {
|
|
18
290
|
const sequence = ++readSequence;
|
|
@@ -41,8 +313,7 @@ export function renderEvidence(container, data, t, locale, actions) {
|
|
|
41
313
|
viewer.append(h("header.viewer-head", null, icon("file"), h("strong", null, value.relativePath), h("span.spacer"), copy));
|
|
42
314
|
viewer.append(h("p.viewer-ref", null, mono(ref)));
|
|
43
315
|
viewer.append(h("p.faint.small", null, t("evidence.fixedNote")));
|
|
44
|
-
|
|
45
|
-
viewer.append(content);
|
|
316
|
+
viewer.append(h("pre.code-block.artifact-text", { tabIndex: 0, "aria-label": t("evidence.fixedText") }, value.content));
|
|
46
317
|
}
|
|
47
318
|
function drawList() {
|
|
48
319
|
clear(list);
|
|
@@ -63,6 +334,7 @@ export function renderEvidence(container, data, t, locale, actions) {
|
|
|
63
334
|
list.dataset.reading = "true";
|
|
64
335
|
try {
|
|
65
336
|
state.artifactList = await actions.artifacts(data.task.id);
|
|
337
|
+
if (placeholder) { placeholder.remove(); placeholder = null; }
|
|
66
338
|
drawList();
|
|
67
339
|
} catch (error) {
|
|
68
340
|
clear(list);
|
|
@@ -82,91 +354,11 @@ export function renderEvidence(container, data, t, locale, actions) {
|
|
|
82
354
|
referenced.append(h("button.file-row", { type: "button", onclick: function () { read(match[2], match[1]); } },
|
|
83
355
|
icon("flag", "icon-sm"), h("span.file-name", null, match[2]), badge(t("evidence.referenced"), "accent")));
|
|
84
356
|
});
|
|
85
|
-
|
|
357
|
+
let placeholder = !state.artifactList && !seen.size ? emptyState(t("evidence.notLoaded"), "file") : null;
|
|
358
|
+
if (placeholder) files.body.append(placeholder);
|
|
86
359
|
files.body.append(referenced, list, viewer);
|
|
87
360
|
drawList();
|
|
88
361
|
drawViewer();
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const delivery = card({ title: t("evidence.delivery"), icon: "layers", hint: t("evidence.deliveryHint") });
|
|
92
|
-
const output = h("div.stack");
|
|
93
|
-
const open = button(t("evidence.readDelivery"), { icon: "eye", variant: "ghost" });
|
|
94
|
-
delivery.querySelector(".card-head").append(h("div.card-actions", null, open));
|
|
95
|
-
delivery.body.append(output);
|
|
96
|
-
open.addEventListener("click", function () {
|
|
97
|
-
open.disabled = true;
|
|
98
|
-
drawDelivery(output, data, t, actions).finally(function () { open.disabled = false; });
|
|
99
|
-
});
|
|
100
|
-
container.append(delivery);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function drawDelivery(output, data, t, actions) {
|
|
104
|
-
clear(output);
|
|
105
|
-
const runtime = data.runtimeStatus === "available" ? data.runtime : null;
|
|
106
|
-
if (!runtime) output.append(note(t("evidence.observationUnavailable")));
|
|
107
|
-
const remote = runtime && runtime.remoteDelivery;
|
|
108
|
-
if (remote) {
|
|
109
|
-
output.append(h("h4.sub-head", null, t("evidence.remote"), badge(t("remoteDelivery." + remote.status, remote.status), remote.status === "merged" ? "ok" : "idle")));
|
|
110
|
-
remote.projects.forEach(function (project) {
|
|
111
|
-
output.append(h("article.record", null,
|
|
112
|
-
h("header.record-head", null, h("strong", null, project.directory), h("span.faint.small", null, project.coverage)),
|
|
113
|
-
h("p.small", null, project.reason),
|
|
114
|
-
h("p.faint.small", null, t("evidence.expectedCommit") + " ", mono(project.expectedLocalCommit || t("common.unknown"))),
|
|
115
|
-
h("p.faint.small", null, "PR/MR · " + (project.state || "none") + " · " + t("evidence.verification") + " " + (project.verification || "unverified")),
|
|
116
|
-
project.adoption ? h("p.faint.small", null, t("evidence.adoption") + " ", mono(project.adoption.id)) : null));
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
const original = h("div.stack", null, h("p.faint", null, t("evidence.readingOriginal")));
|
|
120
|
-
output.append(original);
|
|
121
|
-
try {
|
|
122
|
-
const evidence = await actions.evidence(data.task.id);
|
|
123
|
-
clear(original);
|
|
124
|
-
original.append(h("h4.sub-head", null, t("evidence.integrations")));
|
|
125
|
-
if (!evidence.integrations.length) original.append(note(t("evidence.noIntegrations")));
|
|
126
|
-
evidence.integrations.forEach(function (attempt) {
|
|
127
|
-
const element = h("article.record", null,
|
|
128
|
-
h("header.record-head", null, mono(attempt.id), h("span.spacer"), badge(attempt.status, attempt.status === "succeeded" ? "ok" : "idle")),
|
|
129
|
-
h("p.faint.small", null, t("evidence.candidate") + " ", mono(attempt.candidateCommit || t("evidence.noCandidate"))));
|
|
130
|
-
if (!attempt.checks || !attempt.checks.length) element.append(h("p.faint.small", null, t("evidence.noChecks")));
|
|
131
|
-
(attempt.checks || []).forEach(function (check) {
|
|
132
|
-
element.append(h("p.small", null, check.name + " · " + check.outcome));
|
|
133
|
-
if (check.details) element.append(h("p.faint.small", null, check.details));
|
|
134
|
-
});
|
|
135
|
-
original.append(element);
|
|
136
|
-
});
|
|
137
|
-
original.append(h("h4.sub-head", null, t("evidence.reviews")));
|
|
138
|
-
if (!evidence.reviews.length) original.append(note(t("evidence.noReviews")));
|
|
139
|
-
evidence.reviews.forEach(function (round) {
|
|
140
|
-
const element = h("article.record", null,
|
|
141
|
-
h("header.record-head", null, mono(round.id), h("span.spacer"), badge(round.status, round.status === "completed" ? "ok" : "idle")),
|
|
142
|
-
h("p.faint.small.mono-line", null, round.taskCandidate
|
|
143
|
-
? round.taskCandidate.projects.map(function (p) { return p.projectId + " · " + p.commit; }).join(" ")
|
|
144
|
-
: [round.workItemId, round.candidateId, round.reviewBaseCommit].filter(Boolean).join(" · ")));
|
|
145
|
-
if (round.reviewerRunId) {
|
|
146
|
-
const report = button(t("evidence.readReport"), { icon: "eye", variant: "link" });
|
|
147
|
-
report.addEventListener("click", async function () {
|
|
148
|
-
report.disabled = true;
|
|
149
|
-
try {
|
|
150
|
-
const source = await actions.inspect(data.task.id, { store: "run", refId: round.reviewerRunId });
|
|
151
|
-
element.append(richText(round.reviewerRunId, (source.value.result && source.value.result.output) || t("evidence.noReport"), t));
|
|
152
|
-
report.remove();
|
|
153
|
-
} catch (error) { element.append(note(error.message, "bad")); report.disabled = false; }
|
|
154
|
-
});
|
|
155
|
-
element.append(report);
|
|
156
|
-
}
|
|
157
|
-
original.append(element);
|
|
158
|
-
});
|
|
159
|
-
original.append(h("h4.sub-head", null, t("evidence.workspaces")));
|
|
160
|
-
if (!evidence.workspaces.length) original.append(note(t("evidence.noWorkspaces")));
|
|
161
|
-
evidence.workspaces.forEach(function (workspace) {
|
|
162
|
-
original.append(h("p.small.mono-line", null, workspace.owner.type + " · " + workspace.root));
|
|
163
|
-
});
|
|
164
|
-
original.append(note(t("evidence.cleanupNote")), h("pre.code-block", null, "yui task archive-preflight " + data.task.id + " --integrated"));
|
|
165
|
-
} catch (error) {
|
|
166
|
-
clear(original);
|
|
167
|
-
original.append(note(t("evidence.originalUnavailable") + " " + error.message, "bad"));
|
|
168
|
-
}
|
|
169
|
-
const metrics = runtime && observabilityMetricCard(runtime.observability, t);
|
|
170
|
-
if (metrics) output.append(h("h4.sub-head", null, t("evidence.usage")), metrics);
|
|
362
|
+
return files;
|
|
171
363
|
}
|
|
172
364
|
`;
|