@zq-silk/yui 2.6.0 → 2.8.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 (126) hide show
  1. package/README.md +23 -0
  2. package/dist/cli/commandCatalog.js +34 -7
  3. package/dist/cli/updateCommand.js +3 -0
  4. package/dist/commands/configCommands.js +1 -0
  5. package/dist/commands/globalRoleCommands.js +62 -35
  6. package/dist/commands/inboxCommands.js +372 -0
  7. package/dist/commands/roleSkillValidation.js +2 -0
  8. package/dist/commands/taskCommands.js +39 -7
  9. package/dist/commands/taskRoleRuntimeStatus.js +2 -0
  10. package/dist/context/currentSkills.js +48 -0
  11. package/dist/context/roleSessionContext.js +13 -16
  12. package/dist/context/runContextPack.js +50 -3
  13. package/dist/context/runInputContract.js +1 -1
  14. package/dist/context/sessionBootstrapManifest.js +27 -4
  15. package/dist/context/skillCatalog.js +2 -3
  16. package/dist/context/taskContext.js +38 -12
  17. package/dist/controlPlaneCli.js +58 -7
  18. package/dist/controller/controller.js +8 -2
  19. package/dist/controller/fileSchedulerStoreAdapter.js +26 -6
  20. package/dist/controller/globalInputDelivery.js +86 -16
  21. package/dist/controller/globalRuntimeAttention.js +136 -6
  22. package/dist/controller/operatorNotification.js +6 -1
  23. package/dist/controller/providerRetryDelivery.js +3 -5
  24. package/dist/controller/runtime.js +6 -2
  25. package/dist/controller/structuredProviderObservation.js +51 -15
  26. package/dist/controller/taskHostTerminalRecovery.js +33 -0
  27. package/dist/doctor/doctor.js +25 -2
  28. package/dist/executor/effectiveLaunch.js +2 -2
  29. package/dist/executor/fileRoleLaunchPlanner.js +8 -15
  30. package/dist/interaction/notificationPresentation.js +2 -2
  31. package/dist/interaction/operatorPresentation.js +1 -1
  32. package/dist/message/globalInputBacklog.js +69 -0
  33. package/dist/message/globalMessageQuery.js +1 -0
  34. package/dist/message/inboxReceipt.js +62 -0
  35. package/dist/message/message.js +6 -1
  36. package/dist/message/messageContinuation.js +34 -13
  37. package/dist/message/taskInboxOwnership.js +43 -0
  38. package/dist/models/modelManagement.js +469 -0
  39. package/dist/models/modelManagementConfig.js +46 -0
  40. package/dist/models/modelManagementFiles.js +90 -0
  41. package/dist/models/modelManagementHttp.js +90 -0
  42. package/dist/models/modelManagementTypes.js +1 -0
  43. package/dist/nativeAgent/session/backends.js +1 -1
  44. package/dist/observability/runtimeIdentity.js +1 -1
  45. package/dist/output/boundedRead.js +43 -14
  46. package/dist/resources/sqliteResourceRegistry.js +1 -1
  47. package/dist/runtime/agentEndpointIdentity.js +8 -4
  48. package/dist/runtime/agentEndpointOwnership.js +5 -6
  49. package/dist/runtime/agentHost.js +165 -24
  50. package/dist/runtime/agentHostImplementation.js +18 -0
  51. package/dist/runtime/managedCaller.js +25 -1
  52. package/dist/runtime/managedIdentity.js +1 -1
  53. package/dist/runtime/providerRetry.js +19 -1
  54. package/dist/runtime/sessionCaller.js +53 -0
  55. package/dist/scheduler/operatorEvent.js +49 -0
  56. package/dist/scheduler/operatorInputNotificationProcessor.js +5 -4
  57. package/dist/scheduler/taskWake.js +5 -0
  58. package/dist/sqlite/driver.js +108 -0
  59. package/dist/storage/contextRecords.js +28 -0
  60. package/dist/storage/sqliteSchema.js +21 -1
  61. package/dist/storage/sqliteStore.js +64 -1
  62. package/dist/storage/storageSchema.js +1 -1
  63. package/dist/storage/storageVersions.js +1 -1
  64. package/dist/storage/storeRpc.js +1 -0
  65. package/dist/storage/taskStore.js +4 -0
  66. package/dist/storage/upgrade/upgradeOrchestrator.js +1 -1
  67. package/dist/task/completionReadiness.js +13 -6
  68. package/dist/telemetry/sqliteTelemetryStore.js +1 -1
  69. package/dist/web/assets/assetManifest.js +28 -2
  70. package/dist/web/assets/client/app/manage.js +6 -0
  71. package/dist/web/assets/client/app/models.js +20 -0
  72. package/dist/web/assets/client/app/settings.js +41 -9
  73. package/dist/web/assets/client/i18n/en.js +180 -2
  74. package/dist/web/assets/client/i18n/zh-CN.js +178 -2
  75. package/dist/web/assets/client/index.js +8 -0
  76. package/dist/web/assets/client/layout/manageShell.js +94 -0
  77. package/dist/web/assets/client/lib/api.js +13 -1
  78. package/dist/web/assets/client/lib/i18n.js +5 -3
  79. package/dist/web/assets/client/views/dock/conversation.js +231 -80
  80. package/dist/web/assets/client/views/models.js +777 -0
  81. package/dist/web/assets/client/views/skills.js +22 -15
  82. package/dist/web/assets/shared/icons.js +2 -0
  83. package/dist/web/assets/shared/manageNav.js +13 -0
  84. package/dist/web/assets/shell/manage.js +54 -0
  85. package/dist/web/assets/shell/models.js +7 -0
  86. package/dist/web/assets/shell/settings.js +1 -35
  87. package/dist/web/assets/shell/settingsContent.js +30 -0
  88. package/dist/web/assets/shell/sidebar.js +1 -0
  89. package/dist/web/assets/styles/index.js +6 -0
  90. package/dist/web/assets/styles/views/conversation.js +133 -0
  91. package/dist/web/assets/styles/views/manage.js +71 -0
  92. package/dist/web/assets/styles/views/models.js +129 -0
  93. package/dist/web/assets/styles/views/settings.js +28 -14
  94. package/dist/web/assets/styles/views/sidebar.js +1 -0
  95. package/dist/web/webServer.js +59 -9
  96. package/docs/cli-information-contract.md +42 -11
  97. package/docs/cli-information-contract.zh-CN.md +28 -7
  98. package/docs/managed-turn-and-session-runtime.md +127 -5
  99. package/docs/managed-turn-and-session-runtime.zh-CN.md +83 -3
  100. package/docs/release-workflow.md +105 -9
  101. package/docs/release-workflow.zh-CN.md +73 -5
  102. package/docs/storage-baseline.md +22 -7
  103. package/docs/storage-baseline.zh-CN.md +14 -5
  104. package/docs/task-delivery.md +17 -8
  105. package/docs/task-delivery.zh-CN.md +11 -5
  106. package/docs/testing/review-evaluation.md +148 -0
  107. package/docs/testing/review-evaluation.zh-CN.md +117 -0
  108. package/docs/testing/verification-levels.md +26 -4
  109. package/docs/testing/verification-levels.zh-CN.md +11 -1
  110. package/i18n/README.zh-CN.md +19 -0
  111. package/package.json +5 -2
  112. package/skills/yui-leader/SKILL.md +28 -11
  113. package/skills/yui-leader/VERSION +1 -0
  114. package/skills/yui-leader/references/execution.md +29 -0
  115. package/skills/yui-leader/references/replicated-execution.md +7 -0
  116. package/skills/yui-operator/SKILL.md +15 -3
  117. package/skills/yui-operator/VERSION +1 -0
  118. package/skills/yui-operator/references/task-delivery.md +2 -0
  119. package/skills/yui-reviewer/SKILL.md +43 -1
  120. package/skills/yui-reviewer/VERSION +1 -0
  121. package/skills/yui-runtime/SKILL.md +144 -15
  122. package/skills/yui-runtime/VERSION +1 -0
  123. package/skills/yui-runtime/references/publication.md +16 -5
  124. package/skills/yui-runtime/references/recovery.md +24 -5
  125. package/skills/yui-worker/SKILL.md +8 -1
  126. package/skills/yui-worker/VERSION +1 -0
@@ -1,21 +1,22 @@
1
1
  export const CONVERSATION_SCRIPT = String.raw `
2
- import { h, clear } from "/assets/js/lib/dom.js";
2
+ import { h, clear, icon } from "/assets/js/lib/dom.js";
3
3
  import { api, releaseMutation, requestJson, submitMutation } from "/assets/js/lib/api.js";
4
4
  import { richText } from "/assets/js/ui/text.js";
5
5
  import { readSessionAccessMode, writeSessionAccessMode } from "/assets/js/lib/prefs.js";
6
6
  import { inputCard } from "/assets/js/domain/work.js";
7
7
 
8
8
  export function createConversationController(host, t, locale = function () { return "en"; }) {
9
- let owner = null, selected = null, facts = null, cursor = null, next = null;
9
+ let owner = null, selected = null, facts = null, next = null, latestItems = [], olderItems = [];
10
10
  let generation = 0, busy = false, timer = null, pending = null, historyOk = false;
11
11
  let recoveryUnavailable = false;
12
12
  let rendered = new Map();
13
13
  let materials = [], incomingMaterials = null;
14
14
  let requestSignature = "";
15
15
  let modelPending = null, modelCatalog = null, lastModelObservation = null;
16
- const requests = h("div");
17
- const questions = h("div");
16
+ const requests = h("div.cv-requests");
17
+ const questions = h("div.cv-requests");
18
18
  let questionSignature = "";
19
+ let confirmedModel = null, modelRowMap = new Map();
19
20
  const defaultMode = h("select", { "aria-label": t("conversation.defaultMode") },
20
21
  h("option", { value: "structured" }, t("conversation.title")),
21
22
  h("option", { value: "native" }, t("dock.session")));
@@ -25,44 +26,79 @@ export function createConversationController(host, t, locale = function () { ret
25
26
  receipt.textContent = t(saved ? "conversation.defaultSaved" : "settings.storageFailed");
26
27
  defaultMode.value = readSessionAccessMode();
27
28
  });
28
- const select = h("select", { "aria-label": t("conversation.sessions") });
29
- const status = h("p.feed-note", { role: "status" });
30
- const identity = h("p.feed-note");
29
+ const select = h("select.cv-session", { "aria-label": t("conversation.sessions") });
30
+ const status = h("p.cv-status", { role: "status" });
31
+ const identity = h("p.cv-identity");
31
32
  const feed = h("div.feed", { tabIndex: 0, "aria-label": t("conversation.history") });
32
- const text = h("textarea.composer-input", { rows: 3, "aria-label": t("conversation.input"), maxLength: 12000 });
33
- const receipt = h("p.feed-note", { role: "status" });
34
- const draftStatus = h("p.feed-note", { role: "status" });
35
- const receiptDetail = h("pre");
36
- const modelStatus = h("p.feed-note", { role: "status" });
37
- const modelResult = h("p.feed-note", { role: "status" });
38
- const modelSelect = h("select", { "aria-label": t("conversation.model") });
39
- const modelLoad = h("button.btn", { type: "button" }, t("conversation.modelsLoad"));
40
- const modelApply = h("button.btn", { type: "button" }, t("conversation.modelApply"));
41
- const modelPanel = h("details", null, h("summary", null, t("conversation.model")),
42
- modelStatus, h("div.composer-bar", null, modelLoad, modelSelect, modelApply), modelResult,
43
- h("p.feed-note", null, t("conversation.modelBoundary")));
33
+ const text = h("textarea.cv-input", { rows: 2, placeholder: t("conversation.placeholder"), "aria-label": t("conversation.input"), maxLength: 12000 });
34
+ const receipt = h("p.cv-note", { role: "status" });
35
+ const draftStatus = h("p.cv-note", { role: "status" });
36
+ const receiptDetail = h("pre.cv-code");
37
+ const modelStatus = h("p.cv-note", { role: "status" });
38
+ const modelResult = h("p.cv-note", { role: "status" });
39
+ const modelSelect = h("select", { "aria-label": t("conversation.model"), hidden: true });
40
+ const modelRows = h("div.cv-options", { role: "listbox", "aria-label": t("conversation.selectModel") });
41
+ const modelLabel = h("span.cv-model-label", null, t("conversation.model"));
42
+ const modelLoad = h("button.cv-link", { type: "button" }, t("conversation.modelsLoad"));
43
+ const modelApply = h("button.btn.btn-primary", { type: "button" }, t("conversation.modelApply"));
44
+ const modelMenu = h("details.cv-pop.cv-model", null,
45
+ h("summary.cv-pill", null, modelLabel, icon("down")),
46
+ h("div.cv-popbody", null, h("p.cv-pop-title", null, t("conversation.selectModel")),
47
+ modelRows, modelSelect, modelStatus, modelResult,
48
+ h("div.cv-pop-actions", null, modelLoad, modelApply),
49
+ h("p.cv-note", null, t("conversation.modelBoundary"))));
44
50
  const slash = h("div.conversation-slash", { "aria-label": t("conversation.commands") });
45
- const literal = h("button.btn", { type: "button", hidden: true }, t("conversation.literal"));
46
- const send = h("button.btn.btn-primary", { type: "button" }, t("conversation.send"));
47
- const stop = h("button.btn", { type: "button" }, t("conversation.stop"));
48
- const check = h("button.btn", { type: "button" }, t("conversation.receipt"));
49
- const older = h("button.btn", { type: "button" }, t("conversation.older"));
50
- const latest = h("button.btn", { type: "button" }, t("conversation.latest"));
51
- const current = h("button.btn", { type: "button" }, t("conversation.current"));
52
- const moreSessions = h("button.btn", { type: "button", hidden: true }, t("conversation.moreSessions"));
51
+ const literal = h("button.cv-link", { type: "button", hidden: true }, t("conversation.literal"));
52
+ const send = h("button.cv-send", { type: "button", "aria-label": t("conversation.send"), title: t("conversation.send") }, icon("up"));
53
+ const stop = h("button.cv-stop", { type: "button", hidden: true, "aria-label": t("conversation.stop"), title: t("conversation.stop") }, icon("stop"));
54
+ const check = h("button.cv-link", { type: "button", hidden: true }, t("conversation.receipt"));
55
+ const older = h("button.cv-link", { type: "button", hidden: true }, t("conversation.older"));
56
+ const latest = h("button.cv-jump", { type: "button", hidden: true, "aria-label": t("conversation.jump"), title: t("conversation.latest") }, icon("down"));
57
+ const current = h("button.cv-link", { type: "button", hidden: true }, t("conversation.current"));
58
+ const moreSessions = h("button.cv-link", { type: "button", hidden: true }, t("conversation.moreSessions"));
59
+ const writerChip = h("span.cv-chip.tone-warn", { hidden: true }, icon("alert"), h("span", null, t("conversation.terminalWriter")));
53
60
  const file = h("input", { type: "file", "aria-label": t("materials.attach") });
54
61
  const materialList = h("div.row-stack");
55
- const materialAlert = h("p.feed-note", { role: "alert" });
56
- const materialBar = h("details", null, h("summary", null, t("materials.attach")), file,
57
- h("p.feed-note", null, t("materials.boundary")));
58
- host.append(h("div.dock-sub", null, select, current, moreSessions,
59
- h("label", null, t("conversation.defaultMode"), defaultMode)), identity, status, feed,
60
- h("div.conversation-requests", null, questions, requests),
61
- h("div.composer-bar", null, older, latest),
62
- h("div.composer-wrap", null, modelPanel, materialBar, materialList, materialAlert, text, slash, draftStatus,
63
- h("div.composer-bar", null, send, stop, check, literal), receipt,
64
- h("details", null, h("summary", null, t("conversation.receiptDetails")), receiptDetail),
65
- h("p.feed-note", null, t("conversation.boundary"))));
62
+ const materialAlert = h("p.cv-note", { role: "alert" });
63
+ const attachMenu = h("details.cv-pop.cv-attach", null,
64
+ h("summary.cv-iconbtn", { "aria-label": t("materials.attach"), title: t("materials.attach") }, icon("plus")),
65
+ h("div.cv-popbody", null, h("p.cv-pop-title", null, t("materials.attach")), file,
66
+ h("p.cv-note", null, t("materials.boundary"))));
67
+ const menu = h("details.cv-pop.cv-menu", null,
68
+ h("summary.cv-iconbtn", { "aria-label": t("conversation.more"), title: t("conversation.more") }, icon("dots")),
69
+ h("div.cv-popbody", null,
70
+ h("label.cv-field", null, h("span", null, t("conversation.defaultMode")), defaultMode),
71
+ moreSessions, identity,
72
+ h("details.cv-sub", null, h("summary", null, t("conversation.receiptDetails")), receiptDetail),
73
+ h("p.cv-note", null, t("conversation.boundary"))));
74
+ const pops = [menu, attachMenu, modelMenu];
75
+ pops.forEach(function (pop) {
76
+ pop.addEventListener("toggle", function () {
77
+ if (!pop.open) return;
78
+ pops.forEach(function (other) { if (other !== pop) other.open = false; });
79
+ if (pop === modelMenu && !modelCatalog && !modelLoad.disabled) loadModels();
80
+ });
81
+ });
82
+ host.addEventListener("click", function (event) {
83
+ pops.forEach(function (pop) { if (pop.open && !pop.contains(event.target)) pop.open = false; });
84
+ });
85
+ host.addEventListener("keydown", function (event) {
86
+ if (event.key === "Escape") pops.forEach(function (pop) { pop.open = false; });
87
+ });
88
+ feed.addEventListener("scroll", function () {
89
+ syncJump();
90
+ // Reaching the top loads the next older page in place; the view keeps its position.
91
+ if (feed.scrollTop < 120 && next && historyOk && !busy) refresh(true);
92
+ });
93
+ host.append(
94
+ h("div.cv-head", null, h("div.cv-picker", null, select, current), menu), status,
95
+ h("div.cv-older", null, older),
96
+ h("div.cv-stage", null, feed, latest),
97
+ questions, requests,
98
+ h("div.cv-composer", null, slash,
99
+ h("div.cv-box", null, materialList, materialAlert, text,
100
+ h("div.cv-tools", null, attachMenu, writerChip, h("span.spacer"), modelMenu, stop, send)),
101
+ h("div.cv-hint", null, draftStatus, literal, check, receipt)));
66
102
 
67
103
  function key() { return "yui.conversation." + JSON.stringify(owner) + "." + selected; }
68
104
  function saved(suffix, value, expectedRequestId) {
@@ -98,6 +134,9 @@ export function createConversationController(host, t, locale = function () { ret
98
134
  Object.entries(extra || {}).forEach(function (entry) { if (entry[1] != null) q.set(entry[0], String(entry[1])); });
99
135
  return "/api/conversation?" + q;
100
136
  }
137
+ function syncJump() {
138
+ latest.hidden = !((selected && !historyOk) || feed.scrollHeight - feed.clientHeight - feed.scrollTop > 80);
139
+ }
101
140
  function controls() {
102
141
  if (owner && selected) {
103
142
  try { pending = JSON.parse(saved(".pending") || "null"); }
@@ -112,12 +151,18 @@ export function createConversationController(host, t, locale = function () { ret
112
151
  stop.disabled = busy || recoveryUnavailable || !!pending || !writable || !turn || turn.status !== "accepted" || !turn.nativeTurnId;
113
152
  check.disabled = busy || !pending;
114
153
  older.disabled = busy || !next;
154
+ older.hidden = !next;
155
+ check.hidden = !pending;
156
+ stop.hidden = !turn || turn.status !== "accepted";
157
+ current.hidden = !facts || !selected || selected === facts.currentSessionId;
158
+ writerChip.hidden = !(facts && facts.terminalWriter);
115
159
  literal.disabled = send.disabled;
160
+ syncJump();
116
161
  modelLoad.disabled = busy || !active || active.adapterId !== "codex";
117
162
  modelApply.disabled = busy || !!pending || !!modelPending || !writable || !modelSelect.value
118
163
  || !modelCatalog || modelCatalog.source === "fallback" || !!modelCatalog.failure
119
164
  || turn && ["submitting", "accepted", "delivery-unknown"].includes(turn.status);
120
- materialBar.hidden = !owner || owner.scope !== "task";
165
+ attachMenu.hidden = !owner || owner.scope !== "task";
121
166
  file.disabled = busy || !!pending || !writable || materials.length >= 8;
122
167
  materialList.querySelectorAll("button").forEach(function (control) { control.disabled = busy || !!pending; });
123
168
  }
@@ -135,6 +180,8 @@ export function createConversationController(host, t, locale = function () { ret
135
180
  && observation.axes.find(function (a) { return a.key === "model"; });
136
181
  const value = axis && axis.current.status === "observed" ? axis.current.value : null;
137
182
  const session = facts && facts.sessions.find(function (s) { return s.nativeSessionId === selected; });
183
+ confirmedModel = value;
184
+ modelLabel.textContent = value || (session && session.launchModel) || t("conversation.model");
138
185
  modelStatus.textContent = t("conversation.launchModel") + ": " + (session && session.launchModel || "—")
139
186
  + " · " + t("conversation.confirmedModel") + ": " + (value || t("conversation.unknown"))
140
187
  + (value ? " · " + observation.observedAt : "");
@@ -146,6 +193,14 @@ export function createConversationController(host, t, locale = function () { ret
146
193
  }
147
194
  lastModelObservation = value ? observation.observedAt : null;
148
195
  }
196
+ function pickModel(value) {
197
+ modelSelect.value = value;
198
+ modelRowMap.forEach(function (row, key) {
199
+ row.className = "cv-option" + (key === value ? " is-selected" : "");
200
+ row.ariaSelected = key === value ? "true" : "false";
201
+ });
202
+ controls();
203
+ }
149
204
  async function loadModels() {
150
205
  if (modelLoad.disabled) return;
151
206
  const own = generation;
@@ -153,10 +208,15 @@ export function createConversationController(host, t, locale = function () { ret
153
208
  try {
154
209
  const result = await requestJson(url({ session: selected }).replace("/api/conversation?", "/api/conversation/model?"));
155
210
  if (own !== generation) return;
156
- modelCatalog = result; clear(modelSelect);
211
+ modelCatalog = result; clear(modelSelect); clear(modelRows); modelRowMap = new Map();
157
212
  (result.catalog.models || []).forEach(function (m) {
158
213
  modelSelect.append(h("option", { value: m.value }, m.label + " · " + m.value));
214
+ const row = h("button.cv-option", { type: "button", role: "option" },
215
+ h("span.cv-option-name", null, m.label), h("span.cv-option-id", null, m.value), icon("check"));
216
+ row.addEventListener("click", function () { pickModel(m.value); });
217
+ modelRowMap.set(m.value, row); modelRows.append(row);
159
218
  });
219
+ pickModel(modelRowMap.has(confirmedModel) ? confirmedModel : "");
160
220
  modelResult.textContent = result.source + " · " + (result.fetchedAt || result.attemptedAt)
161
221
  + (result.failure ? " · " + result.failure.message : "");
162
222
  } catch (error) { if (own === generation) modelResult.textContent = error.message; }
@@ -189,7 +249,7 @@ export function createConversationController(host, t, locale = function () { ret
189
249
  literal.hidden = !value.startsWith("/");
190
250
  if (!value.startsWith("/") || /\s/.test(value)) return;
191
251
  commands.filter(function (name) { return ("/" + name).startsWith(value); }).forEach(function (name) {
192
- const button = h("button.btn", { type: "button" }, "/" + name + " · " + t("conversation.command." + name));
252
+ const button = h("button.cv-cmd", { type: "button" }, "/" + name + " · " + t("conversation.command." + name));
193
253
  button.addEventListener("click", function () {
194
254
  text.value = "/" + name; saved(".draft", text.value); drawCommands(); text.focus?.();
195
255
  });
@@ -204,9 +264,9 @@ export function createConversationController(host, t, locale = function () { ret
204
264
  else submit(true);
205
265
  return;
206
266
  }
207
- if (name === "model") { modelPanel.open = true; loadModels(); }
267
+ if (name === "model") { modelMenu.open = true; loadModels(); }
208
268
  else if (name === "status") refresh();
209
- else if (name === "latest") { cursor = null; refresh(); }
269
+ else if (name === "latest") refresh();
210
270
  receipt.textContent = name === "help" ? t("conversation.commandHelp") : t("conversation.command." + name);
211
271
  text.value = ""; saved(".draft", null); drawCommands();
212
272
  }
@@ -214,10 +274,10 @@ export function createConversationController(host, t, locale = function () { ret
214
274
  function drawMaterials() {
215
275
  clear(materialList);
216
276
  materials.forEach(function (ref, index) {
217
- const remove = h("button.btn", { type: "button" }, t("materials.remove"));
277
+ const remove = h("button.cv-x", { type: "button", "aria-label": t("materials.remove"), title: t("materials.remove") }, icon("close"));
218
278
  remove.disabled = busy || !!pending;
219
279
  remove.addEventListener("click", function () { materials.splice(index, 1); saveMaterials(); drawMaterials(); controls(); });
220
- materialList.append(h("div", null, h("span.small", null,
280
+ materialList.append(h("div.cv-file", null, h("span.small", null,
221
281
  ref.relativePath + " · " + ref.commit + " · sha256:" + ref.digest), remove));
222
282
  });
223
283
  }
@@ -226,8 +286,8 @@ export function createConversationController(host, t, locale = function () { ret
226
286
  if (signature === requestSignature) return;
227
287
  requestSignature = signature; clear(requests);
228
288
  rows.forEach(function (request) {
229
- const card = h("section.composer-wrap", null, h("h3", null, t("conversation.nativeRequest")),
230
- h("p.feed-note", null, request.method + " · " + request.turnId));
289
+ const card = h("section.cv-card", null, h("h3.cv-card-title", null, t("conversation.nativeRequest")),
290
+ h("p.cv-note", null, request.method + " · " + request.turnId));
231
291
  const params = request.params, answers = {};
232
292
  const pendingKey = ".nativePending." + request.turnId + "." + request.id;
233
293
  if (request.method === "item/tool/requestUserInput") {
@@ -239,13 +299,13 @@ export function createConversationController(host, t, locale = function () { ret
239
299
  field.addEventListener("input", function () { saved(draftKey, field.value); });
240
300
  }
241
301
  card.append(h("label", null, question.question, field));
242
- if (question.options) card.append(h("p.feed-note", null, question.options.map(function (o) { return o.label + ": " + o.description; }).join(" · ")));
302
+ if (question.options) card.append(h("p.cv-note", null, question.options.map(function (o) { return o.label + ": " + o.description; }).join(" · ")));
243
303
  answers[question.id] = field;
244
304
  });
245
305
  } else {
246
306
  card.append(h("p", null, params.reason || params.command || t("conversation.nativeApproval")));
247
307
  }
248
- const outcome = h("p.feed-note", { role: "status" });
308
+ const outcome = h("p.cv-note", { role: "status" });
249
309
  const buttons = [];
250
310
  function button(label, result) {
251
311
  const node = h("button.btn", { type: "button", disabled: !!facts.terminalWriter || !!saved(pendingKey) }, label);
@@ -278,8 +338,8 @@ export function createConversationController(host, t, locale = function () { ret
278
338
  });
279
339
  else if (request.method === "item/permissions/requestApproval") button(t("conversation.decline"), function () { return { permissions: {}, scope: "turn" }; });
280
340
  else if (request.method === "mcpServer/elicitation/request") {
281
- card.append(h("p.feed-note", null, request.params.message || ""));
282
- card.append(h("p.feed-note", null, t("conversation.nativeForm")));
341
+ card.append(h("p.cv-note", null, request.params.message || ""));
342
+ card.append(h("p.cv-note", null, t("conversation.nativeForm")));
283
343
  button(t("conversation.decline"), function () { return { action: "decline", content: null }; });
284
344
  }
285
345
  else {
@@ -295,7 +355,7 @@ export function createConversationController(host, t, locale = function () { ret
295
355
  if (signature === questionSignature) return;
296
356
  questionSignature = signature; clear(questions);
297
357
  const taskId = owner.taskId;
298
- if (rows.length) questions.append(h("h3", null, t("conversation.leaderQuestions")));
358
+ if (rows.length) questions.append(h("h3.cv-card-title", null, t("conversation.leaderQuestions")));
299
359
  rows.forEach(function (input) {
300
360
  questions.append(inputCard(input, t, locale(), async function (original, answer, control) {
301
361
  const own = generation;
@@ -313,16 +373,17 @@ export function createConversationController(host, t, locale = function () { ret
313
373
  }
314
374
  }));
315
375
  });
316
- if (facts.questionsOmitted) questions.append(h("p.feed-note", null, t("overview.moreInputs")));
376
+ if (facts.questionsOmitted) questions.append(h("p.cv-note", null, t("overview.moreInputs")));
317
377
  }
318
378
  function choose(id) {
319
379
  if (selected) saved(".draft", text.value);
320
- generation++; selected = id; cursor = null; next = null; historyOk = false;
380
+ generation++; selected = id; next = null; latestItems = []; olderItems = []; historyOk = false;
321
381
  text.value = saved(".draft") || "";
322
382
  try { pending = JSON.parse(saved(".pending") || "null"); } catch { pending = null; }
323
383
  try { materials = JSON.parse(saved(".materials") || "[]"); } catch { materials = []; }
324
384
  try { modelPending = JSON.parse(saved(".modelPending") || "null"); } catch { modelPending = null; }
325
- modelCatalog = null; lastModelObservation = null; clear(modelSelect); modelStatus.textContent = ""; modelResult.textContent = ""; receiptDetail.textContent = "";
385
+ modelCatalog = null; lastModelObservation = null; clear(modelSelect); clear(modelRows); modelRowMap = new Map(); confirmedModel = null;
386
+ modelLabel.textContent = t("conversation.model"); modelStatus.textContent = ""; modelResult.textContent = ""; receiptDetail.textContent = "";
326
387
  drawCommands();
327
388
  materialAlert.textContent = "";
328
389
  if (incomingMaterials) {
@@ -346,22 +407,71 @@ export function createConversationController(host, t, locale = function () { ret
346
407
  // Only refresh view state; never repeat a mutation or receipt submission.
347
408
  if (own !== generation && timer !== null) refresh();
348
409
  }
410
+ const GLYPHS = { commandExecution: "terminal", fileChange: "file", webSearch: "search", collabAgentToolCall: "user" };
411
+ function itemKey(item) { return JSON.stringify([item.turnId, item.id]); }
412
+ function itemMeta(item) {
413
+ return (item.source === "yui-input" ? t("conversation.linkedInput") + " " + item.messageId : item.kind)
414
+ + " · " + item.turnId + (item.status ? " · " + item.status : "");
415
+ }
416
+ function activityRow(item) {
417
+ const parts = item.text.split(" · ");
418
+ return h("details.cv-activity", { title: itemMeta(item) },
419
+ h("summary", null, icon(GLYPHS[parts[0]] || "sparkle"), h("span.cv-act-label", null, t("conversation.activity." + parts[0], parts[0])),
420
+ h("span.cv-act-detail", null, parts.slice(1).join(" · "))),
421
+ h("pre.cv-code", null, item.text));
422
+ }
423
+ // Everything a turn did before its final reply (commentary and tool calls)
424
+ // folds into one collapsed line, like the Codex app's "Worked" row.
425
+ function processGroup(group) {
426
+ const counts = new Map();
427
+ group.forEach(function (item) {
428
+ const kind = item.kind === "activity" ? item.text.split(" · ")[0] : "message";
429
+ counts.set(kind, (counts.get(kind) || 0) + 1);
430
+ });
431
+ const parts = [...counts].filter(function (entry) { return entry[0] !== "message"; }).map(function (entry) {
432
+ return t("conversation.activity." + entry[0], entry[0]) + (entry[1] > 1 ? " ×" + entry[1] : "");
433
+ });
434
+ const label = t("conversation.worked") + (parts.length ? " · " + parts.join(" · ") : "");
435
+ return h("details.cv-activity.cv-group", { title: group.length + " · " + itemMeta(group[0]) },
436
+ h("summary", null, h("span.cv-act-label", null, label), icon("down", "cv-chevron")),
437
+ h("div.cv-group-body", null, ...group.map(function (item) {
438
+ return item.kind === "activity" ? activityRow(item) : h("div.cv-step", null, richText(null, item.text, t, { threshold: 1800 }));
439
+ })));
440
+ }
441
+ function messageNode(item) {
442
+ const meta = itemMeta(item);
443
+ if (item.kind === "user") {
444
+ return h("article.cv-msg.cv-user", { title: meta },
445
+ item.source === "yui-input" ? h("span.cv-tag", null, t("conversation.linkedInput") + " " + item.messageId) : null,
446
+ h("div.cv-bubble", null, richText(null, item.text, t, { threshold: 1800 })),
447
+ item.truncated ? h("p.cv-note", null, t("conversation.truncated")) : null);
448
+ }
449
+ const copy = h("button.cv-iconbtn", { type: "button", "aria-label": t("conversation.copy"), title: t("conversation.copy") }, icon("copy"));
450
+ copy.addEventListener("click", function () {
451
+ navigator.clipboard.writeText(item.text).then(function () { copy.title = t("conversation.copied"); });
452
+ });
453
+ return h("article.cv-msg.cv-assistant", { title: meta },
454
+ richText(null, item.text, t, { threshold: 1800 }),
455
+ item.truncated ? h("p.cv-note", null, t("conversation.truncated")) : null,
456
+ h("div.cv-actions", null, copy));
457
+ }
458
+ function allItems() {
459
+ const seen = new Set();
460
+ return latestItems.concat(olderItems).filter(function (item) {
461
+ const k = itemKey(item);
462
+ if (seen.has(k)) return false;
463
+ seen.add(k); return true;
464
+ });
465
+ }
349
466
  function renderItems(items) {
350
467
  const nodes = [], updated = new Map();
351
- items.slice().reverse().forEach(function (item) {
352
- const key = JSON.stringify([item.turnId, item.id]), signature = JSON.stringify(item);
468
+ function place(key, signature, make, keepOpen) {
353
469
  const previous = rendered.get(key);
354
470
  let node = previous && previous.signature === signature ? previous.node : null;
355
471
  if (!node) {
356
- const content = richText(null, item.text, t, { threshold: 1800 });
357
- const meta = (item.source === "yui-input" ? t("conversation.linkedInput") + " " + item.messageId : item.kind)
358
- + " · " + item.turnId + (item.status ? " · " + item.status : "");
359
- node = item.kind === "activity" ? h("details", null, h("summary", null, meta), content)
360
- : h("article.msg.from-" + (item.kind === "user" ? "user" : "role"), null,
361
- h("div.msg-main", null, h("header.msg-head", null, meta), h("div.msg-bubble", null, content),
362
- item.truncated ? h("p.feed-note", null, t("conversation.truncated")) : null));
472
+ node = make();
363
473
  if (previous) {
364
- if (item.kind === "activity") node.open = previous.node.open;
474
+ if (keepOpen) node.open = previous.node.open;
365
475
  const oldProse = previous.node.querySelectorAll(".prose-block");
366
476
  node.querySelectorAll(".prose-block").forEach(function (prose, index) {
367
477
  if (oldProse[index] && !oldProse[index].classList.contains("is-collapsed")
@@ -370,9 +480,33 @@ export function createConversationController(host, t, locale = function () { ret
370
480
  }
371
481
  }
372
482
  updated.set(key, { signature: signature, node: node }); nodes.push(node);
373
- });
483
+ }
484
+ const chronological = items.slice().reverse();
485
+ let index = 0;
486
+ while (index < chronological.length) {
487
+ let end = index;
488
+ while (end < chronological.length && chronological[end].turnId === chronological[index].turnId) end++;
489
+ const segment = chronological.slice(index, end);
490
+ index = end;
491
+ const last = segment[segment.length - 1];
492
+ const final = last.kind === "assistant" ? last : null;
493
+ const process = segment.filter(function (item) { return item.kind !== "user" && item !== final; });
494
+ let emitted = false;
495
+ segment.forEach(function (item) {
496
+ if (item.kind === "user" || item === final) {
497
+ place(itemKey(item), JSON.stringify(item), function () { return messageNode(item); }, false);
498
+ } else if (!emitted) {
499
+ emitted = true;
500
+ if (process.length === 1 && process[0].kind === "activity") {
501
+ place(itemKey(process[0]), JSON.stringify(process[0]), function () { return activityRow(process[0]); }, true);
502
+ } else {
503
+ place("turn:" + segment[0].turnId, JSON.stringify(process), function () { return processGroup(process); }, true);
504
+ }
505
+ }
506
+ });
507
+ }
374
508
  rendered = updated;
375
- if (!nodes.length) nodes.push(h("p.feed-note", null, t("conversation.empty")));
509
+ if (!nodes.length) nodes.push(h("p.cv-note", null, t("conversation.empty")));
376
510
  if (feed.children.length !== nodes.length || nodes.some(function (n, i) { return feed.children[i] !== n; })) {
377
511
  feed.replaceChildren(...nodes);
378
512
  }
@@ -387,11 +521,22 @@ export function createConversationController(host, t, locale = function () { ret
387
521
  select.value = selected || "";
388
522
  moreSessions.hidden = !facts.nextOffset;
389
523
  }
390
- async function refresh() {
524
+ async function refresh(older) {
391
525
  if (!owner || busy || host.hidden || host.closest("[hidden]") || document.hidden) return;
526
+ if (older && (!next || !selected || !facts || !historyOk)) return;
392
527
  busy = true; controls();
393
528
  let own = generation;
394
529
  try {
530
+ if (older) {
531
+ const earlier = await requestJson(url({ session: selected, cursor: next }));
532
+ if (own !== generation) return;
533
+ olderItems = olderItems.concat(earlier.items); next = earlier.nextCursor;
534
+ const before = feed.scrollHeight, top = feed.scrollTop;
535
+ renderItems(allItems());
536
+ feed.scrollTop = top + (feed.scrollHeight - before);
537
+ syncJump();
538
+ return;
539
+ }
395
540
  const result = await requestJson(url());
396
541
  if (own !== generation) return;
397
542
  const previous = facts;
@@ -412,27 +557,30 @@ export function createConversationController(host, t, locale = function () { ret
412
557
  // Preserve a historical choice even if it is outside the newest page.
413
558
  drawSessions(facts.sessions, false);
414
559
  renderQuestions();
415
- const page = await requestJson(url({ session: selected, cursor: cursor }));
560
+ const page = await requestJson(url({ session: selected }));
416
561
  if (own !== generation) return;
417
- historyOk = true; next = page.nextCursor;
562
+ historyOk = true; latestItems = page.items;
563
+ if (!olderItems.length) next = page.nextCursor;
418
564
  const turn = selected === facts.currentSessionId ? facts.turn : null;
419
565
  let state = page.status === "waiting-user" ? "waiting" : page.status === "active" ? "active"
420
566
  : page.status === "systemError" ? "failed" : page.status === "idle" ? "waiting" : "unknown";
421
567
  const session = facts.sessions.find(function (s) { return s.nativeSessionId === selected; });
422
568
  if (session && session.status === "ended") state = session.endReason === "failed" ? "failed" : "ended";
423
569
  if (turn && turn.status === "delivery-unknown") state = "unknown";
424
- status.textContent = t("conversation." + state) + " · " + page.observedAt;
425
- if (page.live === "unavailable" && !cursor) status.textContent += " · " + t("conversation.liveUnavailable");
570
+ status.textContent = t("conversation." + state);
571
+ status.title = page.observedAt + " · " + t(facts.terminalWriter ? "conversation.terminalWriter" : "conversation.structuredWriter");
572
+ status.className = "cv-status is-" + state;
573
+ if (page.live === "unavailable") status.textContent += " · " + t("conversation.liveUnavailable");
426
574
  if (page.linkedInputsOmitted) status.textContent += " · " + t("conversation.linkedOmitted");
427
575
  if (page.nativeRequestsOmitted) status.textContent += " · " + t("conversation.nativeOmitted");
428
- status.textContent += " · " + t(facts.terminalWriter ? "conversation.terminalWriter" : "conversation.structuredWriter");
429
576
  renderRequests(page.nativeRequests || []);
430
577
  showModel(page.runConfiguration);
431
578
  identity.textContent = (owner.taskId || "Global") + " / " + owner.roleName + " / " + selected
432
579
  + (turn ? " / " + (turn.nativeTurnId || turn.attemptId) + (turn.runId ? " / " + turn.runId : "") : "");
433
580
  const scroll = feed.scrollTop, atEnd = feed.scrollHeight - feed.clientHeight - scroll < 40;
434
- renderItems(page.items);
581
+ renderItems(allItems());
435
582
  feed.scrollTop = atEnd ? feed.scrollHeight : scroll;
583
+ syncJump();
436
584
  } catch (error) {
437
585
  if (own === generation) { historyOk = false; status.textContent = t("conversation.disconnected") + " · " + error.message; }
438
586
  } finally { finish(own); }
@@ -542,8 +690,10 @@ export function createConversationController(host, t, locale = function () { ret
542
690
  });
543
691
  select.addEventListener("change", function () { choose(select.value); refresh(); });
544
692
  current.addEventListener("click", function () { if (facts) { choose(facts.currentSessionId); refresh(); } });
545
- older.addEventListener("click", function () { cursor = next; refresh(); });
546
- latest.addEventListener("click", function () { cursor = null; refresh(); });
693
+ older.addEventListener("click", function () { refresh(true); });
694
+ latest.addEventListener("click", function () {
695
+ refresh().then(function () { feed.scrollTop = feed.scrollHeight; syncJump(); });
696
+ });
547
697
  send.addEventListener("click", function () { submit(false); });
548
698
  stop.addEventListener("click", function () { submit(true); });
549
699
  check.addEventListener("click", inspectReceipt);
@@ -584,7 +734,7 @@ export function createConversationController(host, t, locale = function () { ret
584
734
  return { ...owner, ...(selected ? { nativeSessionId: selected } : {}) };
585
735
  },
586
736
  reconnect: function () {
587
- if (!timer) timer = window.setInterval(function () { if (!cursor) refresh(); }, 2000);
737
+ if (!timer) timer = window.setInterval(function () { refresh(); }, 2000);
588
738
  refresh();
589
739
  },
590
740
  open: function (target, options) {
@@ -593,9 +743,10 @@ export function createConversationController(host, t, locale = function () { ret
593
743
  if (JSON.stringify(target) !== JSON.stringify(owner) || options && options.current) {
594
744
  if (selected) saved(".draft", text.value);
595
745
  generation++; owner = target; selected = null; facts = null; pending = null; clear(select); clear(feed);
596
- cursor = null; next = null; historyOk = false; rendered.clear();
746
+ next = null; latestItems = []; olderItems = []; historyOk = false; rendered.clear();
597
747
  text.value = ""; receipt.textContent = ""; identity.textContent = ""; controls();
598
- modelPending = null; modelCatalog = null; clear(modelSelect); modelStatus.textContent = "";
748
+ modelPending = null; modelCatalog = null; clear(modelSelect); clear(modelRows); modelRowMap = new Map(); confirmedModel = null;
749
+ modelLabel.textContent = t("conversation.model"); modelStatus.textContent = "";
599
750
  modelResult.textContent = "";
600
751
  receiptDetail.textContent = ""; draftStatus.textContent = ""; drawCommands();
601
752
  materials = []; incomingMaterials = null; clear(materialList); materialAlert.textContent = "";
@@ -603,7 +754,7 @@ export function createConversationController(host, t, locale = function () { ret
603
754
  clear(questions); questionSignature = "";
604
755
  }
605
756
  if (options && options.materials) incomingMaterials = options.materials;
606
- if (!timer) timer = window.setInterval(function () { if (!cursor) refresh(); }, 2000);
757
+ if (!timer) timer = window.setInterval(function () { refresh(); }, 2000);
607
758
  refresh();
608
759
  },
609
760
  close: function () { if (timer) window.clearInterval(timer); timer = null; generation++; }