@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
|
@@ -9,7 +9,7 @@ import { formatClock } from "/assets/js/format.js";
|
|
|
9
9
|
import { h } from "/assets/js/dom.js";
|
|
10
10
|
import { renderAttentionBar, renderFilters, renderTasks, STATUS_FILTERS, ATTENTION_KINDS } from "/assets/js/sidebar.js";
|
|
11
11
|
import { renderOverview } from "/assets/js/overview.js";
|
|
12
|
-
import { renderTaskDetail, selectTab, updateObservation, TABS } from "/assets/js/detail.js";
|
|
12
|
+
import { renderTaskDetail, selectTab, updateObservation, TABS, TAB_ALIASES } from "/assets/js/detail.js";
|
|
13
13
|
import { renderDiscussion, discussionHasUnsent, createTerminalController, sameTarget } from "/assets/js/dock.js";
|
|
14
14
|
import { entriesOf } from "/assets/js/records.js";
|
|
15
15
|
|
|
@@ -118,6 +118,7 @@ function detailContext() {
|
|
|
118
118
|
openSession: function (roleName) { openSession({ scope: "task", taskId: state.selected, roleName: roleName }); },
|
|
119
119
|
answerInput: answerInput,
|
|
120
120
|
inspect: api.inspect,
|
|
121
|
+
list: api.list,
|
|
121
122
|
artifacts: api.artifacts,
|
|
122
123
|
readArtifact: api.artifact,
|
|
123
124
|
evidence: api.evidence,
|
|
@@ -132,6 +133,7 @@ function dockActions() {
|
|
|
132
133
|
return {
|
|
133
134
|
sendMessage: api.sendMessage,
|
|
134
135
|
inspect: api.inspect,
|
|
136
|
+
list: api.list,
|
|
135
137
|
afterWrite: function () { refreshDashboard({ quiet: true }); }
|
|
136
138
|
};
|
|
137
139
|
}
|
|
@@ -230,15 +232,17 @@ function switchTab(tab, options) {
|
|
|
230
232
|
if (!(options && options.keepScroll)) el.center.scrollTop = 0;
|
|
231
233
|
setSectionParam(tab);
|
|
232
234
|
}
|
|
233
|
-
// A section may name a tab or any anchored block inside
|
|
234
|
-
// "reviews" → the
|
|
235
|
+
// A section may name a tab, a retired tab name, or any anchored block inside
|
|
236
|
+
// one (for example "reviews" → the Delivery tab, scrolled to #detail-reviews).
|
|
235
237
|
function applySection(section) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
+
const tab = (section && TAB_ALIASES[section]) || section;
|
|
239
|
+
if (!tab || TABS.includes(tab)) { switchTab(tab || "overview"); return; }
|
|
240
|
+
const anchor = el.detail.querySelector("#detail-" + CSS.escape(tab));
|
|
238
241
|
const panel = anchor && anchor.closest(".tab-panel");
|
|
239
242
|
if (!panel) { switchTab("overview"); return; }
|
|
240
243
|
state.activeTab = panel.id.replace(/^panel-/, "");
|
|
241
244
|
selectTab(el.detail, state.activeTab);
|
|
245
|
+
if (anchor.tagName === "DETAILS") anchor.open = true;
|
|
242
246
|
anchor.scrollIntoView({ block: "start" });
|
|
243
247
|
}
|
|
244
248
|
|
|
@@ -757,7 +761,7 @@ document.addEventListener("keydown", function (event) {
|
|
|
757
761
|
else if (key === "r") refreshDashboard();
|
|
758
762
|
else if (key === "o") openOperator();
|
|
759
763
|
else if (key === "d") toggleDock();
|
|
760
|
-
else if (/^[1-
|
|
764
|
+
else if (/^[1-9]$/.test(key) && Number(key) <= TABS.length && state.detail) switchTab(TABS[Number(key) - 1]);
|
|
761
765
|
});
|
|
762
766
|
|
|
763
767
|
// --- Boot -------------------------------------------------------------------------
|
|
@@ -39,7 +39,9 @@ const TONES = {
|
|
|
39
39
|
stopped: "bad", idle: "idle", background: "warn" },
|
|
40
40
|
role: { running: "ok", waiting: "warn", idle: "idle", unknown: "idle", failed: "bad", exited: "idle", detached: "idle" },
|
|
41
41
|
decision: { active: "ok", superseded: "idle" },
|
|
42
|
-
input: { open: "warn", answered: "ok", cancelled: "idle", "auto-resolved": "ok" }
|
|
42
|
+
input: { open: "warn", answered: "ok", cancelled: "idle", "auto-resolved": "ok" },
|
|
43
|
+
integration: { running: "info", validating: "info", committed: "ok", conflicted: "warn", blocked: "bad",
|
|
44
|
+
failed: "bad", superseded: "idle" }
|
|
43
45
|
};
|
|
44
46
|
export function tone(kind, value) {
|
|
45
47
|
return (TONES[kind] && TONES[kind][value]) || "idle";
|
|
@@ -81,17 +83,19 @@ export function timeTag(iso, locale, t, options) {
|
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
// --- Containers -------------------------------------------------------------------
|
|
86
|
+
// One panel of the single reading column: a heading (icon tile, title, count
|
|
87
|
+
// and hint) with optional actions, then the body. A "list-card" body holds a
|
|
88
|
+
// .row-stack whose rows are divided by hairlines instead of nested boxes.
|
|
84
89
|
export function card(options) {
|
|
85
90
|
const opts = options || {};
|
|
86
91
|
const element = h("section.card" + (opts.className ? "." + opts.className.split(" ").join(".") : ""));
|
|
87
92
|
if (opts.id) element.id = opts.id;
|
|
88
93
|
if (opts.title || opts.actions) {
|
|
89
|
-
const
|
|
90
|
-
h("h3.card-title", null, opts.icon ? icon(opts.icon) : null, h("span", null, opts.title),
|
|
94
|
+
const heading = h("div.card-heading", null,
|
|
95
|
+
h("h3.card-title", null, opts.icon ? h("span.card-icon", null, icon(opts.icon)) : null, h("span", null, opts.title),
|
|
91
96
|
opts.count !== undefined && opts.count !== null ? h("span.count", null, String(opts.count)) : null),
|
|
92
|
-
opts.
|
|
93
|
-
|
|
94
|
-
element.append(head);
|
|
97
|
+
opts.hint ? h("p.card-hint", null, opts.hint) : null);
|
|
98
|
+
element.append(h("header.card-head", null, heading, opts.actions ? h("div.card-actions", null, opts.actions) : null));
|
|
95
99
|
}
|
|
96
100
|
const body = h("div.card-body");
|
|
97
101
|
element.append(body);
|
|
@@ -99,6 +103,39 @@ export function card(options) {
|
|
|
99
103
|
return element;
|
|
100
104
|
}
|
|
101
105
|
|
|
106
|
+
// A panel whose body is a divided list of rows.
|
|
107
|
+
export function listCard(options) {
|
|
108
|
+
const element = card({ ...options, className: "list-card" + (options.className ? " " + options.className : "") });
|
|
109
|
+
element.list = h("div.row-stack");
|
|
110
|
+
element.body.append(element.list);
|
|
111
|
+
return element;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Update a card heading count after a later read; null removes it.
|
|
115
|
+
export function setCardCount(element, value) {
|
|
116
|
+
const title = element.querySelector(".card-title");
|
|
117
|
+
let count = title && title.querySelector(".count");
|
|
118
|
+
if (value === undefined || value === null) { if (count) count.remove(); return; }
|
|
119
|
+
if (!title) return;
|
|
120
|
+
if (!count) { count = h("span.count"); title.append(count); }
|
|
121
|
+
count.textContent = String(value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Local filter chips; selecting one redraws the list in place.
|
|
125
|
+
export function filterChips(ariaLabel, options, selected, onSelect) {
|
|
126
|
+
const row = h("div.chip-tabs", { role: "group", "aria-label": ariaLabel });
|
|
127
|
+
options.forEach(function (option) {
|
|
128
|
+
row.append(h("button.chip-tab", {
|
|
129
|
+
type: "button", "aria-pressed": String(option.value === selected), dataset: { filter: option.value },
|
|
130
|
+
onclick: function () {
|
|
131
|
+
row.querySelectorAll(".chip-tab").forEach(function (b) { b.setAttribute("aria-pressed", String(b.dataset.filter === option.value)); });
|
|
132
|
+
onSelect(option.value);
|
|
133
|
+
}
|
|
134
|
+
}, option.label, option.count === undefined || option.count === null ? null : h("span.count", null, String(option.count))));
|
|
135
|
+
});
|
|
136
|
+
return row;
|
|
137
|
+
}
|
|
138
|
+
|
|
102
139
|
export function disclosure(title, viewKey, options) {
|
|
103
140
|
const opts = options || {};
|
|
104
141
|
const element = h("details.disclosure" + (opts.className ? "." + opts.className : ""));
|
|
@@ -112,12 +149,6 @@ export function disclosure(title, viewKey, options) {
|
|
|
112
149
|
return element;
|
|
113
150
|
}
|
|
114
151
|
|
|
115
|
-
export function sectionTitle(text, count, actions) {
|
|
116
|
-
return h("div.section-title", null, h("h2", null, text,
|
|
117
|
-
count !== undefined && count !== null ? h("span.count", null, String(count)) : null),
|
|
118
|
-
actions ? h("div.section-actions", null, actions) : null);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
152
|
export function kv(rows) {
|
|
122
153
|
const list = h("dl.kv");
|
|
123
154
|
rows.forEach(function (row) {
|
|
@@ -206,9 +237,24 @@ export function metricTile(labelText, value, options) {
|
|
|
206
237
|
return tile;
|
|
207
238
|
}
|
|
208
239
|
|
|
240
|
+
function pageLocale() { return typeof document === "undefined" ? undefined : document.documentElement.lang || undefined; }
|
|
241
|
+
|
|
242
|
+
// Seconds as the largest whole unit plus the next one ("2d 22h", "2小时48分钟").
|
|
243
|
+
const DURATION_UNITS = [["day", 86400], ["hour", 3600], ["minute", 60], ["second", 1]];
|
|
244
|
+
function durationText(seconds) {
|
|
245
|
+
const total = Math.max(0, Math.round(seconds));
|
|
246
|
+
const found = DURATION_UNITS.findIndex(function (unit) { return total >= unit[1]; });
|
|
247
|
+
const first = found < 0 ? DURATION_UNITS.length - 1 : found;
|
|
248
|
+
return DURATION_UNITS.slice(first, first + 2).map(function (unit, index) {
|
|
249
|
+
const count = index ? Math.floor((total % DURATION_UNITS[first][1]) / unit[1]) : Math.floor(total / unit[1]);
|
|
250
|
+
return index && !count ? "" : new Intl.NumberFormat(pageLocale(), { style: "unit", unit: unit[0], unitDisplay: "narrow" }).format(count);
|
|
251
|
+
}).filter(Boolean).join(" ");
|
|
252
|
+
}
|
|
253
|
+
|
|
209
254
|
export function usageMetricText(metric, t, suffix) {
|
|
210
255
|
if (!metric || metric.value == null) return t("detail.unobserved");
|
|
211
|
-
|
|
256
|
+
const value = suffix === "s" ? durationText(metric.value) : metric.value + (suffix || "");
|
|
257
|
+
return value + (metric.status === "partial" ? " · " + t("detail.partial") : "");
|
|
212
258
|
}
|
|
213
259
|
|
|
214
260
|
function usageTile(labelText, metric, t, suffix) {
|
|
@@ -237,7 +283,7 @@ export function observabilityMetricCard(observability, t) {
|
|
|
237
283
|
const meta = node("p", "usage-meta");
|
|
238
284
|
meta.append(node("span", "", t("usage.scope")));
|
|
239
285
|
meta.append(node("span", "", t("usage.observedThrough") + " "
|
|
240
|
-
+ (cost.observedThrough ? formatDateTime(cost.observedThrough) : t("detail.unobserved"))));
|
|
286
|
+
+ (cost.observedThrough ? formatDateTime(cost.observedThrough, pageLocale()) : t("detail.unobserved"))));
|
|
241
287
|
meta.append(node("span", "", t("usage.contextBytes") + " "
|
|
242
288
|
+ (context.totalBytes == null ? t("detail.partial") : context.totalBytes + " B")));
|
|
243
289
|
wrap.append(meta);
|
|
@@ -300,30 +346,35 @@ export function workItemCard(item, t, locale, titles) {
|
|
|
300
346
|
const settled = item.status !== "open";
|
|
301
347
|
const element = h(settled ? "details.work-card.is-settled" : "article.work-card");
|
|
302
348
|
element.dataset.viewKey = "work:" + item.id;
|
|
303
|
-
|
|
349
|
+
element.append(h(settled ? "summary.work-head" : "header.work-head", null,
|
|
304
350
|
settled ? icon("chevron", "disclosure-chevron") : null,
|
|
305
351
|
dot(tone("work", item.status)),
|
|
306
352
|
h("span.work-title", null, item.title),
|
|
307
|
-
statusBadge(t, "work", "work", item.status));
|
|
308
|
-
element.append(
|
|
309
|
-
|
|
310
|
-
|
|
353
|
+
statusBadge(t, "work", "work", item.status)));
|
|
354
|
+
element.append(h("div.work-body", null, workItemBody(item, t, locale, titles)));
|
|
355
|
+
return element;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// The body of one work item, shared by the open card and a lazily read row.
|
|
359
|
+
export function workItemBody(item, t, locale, titles) {
|
|
360
|
+
const parts = [];
|
|
361
|
+
parts.push(h("div.meta-line", null,
|
|
311
362
|
item.assignee ? h("span.meta-strong", null, icon("user", "icon-sm"), item.assignee) : null,
|
|
312
363
|
mono(item.id), timeTag(item.updatedAt, locale, t)));
|
|
313
|
-
if (item.objective && item.objective !== item.title)
|
|
364
|
+
if (item.objective && item.objective !== item.title) parts.push(richText(t("work.objective"), item.objective, t));
|
|
314
365
|
if (item.acceptance && item.acceptance.length) {
|
|
315
|
-
|
|
366
|
+
parts.push(h("div.prose-block", null, h("h4.prose-label", null, t("work.acceptance")),
|
|
316
367
|
h("ul.checklist", null, item.acceptance.map(function (entry) {
|
|
317
368
|
return h("li" + (item.status === "accepted" ? ".is-done" : ""), null, icon(item.status === "accepted" ? "check" : "target", "icon-sm"), h("span", null, entry));
|
|
318
369
|
}))));
|
|
319
370
|
}
|
|
320
371
|
const deps = (item.dependsOn || []).map(function (id) { return (titles && titles[id]) || id; });
|
|
321
|
-
if (deps.length)
|
|
372
|
+
if (deps.length) parts.push(h("div.meta-line", null, h("span.meta-label", null, t("work.dependsOn")), deps.map(function (d) { return chip(d); })));
|
|
322
373
|
if (item.writeProjectIds && item.writeProjectIds.length) {
|
|
323
|
-
|
|
374
|
+
parts.push(h("div.meta-line", null, h("span.meta-label", null, t("work.writeProjects")), item.writeProjectIds.map(function (d) { return chip(d); })));
|
|
324
375
|
}
|
|
325
376
|
if (item.candidates && item.candidates.length) {
|
|
326
|
-
|
|
377
|
+
parts.push(h("div.prose-block", null, h("h4.prose-label", null, t("work.candidates")),
|
|
327
378
|
h("ol.candidates", null, item.candidates.slice().sort(function (a, b) { return b.sequence - a.sequence; }).map(function (candidate) {
|
|
328
379
|
return h("li", null, h("span.candidate-seq", null, "#" + candidate.sequence), h("span", null, candidate.summary),
|
|
329
380
|
h("span.faint", null, candidate.source && candidate.source.type === "run" ? candidate.source.runId : t("work.direct")),
|
|
@@ -331,18 +382,16 @@ export function workItemCard(item, t, locale, titles) {
|
|
|
331
382
|
}))));
|
|
332
383
|
}
|
|
333
384
|
if (item.disposition) {
|
|
334
|
-
|
|
385
|
+
parts.push(note(t("work.disposition") + ": " + item.disposition.summary
|
|
335
386
|
+ (item.disposition.replacementWorkItemId ? " → " + item.disposition.replacementWorkItemId : "")));
|
|
336
387
|
}
|
|
337
|
-
if (item.outcome)
|
|
338
|
-
|
|
339
|
-
return element;
|
|
388
|
+
if (item.outcome) parts.push(richText(t("work.outcome"), item.outcome, t, { className: "callout" }));
|
|
389
|
+
return parts.filter(Boolean);
|
|
340
390
|
}
|
|
341
391
|
|
|
342
392
|
export function runCard(run, t, locale) {
|
|
343
393
|
const element = h("article.run-card");
|
|
344
394
|
element.dataset.status = run.status;
|
|
345
|
-
const input = run.inputs && run.inputs.length ? run.inputs[0].input : null;
|
|
346
395
|
element.append(h("header.run-head", null,
|
|
347
396
|
dot(tone("run", run.status)),
|
|
348
397
|
h("span.run-role", null, run.roleName),
|
|
@@ -350,20 +399,27 @@ export function runCard(run, t, locale) {
|
|
|
350
399
|
run.purpose ? chip(label(t, "run.purpose", run.purpose)) : null,
|
|
351
400
|
run.workItemId ? chip(run.workItemId) : null,
|
|
352
401
|
h("span.spacer"),
|
|
353
|
-
statusBadge(t, "run", "run", run.status)));
|
|
354
|
-
element
|
|
402
|
+
statusBadge(t, "run", "run", run.status)), runBody(run, t, locale));
|
|
403
|
+
return element;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// What one execution record says: delivery, instruction, report, failure.
|
|
407
|
+
export function runBody(run, t, locale) {
|
|
408
|
+
const parts = [];
|
|
409
|
+
const input = run.inputs && run.inputs.length ? run.inputs[0].input : null;
|
|
410
|
+
parts.push(h("div.meta-line", null,
|
|
355
411
|
h("span", null, t("run.delivery") + ": " + label(t, "run.delivery", (run.execution && run.execution.delivery) || "unobserved")),
|
|
356
412
|
run.mode ? h("span", null, label(t, "mode", run.mode)) : null,
|
|
357
413
|
timeTag((run.result && run.result.completedAt) || run.updatedAt, locale, t),
|
|
358
414
|
agentChips(run.effective || (run.agentId ? run : null))));
|
|
359
415
|
const directive = input && (input.directive || input.action);
|
|
360
|
-
if (directive)
|
|
361
|
-
if (run.result && run.result.output)
|
|
362
|
-
if (run.result && run.result.diagnostic)
|
|
416
|
+
if (directive) parts.push(richText(t("run.instruction"), directive, t, { threshold: 320 }));
|
|
417
|
+
if (run.result && run.result.output) parts.push(richText(t("run.output"), run.result.output, t, { threshold: 320, className: "callout" }));
|
|
418
|
+
if (run.result && run.result.diagnostic) parts.push(richText(t("run.failure"), run.result.diagnostic, t, { threshold: 320, className: "callout.tone-bad" }));
|
|
363
419
|
if (run.executionGroupId) {
|
|
364
|
-
|
|
420
|
+
parts.push(h("p.faint.mono-line", null, t("run.lineage") + " " + run.executionGroupId + (run.executionLaneId ? "/" + run.executionLaneId : "")));
|
|
365
421
|
}
|
|
366
|
-
return
|
|
422
|
+
return parts.filter(Boolean);
|
|
367
423
|
}
|
|
368
424
|
|
|
369
425
|
export function reviewCard(round, t, locale) {
|
|
@@ -377,6 +433,9 @@ export function reviewCard(round, t, locale) {
|
|
|
377
433
|
statusBadge(t, "review", "review", round.status)));
|
|
378
434
|
element.append(h("div.meta-line", null,
|
|
379
435
|
round.workItemId ? mono(round.workItemId + (round.candidateId ? " · " + round.candidateId : "")) : null,
|
|
436
|
+
round.taskCandidate && round.taskCandidate.projects ? round.taskCandidate.projects.map(function (project) {
|
|
437
|
+
return mono(project.projectId + " @ " + String(project.commit).slice(0, 12));
|
|
438
|
+
}) : null,
|
|
380
439
|
round.reviewBaseCommit ? h("span", null, t("review.base") + " ", mono(String(round.reviewBaseCommit).slice(0, 12))) : null,
|
|
381
440
|
round.reviewerRunId ? h("span", null, t("review.run") + " ", mono(round.reviewerRunId)) : null,
|
|
382
441
|
timeTag(round.createdAt, locale, t)));
|
|
@@ -8,11 +8,15 @@ import {
|
|
|
8
8
|
badge, statusBadge, label, tone, card, disclosure, richText, bulletList, emptyState,
|
|
9
9
|
note, button, chip, mono, inputCard, kv, dot, timeTag
|
|
10
10
|
} from "/assets/js/components.js";
|
|
11
|
-
import { entriesOf, valuesOf, recordCard } from "/assets/js/records.js";
|
|
12
|
-
import {
|
|
11
|
+
import { entriesOf, valuesOf, totalOf, recordCard, readExact } from "/assets/js/records.js";
|
|
12
|
+
import { renderRuntime, renderRecords } from "/assets/js/sections.js";
|
|
13
|
+
import { renderDelivery } from "/assets/js/evidence.js";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
// Overview: where it stands. Delivery: what it produced. Runtime: what is
|
|
16
|
+
// live now. Records: what happened. Retired section names stay addressable.
|
|
17
|
+
export const TABS = ["overview", "delivery", "runtime", "records"];
|
|
18
|
+
export const TAB_ALIASES = { work: "delivery", execution: "runtime", history: "records", details: "records" };
|
|
19
|
+
const TAB_ICONS = { overview: "target", delivery: "package", runtime: "pulse", records: "history" };
|
|
16
20
|
const SESSION_GROUPS = ["active", "waiting", "background", "quiet", "diagnostic", "stopped", "unknown", "idle"];
|
|
17
21
|
|
|
18
22
|
export function renderTaskDetail(container, data, t, locale, ctx) {
|
|
@@ -20,6 +24,7 @@ export function renderTaskDetail(container, data, t, locale, ctx) {
|
|
|
20
24
|
const task = data.task;
|
|
21
25
|
const core = data.core;
|
|
22
26
|
container.dataset.taskId = task.id;
|
|
27
|
+
data.viewState.coreKey = core.coreCursor;
|
|
23
28
|
const page = h("div.page.task-page");
|
|
24
29
|
|
|
25
30
|
// --- Header ----------------------------------------------------------------
|
|
@@ -48,10 +53,12 @@ export function renderTaskDetail(container, data, t, locale, ctx) {
|
|
|
48
53
|
(task.projectBindings || []).length ? h("span.meta-item", null, icon("layers", "icon-sm"),
|
|
49
54
|
task.projectBindings.map(function (binding) { return binding.projectId; }).join(", ")) : null);
|
|
50
55
|
|
|
56
|
+
// Tab counts are current totals from the same bounded read, not samples:
|
|
57
|
+
// open questions, open work items and open execution records.
|
|
51
58
|
const counts = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
overview: core.attention.openInputs.count,
|
|
60
|
+
delivery: totalOf(core, "work-item"),
|
|
61
|
+
runtime: totalOf(core, "run")
|
|
55
62
|
};
|
|
56
63
|
const tabs = h("nav.tabs", { role: "tablist", "aria-label": t("tabs.label") }, TABS.map(function (tab, index) {
|
|
57
64
|
return h("button.tab", {
|
|
@@ -63,7 +70,7 @@ export function renderTaskDetail(container, data, t, locale, ctx) {
|
|
|
63
70
|
dataset: { tab: tab },
|
|
64
71
|
onclick: function () { ctx.onTab(tab); }
|
|
65
72
|
}, icon(TAB_ICONS[tab], "icon-sm"), h("span", null, t("tabs." + tab)),
|
|
66
|
-
counts[tab] ? h("span.count", null, String(counts[tab])) : null);
|
|
73
|
+
counts[tab] ? h("span.count" + (tab === "overview" ? ".tone-warn" : ""), null, String(counts[tab])) : null);
|
|
67
74
|
}));
|
|
68
75
|
tabs.addEventListener("keydown", function (event) {
|
|
69
76
|
if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return;
|
|
@@ -87,29 +94,38 @@ export function renderTaskDetail(container, data, t, locale, ctx) {
|
|
|
87
94
|
page.append(panels[tab]);
|
|
88
95
|
});
|
|
89
96
|
renderOverviewPanel(panels.overview, data, t, locale, ctx);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
renderDetails(panels.details, data, t, locale, ctx);
|
|
97
|
+
renderDelivery(panels.delivery, data, t, locale, ctx);
|
|
98
|
+
renderRuntime(panels.runtime, data, t, locale, ctx);
|
|
99
|
+
renderRecords(panels.records, data, t, locale, ctx);
|
|
94
100
|
container.append(page);
|
|
95
101
|
updateObservation(container, data, t, locale, ctx);
|
|
102
|
+
const active = panels[ctx.activeTab];
|
|
103
|
+
if (active && active.onShow) active.onShow();
|
|
96
104
|
}
|
|
97
105
|
|
|
106
|
+
// A panel may read on first show (evidence, record pages); it keeps those
|
|
107
|
+
// reads in view state, so showing it again does not repeat them.
|
|
98
108
|
export function selectTab(container, tab) {
|
|
99
109
|
container.querySelectorAll(".tab").forEach(function (button) {
|
|
100
110
|
const active = button.dataset.tab === tab;
|
|
101
111
|
button.setAttribute("aria-selected", String(active));
|
|
102
112
|
button.tabIndex = active ? 0 : -1;
|
|
103
113
|
});
|
|
104
|
-
container.querySelectorAll(".tab-panel").forEach(function (panel) {
|
|
114
|
+
container.querySelectorAll(".tab-panel").forEach(function (panel) {
|
|
115
|
+
panel.hidden = panel.id !== "panel-" + tab;
|
|
116
|
+
if (!panel.hidden && panel.onShow) panel.onShow();
|
|
117
|
+
});
|
|
105
118
|
}
|
|
106
119
|
|
|
120
|
+
// One reading column, top to bottom: where the Task stands now, what needs the
|
|
121
|
+
// user, what it has reported, its goal and its current decisions.
|
|
107
122
|
function renderOverviewPanel(panel, data, t, locale, ctx) {
|
|
108
123
|
const task = data.task;
|
|
109
124
|
const core = data.core;
|
|
110
|
-
// Now: the derived execution status (observation)
|
|
125
|
+
// Now: the derived execution status (observation), the open questions and
|
|
126
|
+
// who acts next. drawNow() tints the banner with the status tone.
|
|
111
127
|
const now = card({ className: "now-card" });
|
|
112
|
-
now.body.append(h("div", { dataset: { slot: "now" } }));
|
|
128
|
+
now.body.append(h("div", { dataset: { slot: "now" } }), h("p.next-line", { dataset: { slot: "next" } }));
|
|
113
129
|
panel.append(now);
|
|
114
130
|
|
|
115
131
|
// Needs you: open InputRequests with their answer controls.
|
|
@@ -129,15 +145,28 @@ function renderOverviewPanel(panel, data, t, locale, ctx) {
|
|
|
129
145
|
panel.append(needs);
|
|
130
146
|
}
|
|
131
147
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const sessions = card({ title: t("sessions.title"), icon: "terminal", hint: t("sessions.hint") });
|
|
136
|
-
sessions.body.append(h("div", { dataset: { slot: "sessions" } }));
|
|
137
|
-
panel.append(h("div.card-columns", null,
|
|
138
|
-
h("div.card-column", null, progressCard(data, t, locale, ctx), goalCard(data, t, ctx)),
|
|
139
|
-
h("div.card-column", null, sessions, decisionsCard(data, t, locale, ctx))));
|
|
148
|
+
const progress = progressCard(data, t, locale, ctx);
|
|
149
|
+
const goal = goalCard(data, t, ctx);
|
|
150
|
+
panel.append(progress);
|
|
140
151
|
if (task.status === "draft") panel.append(planningCard(data, t, locale));
|
|
152
|
+
panel.append(goal, decisionsCard(data, t, locale, ctx));
|
|
153
|
+
// The Brief is the Overview's main source; when the bounded read withheld
|
|
154
|
+
// its value, read it exactly once per digest and redraw both cards.
|
|
155
|
+
const briefEntry = entriesOf(core, "task-brief")[0];
|
|
156
|
+
if (briefEntry && briefEntry.omitted) {
|
|
157
|
+
const view = data.viewState;
|
|
158
|
+
view.exact = view.exact || {};
|
|
159
|
+
readExact(briefEntry, task.id, ctx, view.exact).then(function (result) {
|
|
160
|
+
if (!progress.isConnected) return;
|
|
161
|
+
const exact = { ...data, briefValue: result.value };
|
|
162
|
+
progress.replaceWith(progressCard(exact, t, locale, ctx));
|
|
163
|
+
goal.replaceWith(goalCard(exact, t, ctx));
|
|
164
|
+
}, function () {});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function briefOf(data) {
|
|
169
|
+
return data.briefValue || valuesOf(data.core, "task-brief")[0];
|
|
141
170
|
}
|
|
142
171
|
|
|
143
172
|
function progressCard(data, t, locale, ctx) {
|
|
@@ -148,20 +177,19 @@ function progressCard(data, t, locale, ctx) {
|
|
|
148
177
|
if (task.retirementSummary) element.body.append(richText(t("progress.retirement"), task.retirementSummary, t, { className: "callout" }));
|
|
149
178
|
if (task.archiveSummary || task.archiveReason) element.body.append(richText(t("progress.archive"), [task.archiveSummary, task.archiveReason].filter(Boolean).join("\n\n"), t, { className: "callout" }));
|
|
150
179
|
const briefEntry = entriesOf(data.core, "task-brief")[0];
|
|
151
|
-
const brief =
|
|
180
|
+
const brief = briefOf(data);
|
|
152
181
|
if (brief) {
|
|
153
182
|
element.body.append(richText(t(working ? "progress.focus" : "progress.lastFocus"), brief.currentFocus, t));
|
|
154
183
|
element.body.append(richText(t(working ? "progress.report" : "progress.lastReport"), brief.leaderSummary, t, { muted: true }));
|
|
155
184
|
element.body.append(h("p.faint.small", null, t("progress.briefUpdated") + " " + formatDateTime(brief.updatedAt, locale)));
|
|
156
185
|
} else if (briefEntry) element.body.append(recordCard(briefEntry, task.id, t, ctx, { compact: true }));
|
|
157
186
|
else element.body.append(emptyState(t("progress.none")));
|
|
158
|
-
element.body.append(h("p.next-line", { dataset: { slot: "next" } }));
|
|
159
187
|
return element;
|
|
160
188
|
}
|
|
161
189
|
|
|
162
190
|
function goalCard(data, t, ctx) {
|
|
163
191
|
const task = data.task;
|
|
164
|
-
const brief =
|
|
192
|
+
const brief = briefOf(data);
|
|
165
193
|
const element = card({ title: t("goal.title"), icon: "target" });
|
|
166
194
|
if (brief && brief.objective) element.body.append(richText(null, brief.objective, t));
|
|
167
195
|
if (brief && brief.boundaries && brief.boundaries.length) {
|
|
@@ -184,7 +212,8 @@ function goalCard(data, t, ctx) {
|
|
|
184
212
|
function decisionsCard(data, t, locale, ctx) {
|
|
185
213
|
const entries = entriesOf(data.core, "task-decision");
|
|
186
214
|
const active = entries.filter(function (entry) { return !entry.omitted && entry.value.status === "active"; });
|
|
187
|
-
const
|
|
215
|
+
const total = totalOf(data.core, "task-decision");
|
|
216
|
+
const element = card({ id: "detail-decisions", title: t("decisions.title"), icon: "check", count: total || null, hint: t("decisions.hint") });
|
|
188
217
|
active.slice(0, 3).forEach(function (entry) {
|
|
189
218
|
const value = entry.value;
|
|
190
219
|
element.body.append(h("article.decision", null,
|
|
@@ -192,9 +221,10 @@ function decisionsCard(data, t, locale, ctx) {
|
|
|
192
221
|
value.rationale ? richText(null, value.rationale, t, { muted: true, threshold: 260 }) : null));
|
|
193
222
|
});
|
|
194
223
|
if (!active.length) element.body.append(emptyState(t("decisions.none")));
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
224
|
+
element.body.append(button(t("decisions.more"), { variant: "link", icon: "chevron", onClick: function () {
|
|
225
|
+
data.viewState.recordsFilter = "decision";
|
|
226
|
+
ctx.onTab("records");
|
|
227
|
+
} }));
|
|
198
228
|
return element;
|
|
199
229
|
}
|
|
200
230
|
|
|
@@ -240,12 +270,7 @@ export function updateObservation(container, data, t, locale, ctx) {
|
|
|
240
270
|
if (execution && !(execution.next.owner === "none" && execution.next.action === "none")) next.append(icon("flag", "icon-sm"), h("span", null, t("now.nextOwner") + " "),
|
|
241
271
|
h("strong", null, label(t, "exec.owner", execution.next.owner)), h("span", null, " · " + label(t, "exec.action", execution.next.action)));
|
|
242
272
|
}
|
|
243
|
-
|
|
244
|
-
if (sessions && !sessions.contains(document.activeElement)) {
|
|
245
|
-
const open = sessions.querySelector("details") && sessions.querySelector("details").open;
|
|
246
|
-
drawSessions(sessions, available ? data.runtime.sessions : null, t, locale);
|
|
247
|
-
if (open && sessions.querySelector("details")) sessions.querySelector("details").open = true;
|
|
248
|
-
}
|
|
273
|
+
drawSessions(container, available ? data.runtime.sessions : null, t, locale);
|
|
249
274
|
const raw = container.querySelector('[data-slot="runtime-raw"]');
|
|
250
275
|
if (raw) raw.textContent = available ? JSON.stringify({ roles: data.runtime.roles, runtimeHealth: data.runtime.runtimeHealth }, null, 2) : "";
|
|
251
276
|
const rawStatus = container.querySelector('[data-slot="runtime-status"]');
|
|
@@ -257,9 +282,16 @@ export function updateObservation(container, data, t, locale, ctx) {
|
|
|
257
282
|
});
|
|
258
283
|
}
|
|
259
284
|
|
|
285
|
+
const BANNER_TONES = ["tone-info", "tone-ok", "tone-warn", "tone-bad", "tone-idle", "tone-accent"];
|
|
286
|
+
|
|
260
287
|
function drawNow(slot, data, execution, t, locale) {
|
|
261
288
|
clear(slot);
|
|
262
289
|
const inputCount = data.core.attention.openInputs.count;
|
|
290
|
+
const banner = slot.closest(".now-card");
|
|
291
|
+
if (banner) {
|
|
292
|
+
banner.classList.remove.apply(banner.classList, BANNER_TONES);
|
|
293
|
+
banner.classList.add("tone-" + (execution ? tone("exec", execution.status) : data.runtimeStatus === "unavailable" ? "bad" : "idle"));
|
|
294
|
+
}
|
|
263
295
|
if (!execution) {
|
|
264
296
|
slot.append(h("div.now-head", null,
|
|
265
297
|
badge(t("observation." + data.runtimeStatus), data.runtimeStatus === "unavailable" ? "bad" : "idle", { dot: true }),
|
|
@@ -300,34 +332,48 @@ function drawNow(slot, data, execution, t, locale) {
|
|
|
300
332
|
else slot.append(h("p.now-note", null, inputCount ? t("now.inputs").replace("{count}", String(inputCount)) : t("now.clear")));
|
|
301
333
|
}
|
|
302
334
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
h("
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
observation.sessions.forEach(function (session) {
|
|
321
|
-
facts.body.append(kv([
|
|
322
|
-
[t("sessions.role"), session.roleName],
|
|
323
|
-
[t("sessions.lastActivity"), session.lastActivityAt ? formatDateTime(session.lastActivityAt, locale) : t("sessions.unobserved")],
|
|
324
|
-
[t("sessions.inputUpdated"), formatDateTime(session.sourceUpdatedAt, locale)],
|
|
325
|
-
[t("sessions.operations"), session.operations.length ? session.operations.join(", ") : null],
|
|
326
|
-
[t("sessions.background"), session.background.length ? session.background.map(function (item) { return item.id + " · " + item.execution; }).join(", ") : null],
|
|
327
|
-
[t("sessions.identity"), mono([session.nativeSessionId, session.nativeTurnId, session.attemptId].filter(Boolean).join(" / "))]
|
|
328
|
-
]));
|
|
335
|
+
// Native Session observation, drawn into the Runtime tab: one line per Role,
|
|
336
|
+
// the group counts beside the Roles heading, and the timestamps under
|
|
337
|
+
// Diagnostics. Activity is never delivery progress.
|
|
338
|
+
function drawSessions(container, observation, t, locale) {
|
|
339
|
+
const sessions = observation ? observation.sessions : [];
|
|
340
|
+
container.querySelectorAll("[data-role-session]").forEach(function (slot) {
|
|
341
|
+
clear(slot);
|
|
342
|
+
const session = sessions.find(function (item) { return item.roleName === slot.dataset.roleSession; });
|
|
343
|
+
if (!session) {
|
|
344
|
+
slot.append(h("span.faint.small", null, observation ? t("session.noneRecorded") : t("sessions.unavailable")));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
slot.append(h("div.role-session-head", null, dot(tone("session", session.group)),
|
|
348
|
+
h("strong", null, t("session." + session.group)),
|
|
349
|
+
h("span.faint.small", null, session.lastActivityAt ? relativeTime(session.lastActivityAt, locale, t) : t("sessions.unobserved"))));
|
|
350
|
+
if (session.reason) slot.append(h("p.small", null, session.reason));
|
|
351
|
+
if (session.waitingReason) slot.append(h("p.small", null, t("sessions.waitingFor") + " " + session.waitingReason));
|
|
329
352
|
});
|
|
330
|
-
|
|
331
|
-
|
|
353
|
+
const counts = container.querySelector('[data-slot="session-counts"]');
|
|
354
|
+
if (counts) {
|
|
355
|
+
clear(counts);
|
|
356
|
+
if (observation) SESSION_GROUPS.forEach(function (group) {
|
|
357
|
+
if (observation.counts[group]) counts.append(badge(observation.counts[group] + " " + t("session." + group), tone("session", group), { dot: true }));
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
const facts = container.querySelector('[data-slot="session-facts"]');
|
|
361
|
+
if (facts) {
|
|
362
|
+
clear(facts);
|
|
363
|
+
if (!observation) return;
|
|
364
|
+
facts.append(h("h4.sub-head", null, t("sessions.facts")));
|
|
365
|
+
if (!sessions.length) facts.append(note(t("sessions.none")));
|
|
366
|
+
sessions.forEach(function (session) {
|
|
367
|
+
facts.append(kv([
|
|
368
|
+
[t("sessions.role"), session.roleName],
|
|
369
|
+
[t("sessions.lastActivity"), session.lastActivityAt ? formatDateTime(session.lastActivityAt, locale) : t("sessions.unobserved")],
|
|
370
|
+
[t("sessions.inputUpdated"), formatDateTime(session.sourceUpdatedAt, locale)],
|
|
371
|
+
[t("sessions.operations"), session.operations.length ? session.operations.join(", ") : null],
|
|
372
|
+
[t("sessions.background"), session.background.length ? session.background.map(function (item) { return item.id + " · " + item.execution; }).join(", ") : null],
|
|
373
|
+
[t("sessions.identity"), mono([session.nativeSessionId, session.nativeTurnId, session.attemptId].filter(Boolean).join(" / "))]
|
|
374
|
+
]));
|
|
375
|
+
});
|
|
376
|
+
facts.append(h("p.faint.small", null, t("sessions.scope") + " " + formatDateTime(observation.readAt, locale)));
|
|
377
|
+
}
|
|
332
378
|
}
|
|
333
379
|
`;
|