@zq-silk/yui 2.15.0 → 2.17.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.
- package/dist/cli/commandCatalog.js +17 -11
- package/dist/cli/interactionCandidates.js +20 -5
- package/dist/cli/interactionPolicy.js +5 -4
- package/dist/cli/roleWizard.js +7 -0
- package/dist/commands/globalRoleCommands.js +13 -2
- package/dist/commands/operatorCommands.js +2 -0
- package/dist/commands/profileCommands.js +19 -2
- package/dist/commands/roleConfiguration.js +3 -0
- package/dist/commands/taskCommands.js +16 -0
- package/dist/commands/taskRoleRuntimeStatus.js +3 -0
- package/dist/controller/agentJobControl.js +9 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +35 -4
- package/dist/controller/globalInputDelivery.js +14 -8
- package/dist/controller/providerRetryAdmission.js +9 -0
- package/dist/controller/providerRetryDelivery.js +16 -0
- package/dist/controller/runtime.js +14 -0
- package/dist/controller/runtimeLaunchCoordinator.js +3 -1
- package/dist/executor/fileRoleLaunchPlanner.js +3 -0
- package/dist/message/messageContinuation.js +4 -0
- package/dist/output/rolePresentation.js +3 -0
- package/dist/profile/agentProfile.js +8 -0
- package/dist/profile/agentProfileRuntime.js +16 -1
- package/dist/review/reviewDecision.js +6 -0
- package/dist/role/availability.js +28 -0
- package/dist/role/role.js +6 -0
- package/dist/runtime/codexNativeAccess.js +21 -4
- package/dist/runtime/tmuxAdapters.js +5 -0
- package/dist/scheduler/activeRoleRunDelivery.js +9 -0
- package/dist/scheduler/leaderWakeupProcessor.js +8 -1
- package/dist/scheduler/roleRunLiveness.js +3 -0
- package/dist/storage/sqliteSchema.js +10 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/web/assets/client/app/catalog.js +40 -14
- package/dist/web/assets/client/app/main.js +43 -26
- package/dist/web/assets/client/app/manage.js +18 -0
- package/dist/web/assets/client/app/selection.js +4 -1
- package/dist/web/assets/client/app/taskView.js +29 -16
- package/dist/web/assets/client/i18n/en.js +72 -21
- package/dist/web/assets/client/i18n/zh-CN.js +72 -21
- package/dist/web/assets/client/index.js +6 -0
- package/dist/web/assets/client/layout/manageShell.js +30 -8
- package/dist/web/assets/client/layout/shortcuts.js +9 -5
- package/dist/web/assets/client/layout/sidebarNav.js +147 -0
- package/dist/web/assets/client/layout/sizing.js +5 -2
- package/dist/web/assets/client/layout/workspace.js +193 -107
- package/dist/web/assets/client/lib/motion.js +59 -0
- package/dist/web/assets/client/lib/prefs.js +29 -2
- package/dist/web/assets/client/lib/theme.js +11 -4
- package/dist/web/assets/client/ui/primitives.js +40 -0
- package/dist/web/assets/client/views/dock/conversation.js +421 -123
- package/dist/web/assets/client/views/dock/session.js +229 -0
- package/dist/web/assets/client/views/dock/terminal.js +48 -39
- package/dist/web/assets/client/views/overview.js +8 -5
- package/dist/web/assets/client/views/task/page.js +7 -17
- package/dist/web/assets/shared/brand.js +22 -0
- package/dist/web/assets/shared/geometry.js +7 -1
- package/dist/web/assets/shared/icons.js +4 -0
- package/dist/web/assets/shared/motion.js +74 -0
- package/dist/web/assets/shell/appRail.js +14 -0
- package/dist/web/assets/shell/dialogs.js +1 -0
- package/dist/web/assets/shell/dock.js +19 -24
- package/dist/web/assets/shell/index.js +6 -2
- package/dist/web/assets/shell/login.js +9 -8
- package/dist/web/assets/shell/manage.js +32 -20
- package/dist/web/assets/shell/settingsContent.js +1 -1
- package/dist/web/assets/shell/sidebar.js +16 -23
- package/dist/web/assets/styles/base.js +1 -1
- package/dist/web/assets/styles/index.js +2 -0
- package/dist/web/assets/styles/layout/responsive.js +63 -19
- package/dist/web/assets/styles/layout/workspace.js +53 -16
- package/dist/web/assets/styles/motion.js +51 -0
- package/dist/web/assets/styles/tokens.js +5 -3
- package/dist/web/assets/styles/ui/controls.js +1 -1
- package/dist/web/assets/styles/ui/forms.js +1 -1
- package/dist/web/assets/styles/ui/surfaces.js +1 -2
- package/dist/web/assets/styles/views/conversation.js +97 -18
- package/dist/web/assets/styles/views/dock.js +34 -2
- package/dist/web/assets/styles/views/login.js +16 -13
- package/dist/web/assets/styles/views/manage.js +25 -42
- package/dist/web/assets/styles/views/page.js +2 -3
- package/dist/web/assets/styles/views/records.js +1 -1
- package/dist/web/assets/styles/views/settings.js +1 -1
- package/dist/web/assets/styles/views/sidebar.js +25 -20
- package/dist/web/assets/styles/views/task.js +8 -15
- package/dist/web/assets/styles/views/terminal.js +13 -9
- package/dist/web/webConversation.js +1 -0
- package/dist/web/webSettings.js +10 -2
- package/docs/managed-turn-and-session-runtime.md +10 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +6 -0
- package/docs/release-workflow.md +46 -0
- package/docs/release-workflow.zh-CN.md +34 -0
- package/docs/roles-and-configuration.md +45 -1
- package/docs/roles-and-configuration.zh-CN.md +35 -0
- package/docs/storage-baseline.md +6 -1
- package/docs/storage-baseline.zh-CN.md +5 -1
- package/docs/testing/review-evaluation.md +35 -1
- package/docs/testing/review-evaluation.zh-CN.md +25 -1
- package/docs/testing/verification-levels.md +2 -2
- package/docs/testing/verification-levels.zh-CN.md +2 -2
- package/package.json +1 -1
- package/skills/yui-leader/VERSION +1 -1
- package/skills/yui-leader/references/execution.md +19 -8
- package/skills/yui-reviewer/SKILL.md +13 -2
- package/skills/yui-reviewer/VERSION +1 -1
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
import { BREAKPOINTS, DOCK } from "../../shared/geometry.js";
|
|
2
2
|
export const WORKSPACE_SCRIPT = String.raw `
|
|
3
|
-
// The sidebar | center | dock workspace. The dock holds the
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// The sidebar | center | dock workspace. The dock holds the session page
|
|
4
|
+
// (one Role and Session, as chat or terminal), the Task discussion or the
|
|
5
|
+
// preview; on desktop it is a resizable column (at the end or in the center),
|
|
6
|
+
// below the narrow breakpoint a full-screen sheet opened explicitly per Task.
|
|
7
|
+
// Everything here is view state and browser preference.
|
|
7
8
|
import { readPreference, writePreference, readSessionAccessMode } from "/assets/js/lib/prefs.js";
|
|
8
|
-
import { markSelected } from "/assets/js/lib/dom.js";
|
|
9
|
+
import { markSelected, h, icon, clear } from "/assets/js/lib/dom.js";
|
|
9
10
|
import { createSizing } from "/assets/js/layout/sizing.js";
|
|
11
|
+
import { layoutTransition, leave } from "/assets/js/lib/motion.js";
|
|
10
12
|
|
|
11
13
|
const NARROW_QUERY = "(max-width: ${BREAKPOINTS.narrow}px)";
|
|
12
14
|
const DOCK_INITIAL = ${DOCK.initial};
|
|
13
15
|
|
|
14
|
-
// deps: { el, state,
|
|
15
|
-
// el the shell elements (dock, dividers, sidebar,
|
|
16
|
-
// and the
|
|
16
|
+
// deps: { el, state, t, session, preview }
|
|
17
|
+
// el the shell elements (dock, dividers, sidebar, the dock's target
|
|
18
|
+
// menu, and the page's [data-panel-toggle] / legacy [data-operator-toggle]
|
|
19
|
+
// buttons inside el.detail)
|
|
17
20
|
// state read-only here: state.selected
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
21
|
+
// session the dock's session page (views/dock/session): owner, selected
|
|
22
|
+
// Session and its chat / terminal mode
|
|
23
|
+
// The dock state (open, view, side, width, sheet) and the Role list offered
|
|
24
|
+
// by the target menu are private to this module.
|
|
22
25
|
export function createWorkspace(deps) {
|
|
23
26
|
const ws = {
|
|
24
27
|
deps: deps,
|
|
25
28
|
dock: readDockState(),
|
|
29
|
+
// Roles the target menu offers besides Operator (setTargets, app/taskView).
|
|
30
|
+
targets: [],
|
|
26
31
|
// Task list width: null keeps the CSS default (--sidebar-w); otherwise
|
|
27
32
|
// the width the user dragged to.
|
|
28
33
|
sidebar: { width: Number(readPreference("yui.sidebar.width", "")) || null },
|
|
@@ -36,41 +41,43 @@ export function createWorkspace(deps) {
|
|
|
36
41
|
dockVisible: function () { return dockVisible(ws); },
|
|
37
42
|
updateLayout: function () { updateLayout(ws); },
|
|
38
43
|
syncDockButtons: function () { syncDockButtons(ws); },
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
setTargets: function (targets) { ws.targets = targets; drawTarget(ws); },
|
|
45
|
+
setDockOpen: function (open) { glide(function () { setDockOpen(ws, open); }); },
|
|
46
|
+
toggleDock: function (mode) { glide(function () { toggleDock(ws, mode); }); },
|
|
47
|
+
// Shortcuts into the session page: a Role's live Session opens in the
|
|
48
|
+
// terminal mode, a continuation of the Leader in the chat mode.
|
|
49
|
+
openSession: function (target) { openSessionPage(ws, target, { mode: "terminal", current: true }); },
|
|
42
50
|
openConversation: function (target, materials) {
|
|
43
|
-
ws
|
|
44
|
-
showDock(ws, true);
|
|
45
|
-
ws.dock.mode = "conversation";
|
|
46
|
-
updateLayout(ws);
|
|
47
|
-
ws.deps.conversation.open(target, { current: true, materials: materials });
|
|
48
|
-
syncDockButtons(ws);
|
|
49
|
-
},
|
|
50
|
-
openOperator: function () { openOperator(ws); },
|
|
51
|
-
restoreDefaultMode: function () {
|
|
52
|
-
if (!ws.deps.state.selected && ws.dock.open && !ws.narrow.matches && preferredMode() === "session") {
|
|
53
|
-
openSession(ws, { scope: "global", roleName: "operator" });
|
|
54
|
-
}
|
|
51
|
+
openSessionPage(ws, target, { mode: "chat", current: true, ...(materials ? { materials: materials } : {}) });
|
|
55
52
|
},
|
|
53
|
+
openOperator: function () { glide(function () { openOperator(ws); }); },
|
|
54
|
+
toggleOperator: function () { glide(function () { toggleOperator(ws); }); },
|
|
56
55
|
// A newly selected Task starts with the narrow-screen sheet closed.
|
|
57
56
|
enterTask: function () {
|
|
58
|
-
ws.deps.terminal.close();
|
|
59
|
-
ws.terminalTarget = { scope: "task", taskId: ws.deps.state.selected, roleName: "leader" };
|
|
60
57
|
ws.dock.sheet = false;
|
|
61
|
-
ws.dock.mode =
|
|
62
|
-
ws.deps.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
ws.dock.mode = "conversation";
|
|
59
|
+
ws.deps.session.open({ scope: "task", taskId: ws.deps.state.selected, roleName: "leader" },
|
|
60
|
+
{ current: true, mode: preferredMode() });
|
|
61
|
+
pauseHiddenSession(ws);
|
|
62
|
+
syncDockButtons(ws);
|
|
66
63
|
},
|
|
67
64
|
leaveTask: function () { leaveTask(ws); }
|
|
68
65
|
};
|
|
69
66
|
}
|
|
70
67
|
|
|
68
|
+
// User-initiated dock changes (open, close, swap sides) glide the columns to
|
|
69
|
+
// their new widths; internal re-layouts stay immediate.
|
|
70
|
+
function glide(update) {
|
|
71
|
+
if (typeof layoutTransition === "function") layoutTransition(update);
|
|
72
|
+
else update();
|
|
73
|
+
}
|
|
74
|
+
|
|
71
75
|
function readDockState() {
|
|
72
76
|
return {
|
|
73
|
-
|
|
77
|
+
// The panel starts closed on every load and opens only on request.
|
|
78
|
+
open: false,
|
|
79
|
+
// Which dock view: the session page ("conversation"), the Task
|
|
80
|
+
// discussion or the preview.
|
|
74
81
|
mode: "conversation",
|
|
75
82
|
center: readPreference("yui.dock.side", "end") === "center",
|
|
76
83
|
width: Number(readPreference("yui.dock.width", String(DOCK_INITIAL))) || DOCK_INITIAL,
|
|
@@ -80,30 +87,29 @@ function readDockState() {
|
|
|
80
87
|
};
|
|
81
88
|
}
|
|
82
89
|
|
|
90
|
+
// The Settings preference picks the session page's starting mode.
|
|
83
91
|
function preferredMode() {
|
|
84
|
-
return readSessionAccessMode() === "native" ? "
|
|
92
|
+
return readSessionAccessMode() === "native" ? "terminal" : "chat";
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
function dockVisible(ws) {
|
|
88
96
|
const dock = ws.dock;
|
|
89
97
|
if (ws.narrow.matches ? !dock.sheet : !dock.open) return false;
|
|
90
|
-
|
|
91
|
-
if (dock.mode === "conversation") return true;
|
|
92
|
-
return dock.mode === "session" && !!ws.deps.terminal.current();
|
|
98
|
+
return !!ws.deps.state.selected || dock.mode === "conversation";
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
function updateLayout(ws) {
|
|
96
102
|
const el = ws.deps.el;
|
|
97
103
|
const dock = ws.dock;
|
|
98
104
|
const selected = !!ws.deps.state.selected;
|
|
105
|
+
if (!selected && dock.mode !== "conversation") dock.mode = "conversation";
|
|
99
106
|
const visible = dockVisible(ws);
|
|
100
107
|
document.body.classList.toggle("task-open", selected);
|
|
101
108
|
document.body.classList.toggle("dock-open", visible);
|
|
102
109
|
document.body.classList.toggle("dock-center", dock.center);
|
|
103
|
-
|
|
110
|
+
revealDock(ws, visible);
|
|
104
111
|
el.divider.hidden = !visible;
|
|
105
112
|
el.discussion.hidden = dock.mode !== "discussion";
|
|
106
|
-
el.session.hidden = dock.mode !== "session";
|
|
107
113
|
el.conversation.hidden = dock.mode !== "conversation";
|
|
108
114
|
el.preview.hidden = dock.mode !== "preview";
|
|
109
115
|
el.dockTabPreview.setAttribute("aria-selected", String(dock.mode === "preview"));
|
|
@@ -112,118 +118,196 @@ function updateLayout(ws) {
|
|
|
112
118
|
else ws.deps.preview.close();
|
|
113
119
|
el.dockTabConversation.setAttribute("aria-selected", String(dock.mode === "conversation"));
|
|
114
120
|
el.dockTabDiscussion.setAttribute("aria-selected", String(dock.mode === "discussion"));
|
|
115
|
-
el.dockTabSession.setAttribute("aria-selected", String(dock.mode === "session"));
|
|
116
121
|
el.dockTabDiscussion.disabled = !selected;
|
|
117
122
|
el.dockSwap.setAttribute("aria-pressed", String(dock.center));
|
|
118
123
|
ws.sizing.applySidebarWidth();
|
|
119
124
|
syncDockButtons(ws);
|
|
120
125
|
}
|
|
121
126
|
|
|
127
|
+
// On narrow screens the dock is a full-screen sheet: CSS slides it up on
|
|
128
|
+
// open, and closing slides it back down before it hides (an immediate close
|
|
129
|
+
// would cut the motion off). Wider screens change the grid; user-initiated
|
|
130
|
+
// changes there glide through glide().
|
|
131
|
+
function revealDock(ws, visible) {
|
|
132
|
+
const dockEl = ws.deps.el.dock;
|
|
133
|
+
if (ws.closingDock) { ws.closingDock.cancel(); ws.closingDock = null; }
|
|
134
|
+
if (visible || dockEl.hidden || !ws.narrow.matches || typeof leave !== "function") { dockEl.hidden = !visible; return; }
|
|
135
|
+
const closing = leave(dockEl, "sheetDown");
|
|
136
|
+
if (!closing.animation) { dockEl.hidden = true; return; }
|
|
137
|
+
ws.closingDock = closing;
|
|
138
|
+
closing.finished.then(function () {
|
|
139
|
+
if (ws.closingDock !== closing) return;
|
|
140
|
+
ws.closingDock = null;
|
|
141
|
+
dockEl.hidden = true;
|
|
142
|
+
closing.cancel();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
122
146
|
function syncDockButtons(ws) {
|
|
123
147
|
const el = ws.deps.el;
|
|
124
148
|
const visible = dockVisible(ws);
|
|
125
|
-
|
|
126
|
-
|
|
149
|
+
el.detail.querySelectorAll("[data-panel-toggle]").forEach(function (button) {
|
|
150
|
+
button.setAttribute("aria-expanded", String(visible));
|
|
151
|
+
button.setAttribute("aria-label", ws.deps.t(visible ? "dock.hide" : "dock.show"));
|
|
152
|
+
});
|
|
153
|
+
markSelected(el.detail.querySelectorAll("[data-operator-toggle]"), "aria-pressed", function () { return operatorShown(ws); });
|
|
154
|
+
drawTarget(ws);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// --- Conversation target -----------------------------------------------------
|
|
158
|
+
// The dock header names who the session page talks to; it is the only Role
|
|
159
|
+
// selector for both chat and terminal. Operator is always offered; a selected
|
|
160
|
+
// Task adds its Roles. Other global Roles use the global message dialog.
|
|
161
|
+
function currentTarget(ws) {
|
|
162
|
+
const selected = ws.deps.state.selected;
|
|
163
|
+
if (ws.dock.mode === "conversation") return ws.deps.session.current();
|
|
164
|
+
return selected ? { scope: "task", taskId: selected, roleName: "leader" } : null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function targetLabel(ws, target) {
|
|
168
|
+
if (!target) return ws.deps.t("actions.operator");
|
|
169
|
+
if (target.scope === "global") return target.roleName === "operator" ? ws.deps.t("actions.operator") : target.roleName;
|
|
170
|
+
return target.roleName + " · " + target.taskId;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function sameTarget(a, b) {
|
|
174
|
+
return !!a && !!b && a.scope === b.scope && a.roleName === b.roleName && (a.scope !== "task" || a.taskId === b.taskId);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function targetOptions(ws, current) {
|
|
178
|
+
const selected = ws.deps.state.selected;
|
|
179
|
+
const options = [{ scope: "global", roleName: "operator" }];
|
|
180
|
+
if (selected) {
|
|
181
|
+
const roles = ws.targets.filter(function (target) { return target.scope === "task" && target.taskId === selected; });
|
|
182
|
+
if (!roles.some(function (target) { return target.roleName === "leader"; })) options.push({ scope: "task", taskId: selected, roleName: "leader" });
|
|
183
|
+
roles.forEach(function (target) { options.push(target); });
|
|
184
|
+
}
|
|
185
|
+
if (current && !options.some(function (option) { return sameTarget(option, current); })) options.push(current);
|
|
186
|
+
return options;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function drawTarget(ws) {
|
|
190
|
+
const el = ws.deps.el;
|
|
191
|
+
const current = currentTarget(ws);
|
|
192
|
+
el.dockTargetLabel.textContent = targetLabel(ws, current);
|
|
193
|
+
el.dockTargetIcon.replaceChildren(icon(current && current.scope === "task" ? "target" : "broadcast"));
|
|
194
|
+
const options = targetOptions(ws, current);
|
|
195
|
+
const signature = JSON.stringify([options, current, ws.deps.t("actions.operator")]);
|
|
196
|
+
if (el.dockTargetOptions.dataset.signature === signature) return;
|
|
197
|
+
el.dockTargetOptions.dataset.signature = signature;
|
|
198
|
+
clear(el.dockTargetOptions);
|
|
199
|
+
options.forEach(function (target) {
|
|
200
|
+
const chosen = sameTarget(target, current);
|
|
201
|
+
el.dockTargetOptions.append(h("button.dock-target-item", {
|
|
202
|
+
type: "button", "aria-pressed": String(chosen),
|
|
203
|
+
onclick: function () { chooseTarget(ws, target); }
|
|
204
|
+
}, target.scope === "task" ? h("i.dock-target-dot" + (target.live ? ".is-live" : ""), { "aria-hidden": "true" }) : icon("broadcast"),
|
|
205
|
+
h("span", null, targetLabel(ws, target)),
|
|
206
|
+
chosen ? icon("check", "icon-sm dock-target-check") : null));
|
|
127
207
|
});
|
|
128
|
-
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Keep the session page's mode (chat or terminal) and switch only its owner;
|
|
211
|
+
// from the discussion or preview, the session page opens again.
|
|
212
|
+
function chooseTarget(ws, target) {
|
|
213
|
+
ws.deps.el.dockTarget.open = false;
|
|
214
|
+
openSessionPage(ws, { scope: target.scope, roleName: target.roleName, ...(target.scope === "task" ? { taskId: target.taskId } : {}) }, {});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function openSessionPage(ws, target, options) {
|
|
218
|
+
showDock(ws, true);
|
|
219
|
+
ws.dock.mode = "conversation";
|
|
220
|
+
updateLayout(ws);
|
|
221
|
+
ws.deps.session.open(target, options);
|
|
222
|
+
syncDockButtons(ws);
|
|
129
223
|
}
|
|
130
224
|
|
|
131
225
|
// The dock currently shows the global operator Session.
|
|
132
226
|
function operatorShown(ws) {
|
|
133
|
-
|
|
134
|
-
const current = ws.deps.terminal.current();
|
|
135
|
-
return dockVisible(ws) && ws.dock.mode === "session" && !!current && current.scope === "global";
|
|
227
|
+
return dockVisible(ws) && ws.dock.mode === "conversation" && ws.deps.session.current()?.scope === "global";
|
|
136
228
|
}
|
|
137
229
|
|
|
138
230
|
function showDock(ws, open) {
|
|
139
231
|
if (ws.narrow.matches) ws.dock.sheet = open;
|
|
140
232
|
else {
|
|
141
233
|
ws.dock.open = open;
|
|
142
|
-
writePreference("yui.dock.open", String(open));
|
|
143
234
|
}
|
|
144
235
|
}
|
|
145
236
|
|
|
146
237
|
function setDockOpen(ws, open) {
|
|
238
|
+
// Closing with focus inside the panel returns it to the page's panel switch
|
|
239
|
+
// rather than leaving it on a hidden element.
|
|
240
|
+
const active = document.activeElement;
|
|
241
|
+
const focusInside = !open && !!active && ws.deps.el.dock.contains(active);
|
|
147
242
|
showDock(ws, open);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (open && ws.dock.mode === "session") {
|
|
155
|
-
// Keep the selected owner, not a Session identity cached before hiding.
|
|
156
|
-
const target = ws.terminalTarget || { scope: "global", roleName: "operator" };
|
|
157
|
-
ws.deps.terminal.open({ scope: target.scope, roleName: target.roleName,
|
|
158
|
-
...(target.scope === "task" ? { taskId: target.taskId } : {}) });
|
|
243
|
+
// Hiding stops polling and detaches the terminal; showing resumes the same
|
|
244
|
+
// owner and selected Session in the same mode.
|
|
245
|
+
if (!open) ws.deps.session.close();
|
|
246
|
+
else if (ws.dock.mode === "conversation") {
|
|
247
|
+
if (ws.deps.session.current()) ws.deps.session.reconnect();
|
|
248
|
+
else ws.deps.session.open({ scope: "global", roleName: "operator" }, { mode: preferredMode() });
|
|
159
249
|
}
|
|
160
250
|
updateLayout(ws);
|
|
251
|
+
if (focusInside) {
|
|
252
|
+
const toggle = ws.deps.el.detail.querySelector("[data-panel-toggle]");
|
|
253
|
+
if (toggle) toggle.focus();
|
|
254
|
+
}
|
|
161
255
|
}
|
|
162
256
|
|
|
163
257
|
function setDockMode(ws, mode) {
|
|
164
|
-
if (mode ===
|
|
165
|
-
ws.deps.terminal.close();
|
|
166
|
-
ws.deps.conversation.close();
|
|
167
|
-
ws.dock.mode = "preview";
|
|
168
|
-
updateLayout(ws);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
258
|
+
if (mode === ws.dock.mode) return;
|
|
171
259
|
if (mode === "conversation") {
|
|
172
|
-
const target = ws.deps.terminal.current() || ws.deps.conversation.current();
|
|
173
|
-
ws.deps.terminal.close();
|
|
174
260
|
ws.dock.mode = mode;
|
|
175
261
|
updateLayout(ws);
|
|
176
|
-
ws.deps.
|
|
262
|
+
if (ws.deps.session.current()) ws.deps.session.reconnect();
|
|
263
|
+
else ws.deps.session.open(ws.deps.state.selected
|
|
177
264
|
? { scope: "task", taskId: ws.deps.state.selected, roleName: "leader" }
|
|
178
|
-
: { scope: "global", roleName: "operator" });
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
if (mode === "session") {
|
|
182
|
-
const target = ws.deps.conversation.selection();
|
|
183
|
-
if (!target) return;
|
|
184
|
-
openSession(ws, target);
|
|
265
|
+
: { scope: "global", roleName: "operator" }, { mode: preferredMode() });
|
|
266
|
+
syncDockButtons(ws);
|
|
185
267
|
return;
|
|
186
268
|
}
|
|
187
|
-
ws.deps.
|
|
188
|
-
ws.deps.
|
|
189
|
-
ws.dock.mode = mode === "
|
|
269
|
+
if (!ws.deps.state.selected) return;
|
|
270
|
+
ws.deps.session.close();
|
|
271
|
+
ws.dock.mode = mode === "preview" ? "preview" : "discussion";
|
|
190
272
|
updateLayout(ws);
|
|
191
273
|
}
|
|
192
274
|
|
|
275
|
+
// mode "session" (the legacy native entry) opens the session page in its
|
|
276
|
+
// terminal mode; "discussion" and "preview" need a selected Task.
|
|
193
277
|
function toggleDock(ws, mode) {
|
|
194
278
|
const dock = ws.dock;
|
|
195
279
|
const selected = ws.deps.state.selected;
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
if (
|
|
280
|
+
const view = mode === "session" ? "conversation" : mode;
|
|
281
|
+
if (dockVisible(ws) && (!view || dock.mode === view)) { setDockOpen(ws, false); return; }
|
|
282
|
+
if (view) dock.mode = view === "conversation" || selected ? view : "conversation";
|
|
283
|
+
if (!selected && dock.mode !== "conversation") dock.mode = "conversation";
|
|
199
284
|
setDockOpen(ws, true);
|
|
285
|
+
if (mode === "session") ws.deps.session.setMode("terminal");
|
|
200
286
|
}
|
|
201
287
|
|
|
202
|
-
function
|
|
203
|
-
ws
|
|
204
|
-
ws
|
|
205
|
-
ws.dock.mode = "session";
|
|
206
|
-
showDock(ws, true);
|
|
207
|
-
ws.deps.terminal.open(target);
|
|
208
|
-
updateLayout(ws);
|
|
209
|
-
ws.deps.sessionOpened();
|
|
288
|
+
function toggleOperator(ws) {
|
|
289
|
+
if (operatorShown(ws)) setDockOpen(ws, false);
|
|
290
|
+
else openOperator(ws);
|
|
210
291
|
}
|
|
211
292
|
|
|
212
293
|
function openOperator(ws) {
|
|
213
|
-
ws.deps.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
ws.deps.conversation.open({ scope: "global", roleName: "operator" });
|
|
217
|
-
if (preferredMode() === "session") openSession(ws, { scope: "global", roleName: "operator" });
|
|
218
|
-
syncDockButtons(ws);
|
|
294
|
+
const current = ws.deps.session.current();
|
|
295
|
+
openSessionPage(ws, { scope: "global", roleName: "operator" },
|
|
296
|
+
current && current.scope === "global" && current.roleName === "operator" ? {} : { mode: preferredMode() });
|
|
219
297
|
}
|
|
220
298
|
|
|
221
|
-
// Leaving a Task:
|
|
222
|
-
// longer connected, the dock returns to the discussion mode.
|
|
299
|
+
// Leaving a Task: the session page returns to Operator, hidden unless shown.
|
|
223
300
|
function leaveTask(ws) {
|
|
224
|
-
ws.deps.terminal.close();
|
|
225
301
|
ws.dock.mode = "conversation";
|
|
226
|
-
ws.deps.
|
|
302
|
+
ws.deps.session.open({ scope: "global", roleName: "operator" });
|
|
303
|
+
pauseHiddenSession(ws);
|
|
304
|
+
syncDockButtons(ws);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// The session page keeps its owner while the panel is hidden but stops
|
|
308
|
+
// polling and detaches; opening the panel reconnects it.
|
|
309
|
+
function pauseHiddenSession(ws) {
|
|
310
|
+
if (!dockVisible(ws) || ws.dock.mode !== "conversation") ws.deps.session.close();
|
|
227
311
|
}
|
|
228
312
|
|
|
229
313
|
function bindDockControls(ws) {
|
|
@@ -231,17 +315,19 @@ function bindDockControls(ws) {
|
|
|
231
315
|
const dock = ws.dock;
|
|
232
316
|
el.dockTabDiscussion.addEventListener("click", function () { setDockMode(ws, "discussion"); });
|
|
233
317
|
el.dockTabConversation.addEventListener("click", function () { setDockMode(ws, "conversation"); });
|
|
234
|
-
el.dockTabSession.addEventListener("click", function () { setDockMode(ws, "session"); });
|
|
235
318
|
el.dockTabPreview.addEventListener("click", function () { setDockMode(ws, "preview"); });
|
|
236
|
-
el.dockClose.addEventListener("click", function () { setDockOpen(ws, false); });
|
|
319
|
+
el.dockClose.addEventListener("click", function () { glide(function () { setDockOpen(ws, false); }); });
|
|
237
320
|
el.dockSwap.addEventListener("click", function () {
|
|
238
321
|
dock.center = !dock.center;
|
|
239
322
|
writePreference("yui.dock.side", dock.center ? "center" : "end");
|
|
240
|
-
updateLayout(ws);
|
|
323
|
+
glide(function () { updateLayout(ws); });
|
|
324
|
+
});
|
|
325
|
+
el.globalInputOpen.addEventListener("click", function () { el.dockTarget.open = false; });
|
|
326
|
+
document.addEventListener("click", function (event) {
|
|
327
|
+
if (el.dockTarget.open && !el.dockTarget.contains(event.target)) el.dockTarget.open = false;
|
|
241
328
|
});
|
|
242
|
-
el.
|
|
243
|
-
if (
|
|
244
|
-
else openOperator(ws);
|
|
329
|
+
el.dockTarget.addEventListener("keydown", function (event) {
|
|
330
|
+
if (event.key === "Escape" && el.dockTarget.open) { event.stopPropagation(); el.dockTarget.open = false; el.dockTarget.querySelector("summary").focus(); }
|
|
245
331
|
});
|
|
246
332
|
}
|
|
247
333
|
`;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { BREAKPOINTS } from "../../shared/geometry.js";
|
|
2
|
+
import { MOTION } from "../../shared/motion.js";
|
|
3
|
+
export const MOTION_SCRIPT = String.raw `
|
|
4
|
+
// Script-side motion, on the same durations, curves and presets as the
|
|
5
|
+
// stylesheets (shared/motion.ts). Every helper degrades to an immediate,
|
|
6
|
+
// synchronous change when the browser lacks the API, the user prefers
|
|
7
|
+
// reduced motion, or (for layout transitions) the screen is narrow, where
|
|
8
|
+
// drawers and sheets carry their own CSS motion.
|
|
9
|
+
const M = ${JSON.stringify(MOTION)};
|
|
10
|
+
const NARROW = ${BREAKPOINTS.narrow};
|
|
11
|
+
|
|
12
|
+
export const motion = M;
|
|
13
|
+
|
|
14
|
+
export function reducedMotion() {
|
|
15
|
+
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function options(preset, extra) {
|
|
19
|
+
return Object.assign({ duration: M.durations[preset.duration], easing: M.easings[preset.easing] }, extra || {});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function canAnimate(el) { return Boolean(el) && typeof el.animate === "function" && !reducedMotion(); }
|
|
23
|
+
|
|
24
|
+
// Plays a preset on an element that has just appeared. Returns the
|
|
25
|
+
// Animation, or null when nothing was played.
|
|
26
|
+
export function enter(el, name) {
|
|
27
|
+
const preset = M.presets[name || "rise"];
|
|
28
|
+
if (!preset || !canAnimate(el)) return null;
|
|
29
|
+
return el.animate(preset.frames, options(preset));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Plays an exit preset and resolves once it has finished (or at once when
|
|
33
|
+
// motion is unavailable). The final frame is held until the caller hides
|
|
34
|
+
// the element; call cancel() on the returned animation to abort.
|
|
35
|
+
export function leave(el, name) {
|
|
36
|
+
const preset = M.presets[name || "fade"];
|
|
37
|
+
if (!preset || !canAnimate(el)) return { finished: Promise.resolve(), cancel() {} , animation: null };
|
|
38
|
+
const animation = el.animate(preset.frames, options(preset, { fill: "forwards" }));
|
|
39
|
+
return { finished: animation.finished.then(function () {}, function () {}), cancel() { animation.cancel(); }, animation };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Runs a DOM update that moves the workspace columns (sidebar collapse,
|
|
43
|
+
// dock open or close) inside a View Transition, so the columns glide to
|
|
44
|
+
// their new widths. Without support the update runs synchronously.
|
|
45
|
+
let layoutRunning = 0;
|
|
46
|
+
export function layoutTransition(update) {
|
|
47
|
+
const supported = typeof document !== "undefined" && typeof document.startViewTransition === "function" &&
|
|
48
|
+
typeof window !== "undefined" && window.innerWidth > NARROW && !reducedMotion() && !document.hidden;
|
|
49
|
+
if (!supported) { update(); return; }
|
|
50
|
+
// The column names exist only during the transition (styles/motion).
|
|
51
|
+
const root = document.documentElement;
|
|
52
|
+
const done = function () { if (--layoutRunning === 0) root.classList.remove("vt-layout"); };
|
|
53
|
+
layoutRunning += 1;
|
|
54
|
+
root.classList.add("vt-layout");
|
|
55
|
+
let transition;
|
|
56
|
+
try { transition = document.startViewTransition(update); } catch { done(); update(); return; }
|
|
57
|
+
transition.finished.finally(done);
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
@@ -14,11 +14,38 @@ export function clearPreference(key) {
|
|
|
14
14
|
try { localStorage.removeItem(key); return true; } catch { return false; }
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// Per-tab return points between the workbench and the management areas, so
|
|
18
|
+
// switching back restores the filters, Task or section the user left. Only
|
|
19
|
+
// same-origin paths of the expected area are accepted; anything else falls back.
|
|
20
|
+
const PLACES = { workbench: /^\/(?:\?|$)/, models: /^\/models(?:[/?]|$)/, settings: /^\/settings(?:[/?]|$)/,
|
|
21
|
+
manage: /^\/(?:models|settings)(?:[/?]|$)/ };
|
|
22
|
+
|
|
23
|
+
export function rememberPlace(area, href) {
|
|
24
|
+
if (!PLACES[area] || !PLACES[area].test(href)) return;
|
|
25
|
+
try { sessionStorage.setItem("yui.nav." + area, href); } catch { /* storage unavailable: links keep defaults */ }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function recallPlace(area, fallback) {
|
|
29
|
+
try {
|
|
30
|
+
const value = sessionStorage.getItem("yui.nav." + area);
|
|
31
|
+
return value && PLACES[area] && PLACES[area].test(value) ? value : fallback;
|
|
32
|
+
} catch { return fallback; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Point every a[data-nav-area] inside root at its area's return point.
|
|
36
|
+
export function linkPlaces(root) {
|
|
37
|
+
root.querySelectorAll("a[data-nav-area]").forEach(function (link) {
|
|
38
|
+
if (!link.dataset.navDefault) link.dataset.navDefault = link.getAttribute("href");
|
|
39
|
+
link.setAttribute("href", recallPlace(link.dataset.navArea, link.dataset.navDefault));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
17
43
|
// Shared with session access consumers: no server configuration or live
|
|
18
44
|
// Session is changed by this browser-local preference.
|
|
19
45
|
export function readSessionAccessMode() {
|
|
20
|
-
|
|
21
|
-
|
|
46
|
+
// The structured conversation is the default; the native terminal is opt-in.
|
|
47
|
+
const value = readPreference("yui.session.accessMode", "structured");
|
|
48
|
+
return value === "native" ? "native" : "structured";
|
|
22
49
|
}
|
|
23
50
|
|
|
24
51
|
export function writeSessionAccessMode(value) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BRAND_ICONS } from "../../shared/brand.js";
|
|
1
2
|
import { DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, THEME_OPTIONS } from "../../shared/themes.js";
|
|
2
3
|
// Shared by the parser-blocking head script and the later settings controller.
|
|
3
4
|
// Both resolve the same browser preference without publishing global state.
|
|
@@ -16,15 +17,21 @@ function storedPreference() {
|
|
|
16
17
|
function resolveTheme(preference, media) {
|
|
17
18
|
return preference === "system" ? (media.matches ? DEFAULT_LIGHT : DEFAULT_DARK) : preference;
|
|
18
19
|
}
|
|
20
|
+
|
|
21
|
+
// Stamp the resolved theme on <html> and swap in its pre-coloured favicon.
|
|
22
|
+
const BRAND_ICONS = ${JSON.stringify(BRAND_ICONS)};
|
|
23
|
+
function stampTheme(id) {
|
|
24
|
+
document.documentElement.dataset.theme = id;
|
|
25
|
+
const icon = document.querySelector('link[rel="icon"]');
|
|
26
|
+
if (icon && BRAND_ICONS[id] && icon.getAttribute("href") !== BRAND_ICONS[id]) icon.setAttribute("href", BRAND_ICONS[id]);
|
|
27
|
+
}
|
|
19
28
|
`;
|
|
20
29
|
// A same-origin classic script runs before styles/body, unlike the deferred
|
|
21
30
|
// application module graph. No inline script or weaker CSP is needed.
|
|
22
31
|
export const THEME_BOOTSTRAP_SCRIPT = String.raw `
|
|
23
32
|
(function () {
|
|
24
33
|
${THEME_PREFERENCE_SCRIPT}
|
|
25
|
-
|
|
26
|
-
storedPreference(), window.matchMedia("(prefers-color-scheme: light)")
|
|
27
|
-
);
|
|
34
|
+
stampTheme(resolveTheme(storedPreference(), window.matchMedia("(prefers-color-scheme: light)")));
|
|
28
35
|
})();
|
|
29
36
|
`;
|
|
30
37
|
export const THEME_SCRIPT = String.raw `
|
|
@@ -60,7 +67,7 @@ export function createThemeController(container, t) {
|
|
|
60
67
|
return resolveTheme(preference, media);
|
|
61
68
|
}
|
|
62
69
|
function apply() {
|
|
63
|
-
|
|
70
|
+
stampTheme(resolved());
|
|
64
71
|
if (container) markSelected(container.querySelectorAll("[data-theme-option]"), "aria-pressed", function (button) {
|
|
65
72
|
return button.dataset.themeOption === preference;
|
|
66
73
|
});
|
|
@@ -42,6 +42,46 @@ export function button(text, options) {
|
|
|
42
42
|
return element;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// The page header's session-panel switch: the same place on the overview and
|
|
46
|
+
// on every Task page. It shows or hides the dock with its last target and view
|
|
47
|
+
// (Operator, discussion, terminal and preview are chosen inside the panel);
|
|
48
|
+
// layout/workspace keeps aria-expanded and the accessible name current.
|
|
49
|
+
export function panelToggle(t, onClick) {
|
|
50
|
+
const element = h("button.btn.btn-ghost.dock-toggle.panel-toggle", {
|
|
51
|
+
type: "button", "aria-label": t("dock.show"), title: t("dock.label") + " · D",
|
|
52
|
+
"aria-expanded": "false", "aria-controls": "dock", onclick: onClick
|
|
53
|
+
}, icon("dock"), h("span", null, t("dock.label")));
|
|
54
|
+
element.dataset.panelToggle = "true";
|
|
55
|
+
return element;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The former Operator-only header switch, kept unchanged for pages that still
|
|
59
|
+
// import it; layout/workspace marks it pressed while the dock shows Operator.
|
|
60
|
+
export function operatorToggle(t, onClick) {
|
|
61
|
+
const element = h("button.btn.btn-ghost.dock-toggle.operator-toggle", {
|
|
62
|
+
type: "button", title: t("actions.operator") + " · O", "aria-pressed": "false", onclick: onClick
|
|
63
|
+
}, icon("broadcast"), h("span", null, t("actions.operator")));
|
|
64
|
+
element.dataset.operatorToggle = "true";
|
|
65
|
+
return element;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The task-list switch at the start of the page header. It shows only while
|
|
69
|
+
// the list is out of view (collapsed, or a drawer on smaller screens);
|
|
70
|
+
// layout/sidebarNav keeps aria-expanded current.
|
|
71
|
+
export function sidebarToggle(t, onClick) {
|
|
72
|
+
const element = h("button.icon-btn.side-expand", {
|
|
73
|
+
type: "button", "aria-label": t("sidebar.show"), title: t("sidebar.show") + " · [", "aria-expanded": "false", onclick: onClick
|
|
74
|
+
}, h("span.i-sidebar", null, icon("sidebar")), h("span.i-menu", null, icon("menu")));
|
|
75
|
+
element.dataset.sidebarToggle = "true";
|
|
76
|
+
return element;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The top bar every page starts with (the management shell renders the same
|
|
80
|
+
// markup statically): the list switch, the location, then page actions.
|
|
81
|
+
export function topBar(t, revealSidebar, location, actions) {
|
|
82
|
+
return h("header.top-bar", null, sidebarToggle(t, revealSidebar), location, h("span.spacer"), actions || []);
|
|
83
|
+
}
|
|
84
|
+
|
|
45
85
|
export function note(text, toneName) {
|
|
46
86
|
return h("p.note" + (toneName ? ".tone-" + toneName : ""), null, text);
|
|
47
87
|
}
|