@zq-silk/yui 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +8 -2
  2. package/dist/cli/commandCatalog.js +32 -6
  3. package/dist/cli/commandDiscovery.js +4 -1
  4. package/dist/cli/interactionPolicy.js +4 -0
  5. package/dist/cli.js +23 -3078
  6. package/dist/commands/taskCommands.js +84 -28
  7. package/dist/commands/taskIntegrationCommands.js +8 -17
  8. package/dist/commands/taskUpstreamCommands.js +3 -1
  9. package/dist/context/runContextPack.js +132 -5
  10. package/dist/context/sourceRunContext.js +4 -2
  11. package/dist/context/taskContext.js +6 -1
  12. package/dist/controlPlaneCli.js +3099 -0
  13. package/dist/controller/clientRuntime.js +3 -2
  14. package/dist/controller/fileSchedulerStoreAdapter.js +19 -6
  15. package/dist/controller/jobControl.js +21 -0
  16. package/dist/executor/agentExecutor.js +1 -1
  17. package/dist/executor/effectiveLaunch.js +18 -5
  18. package/dist/executor/fileRoleLaunchPlanner.js +15 -19
  19. package/dist/integration/gitIntegrationService.js +58 -82
  20. package/dist/job/jobCleanupInspection.js +67 -0
  21. package/dist/message/messageContinuation.js +7 -2
  22. package/dist/nativeAgent/agent.js +176 -74
  23. package/dist/nativeAgent/cliDemo.js +37 -0
  24. package/dist/nativeAgent/codingTools.js +11 -0
  25. package/dist/nativeAgent/commandTool.js +215 -0
  26. package/dist/nativeAgent/compactionDemo.js +158 -0
  27. package/dist/nativeAgent/composition.js +71 -0
  28. package/dist/nativeAgent/context/budget.js +44 -0
  29. package/dist/nativeAgent/context/index.js +339 -0
  30. package/dist/nativeAgent/context/providerCompressor.js +103 -0
  31. package/dist/nativeAgent/demo.js +12 -1
  32. package/dist/nativeAgent/evaluation/cases.js +38 -0
  33. package/dist/nativeAgent/evaluation/checks.js +91 -0
  34. package/dist/nativeAgent/evaluation/demo.js +19 -0
  35. package/dist/nativeAgent/evaluation/files.js +54 -0
  36. package/dist/nativeAgent/evaluation/fixture.js +36 -0
  37. package/dist/nativeAgent/evaluation/index.js +239 -0
  38. package/dist/nativeAgent/executionOwner.js +209 -0
  39. package/dist/nativeAgent/filePatterns.js +170 -0
  40. package/dist/nativeAgent/fileToolsSupport.js +202 -0
  41. package/dist/nativeAgent/index.js +13 -0
  42. package/dist/nativeAgent/interaction/cli.js +358 -0
  43. package/dist/nativeAgent/interaction/contracts.js +1 -0
  44. package/dist/nativeAgent/interaction/index.js +3 -0
  45. package/dist/nativeAgent/interaction/memoryDemo.js +115 -0
  46. package/dist/nativeAgent/interaction/renderer.js +34 -0
  47. package/dist/nativeAgent/localSafety.js +258 -0
  48. package/dist/nativeAgent/model/anthropicMessages.js +204 -0
  49. package/dist/nativeAgent/model/chatCompletions.js +210 -0
  50. package/dist/nativeAgent/model/errors.js +47 -0
  51. package/dist/nativeAgent/model/gateway.js +423 -0
  52. package/dist/nativeAgent/model/index.js +7 -0
  53. package/dist/nativeAgent/model/observationAdapter.js +21 -0
  54. package/dist/nativeAgent/model/protocols.js +19 -0
  55. package/dist/nativeAgent/model/responses.js +263 -0
  56. package/dist/nativeAgent/model/types.js +1 -0
  57. package/dist/nativeAgent/model/wire.js +73 -0
  58. package/dist/nativeAgent/observability/index.js +220 -0
  59. package/dist/nativeAgent/product/catalog.js +82 -0
  60. package/dist/nativeAgent/product/config.js +295 -0
  61. package/dist/nativeAgent/product/facts.js +30 -0
  62. package/dist/nativeAgent/product/index.js +62 -0
  63. package/dist/nativeAgent/product/location.js +44 -0
  64. package/dist/nativeAgent/product/runtime.js +276 -0
  65. package/dist/nativeAgent/product/storage.js +49 -0
  66. package/dist/nativeAgent/product/tools.js +47 -0
  67. package/dist/nativeAgent/product/transport.js +54 -0
  68. package/dist/nativeAgent/projectGuidance/index.js +425 -0
  69. package/dist/nativeAgent/searchTools.js +305 -0
  70. package/dist/nativeAgent/session/backends.js +293 -0
  71. package/dist/nativeAgent/session/catalog.js +97 -0
  72. package/dist/nativeAgent/session/catalogDemo.js +87 -0
  73. package/dist/nativeAgent/session/contracts.js +1 -0
  74. package/dist/nativeAgent/session/format.js +269 -0
  75. package/dist/nativeAgent/session/index.js +5 -0
  76. package/dist/nativeAgent/session/location.js +36 -0
  77. package/dist/nativeAgent/session/sqliteFormat.js +134 -0
  78. package/dist/nativeAgent/session/store.js +248 -0
  79. package/dist/nativeAgent/textTools.js +270 -133
  80. package/dist/nativeAgent/toolManager/executor.js +290 -0
  81. package/dist/nativeAgent/toolManager/index.js +4 -0
  82. package/dist/nativeAgent/validation.js +2 -2
  83. package/dist/repository/gitWorkspace.js +41 -12
  84. package/dist/repository/taskWorkspaceCoordinator.js +72 -145
  85. package/dist/repository/taskWorkspacePreparer.js +39 -43
  86. package/dist/repository/workspaceCleanupInspection.js +88 -7
  87. package/dist/resources/resourceRegistrar.js +16 -10
  88. package/dist/runtime/integrationRuntimeIsolation.js +21 -0
  89. package/dist/runtime/taskRuntimeIsolation.js +91 -21
  90. package/dist/task/archiveDiagnostics.js +26 -8
  91. package/dist/task/archivePreflight.js +26 -9
  92. package/dist/task/leaderArchive.js +9 -15
  93. package/dist/task/taskAuthority.js +56 -0
  94. package/dist/web/assets/client/api.js +8 -0
  95. package/dist/web/assets/client/app.js +74 -9
  96. package/dist/web/assets/client/components.js +95 -36
  97. package/dist/web/assets/client/detail.js +110 -64
  98. package/dist/web/assets/client/dock.js +95 -11
  99. package/dist/web/assets/client/evidence.js +286 -94
  100. package/dist/web/assets/client/i18n.js +182 -62
  101. package/dist/web/assets/client/overview.js +9 -10
  102. package/dist/web/assets/client/records.js +142 -7
  103. package/dist/web/assets/client/sections.js +234 -198
  104. package/dist/web/assets/client/sidebar.js +31 -2
  105. package/dist/web/assets/icons.js +4 -0
  106. package/dist/web/assets/shell.js +2 -1
  107. package/dist/web/assets/styles/components.js +64 -15
  108. package/dist/web/assets/styles/layout.js +11 -4
  109. package/dist/web/assets/styles/responsive.js +16 -6
  110. package/dist/web/assets/styles/tokens.js +1 -1
  111. package/dist/web/assets/styles/views.js +62 -42
  112. package/dist/web/webServer.js +12 -1
  113. package/dist/web/webTaskSurface.js +8 -1
  114. package/docs/agent-result-consumption.md +16 -0
  115. package/docs/agent-result-consumption.zh-CN.md +13 -0
  116. package/docs/examples/agent-offline.mjs +194 -0
  117. package/docs/native-agent.md +283 -0
  118. package/docs/observability/README.md +13 -3
  119. package/docs/observability/README.zh-CN.md +10 -3
  120. package/docs/release-workflow.md +72 -9
  121. package/docs/release-workflow.zh-CN.md +55 -6
  122. package/docs/roles-and-configuration.md +32 -0
  123. package/docs/roles-and-configuration.zh-CN.md +26 -0
  124. package/docs/task-delivery.md +36 -8
  125. package/docs/task-delivery.zh-CN.md +24 -7
  126. package/i18n/README.zh-CN.md +3 -2
  127. package/package.json +1 -1
  128. package/skills/yui-leader/SKILL.md +9 -0
  129. package/skills/yui-leader/references/execution.md +2 -1
  130. package/skills/yui-leader/references/integration.md +20 -0
  131. package/skills/yui-operator/SKILL.md +4 -0
  132. package/skills/yui-reviewer/SKILL.md +8 -0
  133. package/skills/yui-runtime/SKILL.md +11 -0
  134. package/skills/yui-runtime/references/publication.md +20 -4
  135. package/skills/yui-worker/SKILL.md +3 -0
@@ -1,72 +1,42 @@
1
1
  export const SECTIONS_SCRIPT = String.raw `
2
- // Task sections behind the Work, Execution, History and Details tabs.
2
+ // The Runtime and Records tabs. Runtime is what is live now (Roles and their
3
+ // Sessions, open execution, open operations, usage, diagnostics); Records is
4
+ // what happened (a paged timeline of decisions, milestones, executions and
5
+ // questions) plus Task facts. Every #detail-* anchor stays addressable via
6
+ // ?section=.
3
7
  import { h, icon, clear } from "/assets/js/dom.js";
4
8
  import { formatDateTime } from "/assets/js/format.js";
5
9
  import {
6
- sectionTitle, card, disclosure, emptyState, note, button, statusBadge, label, tone, dot, chip, mono,
7
- kv, richText, pagedList, workItemCard, runCard, reviewCard, roleCard, timeTag, observabilityMetricCard
10
+ card, listCard, filterChips, disclosure, emptyState, note, button, statusBadge, label, tone, dot, chip, mono,
11
+ kv, richText, runCard, runBody, roleCard, timeTag, observabilityMetricCard
8
12
  } from "/assets/js/components.js";
9
- import { entriesOf, valuesOf, recordCard, recordReader } from "/assets/js/records.js";
10
- import { renderEvidence } from "/assets/js/evidence.js";
13
+ import {
14
+ entriesOf, totalOf, summaryFields, exactRow, lazyRow, recordCard, familyState, loadFamily
15
+ } from "/assets/js/records.js";
11
16
  import { controlForm, titleForm } from "/assets/js/forms.js";
12
17
 
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) {
18
+ // --- Runtime ----------------------------------------------------------------------
19
+ export function renderRuntime(panel, data, t, locale, ctx) {
58
20
  const core = data.core;
59
21
  const task = data.task;
22
+ const view = data.viewState;
23
+ view.exact = view.exact || {};
60
24
  const runtimeRoles = (data.runtime && data.runtime.roles) || [];
61
- const runs = valuesOf(core, "run");
25
+ // Active runs already read exactly (this view or an earlier render).
26
+ const knownRuns = entriesOf(core, "run").map(function (entry) {
27
+ const hit = view.exact["run/" + entry.ref.refId];
28
+ return hit && hit.digest === entry.ref.digest && hit.result ? hit.result.value : null;
29
+ }).filter(Boolean);
62
30
 
63
31
  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");
32
+ const roles = listCard({
33
+ id: "detail-roles", title: t("roles.title"), icon: "user", count: totalOf(core, "role"), hint: t("roles.hint"),
34
+ actions: h("div.chip-row", { dataset: { slot: "session-counts" } })
35
+ });
66
36
  roleEntries.forEach(function (entry) {
67
- if (entry.omitted) { roleGrid.append(recordCard(entry, task.id, t, ctx)); return; }
37
+ if (entry.omitted) { roles.list.append(recordCard(entry, task.id, t, ctx)); return; }
68
38
  const role = entry.value;
69
- const active = runs.find(function (run) { return run.roleName === role.name && run.status === "active"; });
39
+ const active = knownRuns.find(function (run) { return run.roleName === role.name && run.status === "active"; });
70
40
  const runtimeRole = runtimeRoles.find(function (item) { return item.name === role.name; });
71
41
  const element = roleCard(role, t, locale, {
72
42
  runtimeRole: runtimeRole,
@@ -74,162 +44,228 @@ export function renderExecution(panel, data, t, locale, ctx) {
74
44
  launchDrift: active && active.effective && active.effective.sourceDesiredRevision !== role.launchRevision,
75
45
  onOpen: task.status === "archived" ? null : ctx.openSession
76
46
  });
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);
47
+ const head = element.querySelector(".role-head");
48
+ head.append(h("span", { dataset: { roleStatus: role.name } }));
49
+ head.after(h("div.role-session", { dataset: { roleSession: role.name } }));
50
+ roles.list.append(element);
81
51
  });
82
- if (!roleEntries.length) roleGrid.append(emptyState(omittedNote(core, t)));
83
- roles.append(roleGrid);
52
+ if (!roleEntries.length) roles.list.append(emptyState(t("records.none")));
84
53
  panel.append(roles);
85
54
 
86
55
  const redirect = disclosure(t("control.title"), "control", { className: "card-disclosure", meta: t("control.meta") });
56
+ redirect.id = "detail-control";
87
57
  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));
58
+ panel.append(redirect);
89
59
 
90
60
  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))));
61
+ const runTotal = totalOf(core, "run");
62
+ const runList = listCard({ id: "detail-exec", title: t("runs.title"), icon: "pulse", count: runTotal || null, hint: t("runs.hint") });
63
+ if (!runEntries.length) runList.list.append(emptyState(t("runs.none"), "pulse"));
64
+ runEntries.forEach(function (entry) {
65
+ runList.list.append(exactRow(entry, task.id, t, ctx, view.exact, function (value, result) {
66
+ return runCard({ ...value, execution: (result && result.execution) || entry.execution }, t, locale);
67
+ }));
115
68
  });
116
- runSection.append(sectionTitle(t("runs.title"), runEntries.length), note(t("runs.hint")), filterRow, list);
117
- draw();
118
- panel.append(runSection);
69
+ if (runTotal > runEntries.length) runList.list.append(note(t("runs.more").replace("{count}", String(runTotal - runEntries.length))));
70
+ panel.append(runList);
119
71
 
120
72
  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,
73
+ const jobTotal = totalOf(core, "job");
74
+ const jobList = listCard({
75
+ id: "detail-operations", title: t("jobs.title"), icon: "refresh", count: jobTotal || null,
76
+ hint: jobTotal ? t("jobs.hint") : null
77
+ });
78
+ if (!jobs.length) jobList.list.append(emptyState(t("jobs.none")));
79
+ jobs.forEach(function (entry) {
80
+ jobList.list.append(exactRow(entry, task.id, t, ctx, view.exact, function (job) {
81
+ return h("article.record", null,
130
82
  h("header.record-head", null, dot(tone("job", job.status)), mono(task.id + "/" + job.id), h("span.spacer"),
131
83
  statusBadge(t, "job", "job", job.status)),
132
84
  h("p.small", null, t("job." + job.status + ".note", t("job.terminal.note"))),
133
85
  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);
86
+ timeTag(job.terminalAt || job.startedAt || job.createdAt, locale, t));
87
+ }));
88
+ });
89
+ panel.append(jobList);
139
90
 
140
- const usage = h("section.section", { id: "detail-usage" }, sectionTitle(t("usage.title")));
91
+ const usage = card({ id: "detail-usage", title: t("usage.title"), icon: "clock" });
141
92
  const usageBody = h("div", { dataset: { slot: "usage" } });
142
93
  const metrics = data.runtime && observabilityMetricCard(data.runtime.observability, t);
143
94
  usageBody.append(metrics || emptyState(t("usage.unavailable")));
144
- usage.append(usageBody);
95
+ usage.body.append(usageBody);
145
96
  panel.append(usage);
146
97
 
147
- const observations = disclosure(t("observation.title"), "execution", { className: "card-disclosure" });
148
- observations.body.append(note(t("observation.help")));
98
+ const diagnostics = disclosure(t("diagnostics.title"), "diagnostics", { className: "card-disclosure", meta: t("diagnostics.meta") });
99
+ diagnostics.id = "detail-diagnostics";
100
+ diagnostics.body.append(note(t("observation.help")));
149
101
  (core.observations || []).forEach(function (observation) {
150
- observations.body.append(h("p.small.mono-line", null,
102
+ diagnostics.body.append(h("p.small.mono-line", null,
151
103
  observation.source + " · " + observation.status + " · " + observation.coverage + " · " + observation.observedAt));
152
104
  });
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));
105
+ diagnostics.body.append(h("div.stack", { dataset: { slot: "session-facts" } }),
106
+ h("p.faint.small", { dataset: { slot: "runtime-status" } }), h("pre.code-block", { dataset: { slot: "runtime-raw" } }));
107
+ panel.append(diagnostics);
155
108
  }
156
109
 
157
- // --- History ----------------------------------------------------------------------
158
- export function renderHistory(panel, data, t, locale, ctx) {
110
+ // --- Records ----------------------------------------------------------------------
111
+ // One timeline over four Context families, read as paged summaries. Messages
112
+ // live in the Discussion dock. Ascending families are read in full (bounded);
113
+ // descending runs page on request, and older rows of other families are held
114
+ // back until the run pages reach them so the merged order stays honest.
115
+ const TIMELINE = [
116
+ { kind: "decision", store: "task-decision", icon: "check", ascending: true },
117
+ { kind: "milestone", store: "task-milestone", icon: "flag", ascending: true },
118
+ { kind: "run", store: "run", icon: "pulse", ascending: false },
119
+ { kind: "question", store: "input-request", icon: "inbox", ascending: true }
120
+ ];
121
+ const FULL_PAGES = 5;
122
+
123
+ function at(item) { return Date.parse(item.createdAt || 0) || 0; }
124
+
125
+ export function renderRecords(panel, data, t, locale, ctx) {
159
126
  const core = data.core;
160
127
  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") });
128
+ const view = data.viewState;
129
+ view.panels = view.panels || {};
130
+ view.panels.records = panel;
131
+ view.exact = view.exact || {};
132
+ view.openRows = view.openRows || {};
133
+ if (!TIMELINE.some(function (family) { return family.kind === view.recordsFilter; })) view.recordsFilter = "all";
134
+ // Values the current snapshot already carries need no read.
135
+ const known = {};
136
+ core.records.forEach(function (entry) { if (!entry.omitted) known[entry.ref.store + "/" + entry.ref.refId] = entry; });
137
+
138
+ const history = card({
139
+ id: "detail-history", title: t("history.title"), icon: "history", hint: t("history.hint"),
140
+ actions: filterChips(t("history.filter"), [{ value: "all", label: t("history.all") }].concat(TIMELINE.map(function (family) {
141
+ return { value: family.kind, label: t("history." + family.kind) };
142
+ })), view.recordsFilter, function (value) { view.recordsFilter = value; draw(); })
143
+ });
174
144
  const list = h("ol.timeline");
145
+ const foot = h("div.list-foot");
146
+ history.body.append(list, foot);
147
+
148
+ function families() {
149
+ return TIMELINE.filter(function (family) { return view.recordsFilter === "all" || view.recordsFilter === family.kind; });
150
+ }
175
151
  function draw() {
176
152
  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);
153
+ clear(foot);
154
+ history.querySelectorAll(".chip-tab").forEach(function (chipButton) {
155
+ chipButton.setAttribute("aria-pressed", String(chipButton.dataset.filter === view.recordsFilter));
156
+ });
157
+ const selected = families();
158
+ const states = selected.map(function (family) { return { family: family, state: familyState(view, family.store) }; });
159
+ let horizon = -Infinity;
160
+ states.forEach(function (row) {
161
+ if (row.state.nextCursor && !row.family.ascending && row.state.items.length) {
162
+ horizon = Math.max(horizon, Math.min.apply(null, row.state.items.map(at)));
163
+ }
164
+ });
165
+ const items = [];
166
+ states.forEach(function (row) {
167
+ row.state.items.forEach(function (item) { if (at(item) >= horizon) items.push({ family: row.family, item: item }); });
168
+ });
169
+ items.sort(function (a, b) { return at(b.item) - at(a.item); });
170
+ const loading = states.some(function (row) { return row.state.pending && !row.state.pages; });
171
+ const loaded = states.every(function (row) { return row.state.pages || row.state.error; });
172
+ states.forEach(function (row) {
173
+ if (row.state.error) list.append(h("li.timeline-empty", null, note(t("history." + row.family.kind) + " · " + t("records.unavailable") + " " + row.state.error, "bad")));
174
+ });
175
+ if (!items.length) {
176
+ list.append(h("li.timeline-empty", null, emptyState(loading || !loaded ? t("history.loading") : t("records.none"), "history")));
177
+ }
178
+ items.forEach(function (row) { list.append(timelineItem(row.family, row.item)); });
179
+ const total = states.reduce(function (sum, row) { return sum + (row.state.total || 0); }, 0);
180
+ if (!loaded) return;
181
+ foot.append(h("span.faint.small", null, t("history.loaded").replace("{shown}", String(items.length)).replace("{total}", String(total))));
182
+ const stale = states.some(function (row) { return row.state.key && row.state.key !== core.coreCursor; });
183
+ if (stale) {
184
+ foot.append(button(t("history.stale"), { icon: "refresh", variant: "ghost", onClick: function () { show(true); } }));
185
+ }
186
+ if (states.some(function (row) { return row.state.nextCursor; })) {
187
+ const more = button(t("history.more"), { icon: "chevron", variant: "ghost" });
188
+ more.addEventListener("click", function () {
189
+ more.disabled = true;
190
+ Promise.all(states.filter(function (row) { return row.state.nextCursor; }).map(function (row) {
191
+ return loadFamily(view, task.id, row.family.store, row.state.key, ctx, { more: true, limit: 40 });
192
+ })).then(refresh);
193
+ });
194
+ foot.append(more);
195
+ }
180
196
  }
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();
197
+
198
+ function timelineItem(family, item) {
199
+ const fields = summaryFields(item.summary);
200
+ const entry = known[item.ref.store + "/" + item.ref.refId];
201
+ const value = entry && entry.ref.digest === item.ref.digest ? entry.value : null;
202
+ const element = h("li.timeline-item.kind-" + family.kind);
203
+ element.append(h("span.timeline-mark", { "aria-hidden": "true" }, icon(family.icon, "icon-sm")));
204
+ const body = h("div.timeline-body");
205
+ let title;
206
+ if (family.kind === "run") title = [item.roleName || t("store.run"), item.purpose ? label(t, "run.purpose", item.purpose) : null].filter(Boolean).join(" · ");
207
+ else if (family.kind === "question") title = fields.question || fields.title || item.ref.refId;
208
+ else title = fields.title || item.ref.refId;
209
+ const status = family.kind === "decision" ? statusBadge(t, "decision", "decision", item.status)
210
+ : family.kind === "run" ? statusBadge(t, "run", "run", item.status)
211
+ : family.kind === "question" ? statusBadge(t, "input", "inputStatus", item.status) : null;
212
+ const head = h("div.timeline-summary", null, h("header.timeline-head", null, h("strong.timeline-title", null, title),
213
+ family.kind === "run" ? mono(item.ref.refId) : null,
214
+ item.workItemId ? chip(item.workItemId) : null,
215
+ item.history === "retired" ? chip(t("history.retired")) : null,
216
+ item.status ? status : null, h("span.spacer"), timeTag(item.createdAt, locale, t)));
217
+ const preview = value && family.kind === "decision" ? value.rationale : fields.summary !== title ? fields.summary : null;
218
+ if (preview) head.append(h("p.small.muted.timeline-preview", null, preview));
219
+ // The row opens in place; its exact record is read on first open.
220
+ body.append(lazyRow(item, task.id, t, ctx, { cache: view.exact, openRows: view.openRows, head: head, render: function (exact, result) {
221
+ if (family.kind === "run") return h("div.stack", null, runBody({ ...exact, execution: result && result.execution }, t, locale));
222
+ if (family.kind === "decision") return h("div.stack", null,
223
+ exact.rationale ? richText(t("record.rationale"), exact.rationale, t, { muted: true }) : null,
224
+ exact.supersededReason ? richText(t("history.superseded"), exact.supersededReason, t, { muted: true }) : null);
225
+ if (family.kind === "milestone") return h("div.stack", null, exact.summary ? richText(null, exact.summary, t) : null);
226
+ if (family.kind === "question") {
227
+ const answer = exact.resolution && exact.resolution.answer;
228
+ return h("div.stack", null, exact.question ? richText(null, exact.question, t) : null,
229
+ answer ? richText(t("history.answer") + " · " + label(t, "answeredBy", exact.resolution.answeredBy),
230
+ answer.text || answer.choiceKey, t, { muted: true }) : null,
231
+ exact.cancellation ? note(exact.cancellation.reason) : null);
189
232
  }
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
- }
233
+ return null;
234
+ } }));
235
+ element.append(body);
236
+ return element;
237
+ }
197
238
 
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 }));
239
+ // Reads redraw whichever Records panel is current.
240
+ function refresh() {
241
+ const current = view.panels.records;
242
+ if (current && current.isConnected) current.redraw();
221
243
  }
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
- }
244
+ function loadFull(family, key) {
245
+ return loadFamily(view, task.id, family.store, key, ctx, { limit: 40 }).then(function (state) {
246
+ if (!family.ascending || !state.nextCursor || state.pages >= FULL_PAGES || state.error) return state;
247
+ return loadFamily(view, task.id, family.store, key, ctx, { more: true, limit: 40 }).then(function () { return loadFull(family, key); });
248
+ });
249
+ }
250
+ // A stale first page re-reads silently; once the user paged further, the
251
+ // read waits for an explicit refresh so their position is kept.
252
+ function show(force) {
253
+ const key = core.coreCursor;
254
+ TIMELINE.forEach(function (family) {
255
+ const state = familyState(view, family.store);
256
+ if (state.pending || state.key === key || (!force && state.errorKey === key)) return;
257
+ if (!force && state.key && !family.ascending && state.pages > 1) return;
258
+ loadFull(family, key).then(refresh);
259
+ });
260
+ refresh();
261
+ }
262
+ panel.redraw = draw;
263
+ panel.onShow = function () { show(false); };
264
+ draw();
265
+ panel.append(history);
227
266
 
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" });
267
+ // --- Task information --------------------------------------------------------
268
+ const facts = card({ id: "detail-facts", title: t("details.facts"), icon: "info" });
233
269
  facts.body.append(kv([
234
270
  [t("details.id"), mono(task.id)],
235
271
  [t("details.status"), statusBadge(t, "task", "status", task.status)],
@@ -240,37 +276,37 @@ export function renderDetails(panel, data, t, locale, ctx) {
240
276
  [t("details.updated"), formatDateTime(task.updatedAt, locale)],
241
277
  [t("details.workspace"), task.cwd ? mono(task.cwd) : null]
242
278
  ]));
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));
279
+ facts.body.append(h("div.prose-block", null, h("h4.prose-label", null, t("details.title")), titleForm(task, t, ctx)));
280
+ panel.append(facts);
248
281
 
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
282
+ // --- Advanced -------------------------------------------------------------------
283
+ const advanced = disclosure(t("advanced.title"), "advanced", { className: "card-disclosure", meta: t("advanced.meta") });
284
+ advanced.id = "detail-advanced";
285
+ advanced.body.append(h("h4.sub-head", null, t("details.context")), h("pre.code-block", null, JSON.stringify({
286
+ coreCursor: core.coreCursor, throughCursor: core.throughCursor, count: core.count, collections: core.collections, omitted: core.omitted
252
287
  }, 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" });
288
+ const panels = h("div.stack");
289
+ advanced.body.append(h("h4.sub-head", null, t("panels.title")), panels);
256
290
  let loading = false;
257
- panels.addEventListener("toggle", async function () {
258
- if (!panels.open || loading) return;
291
+ let loadedPanels = false;
292
+ advanced.addEventListener("toggle", async function () {
293
+ if (!advanced.open || loading || loadedPanels) return;
259
294
  loading = true;
260
- clear(panels.body);
261
- panels.body.append(h("p.faint", null, t("panels.reading")));
295
+ clear(panels);
296
+ panels.append(h("p.faint", null, t("panels.reading")));
262
297
  try {
263
298
  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)); });
299
+ clear(panels);
300
+ panels.append(h("p.faint.small", null, t("panels.observed") + " " + formatDateTime(current.observedAt, locale)));
301
+ if (!current.panels.length) panels.append(emptyState(t("panels.none")));
302
+ current.panels.forEach(function (item) { panels.append(panelCard(item, task, t, ctx)); });
303
+ loadedPanels = true;
268
304
  } catch (error) {
269
- clear(panels.body);
270
- panels.body.append(note(t("panels.unavailable") + " " + error.message, "bad"));
305
+ clear(panels);
306
+ panels.append(note(t("panels.unavailable") + " " + error.message, "bad"));
271
307
  } finally { loading = false; }
272
308
  });
273
- panel.append(h("section.section", null, panels));
309
+ panel.append(advanced);
274
310
  }
275
311
 
276
312
  function panelCard(item, task, t, ctx) {
@@ -30,7 +30,24 @@ export function renderAttentionBar(container, state, t, onPick) {
30
30
  }
31
31
 
32
32
  // The tab row is created once and updated in place so its horizontal scroll
33
- // position survives every refresh.
33
+ // position survives every refresh. When the sidebar is too narrow for every
34
+ // tab, the clipped edge fades out, the wheel scrolls the row sideways and a
35
+ // newly selected tab is scrolled into view.
36
+ function syncTabsOverflow(row) {
37
+ const max = row.scrollWidth - row.clientWidth;
38
+ const before = row.scrollLeft > 1;
39
+ const after = row.scrollLeft < max - 1;
40
+ row.dataset.fade = before && after ? "both" : after ? "end" : before ? "start" : "none";
41
+ }
42
+
43
+ function revealTab(row, tab) {
44
+ const margin = 28;
45
+ const left = tab.offsetLeft - margin;
46
+ const right = tab.offsetLeft + tab.offsetWidth + margin;
47
+ if (left < row.scrollLeft) row.scrollLeft = Math.max(0, left);
48
+ else if (right > row.scrollLeft + row.clientWidth) row.scrollLeft = right - row.clientWidth;
49
+ }
50
+
34
51
  export function renderFilters(container, state, t, onFilter) {
35
52
  const counts = state.counts || {};
36
53
  let row = container.querySelector(":scope > .tabs-row");
@@ -41,6 +58,13 @@ export function renderFilters(container, state, t, onFilter) {
41
58
  h("span.status-tab-label"), h("span.status-tab-count")));
42
59
  });
43
60
  container.append(row);
61
+ row.addEventListener("scroll", function () { syncTabsOverflow(row); }, { passive: true });
62
+ row.addEventListener("wheel", function (event) {
63
+ if (Math.abs(event.deltaY) <= Math.abs(event.deltaX) || row.scrollWidth <= row.clientWidth) return;
64
+ event.preventDefault();
65
+ row.scrollLeft += event.deltaY;
66
+ }, { passive: false });
67
+ new ResizeObserver(function () { syncTabsOverflow(row); }).observe(row);
44
68
  }
45
69
  STATUS_FILTERS.forEach(function (status) {
46
70
  const tab = row.querySelector('[data-status="' + status + '"]');
@@ -56,6 +80,11 @@ export function renderFilters(container, state, t, onFilter) {
56
80
  badge.hidden = hide;
57
81
  if (!hide) badge.textContent = String(count);
58
82
  });
83
+ if (row.dataset.selected !== state.filter) {
84
+ row.dataset.selected = state.filter;
85
+ revealTab(row, row.querySelector('[data-status="' + state.filter + '"]'));
86
+ }
87
+ syncTabsOverflow(row);
59
88
  }
60
89
 
61
90
  function groupOf(task) {
@@ -106,7 +135,7 @@ function taskRow(task, state, t, locale, onSelect) {
106
135
  h("span.task-body", null,
107
136
  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
137
  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,
138
+ counts.workItems > 0 ? h("span.task-count", { title: t("sidebar.workItems") }, icon("layers", "icon-sm"), String(counts.workItems)) : null,
110
139
  signals)));
111
140
  return row;
112
141
  }
@@ -21,6 +21,10 @@ export const ICON_PATHS = Object.freeze({
21
21
  file: ["M7 3h7l5 5v12a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z", "M14 3v5h5"],
22
22
  copy: ["M9 9h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1z", "M5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1"],
23
23
  layers: ["M12 3l9 5-9 5-9-5z", "M3 13l9 5 9-5"],
24
+ package: ["M21 8l-9-5-9 5v8l9 5 9-5z", "M3 8l9 5 9-5", "M12 13v8"],
25
+ merge: ["M18 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z", "M6 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6z", "M6 21V9a9 9 0 0 0 9 9"],
26
+ folder: ["M3 7a1 1 0 0 1 1-1h5l2 2h9a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z"],
27
+ info: ["M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18z", "M12 11v5", "M12 8h.01"],
24
28
  pulse: ["M3 12h4l3-7 4 14 3-7h4"],
25
29
  history: ["M3 12a9 9 0 1 0 3-6.7", "M3 4v4h4", "M12 8v4l3 2"],
26
30
  dots: ["M5 12h.01", "M12 12h.01", "M19 12h.01"],
@@ -56,12 +56,13 @@ export const DASHBOARD_HTML = `<!doctype html>
56
56
  <button id="operator-terminal" class="foot-btn" type="button" title="Operator session · O">
57
57
  ${iconSvg("terminal")}<span data-i18n="actions.operator">Operator</span><kbd>O</kbd>
58
58
  </button>
59
- <button id="global-input-open" class="foot-btn" type="button">
59
+ <button id="global-input-open" class="foot-btn" type="button" title="Global input" data-i18n-title="global.open">
60
60
  ${iconSvg("broadcast")}<span data-i18n="global.open">Global input</span>
61
61
  </button>
62
62
  <button id="settings-open" class="icon-btn" type="button" aria-label="Settings" data-i18n-aria-label="settings.title" title="Settings">${iconSvg("settings")}</button>
63
63
  </footer>
64
64
  </aside>
65
+ <div id="sidebar-divider" class="sidebar-divider" role="separator" tabindex="0" aria-orientation="vertical" aria-valuemin="240" aria-valuemax="560" aria-valuenow="264" aria-label="Resize task list" data-i18n-aria-label="sidebar.resize" title="Drag to resize · double-click to reset" data-i18n-title="sidebar.resizeHint"><span aria-hidden="true"></span></div>
65
66
  <main id="center" class="center" tabindex="-1">
66
67
  <div id="detail" class="detail"></div>
67
68
  </main>