@zq-silk/yui 2.3.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 (183) hide show
  1. package/README.md +15 -1
  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 +17 -8
  7. package/dist/cli/invocationAuthority.js +1 -1
  8. package/dist/commands/executionAuditCommands.js +5 -0
  9. package/dist/commands/globalRoleCommands.js +41 -3
  10. package/dist/commands/projectCommands.js +89 -94
  11. package/dist/commands/roleSkillValidation.js +4 -8
  12. package/dist/commands/taskCommands.js +25 -2
  13. package/dist/commands/taskUsageCommand.js +38 -0
  14. package/dist/context/roleSessionContext.js +18 -2
  15. package/dist/context/runContextPack.js +5 -1
  16. package/dist/context/sessionBootstrapManifest.js +10 -1
  17. package/dist/context/skillCatalog.js +108 -0
  18. package/dist/context/skillPackage.js +161 -0
  19. package/dist/context/taskContext.js +3 -2
  20. package/dist/context/taskSearch.js +127 -0
  21. package/dist/controlPlaneCli.js +25 -20
  22. package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
  23. package/dist/controller/jobControl.js +2 -0
  24. package/dist/controller/nativeHostInput.js +37 -0
  25. package/dist/controller/runtime.js +93 -2
  26. package/dist/controller/runtimeEventInbox.js +2 -2
  27. package/dist/controller/runtimeEventProcessor.js +3 -3
  28. package/dist/doctor/doctor.js +10 -0
  29. package/dist/execution/reviewMainRun.js +1 -0
  30. package/dist/execution/workItemMainRun.js +1 -0
  31. package/dist/executor/codexConfigConflict.js +6 -4
  32. package/dist/executor/effectiveLaunch.js +24 -1
  33. package/dist/executor/fileRoleLaunchPlanner.js +13 -5
  34. package/dist/integration/gitIntegrationService.js +7 -12
  35. package/dist/integration/integrationSourceApplication.js +4 -2
  36. package/dist/job/httpPreview.js +61 -0
  37. package/dist/job/jobRunner.js +12 -2
  38. package/dist/kernel/builtinCapabilities.js +3 -1
  39. package/dist/message/message.js +15 -1
  40. package/dist/output/rolePresentation.js +4 -1
  41. package/dist/repository/executionLaneGitSnapshot.js +2 -1
  42. package/dist/repository/gitCompatibility.js +62 -0
  43. package/dist/repository/gitWorkspace.js +48 -71
  44. package/dist/repository/gitWorktreeInventory.js +121 -0
  45. package/dist/repository/project.js +74 -25
  46. package/dist/resources/resourceDiscovery.js +2 -2
  47. package/dist/resources/resourceGc.js +7 -4
  48. package/dist/resources/resourceRegistrar.js +2 -1
  49. package/dist/runtime/agentEndpoint.js +8 -0
  50. package/dist/runtime/agentHost.js +170 -3
  51. package/dist/runtime/codexAppServerRuntime.js +1 -1
  52. package/dist/runtime/codexNativeAccess.js +175 -0
  53. package/dist/runtime/controlledCodexTui.js +116 -0
  54. package/dist/runtime/publicReply.js +23 -0
  55. package/dist/runtime/runtimeObservation.js +48 -0
  56. package/dist/runtime/structuredProviderHost.js +16 -7
  57. package/dist/runtime/taskCostMetrics.js +133 -0
  58. package/dist/runtime/taskUsageMetrics.js +11 -2
  59. package/dist/runtime/taskUsageQuery.js +38 -0
  60. package/dist/scheduler/taskExecutionProjection.js +2 -0
  61. package/dist/scheduler/taskObservabilityProjection.js +9 -1
  62. package/dist/storage/homeLayout.js +13 -0
  63. package/dist/storage/sqliteSchema.js +44 -0
  64. package/dist/storage/sqliteStore.js +53 -0
  65. package/dist/storage/storageVersions.js +1 -1
  66. package/dist/storage/storeRpc.js +2 -0
  67. package/dist/storage/taskSearch.js +35 -0
  68. package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
  69. package/dist/task/ordinaryArchive.js +42 -0
  70. package/dist/tmux/tmuxManager.js +8 -8
  71. package/dist/web/assets/assetManifest.js +11 -51
  72. package/dist/web/assets/client/app/catalog.js +215 -0
  73. package/dist/web/assets/client/app/main.js +148 -0
  74. package/dist/web/assets/client/app/selection.js +139 -0
  75. package/dist/web/assets/client/app/settings.js +326 -0
  76. package/dist/web/assets/client/app/taskView.js +280 -0
  77. package/dist/web/assets/client/domain/context.js +96 -0
  78. package/dist/web/assets/client/domain/records.js +126 -0
  79. package/dist/web/assets/client/domain/runs.js +119 -0
  80. package/dist/web/assets/client/domain/taskForms.js +197 -0
  81. package/dist/web/assets/client/domain/vocab.js +69 -0
  82. package/dist/web/assets/client/domain/work.js +116 -0
  83. package/dist/web/assets/client/i18n/en.js +900 -0
  84. package/dist/web/assets/client/i18n/zh-CN.js +898 -0
  85. package/dist/web/assets/client/index.js +114 -0
  86. package/dist/web/assets/client/layout/resizer.js +37 -0
  87. package/dist/web/assets/client/layout/router.js +35 -0
  88. package/dist/web/assets/client/layout/shortcuts.js +45 -0
  89. package/dist/web/assets/client/layout/sizing.js +95 -0
  90. package/dist/web/assets/client/layout/workspace.js +247 -0
  91. package/dist/web/assets/client/{api.js → lib/api.js} +20 -4
  92. package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
  93. package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
  94. package/dist/web/assets/client/lib/i18n.js +54 -0
  95. package/dist/web/assets/client/lib/markdown.js +167 -0
  96. package/dist/web/assets/client/lib/prefs.js +28 -0
  97. package/dist/web/assets/client/lib/theme.js +68 -0
  98. package/dist/web/assets/client/ui/containers.js +101 -0
  99. package/dist/web/assets/client/ui/controls.js +51 -0
  100. package/dist/web/assets/client/ui/forms.js +48 -0
  101. package/dist/web/assets/client/ui/metrics.js +137 -0
  102. package/dist/web/assets/client/ui/primitives.js +92 -0
  103. package/dist/web/assets/client/ui/text.js +33 -0
  104. package/dist/web/assets/client/ui/toast.js +12 -0
  105. package/dist/web/assets/client/views/bodySearch.js +172 -0
  106. package/dist/web/assets/client/views/createTask.js +85 -0
  107. package/dist/web/assets/client/views/dock/conversation.js +612 -0
  108. package/dist/web/assets/client/views/dock/discussion.js +173 -0
  109. package/dist/web/assets/client/views/dock/preview.js +129 -0
  110. package/dist/web/assets/client/views/dock/terminal.js +199 -0
  111. package/dist/web/assets/client/views/globalInput.js +67 -0
  112. package/dist/web/assets/client/{overview.js → views/overview.js} +61 -51
  113. package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
  114. package/dist/web/assets/client/views/skills.js +152 -0
  115. package/dist/web/assets/client/views/task/actions.js +36 -0
  116. package/dist/web/assets/client/views/task/delivery.js +185 -0
  117. package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
  118. package/dist/web/assets/client/views/task/files.js +272 -0
  119. package/dist/web/assets/client/views/task/observation.js +153 -0
  120. package/dist/web/assets/client/views/task/overview.js +161 -0
  121. package/dist/web/assets/client/views/task/page.js +131 -0
  122. package/dist/web/assets/client/views/task/records.js +163 -0
  123. package/dist/web/assets/client/views/task/runtime.js +130 -0
  124. package/dist/web/assets/client/views/task/timeline.js +190 -0
  125. package/dist/web/assets/shared/geometry.js +19 -0
  126. package/dist/web/assets/shared/taskTabs.js +8 -0
  127. package/dist/web/assets/shared/themes.js +93 -0
  128. package/dist/web/assets/shell/dialogs.js +63 -0
  129. package/dist/web/assets/shell/dock.js +40 -0
  130. package/dist/web/assets/shell/index.js +40 -0
  131. package/dist/web/assets/shell/settings.js +34 -0
  132. package/dist/web/assets/shell/sidebar.js +45 -0
  133. package/dist/web/assets/styles/index.js +41 -0
  134. package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +10 -10
  135. package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
  136. package/dist/web/assets/styles/markdown.js +22 -3
  137. package/dist/web/assets/styles/tokens.js +10 -63
  138. package/dist/web/assets/styles/ui/controls.js +76 -0
  139. package/dist/web/assets/styles/ui/forms.js +46 -0
  140. package/dist/web/assets/styles/ui/surfaces.js +122 -0
  141. package/dist/web/assets/styles/views/delivery.js +29 -0
  142. package/dist/web/assets/styles/views/dock.js +71 -0
  143. package/dist/web/assets/styles/views/page.js +32 -0
  144. package/dist/web/assets/styles/views/records.js +70 -0
  145. package/dist/web/assets/styles/views/settings.js +26 -0
  146. package/dist/web/assets/styles/views/sidebar.js +74 -0
  147. package/dist/web/assets/styles/views/task.js +80 -0
  148. package/dist/web/assets/styles/views/terminal.js +38 -0
  149. package/dist/web/taskPreviews.js +205 -0
  150. package/dist/web/tmuxWebTerminal.js +16 -9
  151. package/dist/web/webConversation.js +373 -0
  152. package/dist/web/webMaterials.js +155 -0
  153. package/dist/web/webServer.js +295 -18
  154. package/dist/web/webSettings.js +331 -0
  155. package/dist/web/webSkills.js +86 -0
  156. package/dist/web/webSnapshot.js +40 -8
  157. package/dist/web/webTaskSurface.js +106 -8
  158. package/dist/workspace/gitChangeSetCapture.js +3 -1
  159. package/docs/managed-turn-and-session-runtime.md +9 -0
  160. package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
  161. package/docs/release-workflow.md +28 -0
  162. package/docs/release-workflow.zh-CN.md +21 -0
  163. package/docs/storage-baseline.md +38 -1
  164. package/docs/storage-baseline.zh-CN.md +22 -0
  165. package/docs/task-discovery.md +28 -0
  166. package/docs/testing/verification-levels.md +2 -2
  167. package/i18n/README.zh-CN.md +1 -1
  168. package/package.json +1 -1
  169. package/dist/web/assets/client/app.js +0 -808
  170. package/dist/web/assets/client/components.js +0 -477
  171. package/dist/web/assets/client/detail.js +0 -379
  172. package/dist/web/assets/client/dock.js +0 -309
  173. package/dist/web/assets/client/evidence.js +0 -364
  174. package/dist/web/assets/client/forms.js +0 -230
  175. package/dist/web/assets/client/i18n.js +0 -1277
  176. package/dist/web/assets/client/markdown.js +0 -114
  177. package/dist/web/assets/client/records.js +0 -198
  178. package/dist/web/assets/client/sections.js +0 -349
  179. package/dist/web/assets/client/theme.js +0 -71
  180. package/dist/web/assets/shell.js +0 -161
  181. package/dist/web/assets/styles/components.js +0 -216
  182. package/dist/web/assets/styles/views.js +0 -309
  183. /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
@@ -0,0 +1,173 @@
1
+ export const DISCUSSION_SCRIPT = String.raw `
2
+ // The Task discussion in the dock: recorded Task messages plus the composer.
3
+ // The feed is a read-only projection and redraws on every refresh; the
4
+ // composer is kept per Task so a refresh can never discard an unsent draft or
5
+ // a pending receipt.
6
+ import { h, clear } from "/assets/js/lib/dom.js";
7
+ import { fill, formatShort } from "/assets/js/lib/format.js";
8
+ import { chip, emptyState } from "/assets/js/ui/primitives.js";
9
+ import { richText } from "/assets/js/ui/text.js";
10
+ import { label } from "/assets/js/domain/vocab.js";
11
+ import { cachedExact, entriesOf, exactCache, totalOf } from "/assets/js/domain/context.js";
12
+ import { recordCard } from "/assets/js/domain/records.js";
13
+ import { messageComposer } from "/assets/js/domain/taskForms.js";
14
+
15
+ export function renderDiscussion(host, data, t, locale, actions) {
16
+ const task = data.task;
17
+ const composerKey = task.id + "|" + task.status + "|" + locale;
18
+ let feed = host.querySelector(".feed");
19
+ const composerWrap = host.querySelector(".composer-wrap");
20
+ const composer = composerWrap && composerWrap.firstChild;
21
+ const keepComposer = host.dataset.taskId === task.id && composer
22
+ && (composer.dataset.key === composerKey || composer.dataset.unsent === "true" || composer.contains(document.activeElement));
23
+ const previousScroll = feed ? feed.scrollTop : 0;
24
+ const atEnd = !feed || host.dataset.taskId !== task.id || feed.scrollHeight - feed.clientHeight - feed.scrollTop < 40;
25
+ if (host.dataset.taskId !== task.id || !feed) feed = discussionFrame(host, t);
26
+ host.dataset.taskId = task.id;
27
+ latest.set(task.id, { data: data, t: t, locale: locale, actions: actions });
28
+ if (!feed.querySelector('[data-reading="true"]')) drawFeed(feed, data, t, locale, actions);
29
+ feed.scrollTop = atEnd ? feed.scrollHeight : previousScroll;
30
+ if (!keepComposer) {
31
+ const next = messageComposer(task, t, actions);
32
+ next.dataset.key = composerKey;
33
+ host.querySelector(".composer-wrap").replaceChildren(next);
34
+ }
35
+ }
36
+
37
+ function discussionFrame(host, t) {
38
+ clear(host);
39
+ host.append(
40
+ h("div.dock-sub", null, h("span.dock-sub-title", null, t("discussion.title")), h("span.dock-sub-note", null, t("discussion.source"))),
41
+ h("div.feed", { "aria-label": t("discussion.feed"), tabIndex: 0 }),
42
+ h("div.composer-wrap"));
43
+ return host.querySelector(".feed");
44
+ }
45
+
46
+ export function discussionHasUnsent(host) {
47
+ return !!host.querySelector('[data-unsent="true"]');
48
+ }
49
+
50
+ // The Context read carries bounded summaries. Full bodies are opened by the
51
+ // reader, never downloaded automatically because the dock was rendered.
52
+ const older = new Map();
53
+ const latest = new Map();
54
+
55
+ function exactValue(entry, cache) {
56
+ if (!entry.omitted) return entry.value;
57
+ const hit = cachedExact(cache, entry);
58
+ return hit ? hit.value : null;
59
+ }
60
+
61
+ function loadOlder(data, actions, state, shownIds) {
62
+ state.pending = true;
63
+ state.error = null;
64
+ let rounds = 0;
65
+ function next() {
66
+ return actions.list(data.task.id, "task-message", { limit: 20, cursor: state.cursor }).then(function (page) {
67
+ const known = new Set(state.items.map(function (item) { return item.ref.refId; }));
68
+ const fresh = page.items.filter(function (item) { return !known.has(item.ref.refId) && !shownIds.has(item.ref.refId); });
69
+ state.items = state.items.concat(fresh);
70
+ state.cursor = page.nextCursor;
71
+ state.done = !page.nextCursor;
72
+ rounds += 1;
73
+ if (!fresh.length && page.nextCursor && rounds < 10) return next();
74
+ });
75
+ }
76
+ // A listing that moved invalidates its continuation; the next request
77
+ // starts again from the newest page and skips what is already shown.
78
+ return next().catch(function (error) { state.error = error.message; state.cursor = null; })
79
+ .then(function () { state.pending = false; });
80
+ }
81
+
82
+ function olderState(taskId) {
83
+ if (!older.has(taskId)) older.set(taskId, { items: [], cursor: null, done: false, pending: false, error: null });
84
+ return older.get(taskId);
85
+ }
86
+
87
+ // Feed rows oldest first, reusing an exact value only when already read.
88
+ function feedRows(olderItems, contextEntries, cache) {
89
+ // Context lists messages newest first; the feed reads oldest first.
90
+ const rows = olderItems.map(function (item) { return { entry: { ref: item.ref, summary: item.summary, omitted: true }, at: item.createdAt }; })
91
+ .concat(contextEntries.slice().reverse().map(function (entry) { return { entry: entry, at: null }; }));
92
+ rows.forEach(function (row) {
93
+ row.value = exactValue(row.entry, cache);
94
+ row.at = (row.value && row.value.createdAt) || row.at;
95
+ });
96
+ if (rows.every(function (row) { return row.at; })) rows.sort(function (a, b) { return Date.parse(a.at) - Date.parse(b.at); });
97
+ return rows;
98
+ }
99
+
100
+ function drawFeed(feed, data, t, locale, actions) {
101
+ clear(feed);
102
+ const task = data.task;
103
+ const cache = exactCache(data.viewState);
104
+ const contextEntries = entriesOf(data.core, "task-message");
105
+ const shownIds = new Set(contextEntries.map(function (entry) { return entry.ref.refId; }));
106
+ const state = olderState(task.id);
107
+ const olderItems = state.items.filter(function (item) { return !shownIds.has(item.ref.refId); })
108
+ .sort(function (a, b) { return Date.parse(a.createdAt || 0) - Date.parse(b.createdAt || 0); });
109
+ const rows = feedRows(olderItems, contextEntries, cache);
110
+ const remaining = totalOf(data.core, "task-message") - shownIds.size - olderItems.length;
111
+ if (actions.list && remaining > 0 && !state.done) {
112
+ feed.append(olderButton(remaining, state, t, function () {
113
+ return loadOlder(data, actions, state, shownIds).then(function () { redraw(feed, task.id, true); });
114
+ }));
115
+ }
116
+ if (state.error) feed.append(h("p.feed-note", null, t("records.unavailable") + " " + state.error));
117
+ if (!rows.length) {
118
+ feed.append(emptyState(t("discussion.empty"), "chat"));
119
+ return;
120
+ }
121
+ let lastDay = "";
122
+ rows.forEach(function (row) {
123
+ if (!row.value) { feed.append(placeholderRow(row, task.id, t, actions)); return; }
124
+ const day = row.value.createdAt ? new Intl.DateTimeFormat(locale, { dateStyle: "medium" }).format(new Date(row.value.createdAt)) : "";
125
+ if (day && day !== lastDay) { feed.append(h("div.feed-day", null, h("span", null, day))); lastDay = day; }
126
+ feed.append(messageRow(row, t, locale));
127
+ });
128
+ }
129
+
130
+ function olderButton(remaining, state, t, onLoad) {
131
+ const more = h("button.show-more.feed-older", { type: "button", disabled: state.pending },
132
+ state.pending ? t("discussion.olderLoading") : fill(t("discussion.older"), { count: remaining }));
133
+ more.addEventListener("click", function () {
134
+ more.disabled = true;
135
+ more.textContent = t("discussion.olderLoading");
136
+ onLoad();
137
+ });
138
+ return more;
139
+ }
140
+
141
+ function placeholderRow(row, taskId, t, actions) {
142
+ const placeholder = recordCard(row.entry, taskId, t, actions, { compact: true });
143
+ if (row.reading) placeholder.dataset.reading = "true";
144
+ return placeholder;
145
+ }
146
+
147
+ function messageRow(row, t, locale) {
148
+ const value = row.value;
149
+ const type = (value.author && value.author.type) || "system";
150
+ const author = (value.author && value.author.roleName) || label(t, "author", type);
151
+ const message = h("article.msg.from-" + (type === "user" || type === "operator" ? "user" : type === "role" ? "role" : "system"));
152
+ message.append(h("span.msg-avatar", { "aria-hidden": "true" }, String(author).slice(0, 1).toUpperCase()));
153
+ message.append(h("div.msg-main", null,
154
+ h("header.msg-head", null, h("strong", null, author),
155
+ value.intent ? chip(label(t, "intent", value.intent)) : null,
156
+ value.kind === "role-result" ? chip(label(t, "messageKind", value.kind)) : null,
157
+ h("time", { dateTime: value.createdAt || "", title: row.entry.ref.refId }, formatShort(value.createdAt, locale))),
158
+ h("div.msg-bubble", null, richText(null, value.body || "", t, { threshold: 900 }))));
159
+ return message;
160
+ }
161
+
162
+ // Redraw after a read without losing the reader's place: the bottom stays
163
+ // pinned, and prepended older messages keep the current message in view.
164
+ function redraw(feed, taskId, prepend) {
165
+ const host = feed.closest("[data-task-id]");
166
+ const current = latest.get(taskId);
167
+ if (!feed.isConnected || !host || host.dataset.taskId !== taskId || !current) return;
168
+ const atEnd = feed.scrollHeight - feed.clientHeight - feed.scrollTop < 40;
169
+ const fromBottom = feed.scrollHeight - feed.scrollTop;
170
+ drawFeed(feed, current.data, current.t, current.locale, current.actions);
171
+ feed.scrollTop = atEnd && !prepend ? feed.scrollHeight : feed.scrollHeight - fromBottom;
172
+ }
173
+ `;
@@ -0,0 +1,129 @@
1
+ export const PREVIEW_SCRIPT = String.raw `
2
+ import { requestJson, submitMutation } from "/assets/js/lib/api.js";
3
+
4
+ export function createPreviewController(root, t) {
5
+ let taskId = null, generation = 0, revision = 0, timer = null, selected = null, frame = null, frameUrl = null;
6
+ let list, status;
7
+ function close() {
8
+ generation++;
9
+ clearTimeout(timer);
10
+ taskId = null;
11
+ selected = null;
12
+ frame = null;
13
+ frameUrl = null;
14
+ root.replaceChildren();
15
+ }
16
+ function text(tag, value, parent) {
17
+ const node = document.createElement(tag);
18
+ node.textContent = value;
19
+ if (parent) parent.append(node);
20
+ return node;
21
+ }
22
+ function button(label, action, parent) {
23
+ const node = text("button", label, parent);
24
+ node.type = "button";
25
+ node.className = "btn btn-sm";
26
+ node.addEventListener("click", action);
27
+ return node;
28
+ }
29
+ function clearFrame() {
30
+ if (frame) frame.remove();
31
+ frame = null; frameUrl = null;
32
+ }
33
+ function open(id) {
34
+ if (taskId === id) return;
35
+ close();
36
+ if (!id) return;
37
+ taskId = id;
38
+ const actions = document.createElement("div");
39
+ actions.className = "dock-sub";
40
+ root.append(actions);
41
+ text("strong", t("preview.title"), actions);
42
+ button(t("preview.refresh"), function () { clearFrame(); refresh(generation); }, actions);
43
+ text("p", t("preview.limits"), root).className = "feed-note";
44
+ text("p", t("preview.lifecycle"), root).className = "feed-note";
45
+ list = document.createElement("div");
46
+ list.className = "preview-services";
47
+ root.append(list);
48
+ status = text("p", t("preview.loading"), root);
49
+ status.className = "feed-note";
50
+ refresh(generation);
51
+ }
52
+ async function refresh(expected) {
53
+ clearTimeout(timer);
54
+ const read = ++revision;
55
+ const id = taskId;
56
+ if (!id || expected !== generation) return;
57
+ try {
58
+ const data = await requestJson("/api/tasks/" + encodeURIComponent(id) + "/previews", { signal: AbortSignal.timeout(5000) });
59
+ if (expected !== generation || read !== revision) return;
60
+ const focusKey = document.activeElement?.getAttribute("data-preview-focus");
61
+ list.replaceChildren();
62
+ status.textContent = data.services.length ? (data.complete ? "" : t("preview.more")) : t("preview.empty");
63
+ if (!data.services.some(function (service) { return service.id === selected; })) {
64
+ selected = data.services.find(function (service) { return service.state === "ready"; })?.id || null;
65
+ }
66
+ let activeUrl = null;
67
+ data.services.forEach(function (service) {
68
+ const row = document.createElement("section");
69
+ row.className = "preview-service";
70
+ list.append(row);
71
+ text("strong", service.name + " · " + service.id, row);
72
+ text("span", t("preview.state." + service.state), row);
73
+ text("small", service.detail, row).title = service.readAt;
74
+ if (service.url) {
75
+ button(t("preview.view"), function () { selected = service.id; clearFrame(); refresh(generation); }, row)
76
+ .setAttribute("data-preview-focus", service.id + "-view");
77
+ const link = text("a", t("preview.window"), row);
78
+ link.href = service.url;
79
+ link.target = "_blank";
80
+ link.rel = "noopener noreferrer";
81
+ link.className = "btn btn-sm";
82
+ link.setAttribute("data-preview-focus", service.id + "-window");
83
+ if (service.id === selected) activeUrl = service.url;
84
+ }
85
+ if (service.canStop) {
86
+ const stop = button(t("preview.stop"), async function () {
87
+ stop.disabled = true;
88
+ try {
89
+ const result = await submitMutation(id + "/preview/" + service.id,
90
+ "/api/tasks/" + encodeURIComponent(id) + "/previews/" + encodeURIComponent(service.id) + "/stop", {});
91
+ if (expected !== generation) return;
92
+ status.textContent = result.stopped ? t("preview.state.stopped") : t("preview.stopRequested");
93
+ if (selected === service.id) clearFrame();
94
+ refresh(expected);
95
+ } catch (error) {
96
+ if (expected === generation) status.textContent = error.message;
97
+ }
98
+ }, row);
99
+ stop.disabled = service.state === "stopping";
100
+ stop.setAttribute("data-preview-focus", service.id + "-stop");
101
+ }
102
+ if (service.receipt) text("small", t("preview.receipt") + ": " + service.receipt.outcome
103
+ + " · " + service.receipt.artifactsLocator, row);
104
+ });
105
+ if (activeUrl !== frameUrl) {
106
+ clearFrame();
107
+ if (activeUrl) {
108
+ frame = document.createElement("iframe");
109
+ frame.title = t("preview.title");
110
+ frame.setAttribute("sandbox", "allow-scripts");
111
+ frame.referrerPolicy = "no-referrer";
112
+ frame.className = "preview-frame";
113
+ frame.src = activeUrl;
114
+ frameUrl = activeUrl;
115
+ root.append(frame);
116
+ }
117
+ }
118
+ if (focusKey) Array.from(root.querySelectorAll("[data-preview-focus]"))
119
+ .find(function (node) { return node.getAttribute("data-preview-focus") === focusKey; })?.focus();
120
+ } catch (error) {
121
+ if (expected !== generation || read !== revision) return;
122
+ clearFrame();
123
+ status.textContent = error.message;
124
+ }
125
+ if (expected === generation && read === revision && taskId) timer = setTimeout(function () { refresh(expected); }, 5000);
126
+ }
127
+ return { open, close };
128
+ }
129
+ `;
@@ -0,0 +1,199 @@
1
+ export const TERMINAL_SCRIPT = String.raw `
2
+ // The live native Session terminal in the dock: target chips, the xterm view
3
+ // and its WebSocket. One session is open at a time; opening another target
4
+ // disposes the previous one first.
5
+ import { Terminal } from "/assets/vendor/xterm.mjs";
6
+ import { FitAddon } from "/assets/vendor/addon-fit.mjs";
7
+ import { h, icon, clear } from "/assets/js/lib/dom.js";
8
+ import { fill } from "/assets/js/lib/format.js";
9
+ import { pageToken, requestJson } from "/assets/js/lib/api.js";
10
+ import { dot } from "/assets/js/ui/primitives.js";
11
+
12
+ function cssVar(name, fallback) {
13
+ const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
14
+ return value || fallback;
15
+ }
16
+
17
+ function terminalTheme() {
18
+ return {
19
+ background: cssVar("--term-bg", "#0b0c0d"),
20
+ foreground: cssVar("--term-fg", "#e6e6e8"),
21
+ cursor: cssVar("--term-cursor", "#ff7a52"),
22
+ selectionBackground: cssVar("--term-sel", "#3a2a24")
23
+ };
24
+ }
25
+
26
+ function cliFor(target) {
27
+ if (target.scope === "task") return "yui task role session inspect " + target.taskId + " " + target.roleName;
28
+ return target.roleName === "operator" ? "yui operator enter" : "yui session enter " + target.roleName;
29
+ }
30
+
31
+ export function sameTarget(left, right) {
32
+ return !!left && !!right && left.scope === right.scope && left.roleName === right.roleName
33
+ && (left.scope !== "task" || left.taskId === right.taskId);
34
+ }
35
+
36
+ // elements: { host, empty, state, cli, targets }; locale() reads the current
37
+ // locale; notify(message) shows a toast.
38
+ export function createTerminalController(elements, t, locale, notify) {
39
+ const ctl = { elements: elements, t: t, locale: locale, notify: notify, session: null, current: null, generation: 0, stateKey: "terminal.idle", targets: [] };
40
+ drawTargets(ctl);
41
+ return {
42
+ open: function (target) { open(ctl, target); },
43
+ close: function () { close(ctl); },
44
+ current: function () { return ctl.current; },
45
+ connected: function () { return !!ctl.session; },
46
+ setTargets: function (next) { ctl.targets = next; drawTargets(ctl); },
47
+ relabel: function () { setState(ctl, ctl.stateKey, elements.state.dataset.state); drawTargets(ctl); },
48
+ retheme: function () { if (ctl.session) ctl.session.terminal.options.theme = terminalTheme(); }
49
+ };
50
+ }
51
+
52
+ function setState(ctl, key, state) {
53
+ ctl.stateKey = key;
54
+ ctl.elements.state.dataset.state = state;
55
+ ctl.elements.state.lastChild.textContent = ctl.t(key);
56
+ }
57
+
58
+ function drawTargets(ctl) {
59
+ const t = ctl.t;
60
+ const list = ctl.elements.targets;
61
+ clear(list);
62
+ ctl.targets.forEach(function (target) {
63
+ list.append(h("button.target-chip", {
64
+ type: "button",
65
+ "aria-pressed": String(sameTarget(target, ctl.current)),
66
+ title: target.scope === "task" ? target.taskId + " / " + target.roleName : t("terminal.global") + " / " + target.roleName,
67
+ onclick: function () { open(ctl, target); }
68
+ }, target.scope === "task" ? dot(target.live ? "ok" : "idle") : icon("broadcast", "icon-sm"), h("span", null, target.roleName)));
69
+ });
70
+ if (!ctl.targets.length) list.append(h("span.faint.small", null, t("terminal.noTargets")));
71
+ }
72
+
73
+ function dispose(ctl) {
74
+ ctl.generation++;
75
+ if (!ctl.session) return;
76
+ const owned = ctl.session;
77
+ ctl.session = null;
78
+ owned.resizeObserver.disconnect();
79
+ owned.input.dispose();
80
+ owned.socket.close();
81
+ owned.terminal.dispose();
82
+ clear(ctl.elements.host);
83
+ }
84
+
85
+ function close(ctl) {
86
+ dispose(ctl);
87
+ ctl.current = null;
88
+ ctl.elements.empty.hidden = false;
89
+ ctl.elements.cli.textContent = "yui operator enter";
90
+ setState(ctl, "terminal.idle", "idle");
91
+ drawTargets(ctl);
92
+ }
93
+
94
+ async function open(ctl, target) {
95
+ dispose(ctl);
96
+ const generation = ctl.generation;
97
+ ctl.current = target;
98
+ ctl.elements.empty.hidden = true;
99
+ ctl.elements.cli.textContent = cliFor(target);
100
+ setState(ctl, "terminal.connecting", "connecting");
101
+ drawTargets(ctl);
102
+ try {
103
+ if (!target.nativeSessionId) {
104
+ const query = new URLSearchParams({ scope: target.scope, role: target.roleName });
105
+ if (target.scope === "task") query.set("task", target.taskId);
106
+ const facts = await requestJson("/api/conversation?" + query);
107
+ if (generation !== ctl.generation) return;
108
+ if (!facts.currentSessionId) throw new Error(ctl.t("conversation.noSession"));
109
+ target = { ...target, nativeSessionId: facts.currentSessionId };
110
+ ctl.current = target;
111
+ }
112
+ } catch (error) {
113
+ if (generation === ctl.generation) { setState(ctl, "terminal.error", "error"); ctl.notify(error.message); }
114
+ return;
115
+ }
116
+ const view = mountTerminal(ctl.elements.host);
117
+ const terminal = view.terminal;
118
+ const socket = connect(target, terminal);
119
+ const link = { writable: false };
120
+ const input = terminal.onData(function (data) {
121
+ if (link.writable && socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "input", data: data }));
122
+ });
123
+ const resizeObserver = new ResizeObserver(function () {
124
+ if (!ctl.session || ctl.session.terminal !== terminal || !ctl.elements.host.offsetWidth) return;
125
+ try { view.fit.fit(); } catch { return; }
126
+ if (socket.readyState === WebSocket.OPEN) {
127
+ socket.send(JSON.stringify({ type: "resize", columns: terminal.cols, rows: terminal.rows }));
128
+ }
129
+ });
130
+ resizeObserver.observe(ctl.elements.host);
131
+ bindSocket(ctl, socket, terminal, link, generation);
132
+ ctl.session = { terminal, socket, input, resizeObserver };
133
+ }
134
+
135
+ function mountTerminal(host) {
136
+ const terminal = new Terminal({
137
+ cursorBlink: true,
138
+ scrollback: 5000,
139
+ convertEol: false,
140
+ fontFamily: '"JetBrains Mono","SFMono-Regular",Menlo,Consolas,monospace',
141
+ fontSize: 12.5,
142
+ lineHeight: 1.15,
143
+ theme: terminalTheme()
144
+ });
145
+ const fit = new FitAddon();
146
+ terminal.loadAddon(fit);
147
+ terminal.open(host);
148
+ try { fit.fit(); } catch {}
149
+ return { terminal: terminal, fit: fit };
150
+ }
151
+
152
+ function connect(target, terminal) {
153
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
154
+ const parameters = new URLSearchParams({
155
+ scope: target.scope, role: target.roleName, cols: String(terminal.cols), rows: String(terminal.rows), token: pageToken()
156
+ });
157
+ if (target.scope === "task") parameters.set("task", target.taskId);
158
+ if (target.nativeSessionId) parameters.set("session", target.nativeSessionId);
159
+ return new WebSocket(protocol + "//" + window.location.host + "/api/terminal?" + parameters.toString());
160
+ }
161
+
162
+ function bindSocket(ctl, socket, terminal, link, generation) {
163
+ socket.addEventListener("message", function (event) {
164
+ if (generation !== ctl.generation) return;
165
+ let message;
166
+ try { message = JSON.parse(event.data); } catch { return; }
167
+ if (message.type === "ready") ready(ctl, message, terminal, link);
168
+ else if (message.type === "data") terminal.write(message.data);
169
+ else if (message.type === "exit") setState(ctl, "terminal.closed", "closed");
170
+ else if (message.type === "error") {
171
+ setState(ctl, "terminal.error", "error");
172
+ terminal.writeln("\r\n" + message.message);
173
+ }
174
+ });
175
+ socket.addEventListener("close", function () {
176
+ if (generation !== ctl.generation) return;
177
+ link.writable = false;
178
+ if (ctl.stateKey !== "terminal.error") setState(ctl, "terminal.closed", "closed");
179
+ });
180
+ socket.addEventListener("error", function () {
181
+ if (generation !== ctl.generation) return;
182
+ link.writable = false;
183
+ setState(ctl, "terminal.error", "error");
184
+ terminal.writeln(ctl.t("terminal.errorDetail"));
185
+ });
186
+ }
187
+
188
+ function ready(ctl, message, terminal, link) {
189
+ link.writable = !message.readOnly;
190
+ setState(ctl, link.writable ? "terminal.writable" : "terminal.readOnly", link.writable ? "writable" : "readonly");
191
+ if (message.history && message.history.limit < message.history.target) {
192
+ const number = new Intl.NumberFormat(ctl.locale());
193
+ ctl.notify(fill(ctl.t("terminal.historyLimited"), {
194
+ current: number.format(message.history.limit), target: number.format(message.history.target)
195
+ }));
196
+ }
197
+ terminal.focus();
198
+ }
199
+ `;
@@ -0,0 +1,67 @@
1
+ export const GLOBAL_INPUT_SCRIPT = String.raw `
2
+ // The global Role input dialog: queue / steer / interrupt for a Role outside
3
+ // any Task, with an explicit read of its current Turn. The receipt is shown
4
+ // verbatim; an unknown delivery keeps the submit button disabled.
5
+ import { setReceipt, submitWrite } from "/assets/js/ui/forms.js";
6
+
7
+ const UNKNOWN_STATES = ["pending", "delivery-unknown", "steer-unknown", "interrupt-unknown"];
8
+
9
+ export function bindGlobalInput(options) {
10
+ const t = options.t;
11
+ const api = options.api;
12
+ const $ = function (selector) { return document.querySelector(selector); };
13
+ const el = {
14
+ dialog: $("#global-input-dialog"), form: $("#global-input-form"), role: $("#global-input-role"),
15
+ action: $("#global-input-action"), body: $("#global-input-body"), target: $("#global-input-target"),
16
+ then: $("#global-input-then"), submit: $("#global-input-submit"), receipt: $("#global-input-receipt"),
17
+ state: $("#global-input-state")
18
+ };
19
+ $("#global-input-open").addEventListener("click", function () { el.dialog.showModal(); });
20
+ $("#global-input-close").addEventListener("click", function () { el.dialog.close(); });
21
+ el.action.addEventListener("change", function () {
22
+ $("#global-input-body-label").hidden = el.action.value === "interrupt";
23
+ $("#global-input-target-label").hidden = el.action.value === "queue";
24
+ $("#global-input-then-label").hidden = el.action.value !== "interrupt";
25
+ el.body.required = el.action.value !== "interrupt";
26
+ el.target.required = el.action.value !== "queue";
27
+ });
28
+ $("#global-input-inspect").addEventListener("click", function () { inspectRole(el, api); });
29
+ el.form.addEventListener("submit", function (event) {
30
+ event.preventDefault();
31
+ if (el.submit.disabled) return;
32
+ submitWrite({
33
+ control: el.submit, receipt: el.receipt, t: t, unknownKey: "receipt.unknownControl",
34
+ send: function (requestId) { return api.globalControl(el.role.value.trim(), globalPayload(el, requestId)); },
35
+ saved: function (receipt) { return showReceipt(el, receipt); }
36
+ });
37
+ });
38
+ return { dialog: el.dialog };
39
+ }
40
+
41
+ async function inspectRole(el, api) {
42
+ el.state.hidden = false;
43
+ try {
44
+ const facts = await api.globalState(el.role.value.trim());
45
+ el.state.textContent = JSON.stringify(facts, null, 2);
46
+ el.target.value = (facts.turn && (facts.turn.nativeTurnId || facts.turn.attemptId)) || "";
47
+ } catch (error) { el.state.textContent = error.message; }
48
+ }
49
+
50
+ function globalPayload(el, requestId) {
51
+ const action = el.action.value;
52
+ const input = { action: action, requestId: requestId };
53
+ if (action !== "interrupt") input.body = el.body.value;
54
+ if (action !== "queue") input.expectedTarget = el.target.value.trim();
55
+ if (action === "interrupt" && el.then.value.trim()) input.thenMessage = el.then.value.trim();
56
+ return input;
57
+ }
58
+
59
+ // Returns true while the outcome is unknown, which keeps the submit disabled.
60
+ function showReceipt(el, receipt) {
61
+ const status = receipt.steer || receipt.interrupt || receipt.delivery || {};
62
+ setReceipt(el.receipt, JSON.stringify(receipt), "ok");
63
+ const unknown = UNKNOWN_STATES.includes(status.state) || status.code === "DELIVERY_UNKNOWN";
64
+ if (!unknown && !status.code) el.body.value = "";
65
+ return unknown;
66
+ }
67
+ `;