@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
@@ -1,992 +1,418 @@
1
1
  import { AGENT_ADAPTER_CATALOG } from "../../../agent/adapterCatalog.js";
2
2
  import { AGENT_EXECUTION_COMPONENT_CATALOG } from "../../../agent/executionComponents.js";
3
- export const COMPONENTS_SCRIPT = `
4
- // Reusable widgets and record cards. Everything here is a pure builder:
5
- // data + i18n in, DOM out. Page composition lives in view.js.
6
- import { node } from "/assets/js/dom.js";
3
+ export const COMPONENTS_SCRIPT = String.raw `
4
+ // Pure builders: data + translator in, DOM out. Views compose these; nothing
5
+ // here reads application state or performs a request.
6
+ import { h, icon, node } from "/assets/js/dom.js";
7
7
  import { formatDateTime, relativeTime } from "/assets/js/format.js";
8
- import { escapeHtml, inlineMarkdown, renderMarkdown } from "/assets/js/markdown.js";
8
+ import { renderMarkdown } from "/assets/js/markdown.js";
9
9
 
10
10
  // Generated from the server catalogs, not a second product registry.
11
11
  const adapterLabels = ${JSON.stringify(Object.fromEntries(AGENT_ADAPTER_CATALOG.map(({ id, label }) => [id, label])))};
12
12
  const componentLabels = ${JSON.stringify(Object.fromEntries(AGENT_EXECUTION_COMPONENT_CATALOG.map(({ id, label }) => [id, label])))};
13
- function adapterLabel(adapterId) {
14
- return adapterLabels[adapterId] || adapterId;
15
- }
16
- function componentLabel(component, adapterId) {
17
- return component ? componentLabels[component] || component : adapterLabel(adapterId);
13
+ export function agentLabel(component, adapterId) {
14
+ return component ? componentLabels[component] || component : adapterLabels[adapterId] || adapterId;
15
+ }
16
+
17
+ // --- Vocabulary ----------------------------------------------------------------
18
+ // A translated label for a domain value; unknown values stay visible verbatim
19
+ // instead of being hidden or guessed.
20
+ export function label(t, prefix, value) {
21
+ if (value === undefined || value === null || value === "") return "—";
22
+ return t(prefix + "." + value, String(value).replace(/[-_]/g, " "));
23
+ }
24
+
25
+ const TONES = {
26
+ task: { draft: "warn", active: "info", completed: "ok", cancelled: "idle", archived: "idle" },
27
+ exec: {
28
+ "needs-leader-action": "accent", "waiting-on-agents": "info", "waiting-user": "warn",
29
+ recovering: "warn", attention: "bad", "progressing-with-attention": "warn", blocked: "bad",
30
+ conflicted: "warn", working: "info", completed: "ok", retired: "idle", cancelled: "idle",
31
+ accepted: "ok", open: "info", archived: "idle"
32
+ },
33
+ run: { active: "info", completed: "ok", failed: "bad" },
34
+ work: { open: "info", accepted: "ok", retired: "idle" },
35
+ review: { pending: "idle", running: "info", completed: "ok", failed: "bad" },
36
+ job: { queued: "idle", running: "info", succeeded: "ok", failed: "bad", "timed-out": "bad",
37
+ cancelled: "idle", "unknown-needs-attention": "warn" },
38
+ session: { active: "ok", waiting: "warn", quiet: "idle", diagnostic: "bad", unknown: "idle",
39
+ stopped: "bad", idle: "idle", background: "warn" },
40
+ role: { running: "ok", waiting: "warn", idle: "idle", unknown: "idle", failed: "bad", exited: "idle", detached: "idle" },
41
+ decision: { active: "ok", superseded: "idle" },
42
+ input: { open: "warn", answered: "ok", cancelled: "idle", "auto-resolved": "ok" }
43
+ };
44
+ export function tone(kind, value) {
45
+ return (TONES[kind] && TONES[kind][value]) || "idle";
18
46
  }
19
47
 
20
- export function translatedStatus(t, prefix, status) {
21
- return t(prefix + "." + status);
48
+ export function badge(text, toneName, options) {
49
+ const element = h("span.badge.tone-" + (toneName || "idle"), null,
50
+ options && options.dot ? h("i.badge-dot", { "aria-hidden": "true" }) : null, text);
51
+ if (options && options.title) element.title = options.title;
52
+ return element;
22
53
  }
23
54
 
24
- // --- Small widgets -----------------------------------------------------------
25
- export function metaItem(label, value) {
26
- const item = node("span", "detail-meta-item");
27
- item.append(node("small", "", label), node("span", "", value));
28
- return item;
55
+ export function statusBadge(t, kind, prefix, value) {
56
+ const element = badge(label(t, prefix, value), tone(kind, value), { dot: true });
57
+ element.dataset.status = value;
58
+ return element;
29
59
  }
30
60
 
31
- export function pathMetaItem(label, value) {
32
- const item = node("span", "detail-meta-item");
33
- const path = node("span", "meta-path", value);
34
- path.title = value;
35
- item.append(node("small", "", label), path);
36
- return item;
61
+ export function dot(toneName, title) {
62
+ const element = h("span.dot.tone-" + (toneName || "idle"), { "aria-hidden": title ? null : "true" });
63
+ if (title) { element.title = title; element.setAttribute("role", "img"); element.setAttribute("aria-label", title); }
64
+ return element;
37
65
  }
38
66
 
39
67
  export function chip(text, extraClass) {
40
- return node("span", "chip" + (extraClass ? " " + extraClass : ""), text);
41
- }
42
-
43
- export function agentBadge(agent) {
44
- if (!agent) return null;
45
- const badge = node("span", "agent-badge");
46
- if (agent.adapterId || agent.component) {
47
- badge.append(chip(componentLabel(agent.component, agent.adapterId), "is-adapter"));
48
- }
49
- if (agent.model) badge.append(chip(agent.model));
50
- if (agent.effort) badge.append(chip(agent.effort));
51
- return badge.childNodes.length ? badge : null;
68
+ return h("span.chip" + (extraClass ? "." + extraClass : ""), null, text);
52
69
  }
53
70
 
54
- export function chipRow(label, values, activeValue) {
55
- const list = (values || []).filter(function (value) { return value !== undefined && value !== null && value !== ""; });
56
- if (!list.length) return null;
57
- const block = node("div", "record-block");
58
- block.append(node("small", "", label));
59
- const row = node("div", "chip-row");
60
- list.forEach(function (value) {
61
- row.append(chip(String(value), activeValue !== undefined && value === activeValue ? "is-active" : ""));
62
- });
63
- block.append(row);
64
- return block;
71
+ export function mono(text, extraClass) {
72
+ return h("code.id" + (extraClass ? "." + extraClass : ""), null, text);
65
73
  }
66
74
 
67
- export function pill(t, namespace, status) {
68
- const element = node("span", "pill", translatedStatus(t, namespace, status));
69
- element.dataset.status = status;
75
+ export function timeTag(iso, locale, t, options) {
76
+ if (!iso) return null;
77
+ const element = h("time.when", { dateTime: iso }, options && options.absolute
78
+ ? formatDateTime(iso, locale) : relativeTime(iso, locale, t));
79
+ element.title = formatDateTime(iso, locale);
70
80
  return element;
71
81
  }
72
82
 
73
- export function statusDot(status) {
74
- const dot = node("span", "status-dot " + status);
75
- dot.setAttribute("aria-hidden", "true");
76
- return dot;
77
- }
78
-
79
- export function emptyRow(t, key) {
80
- return node("div", "row is-empty", t(key || "empty.none"));
81
- }
82
-
83
- export function sectionHead(label, options) {
84
- const head = node("div", "section-head");
85
- head.append(node("h3", "", label));
86
- if (options && options.count !== undefined) head.append(node("span", "section-count", String(options.count)));
87
- if (options && options.kicker) head.append(node("span", "section-kicker", options.kicker));
88
- if (options && options.right) head.append(node("span", "section-label", options.right));
89
- return head;
90
- }
91
-
92
- export function anchorSection(id, head, body) {
93
- const section = node("section", "detail-section anchor");
94
- section.id = id;
95
- if (head) section.append(head);
96
- section.append(body);
97
- return section;
98
- }
99
-
100
- // --- Task-first execution status -------------------------------------------
101
- // The projection is the current read-model vocabulary: one derived status,
102
- // owner, next action, and the attention/blocker facts behind it.
103
- const EXEC_STATUS_TONE = {
104
- "needs-leader-action": "is-accent",
105
- "waiting-on-agents": "is-active",
106
- "waiting-user": "is-warning",
107
- "recovering": "is-warning",
108
- "attention": "is-danger",
109
- "progressing-with-attention": "is-warning",
110
- "blocked": "is-danger",
111
- "conflicted": "is-warning",
112
- "working": "is-active",
113
- "completed": "is-muted",
114
- "retired": "is-muted",
115
- "cancelled": "is-muted",
116
- "accepted": "is-muted",
117
- "open": "is-active",
118
- "archived": "is-muted"
119
- };
120
-
121
- export function executionBand(projection, t, locale) {
122
- if (!projection) return null;
123
- const tone = EXEC_STATUS_TONE[projection.status] || "";
124
- const band = node("div", "exec-band " + tone);
125
-
126
- const head = node("div", "exec-band-head");
127
- head.append(pill(t, "exec.status", projection.status));
128
- head.append(node("span", "exec-band-owner",
129
- t("exec.owner." + projection.owner) + " · " + t("exec.action." + projection.action)));
130
- if (projection.activeRuns && projection.activeRuns.length > 0) {
131
- head.append(node("span", "exec-band-executors",
132
- projection.activeRuns.length + " " + t("exec.executors")));
133
- }
134
- if (projection.monitoring === "stopped") {
135
- head.append(node("span", "exec-band-stopped", t("exec.monitoring.stopped")));
136
- }
137
- if (projection.failClosed) {
138
- head.append(node("span", "exec-band-failclosed", t("exec.failClosed")));
139
- }
140
- band.append(head);
141
-
142
- if (projection.summary) {
143
- band.append(node("p", "exec-band-summary", projection.summary));
144
- }
145
-
146
- if (projection.attention && projection.attention.length) {
147
- const list = node("div", "exec-signal-list");
148
- projection.attention.forEach(function (item) {
149
- const row = node("div", "exec-signal is-attention");
150
- row.append(node("span", "exec-signal-kind", t("exec.attention." + item.kind)));
151
- row.append(node("span", "exec-signal-text", item.summary));
152
- list.append(row);
153
- });
154
- band.append(list);
155
- }
156
-
157
- if (projection.blockers && projection.blockers.length) {
158
- const list = node("div", "exec-signal-list");
159
- projection.blockers.forEach(function (blocker) {
160
- const row = node("div", "exec-signal is-blocker");
161
- row.append(node("span", "exec-signal-kind",
162
- t("exec.blocker." + blocker.kind) + " · " + t("exec.owner." + blocker.owner)));
163
- row.append(node("span", "exec-signal-text", blocker.summary));
164
- list.append(row);
165
- });
166
- band.append(list);
167
- }
168
-
169
- return band;
83
+ // --- Containers -------------------------------------------------------------------
84
+ export function card(options) {
85
+ const opts = options || {};
86
+ const element = h("section.card" + (opts.className ? "." + opts.className.split(" ").join(".") : ""));
87
+ if (opts.id) element.id = opts.id;
88
+ if (opts.title || opts.actions) {
89
+ const head = h("header.card-head", null,
90
+ h("h3.card-title", null, opts.icon ? icon(opts.icon) : null, h("span", null, opts.title),
91
+ opts.count !== undefined && opts.count !== null ? h("span.count", null, String(opts.count)) : null),
92
+ opts.actions ? h("div.card-actions", null, opts.actions) : null);
93
+ if (opts.hint) head.append(h("p.card-hint", null, opts.hint));
94
+ element.append(head);
95
+ }
96
+ const body = h("div.card-body");
97
+ element.append(body);
98
+ element.body = body;
99
+ return element;
170
100
  }
171
101
 
172
- // --- WorkItem execution and recovery -----------------------------------------
173
- export function workItemExecutionCard(projection, t) {
174
- const card = node("article", "record-card exec-group");
175
- const head = node("div", "record-head");
176
- const titleRow = node("div", "record-title-row");
177
- titleRow.append(node("strong", "record-title", t("workExec.title")));
178
- head.append(titleRow);
179
- const pills = node("div", "record-pills");
180
- pills.append(chip(t("workExec.shape." + projection.shape), "is-active"));
181
- pills.append(chip(t("workExec.synthesis." + projection.synthesis.status)));
182
- head.append(pills);
183
- card.append(head);
184
-
185
- if (projection.lanes && projection.lanes.length) {
186
- const lanes = node("div", "lane-list");
187
- projection.lanes.forEach(function (lane) {
188
- const row = node("div", "lane-row");
189
- row.append(statusDot(lane.status));
190
- row.append(node("span", "lane-role", lane.roleName));
191
- row.append(node("span", "lane-status", t("workExec.lane." + lane.status)));
192
- if (lane.delivery) row.append(chip(t("run.delivery." + lane.delivery)));
193
- row.append(node("span", "mono", lane.currentRunId || t("detail.unobserved")));
194
- if (lane.retryRunId) row.append(chip(t("workExec.retry") + " · " + lane.retryRunId, "is-danger"));
195
- if (lane.settleRunId) row.append(chip(t("workExec.settle") + " · " + lane.settleRunId));
196
- if (lane.session === "unobserved") row.append(chip(t("detail.unobserved")));
197
- lanes.append(row);
198
- });
199
- card.append(lanes);
200
- }
201
-
202
- const facts = node("div", "record-meta execution-resource-meta");
203
- facts.append(node("span", "", t("workExec.main") + " · "
204
- + (projection.mainRun.runId || t("detail.unobserved"))
205
- + " [" + t("workExec.mainStatus." + projection.mainRun.status) + "]"));
206
- facts.append(node("span", "", t("workExec.candidate") + " · "
207
- + (projection.candidate.candidateId || t("workExec.none"))
208
- + " [" + t("workExec.candidateStatus." + projection.candidate.status) + "]"));
209
- card.append(facts);
210
-
211
- if (projection.candidate.sourceExecutionGroupId) {
212
- const provenance = projection.candidate.successfulLaneRuns.map(function (lane) {
213
- return lane.laneId + " → " + lane.successfulRunId;
214
- }).join(", ") || t("detail.unobserved");
215
- card.append(node("p", "muted mono", (projection.candidate.mainRunId || t("detail.unobserved"))
216
- + " → " + projection.candidate.sourceExecutionGroupId + " → " + provenance));
217
- }
218
-
219
- const next = node("div", "exec-resolution");
220
- next.append(node("span", "exec-resolution-decision", t("workExec.next")));
221
- next.append(node("span", "exec-resolution-summary",
222
- t("workExec.action." + projection.nextAction.kind)
223
- + " · " + t("workExec.owner") + " "
224
- + (projection.nextAction.owners.join(", ") || t("workExec.none"))));
225
- if (projection.nextAction.targetIds.length) {
226
- next.append(node("span", "mono", projection.nextAction.targetIds.join(", ")));
227
- }
228
- card.append(next);
229
- return card;
102
+ export function disclosure(title, viewKey, options) {
103
+ const opts = options || {};
104
+ const element = h("details.disclosure" + (opts.className ? "." + opts.className : ""));
105
+ if (viewKey) element.dataset.viewKey = viewKey;
106
+ const summary = h("summary", null, icon("chevron", "disclosure-chevron"), h("span.disclosure-title", null, title));
107
+ if (opts.meta) summary.append(h("span.disclosure-meta", null, opts.meta));
108
+ element.append(summary);
109
+ const body = h("div.disclosure-body");
110
+ element.append(body);
111
+ element.body = body;
112
+ return element;
230
113
  }
231
114
 
232
- export function usageMetricText(metric, t, suffix) {
233
- if (!metric || metric.value == null) return t("detail.unobserved");
234
- return metric.value + (suffix || "") + (metric.status === "partial" ? " · " + t("detail.partial") : "");
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);
235
119
  }
236
120
 
237
- function usageTile(label, metric, t, suffix) {
238
- const tile = metricTile(label, usageMetricText(metric, t, suffix));
239
- if (metric?.reasons?.length) {
240
- tile.append(node("small", "muted", metric.reasons.map(function (reason) {
241
- return t("usage.reason." + reason);
242
- }).join("; ")));
243
- }
244
- return tile;
121
+ export function kv(rows) {
122
+ const list = h("dl.kv");
123
+ rows.forEach(function (row) {
124
+ if (!row || row[1] === undefined || row[1] === null || row[1] === "") return;
125
+ list.append(h("dt", null, row[0]), h("dd", null, row[1]));
126
+ });
127
+ return list;
245
128
  }
246
129
 
247
- export function observabilityMetricCard(observability, t) {
248
- if (!observability) return null;
249
- const card = node("div", "observability-metrics");
250
- const cost = observability.cost || {};
251
- const context = observability.context || {};
252
- card.append(usageTile(t("detail.tokens"), cost.tokens, t));
253
- card.append(usageTile(t("detail.toolCalls"), cost.toolCalls, t));
254
- card.append(usageTile(t("detail.elapsed"), cost.elapsedSeconds, t, "s"));
255
- card.append(usageTile(t("detail.executionSum"), cost.executionSeconds, t, "s"));
256
- card.append(metricTile(t("detail.ready"), (observability.dag?.readyIds || []).length, { hot: true }));
257
- card.append(metricTile(t("detail.contextSnapshots"), context.snapshotCount));
258
- const contextMeta = node("div", "record-meta observability-context-meta");
259
- contextMeta.append(node("span", "", t("detail.usageScope")));
260
- contextMeta.append(node("span", "", t("detail.observedThrough") + " · "
261
- + (cost.observedThrough ? formatDateTime(cost.observedThrough) : t("detail.unobserved"))));
262
- contextMeta.append(node("span", "", t("detail.contextBytes") + " · "
263
- + (context.totalBytes === null ? t("detail.partial") : context.totalBytes + " B")));
264
- contextMeta.append(node("span", "", t("detail.compression") + " · " + t("detail.unavailable")));
265
- contextMeta.append(node("span", "", t("detail.marginalValue") + " · " + t("detail.unavailable")));
266
- card.append(contextMeta);
267
- return card;
130
+ export function emptyState(text, iconName, extra) {
131
+ return h("div.empty", null, iconName ? icon(iconName) : null, h("p", null, text), extra || null);
268
132
  }
269
133
 
270
- export function dagGraph(dag, t) {
271
- if (!dag || !dag.nodes || !dag.nodes.length) return emptyRow(t);
272
- const graph = node("div", "dag-graph");
273
- const edgeByTarget = {};
274
- (dag.edges || []).forEach(function (edge) {
275
- if (!edgeByTarget[edge.to]) edgeByTarget[edge.to] = [];
276
- edgeByTarget[edge.to].push(edge);
277
- });
278
- dag.nodes.forEach(function (item) {
279
- const row = node("div", "dag-node is-" + item.projectedStatus);
280
- const top = node("div", "dag-node-head");
281
- top.append(statusDot(item.projectedStatus), node("strong", "dag-node-title", item.title));
282
- top.append(pill(t, "dag", item.projectedStatus));
283
- if ((dag.readyIds || []).indexOf(item.id) >= 0) top.append(chip(t("dag.ready"), "is-active"));
284
- row.append(top);
285
- const edges = edgeByTarget[item.id] || [];
286
- if (edges.length) {
287
- const deps = node("div", "dag-node-deps");
288
- deps.append(node("small", "", t("dag.dependsOn")));
289
- edges.forEach(function (edge) {
290
- deps.append(chip(edge.from + " · " + t("dag.edge." + edge.status), "is-" + edge.status));
291
- });
292
- row.append(deps);
293
- }
294
- if (item.rootCauseIds && item.rootCauseIds.length) {
295
- row.append(node("small", "dag-root-cause", t("dag.rootCause") + " · " + item.rootCauseIds.join(" ← ")));
296
- }
297
- graph.append(row);
298
- });
299
- return graph;
134
+ export function note(text, toneName) {
135
+ return h("p.note" + (toneName ? ".tone-" + toneName : ""), null, text);
300
136
  }
301
137
 
302
- // --- WorkItem Candidates -----------------------------------------------------
303
- export function candidateList(candidates, t, locale) {
304
- if (!candidates || !candidates.length) return null;
305
- const block = node("div", "record-block");
306
- block.append(node("small", "", t("detail.candidates")));
307
- const list = node("div", "candidate-list");
308
- candidates.slice().sort(function (a, b) { return b.sequence - a.sequence; })
309
- .forEach(function (candidate) {
310
- const row = node("div", "candidate-row");
311
- row.append(node("span", "candidate-seq", "#" + candidate.sequence));
312
- row.append(node("span", "candidate-summary", candidate.summary));
313
- const source = candidate.source.type === "direct"
314
- ? t("candidate.source.direct")
315
- : t("candidate.source.run") + " " + candidate.source.runId;
316
- row.append(node("span", "candidate-source", source));
317
- row.append(node("time", "", formatDateTime(candidate.createdAt, locale)));
318
- list.append(row);
319
- });
320
- block.append(list);
321
- return block;
138
+ export function button(text, options) {
139
+ const opts = options || {};
140
+ const element = h("button.btn" + (opts.variant ? ".btn-" + opts.variant : ""), {
141
+ type: opts.type || "button", onclick: opts.onClick, disabled: !!opts.disabled, title: opts.title
142
+ }, opts.icon ? icon(opts.icon) : null, text ? h("span", null, text) : null);
143
+ if (opts.dataset) Object.assign(element.dataset, opts.dataset);
144
+ return element;
322
145
  }
323
146
 
324
- // --- Progressive disclosure --------------------------------------------------
325
- // Re-render is cheap and happens whenever the underlying data changes, so all
326
- // disclosure state (collapsed blocks, visible pages) is deliberately
327
- // deterministic per render: long content starts collapsed and expanding is a
328
- // pure local toggle.
147
+ // --- Rich text ---------------------------------------------------------------------
329
148
  function shouldCollapse(text, threshold) {
330
- const limit = threshold || 700;
331
149
  const value = String(text);
332
- if (value.length > limit) return true;
150
+ if (value.length > (threshold || 700)) return true;
333
151
  let lines = 0;
334
- for (let i = 0; i < value.length; i += 1) {
335
- if (value[i] === "\\n") lines += 1;
336
- }
152
+ for (let index = 0; index < value.length; index += 1) if (value[index] === "\n") lines += 1;
337
153
  return lines > 12;
338
154
  }
339
155
 
340
- function collapseToggle(block, t) {
341
- block.classList.add("is-collapsible", "is-collapsed");
342
- const toggle = node("button", "md-toggle", t("actions.showMore"));
343
- toggle.type = "button";
344
- toggle.addEventListener("click", function () {
345
- const collapsed = block.classList.toggle("is-collapsed");
346
- toggle.textContent = collapsed ? t("actions.showMore") : t("actions.showLess");
347
- });
348
- block.append(toggle);
349
- }
350
-
351
- // Rich text block: small label + Markdown body, auto-collapsed when long.
352
- export function richText(label, text, t, options) {
156
+ export function richText(title, text, t, options) {
353
157
  if (!text) return null;
354
- const baseClass = (options && options.className) || "record-block";
355
- const extra = options && options.extraClass ? " " + options.extraClass : "";
356
- const block = node("div", baseClass + extra);
357
- if (label) block.append(node("small", "", label));
358
- const body = node("div", "md" + (options && options.muted ? " muted" : ""));
158
+ const opts = options || {};
159
+ const block = h("div.prose-block" + (opts.className ? "." + opts.className : ""));
160
+ if (title) block.append(h("h4.prose-label", null, title));
161
+ const body = h("div.md" + (opts.muted ? ".muted" : ""));
359
162
  body.innerHTML = renderMarkdown(text);
360
163
  block.append(body);
361
- if (shouldCollapse(text, options && options.threshold)) collapseToggle(block, t);
164
+ if (shouldCollapse(text, opts.threshold)) {
165
+ block.classList.add("is-collapsed");
166
+ const toggle = h("button.link-btn", { type: "button" }, t("actions.showMore"));
167
+ toggle.addEventListener("click", function () {
168
+ const collapsed = block.classList.toggle("is-collapsed");
169
+ toggle.textContent = t(collapsed ? "actions.showMore" : "actions.showLess");
170
+ });
171
+ block.append(toggle);
172
+ }
362
173
  return block;
363
174
  }
364
175
 
365
- // Renders the first page of a long list plus a button that reveals the rest
366
- // in place, one page at a time.
176
+ export function bulletList(items, className) {
177
+ const list = (items || []).filter(Boolean);
178
+ if (!list.length) return null;
179
+ return h("ul.bullets" + (className ? "." + className : ""), null, list.map(function (item) { return h("li", null, item); }));
180
+ }
181
+
182
+ // First page of a long list plus an in-place "show more".
367
183
  export function pagedList(container, items, pageSize, renderItem, t) {
368
184
  let shown = 0;
369
185
  let more = null;
370
186
  function renderChunk() {
371
- const chunk = items.slice(shown, shown + pageSize);
372
- chunk.forEach(function (item) {
373
- container.insertBefore(renderItem(item), more);
374
- });
375
- shown += chunk.length;
376
- if (more) {
377
- const remaining = items.length - shown;
378
- if (remaining <= 0) {
379
- more.remove();
380
- more = null;
381
- } else {
382
- more.textContent = t("actions.showRemaining").replace("{count}", String(remaining));
383
- }
384
- }
187
+ items.slice(shown, shown + pageSize).forEach(function (item) { container.insertBefore(renderItem(item), more); });
188
+ shown = Math.min(items.length, shown + pageSize);
189
+ if (!more) return;
190
+ const remaining = items.length - shown;
191
+ if (remaining <= 0) { more.remove(); more = null; }
192
+ else more.textContent = t("actions.showRemaining").replace("{count}", String(remaining));
385
193
  }
386
194
  if (items.length > pageSize) {
387
- more = node("button", "show-more", "");
388
- more.type = "button";
389
- more.addEventListener("click", renderChunk);
195
+ more = h("button.show-more", { type: "button", onclick: renderChunk });
390
196
  container.append(more);
391
197
  }
392
198
  renderChunk();
393
199
  }
394
200
 
395
- export function criteriaList(label, items, t) {
396
- const list = (items || []).filter(Boolean);
397
- if (!list.length) return null;
398
- const block = node("div", "record-block is-wide criteria-block");
399
- block.append(node("small", "", label));
400
- const listElement = node("ul", "criteria-list");
401
- list.forEach(function (item) {
402
- const li = node("li", "");
403
- li.innerHTML = inlineMarkdown(escapeHtml(item));
404
- listElement.append(li);
405
- });
406
- block.append(listElement);
407
- if (list.length > 6) collapseToggle(block, t);
408
- return block;
201
+ // --- Metrics (DOM built with node() only; executed by the usage test) -------------
202
+ export function metricTile(labelText, value, options) {
203
+ const variant = options && options.tone ? " tone-" + options.tone : "";
204
+ const tile = node("div", "metric" + variant);
205
+ tile.append(node("span", "metric-label", labelText), node("strong", "metric-value", String(value)));
206
+ return tile;
409
207
  }
410
208
 
411
- // --- Authors ------------------------------------------------------------------
412
- function messageAuthor(message, t) {
413
- return message.author.type === "role"
414
- ? message.author.roleName
415
- : t("author." + message.author.type);
209
+ export function usageMetricText(metric, t, suffix) {
210
+ if (!metric || metric.value == null) return t("detail.unobserved");
211
+ return metric.value + (suffix || "") + (metric.status === "partial" ? " · " + t("detail.partial") : "");
416
212
  }
417
213
 
418
- function authorName(t, who) {
419
- const key = "author." + who;
420
- const label = t(key);
421
- return label === key ? who : label;
214
+ function usageTile(labelText, metric, t, suffix) {
215
+ const tile = metricTile(labelText, usageMetricText(metric, t, suffix));
216
+ if (metric && metric.reasons && metric.reasons.length) {
217
+ tile.append(node("small", "metric-note", metric.reasons.map(function (reason) {
218
+ return t("usage.reason." + reason);
219
+ }).join("; ")));
220
+ }
221
+ return tile;
222
+ }
223
+
224
+ export function observabilityMetricCard(observability, t) {
225
+ if (!observability) return null;
226
+ const wrap = node("div", "usage");
227
+ const grid = node("div", "metric-grid");
228
+ const cost = observability.cost || {};
229
+ const context = observability.context || {};
230
+ grid.append(usageTile(t("usage.tokens"), cost.tokens, t));
231
+ grid.append(usageTile(t("usage.toolCalls"), cost.toolCalls, t));
232
+ grid.append(usageTile(t("usage.elapsed"), cost.elapsedSeconds, t, "s"));
233
+ grid.append(usageTile(t("usage.executionSum"), cost.executionSeconds, t, "s"));
234
+ grid.append(metricTile(t("usage.ready"), ((observability.dag && observability.dag.readyIds) || []).length));
235
+ grid.append(metricTile(t("usage.contextSnapshots"), context.snapshotCount == null ? t("detail.unobserved") : context.snapshotCount));
236
+ wrap.append(grid);
237
+ const meta = node("p", "usage-meta");
238
+ meta.append(node("span", "", t("usage.scope")));
239
+ meta.append(node("span", "", t("usage.observedThrough") + " "
240
+ + (cost.observedThrough ? formatDateTime(cost.observedThrough) : t("detail.unobserved"))));
241
+ meta.append(node("span", "", t("usage.contextBytes") + " "
242
+ + (context.totalBytes == null ? t("detail.partial") : context.totalBytes + " B")));
243
+ wrap.append(meta);
244
+ return wrap;
422
245
  }
423
246
 
424
- // --- Input (attention) cards ---------------------------------------------------
425
- function answerActions(input, actions, t) {
426
- const answers = node("div", "input-actions");
247
+ // --- Agent identity ------------------------------------------------------------------
248
+ export function agentChips(agent) {
249
+ if (!agent) return null;
250
+ const chips = [];
251
+ if (agent.adapterId || agent.component) chips.push(chip(agentLabel(agent.component, agent.adapterId), "chip-strong"));
252
+ if (agent.model) chips.push(chip(agent.model));
253
+ if (agent.effort) chips.push(chip(agent.effort));
254
+ return chips.length ? h("span.chip-row", null, chips) : null;
255
+ }
256
+
257
+ // --- Input requests ----------------------------------------------------------------
258
+ export function inputCard(input, t, locale, onAnswer) {
259
+ const policy = input.policy && input.policy.kind;
260
+ const element = h("article.input-card");
261
+ element.append(h("div.input-head", null,
262
+ h("span.input-kicker", null, icon("inbox", "icon-sm"), t("input.kicker")),
263
+ policy ? badge(label(t, "input", policy), policy === "required" ? "bad" : "warn") : null,
264
+ input.policy && input.policy.timeoutAt
265
+ ? h("span.input-timeout", null, icon("clock", "icon-sm"),
266
+ t("input.timeoutAt") + " " + relativeTime(input.policy.timeoutAt, locale, t)) : null,
267
+ timeTag(input.createdAt, locale, t)));
268
+ element.append(h("p.input-question", null, input.question));
269
+ const facts = [];
270
+ if (input.requester) facts.push(t("input.from") + " " + input.requester.roleName
271
+ + " · " + (input.requester.runId || input.requester.nativeSessionId || ""));
272
+ if (input.blockedRefs && input.blockedRefs.length) facts.push(t("input.blocks") + " "
273
+ + input.blockedRefs.map(function (ref) { return ref.type + " " + ref.id; }).join(", "));
274
+ if (facts.length) element.append(h("p.input-facts", null, facts.join(" · ")));
275
+ const recommended = input.policy && input.policy.recommendedChoiceKey;
427
276
  if (input.choices && input.choices.length) {
428
- input.choices.forEach(function (choice) {
429
- const button = node("button", "input-answer", choice.label);
430
- button.type = "button";
431
- button.addEventListener("click", function () {
432
- if (actions.answerInput) actions.answerInput(input, { choiceKey: choice.key });
433
- });
434
- answers.append(button);
435
- });
277
+ element.append(h("div.input-choices", null, input.choices.map(function (choice) {
278
+ return h("button.choice" + (choice.key === recommended ? ".is-recommended" : ""), {
279
+ type: "button",
280
+ onclick: function (event) { onAnswer(input, { choiceKey: choice.key }, event.currentTarget); }
281
+ }, h("span", null, choice.label), choice.key === recommended ? badge(t("input.recommended"), "accent") : null);
282
+ })));
436
283
  } else {
437
- const form = node("form", "input-form");
438
- const field = node("input", "");
439
- field.type = "text";
440
- field.required = true;
441
- field.placeholder = t("input.freeText");
442
- const submit = node("button", "input-answer", t("actions.answer"));
443
- submit.type = "submit";
444
- form.append(field, submit);
284
+ const field = h("input", { type: "text", required: true, placeholder: t("input.freeText"), maxLength: 8000 });
285
+ const form = h("form.input-form", null, field,
286
+ h("button.btn.btn-primary", { type: "submit" }, icon("send"), h("span", null, t("actions.answer"))));
287
+ form.addEventListener("input", function () { form.dataset.unsent = field.value ? "true" : "false"; });
445
288
  form.addEventListener("submit", function (event) {
446
289
  event.preventDefault();
447
290
  const text = field.value.trim();
448
- if (text && actions.answerInput) actions.answerInput(input, { text: text });
291
+ if (text) onAnswer(input, { text: text }, form.querySelector("button"));
449
292
  });
450
- answers.append(form);
451
- }
452
- return answers;
453
- }
454
-
455
- export function inputCard(input, _options, t, locale, actions) {
456
- const card = node("article", "input-card");
457
- const top = node("div", "input-card-top");
458
- top.append(node("small", "", t("detail.openInput")));
459
- const question = node("p", "input-question", input.question);
460
- top.append(question);
461
- const context = node("div", "input-context");
462
- if (input.policy) {
463
- if (input.policy.kind === "recommended") {
464
- context.append(pill(t, "input", "recommended"));
465
- if (input.policy.timeoutAt) {
466
- context.append(node("span", "", t("input.timeoutAt") + " · " + relativeTime(input.policy.timeoutAt, locale, t)));
467
- }
468
- } else {
469
- context.append(pill(t, "input", "required"));
470
- }
293
+ element.append(form);
471
294
  }
472
- context.append(node("time", "", formatDateTime(input.createdAt, locale)));
473
- top.append(context);
474
- if (input.blockedRefs && input.blockedRefs.length) {
475
- const blocked = input.blockedRefs.map(function (ref) { return ref.type + "·" + ref.id; }).join(" ");
476
- top.append(node("span", "input-blocked", blocked));
477
- }
478
- if (input.requester) {
479
- top.append(node("span", "input-requester",
480
- t("detail.requester") + " · " + input.requester.roleName + " / " + (input.requester.runId ?? input.requester.nativeSessionId)));
481
- }
482
- card.append(top);
483
- card.append(answerActions(input, actions, t));
484
- return card;
485
- }
486
-
487
- // --- Conclusion band ------------------------------------------------------------
488
- export function conclusionMeta(task, t, locale, kind) {
489
- const meta = node("div", "conclusion-meta");
490
- const actor = kind === "archived" ? task.archivedBy
491
- : kind === "cancelled" ? task.retiredBy
492
- : task.completedBy;
493
- const at = kind === "archived" ? task.archivedAt
494
- : kind === "cancelled" ? task.retiredAt
495
- : task.completedAt;
496
- const label = kind === "archived" ? t("detail.archivedBy")
497
- : kind === "cancelled" ? t("detail.cancelledBy")
498
- : t("detail.completedBy");
499
- if (actor) meta.append(node("span", "", label + " · " + authorName(t, actor)));
500
- if (at) meta.append(node("time", "", formatDateTime(at, locale)));
501
- return meta;
295
+ return element;
502
296
  }
503
297
 
504
- // --- Record cards -----------------------------------------------------------------
505
- // WorkItems that no longer need action render collapsed; everything actionable
506
- // (pending, running, awaiting acceptance, failed) stays expanded.
507
- const WORK_ITEM_OPEN_STATUSES = ["open"];
508
-
509
- export function workItemCard(item, titles, t, locale, actions, taskId) {
510
- const collapsible = WORK_ITEM_OPEN_STATUSES.indexOf(item.status) === -1;
511
- const card = node(collapsible ? "details" : "article", "record-card work-item-card");
512
- const head = node(collapsible ? "summary" : "div", "record-head");
513
- const titleRow = node("div", "record-title-row");
514
- titleRow.append(statusDot(item.status), node("strong", "record-title", item.title));
515
- const pills = node("div", "record-pills");
516
- pills.append(pill(t, "work", item.status));
517
- if (item.workspaceDisposition) pills.append(pill(t, "disposition", item.workspaceDisposition));
518
- head.append(titleRow, pills);
519
- card.append(head);
520
-
521
- const body = node("div", "work-item-body");
522
- const meta = node("div", "record-meta");
523
- if (item.assignee) meta.append(node("span", "meta-name", item.assignee));
524
- meta.append(node("span", "mono", item.id));
525
- meta.append(node("time", "", formatDateTime(item.updatedAt, locale)));
526
- if (item.endedAt) {
527
- meta.append(node("span", "", t("detail.endedAt") + " · " + formatDateTime(item.endedAt, locale)));
528
- }
529
- body.append(meta);
530
-
531
- if (item.objective && item.objective !== item.title) {
532
- body.append(richText(t("detail.objective"), item.objective, t));
533
- }
298
+ // --- Records -----------------------------------------------------------------------
299
+ export function workItemCard(item, t, locale, titles) {
300
+ const settled = item.status !== "open";
301
+ const element = h(settled ? "details.work-card.is-settled" : "article.work-card");
302
+ element.dataset.viewKey = "work:" + item.id;
303
+ const head = h(settled ? "summary.work-head" : "header.work-head", null,
304
+ settled ? icon("chevron", "disclosure-chevron") : null,
305
+ dot(tone("work", item.status)),
306
+ h("span.work-title", null, item.title),
307
+ statusBadge(t, "work", "work", item.status));
308
+ element.append(head);
309
+ const body = h("div.work-body");
310
+ body.append(h("div.meta-line", null,
311
+ item.assignee ? h("span.meta-strong", null, icon("user", "icon-sm"), item.assignee) : null,
312
+ mono(item.id), timeTag(item.updatedAt, locale, t)));
313
+ if (item.objective && item.objective !== item.title) body.append(richText(t("work.objective"), item.objective, t));
534
314
  if (item.acceptance && item.acceptance.length) {
535
- body.append(criteriaList(t("detail.acceptance"), item.acceptance, t));
536
- }
537
-
538
- // Short chip rows (dependencies, writable projects) sit side by side; long
539
- // text blocks above and below stay full-width so the card reads top-to-bottom
540
- // like an issue rather than a mismatched column grid.
541
- const chipCols = node("div", "work-item-chips");
542
- if (item.dependsOn && item.dependsOn.length) {
543
- chipCols.append(chipRow(t("detail.dependsOn"), item.dependsOn.map(function (id) { return titles[id] || id; })));
315
+ body.append(h("div.prose-block", null, h("h4.prose-label", null, t("work.acceptance")),
316
+ h("ul.checklist", null, item.acceptance.map(function (entry) {
317
+ return h("li" + (item.status === "accepted" ? ".is-done" : ""), null, icon(item.status === "accepted" ? "check" : "target", "icon-sm"), h("span", null, entry));
318
+ }))));
544
319
  }
320
+ const deps = (item.dependsOn || []).map(function (id) { return (titles && titles[id]) || id; });
321
+ if (deps.length) body.append(h("div.meta-line", null, h("span.meta-label", null, t("work.dependsOn")), deps.map(function (d) { return chip(d); })));
545
322
  if (item.writeProjectIds && item.writeProjectIds.length) {
546
- chipCols.append(chipRow(t("detail.writeProjects"), item.writeProjectIds));
323
+ body.append(h("div.meta-line", null, h("span.meta-label", null, t("work.writeProjects")), item.writeProjectIds.map(function (d) { return chip(d); })));
547
324
  }
548
- if (chipCols.childNodes.length) body.append(chipCols);
549
-
550
- if (item.execution) {
551
- body.append(workItemExecutionCard(item.execution, t));
552
- }
553
-
554
- if (item.observability) {
555
- const observability = item.observability;
556
- const metrics = node("div", "record-meta work-item-observability");
557
- metrics.append(usageTile(t("detail.tokens"), observability.cost.tokens, t));
558
- metrics.append(usageTile(t("detail.toolCalls"), observability.cost.toolCalls, t));
559
- metrics.append(usageTile(t("detail.executionSum"), observability.cost.executionSeconds, t, "s"));
560
- metrics.append(node("span", "", t("detail.contextSnapshots") + " · "
561
- + observability.context.snapshotCount));
562
- metrics.append(node("span", "", t("detail.results") + " · "
563
- + (observability.resultCount == null
564
- ? t("detail.unobserved")
565
- : observability.resultCount)));
566
- body.append(metrics);
325
+ if (item.candidates && item.candidates.length) {
326
+ body.append(h("div.prose-block", null, h("h4.prose-label", null, t("work.candidates")),
327
+ h("ol.candidates", null, item.candidates.slice().sort(function (a, b) { return b.sequence - a.sequence; }).map(function (candidate) {
328
+ return h("li", null, h("span.candidate-seq", null, "#" + candidate.sequence), h("span", null, candidate.summary),
329
+ h("span.faint", null, candidate.source && candidate.source.type === "run" ? candidate.source.runId : t("work.direct")),
330
+ timeTag(candidate.createdAt, locale, t));
331
+ }))));
567
332
  }
568
-
569
- // Review candidates submitted for this WorkItem, newest first.
570
- const candidates = candidateList(item.candidates, t, locale);
571
- if (candidates) body.append(candidates);
572
-
573
- // Explicit Leader retirement disposition.
574
333
  if (item.disposition) {
575
- const disposition = node("div", "record-block disposition-block");
576
- disposition.append(node("small", "", t("detail.disposition")));
577
- const text = item.disposition.summary
578
- + (item.disposition.replacementWorkItemId
579
- ? " · " + t("detail.replacementWorkItem") + " " + item.disposition.replacementWorkItemId
580
- : "");
581
- disposition.append(node("p", "muted", text));
582
- body.append(disposition);
334
+ body.append(note(t("work.disposition") + ": " + item.disposition.summary
335
+ + (item.disposition.replacementWorkItemId ? " → " + item.disposition.replacementWorkItemId : "")));
583
336
  }
584
-
585
- if (item.outcome) {
586
- body.append(richText(t("detail.outcome"), item.outcome, t, { extraClass: "outcome-callout", muted: true }));
587
- }
588
- card.append(body);
589
- return card;
337
+ if (item.outcome) body.append(richText(t("work.outcome"), item.outcome, t, { className: "callout" }));
338
+ element.append(body);
339
+ return element;
590
340
  }
591
341
 
592
342
  export function runCard(run, t, locale) {
593
- const card = node("article", "execute-card");
594
- card.dataset.status = run.status;
595
-
596
- const idRow = node("div", "execute-id");
597
- idRow.append(statusDot(run.status));
598
- idRow.append(node("span", "role", run.roleName));
599
- idRow.append(node("span", "", run.id));
600
- if (run.workItemId) idRow.append(node("span", "", t("detail.workItem") + " · " + run.workItemId));
601
- if (run.purpose) idRow.append(chip(t("run.purpose." + run.purpose)));
602
- idRow.append(node("time", "", formatDateTime(run.result?.completedAt || run.updatedAt, locale)));
603
- card.append(idRow);
604
- const observation = node("div", "record-meta");
605
- observation.append(node("span", "", t("run.recordState") + " · " + t("status." + run.status)));
606
- observation.append(node("span", "", t("run.delivery") + " · "
607
- + t("run.delivery." + (run.execution?.delivery || "unobserved"))));
608
- card.append(observation);
609
-
610
- const visibleInput = run.inputs && run.inputs.length ? run.inputs[0].input : null;
611
- card.append(richText(t("detail.instruction"), visibleInput?.directive || visibleInput?.action || "-", t, { className: "execute-io", threshold: 320 }));
612
- if (run.result?.output) {
613
- card.append(richText(t("detail.outcome"), run.result.output, t, { className: "execute-io outcome", threshold: 320 }));
614
- }
615
- if (run.result?.diagnostic) {
616
- card.append(richText(t("detail.failure"), run.result.diagnostic, t, { className: "execute-io outcome", threshold: 320 }));
617
- }
618
-
619
- const foot = node("div", "execute-foot");
620
- const tags = node("div", "execute-tags");
621
- tags.append(chip(t("mode." + run.mode)));
343
+ const element = h("article.run-card");
344
+ element.dataset.status = run.status;
345
+ const input = run.inputs && run.inputs.length ? run.inputs[0].input : null;
346
+ element.append(h("header.run-head", null,
347
+ dot(tone("run", run.status)),
348
+ h("span.run-role", null, run.roleName),
349
+ mono(run.id),
350
+ run.purpose ? chip(label(t, "run.purpose", run.purpose)) : null,
351
+ run.workItemId ? chip(run.workItemId) : null,
352
+ h("span.spacer"),
353
+ statusBadge(t, "run", "run", run.status)));
354
+ element.append(h("div.meta-line", null,
355
+ h("span", null, t("run.delivery") + ": " + label(t, "run.delivery", (run.execution && run.execution.delivery) || "unobserved")),
356
+ run.mode ? h("span", null, label(t, "mode", run.mode)) : null,
357
+ timeTag((run.result && run.result.completedAt) || run.updatedAt, locale, t),
358
+ agentChips(run.effective || (run.agentId ? run : null))));
359
+ const directive = input && (input.directive || input.action);
360
+ if (directive) element.append(richText(t("run.instruction"), directive, t, { threshold: 320 }));
361
+ if (run.result && run.result.output) element.append(richText(t("run.output"), run.result.output, t, { threshold: 320, className: "callout" }));
362
+ if (run.result && run.result.diagnostic) element.append(richText(t("run.failure"), run.result.diagnostic, t, { threshold: 320, className: "callout.tone-bad" }));
622
363
  if (run.executionGroupId) {
623
- tags.append(chip(t("detail.lineage") + " · " + run.executionGroupId
624
- + (run.executionLaneId ? "/" + run.executionLaneId : "")));
625
- }
626
- const badge = run.effective ? agentBadge(run.effective) : (run.agentId ? agentBadge(run) : null);
627
- if (badge) tags.append(badge);
628
- foot.append(tags);
629
- foot.append(pill(t, "run", run.status));
630
- card.append(foot);
631
-
632
- if (run.effective) {
633
- const eff = node("div", "record-meta");
634
- eff.append(node("span", "", t("detail.effective") + " · r" + run.effective.sourceDesiredRevision));
635
- eff.append(node("span", "", t("detail.profileIntent") + " · " + run.effective.profileAccess));
636
- eff.append(node("span", "", t("detail.permission") + " · " + run.effective.permission.strategy));
637
- card.append(eff);
364
+ element.append(h("p.faint.mono-line", null, t("run.lineage") + " " + run.executionGroupId + (run.executionLaneId ? "/" + run.executionLaneId : "")));
638
365
  }
639
-
640
- return card;
366
+ return element;
641
367
  }
642
368
 
643
369
  export function reviewCard(round, t, locale) {
644
- const card = node("article", "record-card");
645
- const head = node("div", "record-head");
646
- const titleRow = node("div", "record-title-row");
647
- titleRow.append(node("strong", "record-title", round.id));
648
- head.append(titleRow);
649
- const headPills = node("div", "record-pills");
650
- if (round.scope) headPills.append(pill(t, "review.scope", round.scope));
651
- headPills.append(chip(t(round.executionGroup
652
- ? "reviewExec.replicated"
653
- : "reviewExec.direct"), round.executionGroup ? "is-active" : ""));
654
- headPills.append(pill(t, "review", round.status));
655
- head.append(headPills);
656
- card.append(head);
657
- const meta = node("div", "record-meta");
658
- meta.append(node("span", "meta-name", round.reviewerRoleName));
659
- if (round.workItemId && round.candidateId) {
660
- meta.append(node("span", "mono", round.workItemId + " · " + round.candidateId));
661
- }
662
- if (round.createdAt) meta.append(node("time", "", formatDateTime(round.createdAt, locale)));
663
- meta.append(node("span", "", t("detail.reviewBase") + " · " + round.reviewBaseCommit));
664
- if (round.workspace && round.workspace.root) {
665
- meta.append(pathMetaItem(t("detail.workspace"), round.workspace.root));
666
- }
667
- if (round.reviewerRunId) {
668
- meta.append(node("span", "mono", t("detail.reviewerRun") + " · " + round.reviewerRunId));
669
- }
670
- if (round.workspaceDisposition) {
671
- meta.append(node("span", "", t("detail.workspaceDisposition") + " · "
672
- + t("disposition." + round.workspaceDisposition.kind)));
673
- }
674
- card.append(meta);
675
- if (round.executionGroup) {
676
- const group = round.executionGroup;
677
- const execution = node("div", "record-block exec-group");
678
- const groupMeta = node("div", "record-meta execution-resource-meta");
679
- groupMeta.append(node("span", "mono", group.id));
680
- groupMeta.append(node("span", "", t("reviewExec.assignment") + " · "
681
- + group.assignment.contextSnapshotRef.id));
682
- groupMeta.append(node("span", "", t("reviewExec.main") + " · "
683
- + (round.reviewerRunId || t("detail.unobserved"))));
684
- execution.append(groupMeta);
685
- const lanes = node("div", "lane-list");
686
- group.lanes.forEach(function (lane) {
687
- const row = node("div", "lane-row");
688
- row.append(statusDot(lane.disposition === "open" ? "running" : lane.disposition));
689
- row.append(node("span", "lane-role", t("reviewExec.producer") + " " + lane.ordinal
690
- + " · " + lane.roleName));
691
- row.append(node("span", "lane-status", t("reviewExec.status." + lane.disposition)));
692
- row.append(node("span", "mono", lane.currentRunId || t("detail.unobserved")));
693
- lanes.append(row);
694
- });
695
- execution.append(lanes);
696
- card.append(execution);
697
- }
698
- if (round.failure) {
699
- card.append(richText(
700
- t("detail.failure"),
701
- round.failure.kind + ": " + round.failure.message,
702
- t
703
- ));
704
- }
705
- return card;
706
- }
707
-
708
- export function roleCard(role, task, t, locale, actions) {
709
- const card = node("article", "record-card");
710
- const head = node("div", "record-head");
711
- head.append(node("strong", "record-title", role.name));
712
- const headRight = node("div", "record-pills");
713
- headRight.append(pill(t, "role", role.status));
714
- const open = node("button", "record-open", "");
715
- open.type = "button";
716
- open.append(node("span", "", t("actions.openSession")), node("span", "arrow", "→"));
717
- open.addEventListener("click", function () {
718
- if (actions.openTerminal) actions.openTerminal({ scope: "task", taskId: task.id, roleName: role.name });
719
- });
720
- headRight.append(open);
721
- head.append(headRight);
722
- card.append(head);
723
-
724
- const meta = node("div", "record-meta");
725
- meta.append(node("span", "meta-name", role.activeAgentId));
726
- const activeBinding = role.agentBindings && role.agentBindings[role.activeAgentId];
727
- if (activeBinding) {
728
- const badge = agentBadge({
729
- adapterId: activeBinding.adapterId,
730
- component: activeBinding.component,
731
- model: activeBinding.config && activeBinding.config.model,
732
- effort: activeBinding.config && activeBinding.config.effort
733
- });
734
- if (badge) meta.append(badge);
735
- }
736
- if (role.launchRevision !== undefined) {
737
- meta.append(node("span", "", t("detail.desired") + " · r" + role.launchRevision));
738
- }
739
- if (role.defaultAccess !== undefined) {
740
- meta.append(node("span", "", t("detail.profileIntent") + " · " + role.defaultAccess));
741
- }
742
- if (role.updatedAt) meta.append(node("time", "", formatDateTime(role.updatedAt, locale)));
743
- card.append(meta);
744
-
745
- if (role.effectiveLaunch) {
746
- const eff = node("div", "record-meta");
747
- eff.append(node("span", "", t("detail.effectiveAgent") + " · " + role.effectiveLaunch.agentId));
748
- const effBadge = agentBadge(role.effectiveLaunch);
749
- if (effBadge) eff.append(effBadge);
750
- if (role.effectiveLaunch.sourceDesiredRevision !== undefined) {
751
- eff.append(node("span", "", t("detail.effective") + " · r" + role.effectiveLaunch.sourceDesiredRevision));
752
- }
753
- if (role.effectiveLaunch.profileAccess !== undefined) {
754
- eff.append(node("span", "", t("detail.profileIntent") + " · " + role.effectiveLaunch.profileAccess));
755
- }
756
- if (role.effectiveLaunch.permission && role.effectiveLaunch.permission.strategy !== undefined) {
757
- eff.append(node("span", "", t("detail.permission") + " · " + role.effectiveLaunch.permission.strategy));
758
- }
759
- eff.append(node("span", "", role.launchDrift ? t("launch.drift") : t("launch.current")));
760
- card.append(eff);
761
- }
762
-
763
- if (role.providerRetry) {
764
- const retry = role.providerRetry;
765
- const retryMeta = node("p", "record-meta");
766
- const zh = locale.startsWith("zh");
767
- const labels = zh ? {
768
- waiting: "等待 Provider 重试", "in-flight": "Provider 重试已提交",
769
- recovered: "Provider 已恢复", cancelled: "自动重试已取消",
770
- exhausted: "自动重试预算耗尽", "needs-attention": "需要检查 Provider",
771
- "delivery-unknown": "投递结果未知,禁止重放"
772
- } : {
773
- waiting: "Waiting for Provider retry", "in-flight": "Provider retry submitted",
774
- recovered: "Provider recovered", cancelled: "Automatic retry cancelled",
775
- exhausted: "Automatic retry budget exhausted", "needs-attention": "Provider needs attention",
776
- "delivery-unknown": "Delivery unknown; no replay"
777
- };
778
- retryMeta.textContent = (labels[retry.status] || retry.status) + " · "
779
- + (zh && retry.category === "rate-limit" ? "临时限流" : retry.category) + " · "
780
- + retry.attempts + "/" + retry.limit
781
- + (retry.status === "waiting" ? " · " + new Date(retry.nextEligibleAt).toLocaleString(locale) : "")
782
- + " · " + (zh ? "原成果保留" : "Existing work preserved")
783
- + (retry.reason ? " · " + retry.reason : "");
784
- card.append(retryMeta);
785
- }
786
-
787
- if (role.sessionTokens) {
788
- const tokenMeta = node("div", "record-meta");
789
- const cumulative = role.sessionTokens.cumulativeTotal || {};
790
- const maximum = role.sessionTokens.maximumRequestInput || {};
791
- tokenMeta.append(node("span", "", t("detail.sessionTotalTokens") + " · "
792
- + (cumulative.status === "observed" ? cumulative.totalTokens : t("detail.unobserved"))));
793
- tokenMeta.append(node("span", "", t("detail.maximumRequestInputTokens") + " · "
794
- + (maximum.status === "observed" ? maximum.inputTokens : t("detail.unobserved"))));
795
- card.append(tokenMeta);
796
- }
797
-
798
- if (role.description) card.append(richText(null, role.description, t, { muted: true }));
799
-
800
- const cols = node("div", "record-cols");
801
- const bindingIds = role.agentBindings ? Object.keys(role.agentBindings) : [];
802
- if (bindingIds.length > 1) {
803
- const bindings = bindingIds.map(function (id) {
804
- const binding = role.agentBindings[id];
805
- const model = binding.config && binding.config.model;
806
- return componentLabel(binding.component, binding.adapterId) + (model ? " · " + model : "");
807
- });
808
- const activeBindingLabel = activeBinding
809
- ? componentLabel(activeBinding.component, activeBinding.adapterId) + (activeBinding.config && activeBinding.config.model ? " · " + activeBinding.config.model : "")
810
- : undefined;
811
- cols.append(chipRow(t("detail.agents"), bindings, activeBindingLabel));
812
- }
813
- if (role.skills && role.skills.length) cols.append(chipRow(t("detail.skills"), role.skills));
814
- if (role.responsibilities && role.responsibilities.length) cols.append(criteriaList(t("detail.responsibilities"), role.responsibilities, t));
815
- if (role.constraints && role.constraints.length) cols.append(criteriaList(t("detail.constraints"), role.constraints, t));
816
- if (role.expectedOutput) cols.append(richText(t("detail.expectedOutput"), role.expectedOutput, t, { muted: true }));
817
- if (cols.childNodes.length) card.append(cols);
818
-
819
- if (role.workspace) {
820
- const workspaceMeta = node("div", "record-meta");
821
- workspaceMeta.append(pathMetaItem(t("detail.workspace"), role.workspace));
822
- card.append(workspaceMeta);
823
- }
824
- return card;
825
- }
826
-
827
- export function historyEventRow(event, t, locale) {
828
- if (event.kind === "milestone") {
829
- const milestone = event.item;
830
- const card = node("article", "record-card");
831
- const head = node("div", "record-head");
832
- const titleRow = node("div", "record-title-row");
833
- titleRow.append(node("strong", "record-title", milestone.title));
834
- head.append(titleRow);
835
- head.append(pill(t, "history", "milestone"));
836
- card.append(head);
837
- const meta = node("div", "record-meta");
838
- meta.append(node("span", "mono", milestone.id));
839
- meta.append(node("time", "", formatDateTime(milestone.createdAt, locale)));
840
- card.append(meta);
841
- if (milestone.summary) card.append(richText(null, milestone.summary, t, { muted: true }));
842
- return card;
843
- }
844
- const decision = event.item;
845
- const card = node("article", "record-card");
846
- const head = node("div", "record-head");
847
- const titleRow = node("div", "record-title-row");
848
- titleRow.append(node("strong", "record-title", decision.title));
849
- head.append(titleRow);
850
- head.append(pill(t, "history", "decision"));
851
- card.append(head);
852
- const meta = node("div", "record-meta");
853
- meta.append(node("span", "mono", decision.id));
854
- if (decision.createdAt) meta.append(node("time", "", formatDateTime(decision.createdAt, locale)));
855
- if (decision.status) meta.append(pill(t, "decision", decision.status));
856
- card.append(meta);
857
- if (decision.rationale) card.append(richText(null, decision.rationale, t, { muted: true }));
858
- if (decision.supersededReason) card.append(richText(null, decision.supersededReason, t, { muted: true }));
859
- return card;
860
- }
861
-
862
- export function messageCard(message, t, locale, result) {
863
- const card = node("article", "record-card");
864
- const head = node("div", "record-head");
865
- const titleRow = node("div", "record-title-row");
866
- titleRow.append(node("strong", "record-title", messageAuthor(message, t)));
867
- head.append(titleRow);
868
- if (message.kind) head.append(pill(t, "messageKind", message.kind));
869
- if (message.status) head.append(pill(t, "status", message.status));
870
- card.append(head);
871
-
872
- const meta = node("div", "record-meta");
873
- meta.append(node("span", "mono", message.id));
874
- meta.append(node("time", "", formatDateTime(message.createdAt, locale)));
875
- if (message.runId) meta.append(node("span", "mono", message.runId + (message.workItemId ? " · " + message.workItemId : "")));
876
- if (message.recipient) {
877
- meta.append(node("span", "mono", "→ " + message.recipient.roleName + " · "
878
- + (message.recipient.reviewRoundId || message.recipient.workItemId)));
879
- meta.append(node("span", "mono", !message.recipient.ownerRunId ? t("messageDelivery.notification") : message.continuation?.runId
880
- ? t("messageDelivery.run") + " " + message.continuation.runId
881
- : message.continuation?.notDeliveredReason
882
- ? t("messageDelivery.blocked") + " " + message.continuation.notDeliveredReason
883
- : t("messageDelivery.pending")));
884
- }
885
- card.append(meta);
886
-
887
- card.append(richText(null, message.body, t));
888
- if (message.resultRef) {
889
- card.append(node("small", "mono", message.resultRef.runId));
890
- if (result?.output) card.append(richText(t("detail.outcome"), result.output, t));
891
- if (result?.diagnostic) card.append(richText(t("detail.failure"), result.diagnostic, t));
892
- if (!result) card.append(node("p", "muted", t("detail.unobserved")));
893
- }
894
- return card;
895
- }
896
-
897
- // --- Overview + sidebar rows --------------------------------------------------
898
- export function metricTile(label, value, options) {
899
- const variant = options && options.hot ? " is-hot" : options && options.warning ? " is-warning" : "";
900
- const tile = node("article", "metric" + variant);
901
- tile.append(node("span", "metric-label", label), node("strong", "metric-value", String(value)));
902
- return tile;
903
- }
904
-
905
- export function attentionRow(item, t, locale, onSelect) {
906
- const row = node("button", "inbox-row");
907
- row.type = "button";
908
- const request = item.request || {};
909
- const kind = request.policy && request.policy.kind === "required" ? "required"
910
- : request.policy && request.policy.kind === "recommended" ? "recommended"
911
- : null;
912
-
913
- const lead = node("span", "inbox-lead");
914
- lead.append(node("span", "inbox-dot"));
915
- const head = node("span", "inbox-head");
916
- head.append(node("span", "inbox-task", item.taskTitle));
917
- if (kind) head.append(pill(t, "input", kind));
918
- lead.append(head);
919
- row.append(lead);
920
-
921
- if (request.question) {
922
- const question = node("span", "inbox-question", request.question);
923
- question.title = request.question;
924
- row.append(question);
925
- }
926
-
927
- const foot = node("span", "inbox-foot");
928
- if (request.createdAt) foot.append(node("time", "", relativeTime(request.createdAt, locale, t)));
929
- foot.append(node("span", "inbox-go", t("actions.answer") + " →"));
930
- row.append(foot);
931
-
932
- row.addEventListener("click", function () { onSelect(item.taskId); });
933
- return row;
370
+ const element = h("article.review-card");
371
+ element.append(h("header.run-head", null,
372
+ dot(tone("review", round.status)),
373
+ h("span.run-role", null, round.reviewerRoleName || t("review.reviewer")),
374
+ mono(round.id),
375
+ round.scope ? chip(label(t, "review.scope", round.scope)) : null,
376
+ h("span.spacer"),
377
+ statusBadge(t, "review", "review", round.status)));
378
+ element.append(h("div.meta-line", null,
379
+ round.workItemId ? mono(round.workItemId + (round.candidateId ? " · " + round.candidateId : "")) : null,
380
+ round.reviewBaseCommit ? h("span", null, t("review.base") + " ", mono(String(round.reviewBaseCommit).slice(0, 12))) : null,
381
+ round.reviewerRunId ? h("span", null, t("review.run") + " ", mono(round.reviewerRunId)) : null,
382
+ timeTag(round.createdAt, locale, t)));
383
+ if (round.failure) element.append(note(round.failure.kind + ": " + round.failure.message, "bad"));
384
+ return element;
934
385
  }
935
386
 
936
- export function overviewRow(task, label, variant, t, locale, onSelect) {
937
- const hasInputs = task.openInputCount > 0;
938
- const row = node("button", "overview-row"
939
- + (hasInputs || variant === "has-inputs" ? " has-inputs" : ""));
940
- row.type = "button";
941
- row.append(statusDot(task.status));
942
- const main = node("span", "overview-row-title", task.title);
943
- main.title = task.title;
944
- row.append(main);
945
- if (label) {
946
- row.append(node("span", "overview-row-label", label));
387
+ export function roleCard(role, t, locale, options) {
388
+ const opts = options || {};
389
+ const session = opts.runtimeRole && opts.runtimeRole.runtimeSession;
390
+ const element = h("article.role-card");
391
+ const binding = role.agentBindings && role.agentBindings[role.activeAgentId];
392
+ element.append(h("header.role-head", null,
393
+ h("span.avatar", { "aria-hidden": "true" }, String(role.name || "?").slice(0, 1).toUpperCase()),
394
+ h("div.role-title", null, h("strong", null, role.name),
395
+ h("span.faint", null, role.activeAgentId || "")),
396
+ opts.status ? statusBadge(t, "role", "role", opts.status) : null));
397
+ if (binding) element.append(agentChips({
398
+ adapterId: binding.adapterId, component: binding.component,
399
+ model: binding.config && binding.config.model, effort: binding.config && binding.config.effort
400
+ }));
401
+ if (role.description) element.append(richText(null, role.description, t, { muted: true, threshold: 280 }));
402
+ const rows = [];
403
+ rows.push([t("role.session"), session && session.nativeSessionId ? mono(session.nativeSessionId) : t("role.noSession")]);
404
+ if (role.launchRevision !== undefined) rows.push([t("role.desired"), "r" + role.launchRevision + (opts.launchDrift ? " · " + t("role.drift") : "")]);
405
+ if (role.defaultAccess !== undefined) rows.push([t("role.access"), role.defaultAccess]);
406
+ if (opts.retry) rows.push([t("role.providerRetry"), label(t, "retry", opts.retry.status) + " · "
407
+ + opts.retry.attempts + "/" + opts.retry.limit
408
+ + (opts.retry.status === "waiting" && opts.retry.nextEligibleAt ? " · " + formatDateTime(opts.retry.nextEligibleAt, locale) : "")]);
409
+ if (role.skills && role.skills.length) rows.push([t("role.skills"), h("span.chip-row", null, role.skills.map(function (s) { return chip(s); }))]);
410
+ element.append(kv(rows));
411
+ if (opts.onOpen) {
412
+ element.append(h("div.role-actions", null, button(t("role.openSession"), {
413
+ icon: "terminal", variant: "ghost", onClick: function () { opts.onOpen(role.name); }
414
+ })));
947
415
  }
948
- row.append(node("span", "overview-row-time", relativeTime(task.updatedAt, locale, t)));
949
- row.addEventListener("click", function () { onSelect(task.id); });
950
- return row;
951
- }
952
-
953
- export function taskCard(task, hasAttention, state, t, locale, onSelect) {
954
- const button = node("button", "task");
955
- button.type = "button";
956
- button.dataset.id = task.id;
957
- button.setAttribute("aria-current", String(state.selected === task.id));
958
-
959
- button.append(statusDot(task.status));
960
-
961
- const main = node("span", "task-main");
962
- main.append(node("strong", "task-title", task.title));
963
- main.append(node("span", "task-meta", relativeTime(task.updatedAt, locale, t)));
964
- button.append(main);
965
-
966
- // Derived Task-first execution status for active tasks (blocked, waiting on
967
- // agents, recovering, etc.) — a richer signal than the raw lifecycle status.
968
- if (task.executionStatus && task.status === "active") {
969
- const exec = node("span", "task-exec is-" + task.executionStatus,
970
- t("exec.status." + task.executionStatus));
971
- button.append(exec);
972
- }
973
-
974
- // One signal only: an open input (needs the user) outranks a running count.
975
- if (hasAttention || (task.attention && task.attention.openInputs > 0)) {
976
- const badge = node("span", "task-signal is-input", String(task.attention ? task.attention.openInputs : task.openInputCount || 1));
977
- badge.title = t("stats.inputs");
978
- button.append(badge);
979
- } else if (task.attention && task.attention.executionSignals > 0) {
980
- const badge = node("span", "task-signal", String(task.attention.executionSignals));
981
- badge.title = t("catalog.executionSignals");
982
- button.append(badge);
983
- } else if (task.workItems && task.workItems.running > 0) {
984
- const badge = node("span", "task-signal is-running", String(task.workItems.running));
985
- badge.title = t("stats.running");
986
- button.append(badge);
987
- }
988
-
989
- button.addEventListener("click", function () { onSelect(task.id); });
990
- return button;
416
+ return element;
991
417
  }
992
418
  `;