@zq-silk/yui 1.1.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/ARCHITECTURE.md +2 -1
  2. package/ARCHITECTURE.zh-CN.md +2 -1
  3. package/README.md +2 -0
  4. package/dist/cli/commandCatalog.js +344 -324
  5. package/dist/cli/commandDiscovery.js +69 -0
  6. package/dist/cli/completion.js +22 -227
  7. package/dist/cli/dynamicCompletion.js +12 -6
  8. package/dist/cli/invocationAuthority.js +2 -2
  9. package/dist/cli/invocationRouter.js +18 -7
  10. package/dist/cli/managedDiagnostics.js +2 -1
  11. package/dist/cli/updatePorts.js +0 -5
  12. package/dist/cli.js +184 -130
  13. package/dist/commands/capabilityCommands.js +5 -5
  14. package/dist/commands/controllerCommands.js +2 -1
  15. package/dist/commands/globalRoleCommands.js +86 -44
  16. package/dist/commands/grantCommands.js +48 -8
  17. package/dist/commands/taskCommands.js +157 -196
  18. package/dist/commands/taskContextCommand.js +29 -6
  19. package/dist/commands/taskFactCommands.js +10 -69
  20. package/dist/commands/taskInputCommands.js +26 -35
  21. package/dist/commands/taskPublicationCommands.js +2 -35
  22. package/dist/commands/taskPublicationVerifyCommand.js +1 -2
  23. package/dist/commands/taskUpstreamCommands.js +2 -1
  24. package/dist/context/runContextPack.js +8 -29
  25. package/dist/context/sessionBootstrapManifest.js +5 -101
  26. package/dist/context/taskContext.js +104 -41
  27. package/dist/controller/clientRuntime.js +16 -8
  28. package/dist/controller/fileSchedulerStoreAdapter.js +24 -0
  29. package/dist/controller/runtime.js +6 -0
  30. package/dist/core/controllerClient.js +46 -21
  31. package/dist/core/protocol.js +12 -7
  32. package/dist/doctor/doctor.js +4 -3
  33. package/dist/errors/cliError.js +4 -4
  34. package/dist/errors/cliFailure.js +213 -0
  35. package/dist/executor/fileRoleLaunchPlanner.js +1 -4
  36. package/dist/grant/capabilityGrant.js +13 -0
  37. package/dist/grant/taskAuthorization.js +127 -0
  38. package/dist/kernel/accessAssessment.js +1 -0
  39. package/dist/kernel/builtinCapabilities.js +88 -9
  40. package/dist/kernel/capabilityRegistry.js +64 -19
  41. package/dist/kernel/kernelPorts.js +2 -2
  42. package/dist/nativeAgent/agent.js +173 -0
  43. package/dist/nativeAgent/contracts.js +1 -0
  44. package/dist/nativeAgent/demo.js +29 -0
  45. package/dist/nativeAgent/index.js +3 -0
  46. package/dist/nativeAgent/mockProvider.js +43 -0
  47. package/dist/nativeAgent/textTools.js +156 -0
  48. package/dist/nativeAgent/validation.js +95 -0
  49. package/dist/output/boundedRead.js +118 -0
  50. package/dist/plugins/pluginService.js +98 -8
  51. package/dist/release/releaseWorkflowEngine.js +8 -1
  52. package/dist/resources/projectResourceService.js +23 -1
  53. package/dist/runtime/managedCaller.js +2 -2
  54. package/dist/runtime/managedIdentity.js +6 -0
  55. package/dist/runtime/runtimeCoherence.js +10 -8
  56. package/dist/storage/sqliteSchema.js +10 -0
  57. package/dist/storage/storageVersions.js +1 -1
  58. package/dist/storage/taskStore.js +3 -0
  59. package/dist/surface/surfaceContributions.js +3 -0
  60. package/dist/task/leaderArchive.js +111 -0
  61. package/dist/task/leaderArchiveAuthority.js +24 -0
  62. package/dist/task/taskAuthority.js +5 -10
  63. package/dist/tmux/commandExecutor.js +7 -5
  64. package/dist/web/assets/assetManifest.js +38 -22
  65. package/dist/web/assets/client/api.js +115 -0
  66. package/dist/web/assets/client/app.js +555 -863
  67. package/dist/web/assets/client/components.js +323 -897
  68. package/dist/web/assets/client/detail.js +333 -0
  69. package/dist/web/assets/client/dock.js +225 -0
  70. package/dist/web/assets/client/dom.js +55 -3
  71. package/dist/web/assets/client/evidence.js +172 -0
  72. package/dist/web/assets/client/format.js +25 -14
  73. package/dist/web/assets/client/forms.js +230 -0
  74. package/dist/web/assets/client/i18n.js +1020 -791
  75. package/dist/web/assets/client/overview.js +115 -0
  76. package/dist/web/assets/client/records.js +63 -0
  77. package/dist/web/assets/client/sections.js +313 -0
  78. package/dist/web/assets/client/sidebar.js +117 -0
  79. package/dist/web/assets/client/theme.js +61 -22
  80. package/dist/web/assets/icons.js +39 -0
  81. package/dist/web/assets/shell.js +124 -118
  82. package/dist/web/assets/styles/base.js +41 -0
  83. package/dist/web/assets/styles/components.js +167 -0
  84. package/dist/web/assets/styles/layout.js +49 -77
  85. package/dist/web/assets/styles/markdown.js +16 -24
  86. package/dist/web/assets/styles/responsive.js +33 -44
  87. package/dist/web/assets/styles/tokens.js +65 -89
  88. package/dist/web/assets/styles/views.js +289 -0
  89. package/dist/web/webServer.js +2 -1
  90. package/docs/architecture/README.md +1 -0
  91. package/docs/architecture/README.zh-CN.md +1 -0
  92. package/docs/cli-information-contract.md +107 -0
  93. package/docs/cli-information-contract.zh-CN.md +81 -0
  94. package/docs/managed-turn-and-session-runtime.md +9 -0
  95. package/docs/managed-turn-and-session-runtime.zh-CN.md +6 -0
  96. package/docs/plugin-sdk.md +7 -3
  97. package/docs/plugin-sdk.zh-CN.md +5 -2
  98. package/docs/release-workflow.md +42 -9
  99. package/docs/release-workflow.zh-CN.md +30 -7
  100. package/docs/storage-baseline.md +5 -0
  101. package/docs/testing/verification-levels.md +2 -2
  102. package/i18n/README.zh-CN.md +2 -0
  103. package/package.json +1 -1
  104. package/skills/yui-leader/SKILL.md +8 -1
  105. package/skills/yui-leader/references/authorization.md +64 -0
  106. package/skills/yui-leader/references/execution.md +18 -4
  107. package/skills/yui-leader/references/task-plugins.md +4 -2
  108. package/skills/yui-operator/SKILL.md +9 -2
  109. package/skills/yui-runtime/SKILL.md +77 -9
  110. package/skills/yui-runtime/references/publication.md +3 -1
  111. package/dist/web/assets/client/taskSummary.js +0 -350
  112. package/dist/web/assets/client/taskSurface.js +0 -616
  113. package/dist/web/assets/client/view.js +0 -608
  114. package/dist/web/assets/styles/cards.js +0 -247
  115. package/dist/web/assets/styles/widgets.js +0 -168
@@ -0,0 +1,115 @@
1
+ export const OVERVIEW_SCRIPT = String.raw `
2
+ // Workspace overview, shown in the center while no Task is selected.
3
+ import { h, icon, clear } from "/assets/js/dom.js";
4
+ import { byNewest, formatDateTime, relativeTime } from "/assets/js/format.js";
5
+ import { card, dot, tone, label, metricTile, emptyState, button, badge, note } from "/assets/js/components.js";
6
+ import { ATTENTION_KINDS } from "/assets/js/sidebar.js";
7
+
8
+ const SESSION_GROUPS = ["active", "waiting", "background", "quiet", "diagnostic", "stopped", "unknown", "idle"];
9
+
10
+ export function renderOverview(container, state, t, locale, actions) {
11
+ clear(container);
12
+ const counts = state.counts;
13
+ const page = h("div.page.overview");
14
+ page.append(h("header.page-head", null,
15
+ h("div.page-head-main", null,
16
+ h("p.kicker", null, t("overview.kicker")),
17
+ h("h1.page-title", null, t("overview.title")),
18
+ h("p.page-sub", null, counts
19
+ ? t("overview.sub").replace("{total}", String(counts.total)).replace("{active}", String(counts.active || 0))
20
+ : t("loading.dashboard"))),
21
+ h("div.page-head-actions", null,
22
+ button(t("actions.operator"), { icon: "terminal", variant: "ghost", onClick: actions.openOperator }))));
23
+
24
+ page.append(h("div.metric-strip", null,
25
+ metricTile(t("metrics.inputs"), counts ? counts.openInputs : "—", { tone: counts && counts.openInputs ? "warn" : "" }),
26
+ metricTile(t("metrics.active"), counts ? counts.active : "—", { tone: "info" }),
27
+ metricTile(t("metrics.draft"), counts ? counts.draft : "—"),
28
+ metricTile(t("metrics.completed"), counts ? counts.completed : "—", { tone: "ok" }),
29
+ metricTile(t("metrics.total"), counts ? counts.total : "—")));
30
+
31
+ const grid = h("div.overview-grid");
32
+ // Open questions: the catalog names the affected Tasks, never the questions.
33
+ const inbox = card({ title: t("overview.inbox"), icon: "inbox", count: counts ? counts.openInputs : null });
34
+ const items = state.attention || [];
35
+ if (!items.length) {
36
+ inbox.body.append(emptyState(counts && counts.openInputs > 0 ? t("overview.inboxElsewhere") : t("overview.inboxEmpty"), "check"));
37
+ } else {
38
+ inbox.body.append(h("div.row-list", null, items.slice(0, 8).map(function (item) {
39
+ return h("button.list-row", { type: "button", onclick: function () { actions.select(item.taskId); } },
40
+ dot("warn"), h("span.list-row-title", null, item.taskTitle), h("code.id", null, item.taskId),
41
+ h("span.list-row-go", null, t("overview.answer"), icon("chevron", "icon-sm")));
42
+ })));
43
+ }
44
+ grid.append(inbox);
45
+
46
+ const catalog = card({ title: t("overview.catalog"), icon: "pulse", hint: t("overview.catalogHint") });
47
+ if (state.catalogAttention) {
48
+ catalog.body.append(h("div.row-list", null, ATTENTION_KINDS.map(function (kind) {
49
+ const value = state.catalogAttention[kind];
50
+ if (!value) return null;
51
+ return h("button.list-row", {
52
+ type: "button", disabled: value.taskCount === 0,
53
+ "aria-pressed": String(state.attentionFilter === kind),
54
+ onclick: function () { actions.filterAttention(kind); }
55
+ },
56
+ h("span.list-row-title", null, t("catalog." + kind)),
57
+ h("span.list-row-count", null, String(value.count)),
58
+ h("span.faint", null, value.taskCount + " " + t("overview.tasks")));
59
+ })));
60
+ catalog.body.append(note(t("overview.signalsHelp")));
61
+ } else catalog.body.append(emptyState(t("loading.dashboard")));
62
+ grid.append(catalog);
63
+ page.append(grid);
64
+
65
+ const active = (state.tasks || []).filter(function (task) { return task.status === "active"; });
66
+ const recent = (state.tasks || []).filter(function (task) { return task.status !== "archived"; }).slice().sort(byNewest).slice(0, 8);
67
+ const lists = h("div.overview-grid");
68
+ lists.append(taskListCard(t("overview.activeNow"), "pulse", active, t("overview.activeEmpty"), t, locale, actions));
69
+ lists.append(taskListCard(t("overview.recent"), "history", recent, t("sidebar.empty"), t, locale, actions));
70
+ page.append(lists);
71
+ page.append(sessionCard(state, t, locale, actions));
72
+ page.append(h("p.page-foot", null, t("overview.pageScope")));
73
+ container.append(page);
74
+ }
75
+
76
+ function taskListCard(title, iconName, tasks, empty, t, locale, actions) {
77
+ const element = card({ title: title, icon: iconName, count: tasks.length, hint: t("overview.thisPage") });
78
+ if (!tasks.length) { element.body.append(emptyState(empty)); return element; }
79
+ element.body.append(h("div.row-list", null, tasks.map(function (task) {
80
+ return h("button.list-row", { type: "button", title: task.summary || task.title, onclick: function () { actions.select(task.id); } },
81
+ dot(tone("task", task.status), label(t, "status", task.status)),
82
+ h("span.list-row-title", null, task.title),
83
+ task.attention && task.attention.openInputs ? badge(String(task.attention.openInputs), "warn") : null,
84
+ h("time.faint", { dateTime: task.updatedAt }, relativeTime(task.updatedAt, locale, t)));
85
+ })));
86
+ return element;
87
+ }
88
+
89
+ // Native Session observation is an explicit read of exactly this catalog page.
90
+ function sessionCard(state, t, locale, actions) {
91
+ const element = card({
92
+ title: t("overview.sessions"), icon: "terminal", hint: t("overview.sessionsHint"),
93
+ actions: button(state.sessionOverview ? t("overview.sessionsReread") : t("overview.sessionsRead"), {
94
+ icon: "eye", variant: "ghost", disabled: state.sessionLoading || !(state.tasks || []).length,
95
+ onClick: actions.readSessions
96
+ })
97
+ });
98
+ if (state.sessionError) element.body.append(note(state.sessionError, "bad"));
99
+ const overview = state.sessionOverview;
100
+ if (!overview) {
101
+ element.body.append(emptyState(state.sessionLoading ? t("overview.sessionsReading") : t("overview.sessionsNotRead")));
102
+ return element;
103
+ }
104
+ element.body.append(h("p.faint.small", null, t("overview.readAt") + " " + formatDateTime(overview.readAt, locale)));
105
+ element.body.append(h("div.row-list", null, overview.tasks.map(function (task) {
106
+ const chips = SESSION_GROUPS.filter(function (group) { return task.counts && task.counts[group]; }).map(function (group) {
107
+ return badge(task.counts[group] + " " + t("session." + group), tone("session", group), { dot: true });
108
+ });
109
+ return h("button.list-row.list-row-wrap", { type: "button", onclick: function () { actions.select(task.taskId); } },
110
+ h("span.list-row-title", null, task.title), h("code.id", null, task.taskId),
111
+ h("span.chip-row", null, chips.length ? chips : h("span.faint", null, t("session.noneRecorded"))));
112
+ })));
113
+ return element;
114
+ }
115
+ `;
@@ -0,0 +1,63 @@
1
+ export const RECORDS_SCRIPT = String.raw `
2
+ // Context record access. Every business fact keeps its Context reference; an
3
+ // expanded value is a current read, never a mutation of the snapshot.
4
+ import { h, icon } from "/assets/js/dom.js";
5
+ import { richText, mono, button, note } from "/assets/js/components.js";
6
+
7
+ export function entriesOf(core, store) {
8
+ return core.records.filter(function (entry) { return entry.ref.store === store; });
9
+ }
10
+
11
+ export function valuesOf(core, store) {
12
+ return entriesOf(core, store).filter(function (entry) { return !entry.omitted; }).map(function (entry) { return entry.value; });
13
+ }
14
+
15
+ // A generic, honest card for any record: omitted values say so and can be
16
+ // read by reference; reading never acknowledges or accepts anything.
17
+ export function recordCard(entry, taskId, t, actions, options) {
18
+ const opts = options || {};
19
+ const element = h("article.record" + (opts.compact ? ".is-compact" : ""));
20
+ element.append(h("header.record-head", null,
21
+ h("span.record-store", null, t("store." + entry.ref.store, entry.ref.store)),
22
+ mono(entry.ref.refId),
23
+ h("span.spacer"),
24
+ h("span.faint.small", { title: t("record.revision") }, String(entry.ref.revision).slice(0, 24))));
25
+ if (!entry.omitted && entry.value) {
26
+ const value = entry.value;
27
+ const text = value.content || value.summary || value.body || value.objective || value.title || value.leaderSummary;
28
+ if (text) element.append(richText(null, text, t, { threshold: 360 }));
29
+ if (entry.ref.store === "task-decision" && value.rationale) element.append(richText(t("record.rationale"), value.rationale, t, { muted: true }));
30
+ const facts = [value.status, value.roleName].filter(Boolean);
31
+ if (facts.length) element.append(h("p.faint.small", null, facts.join(" · ")));
32
+ if (entry.ref.store === "job") element.append(note(t("job." + value.status + ".note", t("job.terminal.note")), value.status === "unknown-needs-attention" ? "warn" : ""));
33
+ } else {
34
+ element.append(h("p.faint", null, entry.summary || t("record.omitted")));
35
+ }
36
+ element.append(recordReader(entry, taskId, t, actions));
37
+ return element;
38
+ }
39
+
40
+ // Shared source access for cards, history and discussion, including records
41
+ // whose compact value is present but does not display every durable field.
42
+ export function recordReader(entry, taskId, t, actions) {
43
+ const element = h("div.stack");
44
+ const expand = button(t("record.read"), { icon: "eye", variant: "link" });
45
+ expand.addEventListener("click", async function () {
46
+ expand.disabled = true;
47
+ element.dataset.reading = "true";
48
+ try {
49
+ const result = await actions.inspect(taskId, entry.ref);
50
+ if (result.result && result.result.output) element.append(richText(t("record.output"), result.result.output, t));
51
+ if (result.result && result.result.diagnostic) element.append(richText(t("record.diagnostic"), result.result.diagnostic, t));
52
+ element.append(h("pre.code-block", null, JSON.stringify(result.value, null, 2)));
53
+ if (result.execution) element.append(h("pre.code-block", null, JSON.stringify(result.execution, null, 2)));
54
+ expand.remove();
55
+ } catch {
56
+ expand.disabled = false;
57
+ expand.replaceChildren(icon("refresh"), h("span", null, t("record.changed")));
58
+ } finally { element.dataset.reading = "false"; }
59
+ });
60
+ element.append(h("div.record-actions", null, expand));
61
+ return element;
62
+ }
63
+ `;
@@ -0,0 +1,313 @@
1
+ export const SECTIONS_SCRIPT = String.raw `
2
+ // Task sections behind the Work, Execution, History and Details tabs.
3
+ import { h, icon, clear } from "/assets/js/dom.js";
4
+ import { formatDateTime } from "/assets/js/format.js";
5
+ import {
6
+ sectionTitle, card, disclosure, emptyState, note, button, statusBadge, label, tone, dot, chip, mono,
7
+ kv, richText, pagedList, workItemCard, runCard, reviewCard, roleCard, timeTag, observabilityMetricCard
8
+ } from "/assets/js/components.js";
9
+ import { entriesOf, valuesOf, recordCard, recordReader } from "/assets/js/records.js";
10
+ import { renderEvidence } from "/assets/js/evidence.js";
11
+ import { controlForm, titleForm } from "/assets/js/forms.js";
12
+
13
+ function omittedNote(core, t) {
14
+ return core.omitted.records ? t("records.notIncluded") : t("records.none");
15
+ }
16
+
17
+ // --- Work -------------------------------------------------------------------------
18
+ export function renderWork(panel, data, t, locale, ctx) {
19
+ const core = data.core;
20
+ const items = entriesOf(core, "work-item");
21
+ const titles = {};
22
+ valuesOf(core, "work-item").forEach(function (item) { titles[item.id] = item.title; });
23
+ const rank = function (entry) { return entry.omitted ? 1 : entry.value.status === "open" ? 0 : 2; };
24
+ const sorted = items.slice().sort(function (a, b) {
25
+ return rank(a) - rank(b) || Date.parse((b.value && b.value.updatedAt) || 0) - Date.parse((a.value && a.value.updatedAt) || 0);
26
+ });
27
+ const work = h("section.section", { id: "detail-work" }, sectionTitle(t("work.title"), items.length));
28
+ if (!sorted.length) work.append(emptyState(omittedNote(core, t), "layers"));
29
+ const list = h("div.stack");
30
+ sorted.forEach(function (entry) {
31
+ list.append(entry.omitted ? recordCard(entry, data.task.id, t, ctx) : workItemCard(entry.value, t, locale, titles));
32
+ });
33
+ work.append(list);
34
+ panel.append(work);
35
+
36
+ const results = h("section.section", { id: "detail-results" }, sectionTitle(t("evidence.title")));
37
+ const resultsBody = h("div.card-grid.card-grid-1");
38
+ renderEvidence(resultsBody, data, t, locale, ctx);
39
+ results.append(resultsBody);
40
+ panel.append(results);
41
+
42
+ const reviews = entriesOf(core, "review-round");
43
+ const reviewSection = h("section.section", { id: "detail-reviews" }, sectionTitle(t("reviews.title"), reviews.length));
44
+ if (!reviews.length) reviewSection.append(emptyState(omittedNote(core, t)));
45
+ else {
46
+ reviewSection.append(note(t("reviews.hint")));
47
+ const reviewList = h("div.stack");
48
+ reviews.forEach(function (entry) {
49
+ reviewList.append(entry.omitted ? recordCard(entry, data.task.id, t, ctx) : reviewCard(entry.value, t, locale));
50
+ });
51
+ reviewSection.append(reviewList);
52
+ }
53
+ panel.append(reviewSection);
54
+ }
55
+
56
+ // --- Execution --------------------------------------------------------------------
57
+ export function renderExecution(panel, data, t, locale, ctx) {
58
+ const core = data.core;
59
+ const task = data.task;
60
+ const runtimeRoles = (data.runtime && data.runtime.roles) || [];
61
+ const runs = valuesOf(core, "run");
62
+
63
+ const roleEntries = entriesOf(core, "role");
64
+ const roles = h("section.section", { id: "detail-roles" }, sectionTitle(t("roles.title"), roleEntries.length));
65
+ const roleGrid = h("div.role-grid");
66
+ roleEntries.forEach(function (entry) {
67
+ if (entry.omitted) { roleGrid.append(recordCard(entry, task.id, t, ctx)); return; }
68
+ const role = entry.value;
69
+ const active = runs.find(function (run) { return run.roleName === role.name && run.status === "active"; });
70
+ const runtimeRole = runtimeRoles.find(function (item) { return item.name === role.name; });
71
+ const element = roleCard(role, t, locale, {
72
+ runtimeRole: runtimeRole,
73
+ retry: entry.providerRetry,
74
+ launchDrift: active && active.effective && active.effective.sourceDesiredRevision !== role.launchRevision,
75
+ onOpen: task.status === "archived" ? null : ctx.openSession
76
+ });
77
+ const statusSlot = h("span", { dataset: { roleStatus: role.name } });
78
+ element.querySelector(".role-head").append(statusSlot);
79
+ if (!active) element.append(h("p.faint.small", null, t("roles.noActiveRun")));
80
+ roleGrid.append(element);
81
+ });
82
+ if (!roleEntries.length) roleGrid.append(emptyState(omittedNote(core, t)));
83
+ roles.append(roleGrid);
84
+ panel.append(roles);
85
+
86
+ const redirect = disclosure(t("control.title"), "control", { className: "card-disclosure", meta: t("control.meta") });
87
+ redirect.body.append(note(t("control.help")), controlForm(task, t, ctx, roleEntries.map(function (entry) { return entry.ref.refId; })));
88
+ panel.append(h("section.section", { id: "detail-control" }, redirect));
89
+
90
+ const runEntries = entriesOf(core, "run");
91
+ const runSection = h("section.section", { id: "detail-exec" });
92
+ const filters = ["all", "active", "completed", "failed"];
93
+ let filter = "all";
94
+ const filterRow = h("div.chip-tabs", { role: "group", "aria-label": t("runs.filter") });
95
+ const list = h("div.stack");
96
+ function draw() {
97
+ clear(list);
98
+ const visible = runEntries.filter(function (entry) { return filter === "all" || (!entry.omitted && entry.value.status === filter); });
99
+ if (!visible.length) { list.append(emptyState(omittedNote(core, t), "pulse")); return; }
100
+ pagedList(list, visible, 12, function (entry) {
101
+ return entry.omitted ? recordCard(entry, task.id, t, ctx) : runCard({ ...entry.value, execution: entry.execution }, t, locale);
102
+ }, t);
103
+ }
104
+ filters.forEach(function (status) {
105
+ const count = status === "all" ? runEntries.length : runs.filter(function (run) { return run.status === status; }).length;
106
+ if (status !== "all" && !count) return;
107
+ filterRow.append(h("button.chip-tab", {
108
+ type: "button", "aria-pressed": String(status === filter), dataset: { status },
109
+ onclick: function () {
110
+ filter = status;
111
+ filterRow.querySelectorAll(".chip-tab").forEach(function (b) { b.setAttribute("aria-pressed", String(b.dataset.status === status)); });
112
+ draw();
113
+ }
114
+ }, status === "all" ? t("filter.all") : label(t, "run", status), h("span.count", null, String(count))));
115
+ });
116
+ runSection.append(sectionTitle(t("runs.title"), runEntries.length), note(t("runs.hint")), filterRow, list);
117
+ draw();
118
+ panel.append(runSection);
119
+
120
+ const jobs = entriesOf(core, "job");
121
+ const jobSection = h("section.section", { id: "detail-operations" }, sectionTitle(t("jobs.title"), jobs.length));
122
+ if (!jobs.length) jobSection.append(emptyState(omittedNote(core, t)));
123
+ else {
124
+ jobSection.append(note(t("jobs.hint")));
125
+ const jobList = h("div.stack");
126
+ jobs.forEach(function (entry) {
127
+ if (entry.omitted) { jobList.append(recordCard(entry, task.id, t, ctx)); return; }
128
+ const job = entry.value;
129
+ jobList.append(h("article.record", null,
130
+ h("header.record-head", null, dot(tone("job", job.status)), mono(task.id + "/" + job.id), h("span.spacer"),
131
+ statusBadge(t, "job", "job", job.status)),
132
+ h("p.small", null, t("job." + job.status + ".note", t("job.terminal.note"))),
133
+ h("p.faint.small", null, (job.steps || []).map(function (step) { return step.name; }).join(" → ")),
134
+ timeTag(job.terminalAt || job.startedAt || job.createdAt, locale, t)));
135
+ });
136
+ jobSection.append(jobList);
137
+ }
138
+ panel.append(jobSection);
139
+
140
+ const usage = h("section.section", { id: "detail-usage" }, sectionTitle(t("usage.title")));
141
+ const usageBody = h("div", { dataset: { slot: "usage" } });
142
+ const metrics = data.runtime && observabilityMetricCard(data.runtime.observability, t);
143
+ usageBody.append(metrics || emptyState(t("usage.unavailable")));
144
+ usage.append(usageBody);
145
+ panel.append(usage);
146
+
147
+ const observations = disclosure(t("observation.title"), "execution", { className: "card-disclosure" });
148
+ observations.body.append(note(t("observation.help")));
149
+ (core.observations || []).forEach(function (observation) {
150
+ observations.body.append(h("p.small.mono-line", null,
151
+ observation.source + " · " + observation.status + " · " + observation.coverage + " · " + observation.observedAt));
152
+ });
153
+ observations.body.append(h("p.faint.small", { dataset: { slot: "runtime-status" } }), h("pre.code-block", { dataset: { slot: "runtime-raw" } }));
154
+ panel.append(h("section.section", null, observations));
155
+ }
156
+
157
+ // --- History ----------------------------------------------------------------------
158
+ export function renderHistory(panel, data, t, locale, ctx) {
159
+ const core = data.core;
160
+ const task = data.task;
161
+ const events = [];
162
+ entriesOf(core, "task-decision").forEach(function (entry) { events.push({ kind: "decision", entry }); });
163
+ entriesOf(core, "task-milestone").forEach(function (entry) { events.push({ kind: "milestone", entry }); });
164
+ entriesOf(core, "task-message").forEach(function (entry) { events.push({ kind: "message", entry }); });
165
+ const at = function (event) {
166
+ const value = event.entry.value || {};
167
+ return Date.parse(value.createdAt || value.updatedAt || 0) || 0;
168
+ };
169
+ events.sort(function (a, b) { return at(b) - at(a); });
170
+ const section = h("section.section", { id: "detail-history" }, sectionTitle(t("history.title"), events.length));
171
+ const kinds = ["all", "decision", "milestone", "message"];
172
+ let kind = "all";
173
+ const filterRow = h("div.chip-tabs", { role: "group", "aria-label": t("history.filter") });
174
+ const list = h("ol.timeline");
175
+ function draw() {
176
+ clear(list);
177
+ const visible = events.filter(function (event) { return kind === "all" || event.kind === kind; });
178
+ if (!visible.length) { list.append(h("li.timeline-empty", null, emptyState(omittedNote(core, t), "history"))); return; }
179
+ pagedList(list, visible, 20, function (event) { return timelineItem(event, task.id, t, locale, ctx); }, t);
180
+ }
181
+ kinds.forEach(function (value) {
182
+ const count = value === "all" ? events.length : events.filter(function (event) { return event.kind === value; }).length;
183
+ filterRow.append(h("button.chip-tab", {
184
+ type: "button", "aria-pressed": String(value === kind), dataset: { kind: value },
185
+ onclick: function () {
186
+ kind = value;
187
+ filterRow.querySelectorAll(".chip-tab").forEach(function (b) { b.setAttribute("aria-pressed", String(b.dataset.kind === value)); });
188
+ draw();
189
+ }
190
+ }, t("history." + value), h("span.count", null, String(count))));
191
+ });
192
+ section.append(filterRow, list);
193
+ if (core.omitted.records) section.append(note(t("history.omitted")));
194
+ draw();
195
+ panel.append(section);
196
+ }
197
+
198
+ function timelineItem(event, taskId, t, locale, ctx) {
199
+ const entry = event.entry;
200
+ const item = h("li.timeline-item.kind-" + event.kind);
201
+ item.append(h("span.timeline-mark", { "aria-hidden": "true" }, icon(event.kind === "decision" ? "check" : event.kind === "milestone" ? "flag" : "chat", "icon-sm")));
202
+ if (entry.omitted) { item.append(recordCard(entry, taskId, t, ctx, { compact: true })); return item; }
203
+ const value = entry.value;
204
+ const body = h("div.timeline-body");
205
+ if (event.kind === "message") {
206
+ const author = (value.author && value.author.roleName) || label(t, "author", (value.author && value.author.type) || "system");
207
+ body.append(h("header.timeline-head", null, h("strong", null, author),
208
+ value.kind ? chip(label(t, "messageKind", value.kind)) : null,
209
+ value.intent ? chip(label(t, "intent", value.intent)) : null,
210
+ h("span.spacer"), timeTag(value.createdAt, locale, t)));
211
+ body.append(richText(null, value.body || "", t, { threshold: 480 }));
212
+ if (value.resultRef) body.append(h("p.faint.small", null, t("history.resultOf") + " ", mono(value.resultRef.runId)));
213
+ } else if (event.kind === "decision") {
214
+ body.append(h("header.timeline-head", null, h("strong", null, value.title),
215
+ value.status ? statusBadge(t, "decision", "decision", value.status) : null, h("span.spacer"), timeTag(value.createdAt, locale, t)));
216
+ if (value.rationale) body.append(richText(null, value.rationale, t, { muted: true }));
217
+ if (value.supersededReason) body.append(richText(t("history.superseded"), value.supersededReason, t, { muted: true }));
218
+ } else {
219
+ body.append(h("header.timeline-head", null, h("strong", null, value.title), h("span.spacer"), timeTag(value.createdAt, locale, t)));
220
+ if (value.summary) body.append(richText(null, value.summary, t, { muted: true }));
221
+ }
222
+ body.append(h("p.faint.small", null, mono(entry.ref.refId)));
223
+ body.append(recordReader(entry, taskId, t, ctx));
224
+ item.append(body);
225
+ return item;
226
+ }
227
+
228
+ // --- Details ----------------------------------------------------------------------
229
+ export function renderDetails(panel, data, t, locale, ctx) {
230
+ const task = data.task;
231
+ const core = data.core;
232
+ const facts = card({ title: t("details.facts"), icon: "file" });
233
+ facts.body.append(kv([
234
+ [t("details.id"), mono(task.id)],
235
+ [t("details.status"), statusBadge(t, "task", "status", task.status)],
236
+ [t("details.priority"), task.priority ? label(t, "priority", task.priority) : null],
237
+ [t("details.tags"), (task.tags || []).length ? h("span.chip-row", null, task.tags.map(function (tag) { return chip(tag); })) : null],
238
+ [t("details.projects"), (task.projectBindings || []).length ? h("span.chip-row", null, task.projectBindings.map(function (b) { return chip(b.projectId); })) : null],
239
+ [t("details.created"), formatDateTime(task.createdAt, locale)],
240
+ [t("details.updated"), formatDateTime(task.updatedAt, locale)],
241
+ [t("details.workspace"), task.cwd ? mono(task.cwd) : null]
242
+ ]));
243
+ const edit = card({ title: t("details.title"), icon: "sparkle" });
244
+ edit.body.append(titleForm(task, t, ctx));
245
+ const grid = h("div.card-grid");
246
+ grid.append(facts, edit);
247
+ panel.append(h("section.section", { id: "detail-facts" }, grid));
248
+
249
+ const context = disclosure(t("details.context"), "context", { className: "card-disclosure" });
250
+ context.body.append(h("pre.code-block", null, JSON.stringify({
251
+ coreCursor: core.coreCursor, throughCursor: core.throughCursor, count: core.count, omitted: core.omitted
252
+ }, null, 2)), note(t("details.contextHelp")));
253
+ panel.append(h("section.section", null, context));
254
+
255
+ const panels = disclosure(t("panels.title"), "panels", { className: "card-disclosure" });
256
+ let loading = false;
257
+ panels.addEventListener("toggle", async function () {
258
+ if (!panels.open || loading) return;
259
+ loading = true;
260
+ clear(panels.body);
261
+ panels.body.append(h("p.faint", null, t("panels.reading")));
262
+ try {
263
+ const current = await ctx.panels(task.id);
264
+ clear(panels.body);
265
+ panels.body.append(h("p.faint.small", null, t("panels.observed") + " " + formatDateTime(current.observedAt, locale)));
266
+ if (!current.panels.length) panels.body.append(emptyState(t("panels.none")));
267
+ current.panels.forEach(function (item) { panels.body.append(panelCard(item, task, t, ctx)); });
268
+ } catch (error) {
269
+ clear(panels.body);
270
+ panels.body.append(note(t("panels.unavailable") + " " + error.message, "bad"));
271
+ } finally { loading = false; }
272
+ });
273
+ panel.append(h("section.section", null, panels));
274
+ }
275
+
276
+ function panelCard(item, task, t, ctx) {
277
+ const panel = item.panel;
278
+ const element = h("article.record", null,
279
+ h("header.record-head", null, h("strong", null, panel.title), h("span.spacer"), h("span.faint.small", null, item.capability + " · " + item.provider.id)));
280
+ if (item.unavailable) { element.append(note(item.unavailable, "warn")); return element; }
281
+ if (panel.kind === "text") element.append(h("p.small", null, panel.text));
282
+ else if (panel.kind === "link") {
283
+ try {
284
+ const url = new URL(panel.href);
285
+ if (["http:", "https:"].includes(url.protocol) && !url.username && !url.password) {
286
+ element.append(h("a.link", { href: url.href, rel: "noopener noreferrer", target: "_blank" }, panel.title));
287
+ }
288
+ } catch {}
289
+ } else if (panel.kind === "data" && panel.renderer === "json") {
290
+ const input = h("textarea.mono", { rows: 3 });
291
+ input.value = JSON.stringify({ taskId: task.id });
292
+ const schema = disclosure(t("panels.schema"), null);
293
+ schema.body.append(h("pre.code-block", null, JSON.stringify(item.inputSchema, null, 2)));
294
+ const output = h("pre.code-block");
295
+ output.hidden = true;
296
+ const read = button(t("panels.read"), { icon: "eye", variant: "ghost" });
297
+ read.addEventListener("click", async function () {
298
+ if (read.disabled) return;
299
+ read.disabled = true;
300
+ output.hidden = false;
301
+ try {
302
+ const result = await ctx.readPanel(task.id, {
303
+ capability: item.capability, contractVersion: item.contractVersion, provider: item.provider
304
+ }, JSON.parse(input.value));
305
+ output.textContent = JSON.stringify(result, null, 2);
306
+ } catch (error) { output.textContent = t("panels.unavailable") + " " + error.message; }
307
+ finally { read.disabled = false; }
308
+ });
309
+ element.append(h("label.field", null, h("span", null, t("panels.input")), input), schema, h("div.form-actions", null, read), output);
310
+ }
311
+ return element;
312
+ }
313
+ `;
@@ -0,0 +1,117 @@
1
+ export const SIDEBAR_SCRIPT = String.raw `
2
+ // Task index: attention shortcuts, status tabs and the grouped task list.
3
+ import { h, icon, clear } from "/assets/js/dom.js";
4
+ import { relativeTime } from "/assets/js/format.js";
5
+ import { dot, tone, label } from "/assets/js/components.js";
6
+
7
+ export const STATUS_FILTERS = ["all", "active", "draft", "completed", "cancelled", "archived"];
8
+ export const ATTENTION_KINDS = ["openInputs", "unknownOperations", "pendingOperations", "executionSignals"];
9
+ const ATTENTION_ICON = { openInputs: "inbox", unknownOperations: "alert", pendingOperations: "clock", executionSignals: "pulse" };
10
+ const ATTENTION_TONE = { openInputs: "warn", unknownOperations: "bad", pendingOperations: "info", executionSignals: "idle" };
11
+
12
+ export function renderAttentionBar(container, state, t, onPick) {
13
+ clear(container);
14
+ const attention = state.catalogAttention;
15
+ if (!attention) return;
16
+ const kinds = ATTENTION_KINDS.filter(function (kind) { return attention[kind] && attention[kind].taskCount > 0; });
17
+ if (!kinds.length) {
18
+ container.append(h("p.attention-clear", null, icon("check", "icon-sm"), t("sidebar.allClear")));
19
+ return;
20
+ }
21
+ container.append(h("div.attention-list", null, kinds.map(function (kind) {
22
+ const value = attention[kind];
23
+ return h("button.attention-pill.tone-" + ATTENTION_TONE[kind], {
24
+ type: "button",
25
+ "aria-pressed": String(state.attentionFilter === kind),
26
+ title: t("catalog.help." + kind),
27
+ onclick: function () { onPick(state.attentionFilter === kind ? null : kind); }
28
+ }, icon(ATTENTION_ICON[kind], "icon-sm"), h("span", null, t("catalog." + kind)), h("b", null, String(value.count)));
29
+ })));
30
+ }
31
+
32
+ // The tab row is created once and updated in place so its horizontal scroll
33
+ // position survives every refresh.
34
+ export function renderFilters(container, state, t, onFilter) {
35
+ const counts = state.counts || {};
36
+ let row = container.querySelector(":scope > .tabs-row");
37
+ if (!row) {
38
+ row = h("div.tabs-row");
39
+ STATUS_FILTERS.forEach(function (status) {
40
+ row.append(h("button.status-tab", { type: "button", dataset: { status }, onclick: function () { onFilter(status); } },
41
+ h("span.status-tab-label"), h("span.status-tab-count")));
42
+ });
43
+ container.append(row);
44
+ }
45
+ STATUS_FILTERS.forEach(function (status) {
46
+ const tab = row.querySelector('[data-status="' + status + '"]');
47
+ tab.setAttribute("aria-pressed", String(state.filter === status));
48
+ tab.querySelector(".status-tab-label").textContent = status === "all"
49
+ ? t(state.catalogAll ? "filter.everything" : "filter.unarchived") : label(t, "status", status);
50
+ const count = status === "all"
51
+ ? (counts.total === undefined ? undefined : counts.total - (state.catalogAll ? 0 : counts.archived || 0))
52
+ : counts[status];
53
+ const badge = tab.querySelector(".status-tab-count");
54
+ const hide = count === undefined || count === null
55
+ || (status === "archived" && state.catalogScope && state.catalogScope.archived === "excluded");
56
+ badge.hidden = hide;
57
+ if (!hide) badge.textContent = String(count);
58
+ });
59
+ }
60
+
61
+ function groupOf(task) {
62
+ const attention = task.attention || {};
63
+ if (attention.openInputs > 0 || attention.unknownOperations > 0) return "attention";
64
+ if (task.status === "completed") return "completed";
65
+ return task.status;
66
+ }
67
+
68
+ const GROUP_ORDER = ["attention", "active", "draft", "completed", "cancelled", "archived"];
69
+
70
+ export function renderTasks(container, state, t, locale, onSelect) {
71
+ clear(container);
72
+ const groups = {};
73
+ (state.tasks || []).forEach(function (task) {
74
+ if (state.filter === "all" && !state.catalogAll && task.status === "archived") return;
75
+ if (state.filter !== "all" && task.status !== state.filter) return;
76
+ const group = groupOf(task);
77
+ (groups[group] = groups[group] || []).push(task);
78
+ });
79
+ const present = GROUP_ORDER.filter(function (group) { return groups[group] && groups[group].length; });
80
+ if (!present.length) {
81
+ container.append(h("div.list-empty", null, icon("search"), h("p", null, t(state.query || state.attentionFilter ? "sidebar.noMatch" : "sidebar.empty"))));
82
+ return;
83
+ }
84
+ present.forEach(function (group) {
85
+ container.append(h("section.task-group", null,
86
+ h("h3.task-group-head", null, h("span", null, t("group." + group)), h("span.count", null, String(groups[group].length))),
87
+ groups[group].map(function (task) { return taskRow(task, state, t, locale, onSelect); })));
88
+ });
89
+ }
90
+
91
+ function taskRow(task, state, t, locale, onSelect) {
92
+ const attention = task.attention || {};
93
+ const counts = task.counts || {};
94
+ const signals = h("span.task-signals");
95
+ if (attention.openInputs > 0) signals.append(signal("inbox", attention.openInputs, "warn", t("catalog.openInputs")));
96
+ if (attention.unknownOperations > 0) signals.append(signal("alert", attention.unknownOperations, "bad", t("catalog.unknownOperations")));
97
+ if (counts.activeRuns > 0) signals.append(signal("pulse", counts.activeRuns, "info", t("sidebar.activeRuns")));
98
+ const row = h("button.task-row", {
99
+ type: "button",
100
+ dataset: { id: task.id },
101
+ "aria-current": String(state.selected === task.id),
102
+ title: task.summary || task.title,
103
+ onclick: function () { onSelect(task.id); }
104
+ },
105
+ dot(tone("task", task.status), label(t, "status", task.status)),
106
+ h("span.task-body", null,
107
+ h("span.task-line", null, h("span.task-name", null, task.title), h("time.task-time", { dateTime: task.updatedAt }, relativeTime(task.updatedAt, locale, t))),
108
+ h("span.task-line.task-sub", null, h("code.task-id", null, task.id),
109
+ counts.workItems > 0 ? h("span.task-meta", { title: t("sidebar.workItems") }, icon("layers", "icon-sm"), String(counts.workItems)) : null,
110
+ signals)));
111
+ return row;
112
+ }
113
+
114
+ function signal(iconName, count, toneName, title) {
115
+ return h("span.signal.tone-" + toneName, { title: title }, icon(iconName, "icon-sm"), String(count));
116
+ }
117
+ `;