@zq-silk/yui 2.2.0 → 2.4.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 (206) hide show
  1. package/README.md +19 -3
  2. package/dist/artifacts/artifactCapability.js +39 -10
  3. package/dist/artifacts/imageMetadata.js +120 -0
  4. package/dist/artifacts/managedGit.js +7 -4
  5. package/dist/artifacts/taskArtifactRepository.js +6 -0
  6. package/dist/cli/commandCatalog.js +25 -11
  7. package/dist/cli/interactionPolicy.js +4 -0
  8. package/dist/cli/invocationAuthority.js +1 -1
  9. package/dist/commands/executionAuditCommands.js +5 -0
  10. package/dist/commands/globalRoleCommands.js +41 -3
  11. package/dist/commands/projectCommands.js +89 -94
  12. package/dist/commands/roleSkillValidation.js +4 -8
  13. package/dist/commands/taskCommands.js +30 -9
  14. package/dist/commands/taskIntegrationCommands.js +5 -16
  15. package/dist/commands/taskUsageCommand.js +38 -0
  16. package/dist/context/roleSessionContext.js +18 -2
  17. package/dist/context/runContextPack.js +5 -1
  18. package/dist/context/sessionBootstrapManifest.js +10 -1
  19. package/dist/context/skillCatalog.js +108 -0
  20. package/dist/context/skillPackage.js +161 -0
  21. package/dist/context/taskContext.js +3 -2
  22. package/dist/context/taskSearch.js +127 -0
  23. package/dist/controlPlaneCli.js +51 -39
  24. package/dist/controller/clientRuntime.js +3 -2
  25. package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
  26. package/dist/controller/jobControl.js +2 -0
  27. package/dist/controller/nativeHostInput.js +37 -0
  28. package/dist/controller/runtime.js +93 -2
  29. package/dist/controller/runtimeEventInbox.js +2 -2
  30. package/dist/controller/runtimeEventProcessor.js +3 -3
  31. package/dist/doctor/doctor.js +10 -0
  32. package/dist/execution/reviewMainRun.js +1 -0
  33. package/dist/execution/workItemMainRun.js +1 -0
  34. package/dist/executor/codexConfigConflict.js +6 -4
  35. package/dist/executor/effectiveLaunch.js +24 -1
  36. package/dist/executor/fileRoleLaunchPlanner.js +13 -5
  37. package/dist/integration/gitIntegrationService.js +15 -79
  38. package/dist/integration/integrationSourceApplication.js +4 -2
  39. package/dist/job/httpPreview.js +61 -0
  40. package/dist/job/jobCleanupInspection.js +67 -0
  41. package/dist/job/jobRunner.js +12 -2
  42. package/dist/kernel/builtinCapabilities.js +3 -1
  43. package/dist/message/message.js +15 -1
  44. package/dist/output/rolePresentation.js +4 -1
  45. package/dist/repository/executionLaneGitSnapshot.js +2 -1
  46. package/dist/repository/gitCompatibility.js +62 -0
  47. package/dist/repository/gitWorkspace.js +86 -80
  48. package/dist/repository/gitWorktreeInventory.js +121 -0
  49. package/dist/repository/project.js +74 -25
  50. package/dist/repository/taskWorkspaceCoordinator.js +72 -145
  51. package/dist/repository/taskWorkspacePreparer.js +39 -43
  52. package/dist/repository/workspaceCleanupInspection.js +88 -7
  53. package/dist/resources/resourceDiscovery.js +2 -2
  54. package/dist/resources/resourceGc.js +7 -4
  55. package/dist/resources/resourceRegistrar.js +18 -11
  56. package/dist/runtime/agentEndpoint.js +8 -0
  57. package/dist/runtime/agentHost.js +170 -3
  58. package/dist/runtime/codexAppServerRuntime.js +1 -1
  59. package/dist/runtime/codexNativeAccess.js +175 -0
  60. package/dist/runtime/controlledCodexTui.js +116 -0
  61. package/dist/runtime/integrationRuntimeIsolation.js +21 -0
  62. package/dist/runtime/publicReply.js +23 -0
  63. package/dist/runtime/runtimeObservation.js +48 -0
  64. package/dist/runtime/structuredProviderHost.js +16 -7
  65. package/dist/runtime/taskCostMetrics.js +133 -0
  66. package/dist/runtime/taskRuntimeIsolation.js +91 -21
  67. package/dist/runtime/taskUsageMetrics.js +11 -2
  68. package/dist/runtime/taskUsageQuery.js +38 -0
  69. package/dist/scheduler/taskExecutionProjection.js +2 -0
  70. package/dist/scheduler/taskObservabilityProjection.js +9 -1
  71. package/dist/storage/homeLayout.js +13 -0
  72. package/dist/storage/sqliteSchema.js +44 -0
  73. package/dist/storage/sqliteStore.js +53 -0
  74. package/dist/storage/storageVersions.js +1 -1
  75. package/dist/storage/storeRpc.js +2 -0
  76. package/dist/storage/taskSearch.js +35 -0
  77. package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
  78. package/dist/task/archiveDiagnostics.js +26 -8
  79. package/dist/task/archivePreflight.js +26 -9
  80. package/dist/task/leaderArchive.js +9 -15
  81. package/dist/task/ordinaryArchive.js +42 -0
  82. package/dist/tmux/tmuxManager.js +8 -8
  83. package/dist/web/assets/assetManifest.js +11 -51
  84. package/dist/web/assets/client/app/catalog.js +215 -0
  85. package/dist/web/assets/client/app/main.js +148 -0
  86. package/dist/web/assets/client/app/selection.js +139 -0
  87. package/dist/web/assets/client/app/settings.js +326 -0
  88. package/dist/web/assets/client/app/taskView.js +280 -0
  89. package/dist/web/assets/client/domain/context.js +96 -0
  90. package/dist/web/assets/client/domain/records.js +126 -0
  91. package/dist/web/assets/client/domain/runs.js +119 -0
  92. package/dist/web/assets/client/domain/taskForms.js +197 -0
  93. package/dist/web/assets/client/domain/vocab.js +69 -0
  94. package/dist/web/assets/client/domain/work.js +116 -0
  95. package/dist/web/assets/client/i18n/en.js +900 -0
  96. package/dist/web/assets/client/i18n/zh-CN.js +898 -0
  97. package/dist/web/assets/client/index.js +114 -0
  98. package/dist/web/assets/client/layout/resizer.js +37 -0
  99. package/dist/web/assets/client/layout/router.js +35 -0
  100. package/dist/web/assets/client/layout/shortcuts.js +45 -0
  101. package/dist/web/assets/client/layout/sizing.js +95 -0
  102. package/dist/web/assets/client/layout/workspace.js +247 -0
  103. package/dist/web/assets/client/{api.js → lib/api.js} +28 -4
  104. package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
  105. package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
  106. package/dist/web/assets/client/lib/i18n.js +54 -0
  107. package/dist/web/assets/client/lib/markdown.js +167 -0
  108. package/dist/web/assets/client/lib/prefs.js +28 -0
  109. package/dist/web/assets/client/lib/theme.js +68 -0
  110. package/dist/web/assets/client/ui/containers.js +101 -0
  111. package/dist/web/assets/client/ui/controls.js +51 -0
  112. package/dist/web/assets/client/ui/forms.js +48 -0
  113. package/dist/web/assets/client/ui/metrics.js +137 -0
  114. package/dist/web/assets/client/ui/primitives.js +92 -0
  115. package/dist/web/assets/client/ui/text.js +33 -0
  116. package/dist/web/assets/client/ui/toast.js +12 -0
  117. package/dist/web/assets/client/views/bodySearch.js +172 -0
  118. package/dist/web/assets/client/views/createTask.js +85 -0
  119. package/dist/web/assets/client/views/dock/conversation.js +612 -0
  120. package/dist/web/assets/client/views/dock/discussion.js +173 -0
  121. package/dist/web/assets/client/views/dock/preview.js +129 -0
  122. package/dist/web/assets/client/views/dock/terminal.js +199 -0
  123. package/dist/web/assets/client/views/globalInput.js +67 -0
  124. package/dist/web/assets/client/{overview.js → views/overview.js} +62 -56
  125. package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
  126. package/dist/web/assets/client/views/skills.js +152 -0
  127. package/dist/web/assets/client/views/task/actions.js +36 -0
  128. package/dist/web/assets/client/views/task/delivery.js +185 -0
  129. package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
  130. package/dist/web/assets/client/views/task/files.js +272 -0
  131. package/dist/web/assets/client/views/task/observation.js +153 -0
  132. package/dist/web/assets/client/views/task/overview.js +161 -0
  133. package/dist/web/assets/client/views/task/page.js +131 -0
  134. package/dist/web/assets/client/views/task/records.js +163 -0
  135. package/dist/web/assets/client/views/task/runtime.js +130 -0
  136. package/dist/web/assets/client/views/task/timeline.js +190 -0
  137. package/dist/web/assets/shared/geometry.js +19 -0
  138. package/dist/web/assets/{icons.js → shared/icons.js} +4 -0
  139. package/dist/web/assets/shared/taskTabs.js +8 -0
  140. package/dist/web/assets/shared/themes.js +93 -0
  141. package/dist/web/assets/shell/dialogs.js +63 -0
  142. package/dist/web/assets/shell/dock.js +40 -0
  143. package/dist/web/assets/shell/index.js +40 -0
  144. package/dist/web/assets/shell/settings.js +34 -0
  145. package/dist/web/assets/shell/sidebar.js +45 -0
  146. package/dist/web/assets/styles/index.js +41 -0
  147. package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +15 -13
  148. package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
  149. package/dist/web/assets/styles/markdown.js +22 -3
  150. package/dist/web/assets/styles/tokens.js +10 -63
  151. package/dist/web/assets/styles/ui/controls.js +76 -0
  152. package/dist/web/assets/styles/ui/forms.js +46 -0
  153. package/dist/web/assets/styles/ui/surfaces.js +122 -0
  154. package/dist/web/assets/styles/views/delivery.js +29 -0
  155. package/dist/web/assets/styles/views/dock.js +71 -0
  156. package/dist/web/assets/styles/views/page.js +32 -0
  157. package/dist/web/assets/styles/views/records.js +70 -0
  158. package/dist/web/assets/styles/views/settings.js +26 -0
  159. package/dist/web/assets/styles/views/sidebar.js +74 -0
  160. package/dist/web/assets/styles/views/task.js +80 -0
  161. package/dist/web/assets/styles/views/terminal.js +38 -0
  162. package/dist/web/taskPreviews.js +205 -0
  163. package/dist/web/tmuxWebTerminal.js +16 -9
  164. package/dist/web/webConversation.js +373 -0
  165. package/dist/web/webMaterials.js +155 -0
  166. package/dist/web/webServer.js +306 -18
  167. package/dist/web/webSettings.js +331 -0
  168. package/dist/web/webSkills.js +86 -0
  169. package/dist/web/webSnapshot.js +40 -8
  170. package/dist/web/webTaskSurface.js +114 -9
  171. package/dist/workspace/gitChangeSetCapture.js +3 -1
  172. package/docs/managed-turn-and-session-runtime.md +9 -0
  173. package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
  174. package/docs/observability/README.md +13 -3
  175. package/docs/observability/README.zh-CN.md +10 -3
  176. package/docs/release-workflow.md +53 -0
  177. package/docs/release-workflow.zh-CN.md +40 -0
  178. package/docs/storage-baseline.md +38 -1
  179. package/docs/storage-baseline.zh-CN.md +22 -0
  180. package/docs/task-delivery.md +36 -8
  181. package/docs/task-delivery.zh-CN.md +24 -7
  182. package/docs/task-discovery.md +28 -0
  183. package/docs/testing/verification-levels.md +2 -2
  184. package/i18n/README.zh-CN.md +4 -3
  185. package/package.json +1 -1
  186. package/skills/yui-leader/references/execution.md +2 -1
  187. package/skills/yui-leader/references/integration.md +20 -0
  188. package/skills/yui-operator/SKILL.md +4 -0
  189. package/skills/yui-reviewer/SKILL.md +3 -0
  190. package/skills/yui-runtime/SKILL.md +4 -0
  191. package/skills/yui-runtime/references/publication.md +20 -4
  192. package/skills/yui-worker/SKILL.md +3 -0
  193. package/dist/web/assets/client/app.js +0 -804
  194. package/dist/web/assets/client/components.js +0 -418
  195. package/dist/web/assets/client/detail.js +0 -333
  196. package/dist/web/assets/client/dock.js +0 -225
  197. package/dist/web/assets/client/evidence.js +0 -172
  198. package/dist/web/assets/client/forms.js +0 -230
  199. package/dist/web/assets/client/i18n.js +0 -1161
  200. package/dist/web/assets/client/markdown.js +0 -114
  201. package/dist/web/assets/client/records.js +0 -63
  202. package/dist/web/assets/client/sections.js +0 -313
  203. package/dist/web/assets/client/theme.js +0 -71
  204. package/dist/web/assets/shell.js +0 -161
  205. package/dist/web/assets/styles/components.js +0 -169
  206. package/dist/web/assets/styles/views.js +0 -292
@@ -0,0 +1,612 @@
1
+ export const CONVERSATION_SCRIPT = String.raw `
2
+ import { h, clear } from "/assets/js/lib/dom.js";
3
+ import { api, releaseMutation, requestJson, submitMutation } from "/assets/js/lib/api.js";
4
+ import { richText } from "/assets/js/ui/text.js";
5
+ import { readSessionAccessMode, writeSessionAccessMode } from "/assets/js/lib/prefs.js";
6
+ import { inputCard } from "/assets/js/domain/work.js";
7
+
8
+ export function createConversationController(host, t, locale = function () { return "en"; }) {
9
+ let owner = null, selected = null, facts = null, cursor = null, next = null;
10
+ let generation = 0, busy = false, timer = null, pending = null, historyOk = false;
11
+ let recoveryUnavailable = false;
12
+ let rendered = new Map();
13
+ let materials = [], incomingMaterials = null;
14
+ let requestSignature = "";
15
+ let modelPending = null, modelCatalog = null, lastModelObservation = null;
16
+ const requests = h("div");
17
+ const questions = h("div");
18
+ let questionSignature = "";
19
+ const defaultMode = h("select", { "aria-label": t("conversation.defaultMode") },
20
+ h("option", { value: "structured" }, t("conversation.title")),
21
+ h("option", { value: "native" }, t("dock.session")));
22
+ defaultMode.value = readSessionAccessMode();
23
+ defaultMode.addEventListener("change", function () {
24
+ const saved = writeSessionAccessMode(defaultMode.value);
25
+ receipt.textContent = t(saved ? "conversation.defaultSaved" : "settings.storageFailed");
26
+ defaultMode.value = readSessionAccessMode();
27
+ });
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");
31
+ 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")));
44
+ 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"));
53
+ const file = h("input", { type: "file", "aria-label": t("materials.attach") });
54
+ 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"))));
66
+
67
+ function key() { return "yui.conversation." + JSON.stringify(owner) + "." + selected; }
68
+ function saved(suffix, value, expectedRequestId) {
69
+ try {
70
+ if (suffix === ".pending") {
71
+ const shared = localStorage.getItem(key() + suffix);
72
+ const identity = shared ? JSON.parse(shared) : null;
73
+ const tab = sessionStorage.getItem(key() + suffix);
74
+ recoveryUnavailable = false;
75
+ if (value === undefined) {
76
+ // Share only the unresolved identity, never another tab's draft.
77
+ return identity && (!tab || JSON.parse(tab).requestId !== identity.requestId) ? shared : tab;
78
+ }
79
+ if (value === null) {
80
+ if (!identity || identity.requestId === expectedRequestId) localStorage.removeItem(key() + suffix);
81
+ } else {
82
+ const record = JSON.parse(value);
83
+ localStorage.setItem(key() + suffix, JSON.stringify({ requestId: record.requestId, action: record.action }));
84
+ }
85
+ }
86
+ if (value === undefined) return sessionStorage.getItem(key() + suffix);
87
+ if (value === null) sessionStorage.removeItem(key() + suffix);
88
+ else sessionStorage.setItem(key() + suffix, value);
89
+ } catch {
90
+ if (suffix === ".pending") recoveryUnavailable = true;
91
+ draftStatus.textContent = t("conversation.storageFailed");
92
+ }
93
+ return null;
94
+ }
95
+ function url(extra) {
96
+ const q = new URLSearchParams({ scope: owner.scope, role: owner.roleName });
97
+ if (owner.scope === "task") q.set("task", owner.taskId);
98
+ Object.entries(extra || {}).forEach(function (entry) { if (entry[1] != null) q.set(entry[0], String(entry[1])); });
99
+ return "/api/conversation?" + q;
100
+ }
101
+ function controls() {
102
+ if (owner && selected) {
103
+ try { pending = JSON.parse(saved(".pending") || "null"); }
104
+ catch { recoveryUnavailable = true; }
105
+ }
106
+ defaultMode.value = readSessionAccessMode();
107
+ const active = facts && facts.sessions.find(function (s) { return s.nativeSessionId === selected; });
108
+ const writable = historyOk && active && active.current && active.status === "active" && active.adapterId === "codex"
109
+ && facts.authority && facts.authority.owner === "controller" && !facts.terminalWriter;
110
+ const turn = facts && facts.turn;
111
+ send.disabled = busy || recoveryUnavailable || !!pending || !!modelPending || !writable || (turn && ["submitting", "delivery-unknown"].includes(turn.status));
112
+ stop.disabled = busy || recoveryUnavailable || !!pending || !writable || !turn || turn.status !== "accepted" || !turn.nativeTurnId;
113
+ check.disabled = busy || !pending;
114
+ older.disabled = busy || !next;
115
+ literal.disabled = send.disabled;
116
+ modelLoad.disabled = busy || !active || active.adapterId !== "codex";
117
+ modelApply.disabled = busy || !!pending || !!modelPending || !writable || !modelSelect.value
118
+ || !modelCatalog || modelCatalog.source === "fallback" || !!modelCatalog.failure
119
+ || turn && ["submitting", "accepted", "delivery-unknown"].includes(turn.status);
120
+ materialBar.hidden = !owner || owner.scope !== "task";
121
+ file.disabled = busy || !!pending || !writable || materials.length >= 8;
122
+ materialList.querySelectorAll("button").forEach(function (control) { control.disabled = busy || !!pending; });
123
+ }
124
+ function showReceipt(result) {
125
+ const refused = result.interrupt && result.interrupt.state === "not-interrupted";
126
+ const state = refused ? result.interrupt.code === "DELIVERY_UNKNOWN" ? "unknown" : "failed" : result.state || "submitted";
127
+ receipt.textContent = t("conversation.receipt." + state)
128
+ + (refused ? " · " + result.interrupt.code : "")
129
+ + (result.messageId ? " · " + result.messageId : "")
130
+ + (result.notDelivered ? " · " + (typeof result.notDelivered === "string" ? result.notDelivered : JSON.stringify(result.notDelivered)) : "");
131
+ receiptDetail.textContent = JSON.stringify(result, null, 2);
132
+ }
133
+ function showModel(observation) {
134
+ const axis = observation && observation.status === "observed"
135
+ && observation.axes.find(function (a) { return a.key === "model"; });
136
+ const value = axis && axis.current.status === "observed" ? axis.current.value : null;
137
+ const session = facts && facts.sessions.find(function (s) { return s.nativeSessionId === selected; });
138
+ modelStatus.textContent = t("conversation.launchModel") + ": " + (session && session.launchModel || "—")
139
+ + " · " + t("conversation.confirmedModel") + ": " + (value || t("conversation.unknown"))
140
+ + (value ? " · " + observation.observedAt : "");
141
+ if (modelPending) {
142
+ if (value === modelPending.model && observation.observedAt !== modelPending.previousObservation) {
143
+ modelPending = null; saved(".modelPending", null);
144
+ releaseMutation(url({ session: selected }).replace("/api/conversation?", "/api/conversation/model?"));
145
+ } else modelStatus.textContent += " · " + t("conversation.modelUnknown");
146
+ }
147
+ lastModelObservation = value ? observation.observedAt : null;
148
+ }
149
+ async function loadModels() {
150
+ if (modelLoad.disabled) return;
151
+ const own = generation;
152
+ busy = true; controls();
153
+ try {
154
+ const result = await requestJson(url({ session: selected }).replace("/api/conversation?", "/api/conversation/model?"));
155
+ if (own !== generation) return;
156
+ modelCatalog = result; clear(modelSelect);
157
+ (result.catalog.models || []).forEach(function (m) {
158
+ modelSelect.append(h("option", { value: m.value }, m.label + " · " + m.value));
159
+ });
160
+ modelResult.textContent = result.source + " · " + (result.fetchedAt || result.attemptedAt)
161
+ + (result.failure ? " · " + result.failure.message : "");
162
+ } catch (error) { if (own === generation) modelResult.textContent = error.message; }
163
+ finally { finish(own); }
164
+ }
165
+ async function applyModel() {
166
+ if (modelApply.disabled) return;
167
+ const own = generation;
168
+ const endpoint = url({ session: selected }).replace("/api/conversation?", "/api/conversation/model?");
169
+ modelPending = { model: modelSelect.value, previousObservation: lastModelObservation };
170
+ saved(".modelPending", JSON.stringify(modelPending));
171
+ busy = true; controls();
172
+ modelResult.textContent = t("conversation.modelApplying");
173
+ try {
174
+ const result = await submitMutation(endpoint, endpoint, { model: modelPending.model });
175
+ if (own !== generation) return;
176
+ modelPending = null; saved(".modelPending", null);
177
+ showModel(result.runConfiguration);
178
+ modelResult.textContent = t("conversation.modelConfirmed");
179
+ } catch (error) {
180
+ if (own !== generation) return;
181
+ modelResult.textContent = error.message + " · " + t(error.disposition === "not-submitted" ? "conversation.modelFailed" : "conversation.modelUnknown");
182
+ if (error.disposition === "not-submitted") { modelPending = null; saved(".modelPending", null); }
183
+ } finally { finish(own); }
184
+ }
185
+ const commands = ["help", "status", "model", "stop", "latest"];
186
+ function drawCommands() {
187
+ clear(slash);
188
+ const value = text.value.trim();
189
+ literal.hidden = !value.startsWith("/");
190
+ if (!value.startsWith("/") || /\s/.test(value)) return;
191
+ commands.filter(function (name) { return ("/" + name).startsWith(value); }).forEach(function (name) {
192
+ const button = h("button.btn", { type: "button" }, "/" + name + " · " + t("conversation.command." + name));
193
+ button.addEventListener("click", function () {
194
+ text.value = "/" + name; saved(".draft", text.value); drawCommands(); text.focus?.();
195
+ });
196
+ slash.append(button);
197
+ });
198
+ }
199
+ function runCommand() {
200
+ const name = text.value.trim().slice(1);
201
+ if (!commands.includes(name)) { receipt.textContent = t("conversation.commandUnknown"); return; }
202
+ if (name === "stop") {
203
+ if (stop.disabled) receipt.textContent = t("conversation.stopUnavailable");
204
+ else submit(true);
205
+ return;
206
+ }
207
+ if (name === "model") { modelPanel.open = true; loadModels(); }
208
+ else if (name === "status") refresh();
209
+ else if (name === "latest") { cursor = null; refresh(); }
210
+ receipt.textContent = name === "help" ? t("conversation.commandHelp") : t("conversation.command." + name);
211
+ text.value = ""; saved(".draft", null); drawCommands();
212
+ }
213
+ function saveMaterials() { saved(".materials", JSON.stringify(materials)); }
214
+ function drawMaterials() {
215
+ clear(materialList);
216
+ materials.forEach(function (ref, index) {
217
+ const remove = h("button.btn", { type: "button" }, t("materials.remove"));
218
+ remove.disabled = busy || !!pending;
219
+ remove.addEventListener("click", function () { materials.splice(index, 1); saveMaterials(); drawMaterials(); controls(); });
220
+ materialList.append(h("div", null, h("span.small", null,
221
+ ref.relativePath + " · " + ref.commit + " · sha256:" + ref.digest), remove));
222
+ });
223
+ }
224
+ function renderRequests(rows) {
225
+ const signature = JSON.stringify([selected, rows, !!facts.terminalWriter]);
226
+ if (signature === requestSignature) return;
227
+ requestSignature = signature; clear(requests);
228
+ 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));
231
+ const params = request.params, answers = {};
232
+ const pendingKey = ".nativePending." + request.turnId + "." + request.id;
233
+ if (request.method === "item/tool/requestUserInput") {
234
+ (params.questions || []).forEach(function (question) {
235
+ const field = h("input", { type: question.isSecret ? "password" : "text", "aria-label": question.question });
236
+ const draftKey = ".nativeDraft." + request.turnId + "." + request.id + "." + question.id;
237
+ if (!question.isSecret) {
238
+ field.value = saved(draftKey) || "";
239
+ field.addEventListener("input", function () { saved(draftKey, field.value); });
240
+ }
241
+ 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(" · ")));
243
+ answers[question.id] = field;
244
+ });
245
+ } else {
246
+ card.append(h("p", null, params.reason || params.command || t("conversation.nativeApproval")));
247
+ }
248
+ const outcome = h("p.feed-note", { role: "status" });
249
+ const buttons = [];
250
+ function button(label, result) {
251
+ const node = h("button.btn", { type: "button", disabled: !!facts.terminalWriter || !!saved(pendingKey) }, label);
252
+ buttons.push(node);
253
+ node.addEventListener("click", async function () {
254
+ const own = generation;
255
+ buttons.forEach(function (b) { b.disabled = true; });
256
+ saved(pendingKey, "pending");
257
+ // An answer is sent once. Unknown transport state is inspected by the
258
+ // next read, never retried automatically under a new request id.
259
+ try {
260
+ const endpoint = url({ session: selected });
261
+ await submitMutation(endpoint + "/native/" + request.id, endpoint, {
262
+ action: "native-respond", requestId: crypto.randomUUID(), nativeRequestId: request.id,
263
+ expectedTarget: request.turnId, result: result()
264
+ });
265
+ outcome.textContent = t("conversation.nativeAnswered");
266
+ } catch (error) {
267
+ if (own !== generation) return;
268
+ outcome.textContent = t("conversation.unknown") + " " + error.message;
269
+ if (error.disposition === "not-submitted") {
270
+ saved(pendingKey, null); buttons.forEach(function (b) { b.disabled = !!facts.terminalWriter; });
271
+ }
272
+ }
273
+ });
274
+ card.append(node);
275
+ }
276
+ if (request.method === "item/tool/requestUserInput") button(t("conversation.send"), function () {
277
+ return { answers: Object.fromEntries(Object.entries(answers).map(function (entry) { return [entry[0], { answers: [entry[1].value] }]; })) };
278
+ });
279
+ else if (request.method === "item/permissions/requestApproval") button(t("conversation.decline"), function () { return { permissions: {}, scope: "turn" }; });
280
+ 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")));
283
+ button(t("conversation.decline"), function () { return { action: "decline", content: null }; });
284
+ }
285
+ else {
286
+ button(t("conversation.approve"), function () { return { decision: "accept" }; });
287
+ button(t("conversation.decline"), function () { return { decision: "decline" }; });
288
+ }
289
+ card.append(outcome); requests.append(card);
290
+ });
291
+ }
292
+ function renderQuestions() {
293
+ const rows = selected === facts.currentSessionId ? facts.leaderQuestions || [] : [];
294
+ const signature = JSON.stringify([owner, rows, facts.questionsOmitted]);
295
+ if (signature === questionSignature) return;
296
+ questionSignature = signature; clear(questions);
297
+ const taskId = owner.taskId;
298
+ if (rows.length) questions.append(h("h3", null, t("conversation.leaderQuestions")));
299
+ rows.forEach(function (input) {
300
+ questions.append(inputCard(input, t, locale(), async function (original, answer, control) {
301
+ const own = generation;
302
+ control.disabled = true;
303
+ try {
304
+ await api.answerInput(taskId, original.id, answer);
305
+ releaseMutation(taskId + "/input/" + original.id);
306
+ if (own !== generation) return;
307
+ receipt.textContent = t("input.answered");
308
+ refresh();
309
+ } catch (error) {
310
+ if (own !== generation) return;
311
+ control.disabled = error.disposition !== "not-submitted";
312
+ receipt.textContent = error.disposition === "not-submitted" ? error.message : t("input.unknown");
313
+ }
314
+ }));
315
+ });
316
+ if (facts.questionsOmitted) questions.append(h("p.feed-note", null, t("overview.moreInputs")));
317
+ }
318
+ function choose(id) {
319
+ if (selected) saved(".draft", text.value);
320
+ generation++; selected = id; cursor = null; next = null; historyOk = false;
321
+ text.value = saved(".draft") || "";
322
+ try { pending = JSON.parse(saved(".pending") || "null"); } catch { pending = null; }
323
+ try { materials = JSON.parse(saved(".materials") || "[]"); } catch { materials = []; }
324
+ try { modelPending = JSON.parse(saved(".modelPending") || "null"); } catch { modelPending = null; }
325
+ modelCatalog = null; lastModelObservation = null; clear(modelSelect); modelStatus.textContent = ""; modelResult.textContent = ""; receiptDetail.textContent = "";
326
+ drawCommands();
327
+ materialAlert.textContent = "";
328
+ if (incomingMaterials) {
329
+ const combined = materials.concat(incomingMaterials).filter(function (ref, index, all) {
330
+ return all.findIndex(function (other) { return other.commit === ref.commit && other.relativePath === ref.relativePath; }) === index;
331
+ });
332
+ if (combined.length > 8) materialAlert.textContent = t("materials.capacity");
333
+ else { materials = combined; saveMaterials(); }
334
+ incomingMaterials = null;
335
+ }
336
+ drawMaterials();
337
+ receipt.textContent = pending ? t("conversation.unknown") + " " + pending.requestId : "";
338
+ clear(feed); rendered.clear();
339
+ clear(requests); requestSignature = "";
340
+ clear(questions); questionSignature = "";
341
+ controls();
342
+ }
343
+ function finish(own) {
344
+ busy = false; controls();
345
+ // A read for the old owner may have prevented the new selection's read.
346
+ // Only refresh view state; never repeat a mutation or receipt submission.
347
+ if (own !== generation && timer !== null) refresh();
348
+ }
349
+ function renderItems(items) {
350
+ 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);
353
+ const previous = rendered.get(key);
354
+ let node = previous && previous.signature === signature ? previous.node : null;
355
+ 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));
363
+ if (previous) {
364
+ if (item.kind === "activity") node.open = previous.node.open;
365
+ const oldProse = previous.node.querySelectorAll(".prose-block");
366
+ node.querySelectorAll(".prose-block").forEach(function (prose, index) {
367
+ if (oldProse[index] && !oldProse[index].classList.contains("is-collapsed")
368
+ && prose.classList.contains("is-collapsed")) prose.querySelector("button.link-btn")?.click();
369
+ });
370
+ }
371
+ }
372
+ updated.set(key, { signature: signature, node: node }); nodes.push(node);
373
+ });
374
+ rendered = updated;
375
+ if (!nodes.length) nodes.push(h("p.feed-note", null, t("conversation.empty")));
376
+ if (feed.children.length !== nodes.length || nodes.some(function (n, i) { return feed.children[i] !== n; })) {
377
+ feed.replaceChildren(...nodes);
378
+ }
379
+ }
380
+ function drawSessions(rows, append) {
381
+ if (!append) clear(select);
382
+ rows.forEach(function (s) {
383
+ if ([...select.options].some(function (o) { return o.value === s.nativeSessionId; })) return;
384
+ select.append(h("option", { value: s.nativeSessionId },
385
+ (s.current ? t("conversation.current") : t("conversation.previous")) + " · " + s.executionAuthority + " · " + s.nativeSessionId));
386
+ });
387
+ select.value = selected || "";
388
+ moreSessions.hidden = !facts.nextOffset;
389
+ }
390
+ async function refresh() {
391
+ if (!owner || busy || host.hidden || host.closest("[hidden]") || document.hidden) return;
392
+ busy = true; controls();
393
+ let own = generation;
394
+ try {
395
+ const result = await requestJson(url());
396
+ if (own !== generation) return;
397
+ const previous = facts;
398
+ facts = result;
399
+ if (previous && previous.total === facts.total) facts.nextOffset = previous.nextOffset;
400
+ // Previously discovered old Sessions remain view metadata, not current
401
+ // authority. Refresh current flags from the newest canonical selection.
402
+ if (previous) previous.sessions.forEach(function (s) {
403
+ if (!facts.sessions.some(function (n) { return n.nativeSessionId === s.nativeSessionId; })) {
404
+ facts.sessions.push({ ...s, current: s.nativeSessionId === facts.currentSessionId });
405
+ }
406
+ });
407
+ if (!selected) {
408
+ choose(facts.currentSessionId);
409
+ own = generation;
410
+ }
411
+ if (!selected) { status.textContent = t("conversation.noSession"); return; }
412
+ // Preserve a historical choice even if it is outside the newest page.
413
+ drawSessions(facts.sessions, false);
414
+ renderQuestions();
415
+ const page = await requestJson(url({ session: selected, cursor: cursor }));
416
+ if (own !== generation) return;
417
+ historyOk = true; next = page.nextCursor;
418
+ const turn = selected === facts.currentSessionId ? facts.turn : null;
419
+ let state = page.status === "waiting-user" ? "waiting" : page.status === "active" ? "active"
420
+ : page.status === "systemError" ? "failed" : page.status === "idle" ? "waiting" : "unknown";
421
+ const session = facts.sessions.find(function (s) { return s.nativeSessionId === selected; });
422
+ if (session && session.status === "ended") state = session.endReason === "failed" ? "failed" : "ended";
423
+ 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");
426
+ if (page.linkedInputsOmitted) status.textContent += " · " + t("conversation.linkedOmitted");
427
+ if (page.nativeRequestsOmitted) status.textContent += " · " + t("conversation.nativeOmitted");
428
+ status.textContent += " · " + t(facts.terminalWriter ? "conversation.terminalWriter" : "conversation.structuredWriter");
429
+ renderRequests(page.nativeRequests || []);
430
+ showModel(page.runConfiguration);
431
+ identity.textContent = (owner.taskId || "Global") + " / " + owner.roleName + " / " + selected
432
+ + (turn ? " / " + (turn.nativeTurnId || turn.attemptId) + (turn.runId ? " / " + turn.runId : "") : "");
433
+ const scroll = feed.scrollTop, atEnd = feed.scrollHeight - feed.clientHeight - scroll < 40;
434
+ renderItems(page.items);
435
+ feed.scrollTop = atEnd ? feed.scrollHeight : scroll;
436
+ } catch (error) {
437
+ if (own === generation) { historyOk = false; status.textContent = t("conversation.disconnected") + " · " + error.message; }
438
+ } finally { finish(own); }
439
+ if (own === generation && pending && historyOk) await inspectReceipt();
440
+ }
441
+ async function inspectReceipt() {
442
+ if (!pending || busy) return;
443
+ const own = generation;
444
+ const inspected = pending;
445
+ busy = true; controls();
446
+ try {
447
+ const result = await requestJson(url({ requestId: inspected.requestId }));
448
+ if (own !== generation) return;
449
+ showReceipt(result);
450
+ if (["accepted", "failed"].includes(result.state)) {
451
+ const submitted = inspected;
452
+ pending = null; saved(".pending", null, submitted.requestId);
453
+ if (result.state === "accepted" && submitted.action !== "interrupt" && text.value === submitted.body) {
454
+ text.value = ""; saved(".draft", null);
455
+ drawCommands();
456
+ materials = materials.filter(function (ref) {
457
+ return !(submitted.materials || []).some(function (sent) {
458
+ return sent.commit === ref.commit && sent.relativePath === ref.relativePath;
459
+ });
460
+ });
461
+ saveMaterials(); drawMaterials();
462
+ }
463
+ }
464
+ } catch (error) { if (own === generation) receipt.textContent = t("conversation.unknown") + " " + error.message; }
465
+ finally { finish(own); }
466
+ }
467
+ async function submit(interrupt, asLiteral) {
468
+ controls(); // Another tab may have acquired an unresolved input since refresh.
469
+ if (busy || pending || (interrupt ? stop.disabled : send.disabled)) return;
470
+ if (!interrupt && !text.value.trim()) return;
471
+ if (!interrupt && !asLiteral && text.value.trim().startsWith("/")) { runCommand(); return; }
472
+ const turn = facts.turn;
473
+ const action = interrupt ? "interrupt" : turn && turn.status === "accepted" ? "steer" : "queue";
474
+ const payload = { action: action, requestId: crypto.randomUUID() };
475
+ if (!interrupt) payload.body = text.value;
476
+ if (!interrupt && materials.length) payload.materials = materials.map(function (ref) {
477
+ return { taskId: ref.taskId, relativePath: ref.relativePath, commit: ref.commit, digest: ref.digest };
478
+ });
479
+ if (action !== "queue") payload.expectedTarget = turn.nativeTurnId || turn.attemptId;
480
+ pending = { requestId: payload.requestId, action: action, body: payload.body, materials: payload.materials };
481
+ saved(".pending", JSON.stringify(pending)); saved(".draft", text.value);
482
+ if (recoveryUnavailable) { controls(); return; }
483
+ const own = generation, endpoint = url({ session: selected });
484
+ busy = true; controls();
485
+ receipt.textContent = t("conversation.receipt.sending");
486
+ try {
487
+ const result = await submitMutation(endpoint + "/" + payload.requestId, endpoint, payload);
488
+ if (own !== generation) return;
489
+ showReceipt(result);
490
+ if (action === "interrupt" && result.interrupt?.state === "not-interrupted"
491
+ && ["NO_ACTIVE_TURN", "TARGET_CHANGED", "INTERRUPT_UNSUPPORTED"].includes(result.interrupt.code)) {
492
+ // Preflight proved no native cancel was submitted, so no durable
493
+ // interrupt receipt exists. DELIVERY_UNKNOWN must stay unresolved.
494
+ pending = null; saved(".pending", null, payload.requestId);
495
+ }
496
+ // Saved/queued is not accepted. Only an exact receipt lookup settles it.
497
+ } catch (error) {
498
+ if (own !== generation) return;
499
+ receipt.textContent = t(error.disposition === "not-submitted" ? "conversation.receipt.failed" : "conversation.unknown") + " " + error.message;
500
+ if (error.disposition === "not-submitted") { pending = null; saved(".pending", null, payload.requestId); }
501
+ } finally { finish(own); }
502
+ if (own === generation) await inspectReceipt();
503
+ }
504
+ text.addEventListener("input", function () {
505
+ if (selected) { draftStatus.textContent = t("conversation.draftSaved"); saved(".draft", text.value); }
506
+ drawCommands();
507
+ });
508
+ text.addEventListener("keydown", function (event) {
509
+ if (event.isComposing) return;
510
+ if ((event.ctrlKey || event.metaKey) && event.key === "Enter") { event.preventDefault(); submit(false); }
511
+ if (event.key === "Tab" && text.value.startsWith("/")) {
512
+ const matches = commands.filter(function (name) { return ("/" + name).startsWith(text.value); });
513
+ if (matches.length === 1) {
514
+ event.preventDefault(); text.value = "/" + matches[0]; saved(".draft", text.value); drawCommands();
515
+ }
516
+ }
517
+ });
518
+ modelLoad.addEventListener("click", loadModels);
519
+ modelSelect.addEventListener("change", controls);
520
+ modelApply.addEventListener("click", applyModel);
521
+ literal.addEventListener("click", function () { submit(false, true); });
522
+ file.addEventListener("change", async function () {
523
+ const chosen = file.files[0];
524
+ if (!chosen || file.disabled) return;
525
+ const own = generation;
526
+ const endpoint = url({ session: selected }).replace("/api/conversation?", "/api/conversation/material?");
527
+ const requestId = crypto.randomUUID();
528
+ busy = true; controls();
529
+ try {
530
+ if (chosen.size > 256 * 1024) throw Object.assign(new Error(t("materials.tooLarge")), { disposition: "not-submitted" });
531
+ const content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(await chosen.arrayBuffer());
532
+ if (own !== generation) return;
533
+ const ref = await submitMutation(endpoint + "/" + requestId, endpoint,
534
+ { requestId: requestId, name: chosen.name, content: content });
535
+ if (own !== generation) return;
536
+ materials.push(ref); saveMaterials(); drawMaterials();
537
+ receipt.textContent = t("materials.saved");
538
+ } catch (error) {
539
+ if (own === generation) receipt.textContent = error.message + " · " + t("materials.inspectUpload")
540
+ + " materials/" + requestId + "/" + chosen.name;
541
+ } finally { file.value = ""; finish(own); drawMaterials(); }
542
+ });
543
+ select.addEventListener("change", function () { choose(select.value); refresh(); });
544
+ 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(); });
547
+ send.addEventListener("click", function () { submit(false); });
548
+ stop.addEventListener("click", function () { submit(true); });
549
+ check.addEventListener("click", inspectReceipt);
550
+ moreSessions.addEventListener("click", async function () {
551
+ if (busy || !facts.nextOffset) return;
552
+ const own = generation;
553
+ busy = true;
554
+ try {
555
+ const page = await requestJson(url({ offset: facts.nextOffset }));
556
+ if (own !== generation) return;
557
+ facts.sessions = facts.sessions.concat(page.sessions); facts.nextOffset = page.nextOffset;
558
+ drawSessions(page.sessions, true);
559
+ } catch (error) { status.textContent = error.message; }
560
+ finally { finish(own); }
561
+ });
562
+ controls();
563
+ return {
564
+ captureDraftTarget: function () {
565
+ controls();
566
+ return send.disabled || timer === null || host.hidden || host.closest("[hidden]")
567
+ ? null : JSON.stringify([owner, selected]);
568
+ },
569
+ insertReference: function (target, citation) {
570
+ controls();
571
+ if (!target || target !== JSON.stringify([owner, selected]) || send.disabled
572
+ || timer === null || host.hidden || host.closest("[hidden]")) {
573
+ throw new Error(t("bodySearch.targetChanged"));
574
+ }
575
+ const combined = text.value + (text.value ? "\n\n" : "") + citation;
576
+ if (combined.length > text.maxLength) throw new Error(t("bodySearch.capacity"));
577
+ text.value = combined;
578
+ saved(".draft", text.value);
579
+ drawCommands();
580
+ },
581
+ current: function () { return owner; },
582
+ selection: function () {
583
+ if (!owner || (selected && facts && selected !== facts.currentSessionId)) return null;
584
+ return { ...owner, ...(selected ? { nativeSessionId: selected } : {}) };
585
+ },
586
+ reconnect: function () {
587
+ if (!timer) timer = window.setInterval(function () { if (!cursor) refresh(); }, 2000);
588
+ refresh();
589
+ },
590
+ open: function (target, options) {
591
+ // Explicit workbench continuation must resolve today's current Session
592
+ // from the shared endpoint, not retain a historical selection or stale ID.
593
+ if (JSON.stringify(target) !== JSON.stringify(owner) || options && options.current) {
594
+ if (selected) saved(".draft", text.value);
595
+ generation++; owner = target; selected = null; facts = null; pending = null; clear(select); clear(feed);
596
+ cursor = null; next = null; historyOk = false; rendered.clear();
597
+ text.value = ""; receipt.textContent = ""; identity.textContent = ""; controls();
598
+ modelPending = null; modelCatalog = null; clear(modelSelect); modelStatus.textContent = "";
599
+ modelResult.textContent = "";
600
+ receiptDetail.textContent = ""; draftStatus.textContent = ""; drawCommands();
601
+ materials = []; incomingMaterials = null; clear(materialList); materialAlert.textContent = "";
602
+ clear(requests); requestSignature = "";
603
+ clear(questions); questionSignature = "";
604
+ }
605
+ if (options && options.materials) incomingMaterials = options.materials;
606
+ if (!timer) timer = window.setInterval(function () { if (!cursor) refresh(); }, 2000);
607
+ refresh();
608
+ },
609
+ close: function () { if (timer) window.clearInterval(timer); timer = null; generation++; }
610
+ };
611
+ }
612
+ `;