@zq-silk/yui 2.16.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/main.js +15 -22
- package/dist/web/assets/client/app/taskView.js +7 -11
- package/dist/web/assets/client/i18n/en.js +62 -18
- package/dist/web/assets/client/i18n/zh-CN.js +62 -18
- package/dist/web/assets/client/index.js +2 -0
- package/dist/web/assets/client/layout/shortcuts.js +2 -2
- package/dist/web/assets/client/layout/workspace.js +99 -112
- package/dist/web/assets/client/ui/primitives.js +15 -2
- 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 +2 -2
- package/dist/web/assets/client/views/task/page.js +2 -12
- package/dist/web/assets/shell/dock.js +9 -27
- package/dist/web/assets/styles/views/conversation.js +96 -17
- package/dist/web/assets/styles/views/dock.js +17 -5
- package/dist/web/assets/styles/views/task.js +1 -2
- package/dist/web/assets/styles/views/terminal.js +12 -8
- 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 +24 -0
- package/docs/release-workflow.zh-CN.md +19 -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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
export const SESSION_PAGE_SCRIPT = String.raw `
|
|
2
|
+
// The dock's session page: one owner (a Role) and one selected Session, read
|
|
3
|
+
// either as the structured chat or as the live native terminal. The owner is
|
|
4
|
+
// picked in the dock header and the Session in the chat's picker; both carry
|
|
5
|
+
// across modes. Switching mode destroys the old view (polling, socket,
|
|
6
|
+
// listeners and unsent UI state such as drafts and scroll) and builds the
|
|
7
|
+
// other one afresh. Saved submission receipts and pending model switches are
|
|
8
|
+
// facts, not view state, and survive in the chat's existing storage.
|
|
9
|
+
import { h, icon } from "/assets/js/lib/dom.js";
|
|
10
|
+
import { formatShort } from "/assets/js/lib/format.js";
|
|
11
|
+
import { createConversationController } from "/assets/js/views/dock/conversation.js";
|
|
12
|
+
import { createTerminalController } from "/assets/js/views/dock/terminal.js";
|
|
13
|
+
|
|
14
|
+
export const SESSION_MODES = ["chat", "terminal"];
|
|
15
|
+
|
|
16
|
+
// host: the dock pane the page owns; notify(message) shows a toast;
|
|
17
|
+
// onChange() runs after the mode changed (the workspace redraws its header).
|
|
18
|
+
export function createSessionPage(host, t, locale, notify, onChange) {
|
|
19
|
+
const page = { host: host, t: t, locale: locale, notify: notify || function () {}, onChange: onChange || function () {},
|
|
20
|
+
owner: null, session: null, mode: "chat", view: null, active: false, switcher: null };
|
|
21
|
+
return {
|
|
22
|
+
// options: { mode, current, materials }. current resolves the owner's
|
|
23
|
+
// current Session again instead of keeping the selected one.
|
|
24
|
+
open: function (target, options) { open(page, target, options || {}); },
|
|
25
|
+
close: function () { page.active = false; if (page.view) page.view.stop(); },
|
|
26
|
+
reconnect: function () { page.active = true; if (page.view) page.view.start(); },
|
|
27
|
+
setMode: function (mode) { return setMode(page, mode); },
|
|
28
|
+
mode: function () { return page.mode; },
|
|
29
|
+
current: function () { return page.owner; },
|
|
30
|
+
selection: function () { return page.view ? page.view.selection() : null; },
|
|
31
|
+
connected: function () { return !!page.view && page.view.connected(); },
|
|
32
|
+
captureDraftTarget: function () { return page.view ? page.view.captureDraftTarget() : null; },
|
|
33
|
+
insertReference: function (target, citation) {
|
|
34
|
+
if (!page.view) throw new Error(t("bodySearch.targetChanged"));
|
|
35
|
+
page.view.insertReference(target, citation);
|
|
36
|
+
},
|
|
37
|
+
relabel: function () { if (page.view) page.view.relabel(); },
|
|
38
|
+
retheme: function () { if (page.view) page.view.retheme(); }
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ownerOf(target) {
|
|
43
|
+
return { scope: target.scope, ...(target.scope === "task" ? { taskId: target.taskId } : {}), roleName: target.roleName };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sameOwner(a, b) {
|
|
47
|
+
return !!a && !!b && JSON.stringify(ownerOf(a)) === JSON.stringify(ownerOf(b));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function open(page, target, options) {
|
|
51
|
+
const ownerChanged = !sameOwner(target, page.owner);
|
|
52
|
+
const mode = SESSION_MODES.includes(options.mode) ? options.mode : page.mode;
|
|
53
|
+
if (ownerChanged || options.current) page.session = null;
|
|
54
|
+
page.owner = ownerOf(target);
|
|
55
|
+
page.active = true;
|
|
56
|
+
if (ownerChanged || options.current || mode !== page.mode || !page.view) {
|
|
57
|
+
page.mode = mode;
|
|
58
|
+
build(page, { materials: options.materials });
|
|
59
|
+
page.onChange();
|
|
60
|
+
} else page.view.start();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Why the terminal cannot attach to the selected identity, or null when it
|
|
64
|
+
// may try (the terminal checks again against the newest facts).
|
|
65
|
+
function terminalBlock(page) {
|
|
66
|
+
const session = page.session;
|
|
67
|
+
if (!session || session.currentId === undefined) return null;
|
|
68
|
+
if (!session.currentId) return "terminal.noSession";
|
|
69
|
+
if (session.id && session.id !== session.currentId) return "terminal.notCurrent";
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function setMode(page, mode) {
|
|
74
|
+
if (!SESSION_MODES.includes(mode) || mode === page.mode || !page.owner) return false;
|
|
75
|
+
const block = mode === "terminal" ? terminalBlock(page) : null;
|
|
76
|
+
if (block) { page.notify(page.t(block + "Text")); return false; }
|
|
77
|
+
const focused = page.host.contains(document.activeElement);
|
|
78
|
+
page.mode = mode;
|
|
79
|
+
build(page, { discard: true });
|
|
80
|
+
if (focused && page.switcher) page.switcher.focus(mode);
|
|
81
|
+
page.onChange();
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function build(page, options) {
|
|
86
|
+
const previous = page.view;
|
|
87
|
+
page.view = null;
|
|
88
|
+
if (previous) previous.destroy(!!options.discard);
|
|
89
|
+
const root = h("div.session-view.is-" + page.mode);
|
|
90
|
+
page.host.replaceChildren(root);
|
|
91
|
+
page.switcher = modeSwitch(page);
|
|
92
|
+
const view = page.mode === "terminal" ? terminalView(page, root, page.switcher.node)
|
|
93
|
+
: chatView(page, root, page.switcher.node, options.materials);
|
|
94
|
+
page.view = view;
|
|
95
|
+
syncSwitch(page);
|
|
96
|
+
if (page.active) view.start();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Chat | Terminal for the same owner and Session: a small radio group in the
|
|
100
|
+
// view's own header row, not another navigation layer.
|
|
101
|
+
function modeSwitch(page) {
|
|
102
|
+
const t = page.t;
|
|
103
|
+
const buttons = {};
|
|
104
|
+
const node = h("div.seg.seg-sm.sp-modes", { role: "radiogroup", "aria-label": t("session.mode"), "data-i18n-aria-label": "session.mode" });
|
|
105
|
+
[["chat", "sparkle", "session.modeChat"], ["terminal", "terminal", "session.modeTerminal"]].forEach(function (entry) {
|
|
106
|
+
const button = h("button.seg-btn", { type: "button", role: "radio", dataset: { mode: entry[0] },
|
|
107
|
+
"aria-label": t(entry[2]), "data-i18n-aria-label": entry[2] },
|
|
108
|
+
icon(entry[1]), h("span", { dataset: { i18n: entry[2] } }, t(entry[2])));
|
|
109
|
+
button.addEventListener("click", function () { setMode(page, entry[0]); });
|
|
110
|
+
buttons[entry[0]] = button;
|
|
111
|
+
node.append(button);
|
|
112
|
+
});
|
|
113
|
+
node.addEventListener("keydown", function (event) {
|
|
114
|
+
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)) return;
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
const next = page.mode === "chat" ? "terminal" : "chat";
|
|
117
|
+
if (!setMode(page, next)) buttons[next].focus();
|
|
118
|
+
});
|
|
119
|
+
return { node: node, buttons: buttons, focus: function (mode) { buttons[mode].focus(); } };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function syncSwitch(page) {
|
|
123
|
+
const switcher = page.switcher;
|
|
124
|
+
if (!switcher) return;
|
|
125
|
+
const block = terminalBlock(page);
|
|
126
|
+
Object.keys(switcher.buttons).forEach(function (mode) {
|
|
127
|
+
const button = switcher.buttons[mode];
|
|
128
|
+
const chosen = mode === page.mode;
|
|
129
|
+
button.setAttribute("aria-checked", String(chosen));
|
|
130
|
+
button.tabIndex = chosen ? 0 : -1;
|
|
131
|
+
});
|
|
132
|
+
const terminal = switcher.buttons.terminal;
|
|
133
|
+
// Blocked stays focusable and explains itself on use instead of going silent.
|
|
134
|
+
terminal.setAttribute("aria-disabled", String(!!block && page.mode !== "terminal"));
|
|
135
|
+
terminal.title = block && page.mode !== "terminal" ? page.t(block + "Text") : page.t("session.modeTerminal");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The chat reports its selected Session so the terminal can follow it.
|
|
139
|
+
function chatView(page, root, tools, materials) {
|
|
140
|
+
let view = null, opened = false;
|
|
141
|
+
const ctl = createConversationController(root, page.t, page.locale, {
|
|
142
|
+
tools: tools,
|
|
143
|
+
onSelection: function (id, currentId) {
|
|
144
|
+
if (page.view !== view) return;
|
|
145
|
+
page.session = { id: id || null, currentId: currentId };
|
|
146
|
+
syncSwitch(page);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
view = {
|
|
150
|
+
kind: "chat",
|
|
151
|
+
start: function () {
|
|
152
|
+
if (opened) { ctl.reconnect(); return; }
|
|
153
|
+
opened = true;
|
|
154
|
+
ctl.open(page.owner, { session: page.session && page.session.id, materials: materials });
|
|
155
|
+
},
|
|
156
|
+
stop: function () { ctl.close(); },
|
|
157
|
+
destroy: function (discard) { ctl.close(); if (discard) ctl.discardDraft(); },
|
|
158
|
+
selection: ctl.selection,
|
|
159
|
+
connected: function () { return false; },
|
|
160
|
+
captureDraftTarget: ctl.captureDraftTarget,
|
|
161
|
+
insertReference: ctl.insertReference,
|
|
162
|
+
relabel: function () {},
|
|
163
|
+
retheme: function () {}
|
|
164
|
+
};
|
|
165
|
+
return view;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function sessionName(page, info) {
|
|
169
|
+
const t = page.t;
|
|
170
|
+
const current = !!info.id && info.id === info.currentSessionId;
|
|
171
|
+
const s = info.session;
|
|
172
|
+
const when = s ? formatShort(s.createdAt, page.locale()) : "";
|
|
173
|
+
return (current ? t("conversation.current") : t("conversation.previous")) + ((s && s.title) || when ? " · " + ((s && s.title) || when) : "");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function terminalView(page, root, tools) {
|
|
177
|
+
const t = page.t;
|
|
178
|
+
let view = null;
|
|
179
|
+
const name = h("span.sp-term-name", null, t("conversation.sessions"));
|
|
180
|
+
const state = h("span.conn", { dataset: { state: "idle" } }, h("i", { "aria-hidden": "true" }), h("span", null, t("terminal.idle")));
|
|
181
|
+
const host = h("div.terminal-host");
|
|
182
|
+
const emptyTitle = h("p.terminal-empty-title");
|
|
183
|
+
const emptyText = h("p.terminal-empty-text");
|
|
184
|
+
const toCurrent = h("button.btn", { type: "button", hidden: true }, t("conversation.backCurrent"));
|
|
185
|
+
const empty = h("div.terminal-empty", { hidden: true }, icon("terminal", "icon icon-xl"), emptyTitle, emptyText, toCurrent);
|
|
186
|
+
const cli = h("code");
|
|
187
|
+
root.classList.add("dock-session");
|
|
188
|
+
root.append(h("div.session-bar", null, icon("terminal", "icon-sm sp-term-icon"), name, h("span.spacer"), state, tools),
|
|
189
|
+
host, empty,
|
|
190
|
+
h("footer.session-foot", null, h("span", { dataset: { i18n: "terminal.cliHint" } }, t("terminal.cliHint")), cli));
|
|
191
|
+
const ctl = createTerminalController({ host: host, empty: empty, emptyTitle: emptyTitle, emptyText: emptyText, state: state, cli: cli },
|
|
192
|
+
t, page.locale, page.notify, {
|
|
193
|
+
onSession: function (info) {
|
|
194
|
+
if (page.view !== view) return;
|
|
195
|
+
page.session = { id: info.id, currentId: info.currentSessionId };
|
|
196
|
+
name.textContent = info.id ? sessionName(page, info) : t("conversation.noSessionShort");
|
|
197
|
+
name.title = name.textContent;
|
|
198
|
+
toCurrent.hidden = !(info.id && info.currentSessionId && info.id !== info.currentSessionId);
|
|
199
|
+
syncSwitch(page);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// An explicit choice, like the chat's "Back to current Session".
|
|
203
|
+
toCurrent.addEventListener("click", function () {
|
|
204
|
+
if (!page.session || !page.session.currentId) return;
|
|
205
|
+
page.session = { id: page.session.currentId, currentId: page.session.currentId };
|
|
206
|
+
toCurrent.hidden = true;
|
|
207
|
+
view.start();
|
|
208
|
+
});
|
|
209
|
+
function target() {
|
|
210
|
+
return { ...page.owner, ...(page.session && page.session.id ? { nativeSessionId: page.session.id } : {}) };
|
|
211
|
+
}
|
|
212
|
+
view = {
|
|
213
|
+
kind: "terminal",
|
|
214
|
+
start: function () { ctl.open(target()); },
|
|
215
|
+
stop: function () { ctl.close(); },
|
|
216
|
+
destroy: function () { ctl.close(); },
|
|
217
|
+
selection: function () {
|
|
218
|
+
const session = page.session;
|
|
219
|
+
return session && session.id && session.id === session.currentId ? { ...page.owner, nativeSessionId: session.id } : null;
|
|
220
|
+
},
|
|
221
|
+
connected: ctl.connected,
|
|
222
|
+
captureDraftTarget: function () { return null; },
|
|
223
|
+
insertReference: function () { throw new Error(t("bodySearch.targetChanged")); },
|
|
224
|
+
relabel: function () { ctl.relabel(); },
|
|
225
|
+
retheme: function () { ctl.retheme(); }
|
|
226
|
+
};
|
|
227
|
+
return view;
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export const TERMINAL_SCRIPT = String.raw `
|
|
2
|
-
// The live native
|
|
3
|
-
// and its WebSocket
|
|
4
|
-
//
|
|
2
|
+
// The live native terminal of the session page (views/dock/session): the xterm
|
|
3
|
+
// view and its WebSocket for one owner's current Session. The session page
|
|
4
|
+
// builds the surrounding chrome and destroys this controller with its view;
|
|
5
|
+
// opening another target disposes the previous connection first.
|
|
5
6
|
import { Terminal } from "/assets/vendor/xterm.mjs";
|
|
6
7
|
import { FitAddon } from "/assets/vendor/addon-fit.mjs";
|
|
7
|
-
import {
|
|
8
|
+
import { clear } from "/assets/js/lib/dom.js";
|
|
8
9
|
import { fill } from "/assets/js/lib/format.js";
|
|
9
10
|
import { pageToken, requestJson } from "/assets/js/lib/api.js";
|
|
10
|
-
import { dot } from "/assets/js/ui/primitives.js";
|
|
11
11
|
import { sessionExpired } from "/assets/js/lib/session.js";
|
|
12
12
|
|
|
13
13
|
function cssVar(name, fallback) {
|
|
@@ -34,18 +34,19 @@ export function sameTarget(left, right) {
|
|
|
34
34
|
&& (left.scope !== "task" || left.taskId === right.taskId);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
// elements: { host, empty, state, cli
|
|
38
|
-
// locale; notify(message) shows a toast.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
// elements: { host, empty, emptyTitle, emptyText, state, cli }; locale() reads
|
|
38
|
+
// the current locale; notify(message) shows a toast.
|
|
39
|
+
// hooks.onSession({ id, currentSessionId, session }) reports which Session the
|
|
40
|
+
// open target resolved to (or that it has none), before any connection.
|
|
41
|
+
export function createTerminalController(elements, t, locale, notify, hooks) {
|
|
42
|
+
const ctl = { elements: elements, t: t, locale: locale, notify: notify, hooks: hooks || {}, session: null, current: null,
|
|
43
|
+
generation: 0, stateKey: "terminal.idle", emptyKey: null };
|
|
42
44
|
return {
|
|
43
|
-
open: function (target) { open(ctl, target); },
|
|
45
|
+
open: function (target) { return open(ctl, target); },
|
|
44
46
|
close: function () { close(ctl); },
|
|
45
47
|
current: function () { return ctl.current; },
|
|
46
48
|
connected: function () { return !!ctl.session; },
|
|
47
|
-
|
|
48
|
-
relabel: function () { setState(ctl, ctl.stateKey, elements.state.dataset.state); drawTargets(ctl); },
|
|
49
|
+
relabel: function () { setState(ctl, ctl.stateKey, elements.state.dataset.state); showEmpty(ctl, ctl.emptyKey); },
|
|
49
50
|
retheme: function () { if (ctl.session) ctl.session.terminal.options.theme = terminalTheme(); }
|
|
50
51
|
};
|
|
51
52
|
}
|
|
@@ -56,19 +57,16 @@ function setState(ctl, key, state) {
|
|
|
56
57
|
ctl.elements.state.lastChild.textContent = ctl.t(key);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
ctl.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}, target.scope === "task" ? dot(target.live ? "ok" : "idle") : icon("broadcast", "icon-sm"), h("span", null, target.roleName)));
|
|
70
|
-
});
|
|
71
|
-
if (!ctl.targets.length) list.append(h("span.faint.small", null, t("terminal.noTargets")));
|
|
60
|
+
// Why nothing is attached, in place of the terminal: no Session yet, or the
|
|
61
|
+
// selected Session is no longer the current one. Null hides the message.
|
|
62
|
+
function showEmpty(ctl, key) {
|
|
63
|
+
ctl.emptyKey = key;
|
|
64
|
+
const elements = ctl.elements;
|
|
65
|
+
if (!elements.empty) return;
|
|
66
|
+
elements.empty.hidden = !key;
|
|
67
|
+
if (!key) return;
|
|
68
|
+
if (elements.emptyTitle) elements.emptyTitle.textContent = ctl.t(key + "Title");
|
|
69
|
+
if (elements.emptyText) elements.emptyText.textContent = ctl.t(key + "Text");
|
|
72
70
|
}
|
|
73
71
|
|
|
74
72
|
function dispose(ctl) {
|
|
@@ -86,30 +84,38 @@ function dispose(ctl) {
|
|
|
86
84
|
function close(ctl) {
|
|
87
85
|
dispose(ctl);
|
|
88
86
|
ctl.current = null;
|
|
89
|
-
ctl
|
|
87
|
+
showEmpty(ctl, null);
|
|
90
88
|
ctl.elements.cli.textContent = "yui operator enter";
|
|
91
89
|
setState(ctl, "terminal.idle", "idle");
|
|
92
|
-
drawTargets(ctl);
|
|
93
90
|
}
|
|
94
91
|
|
|
92
|
+
// A target without nativeSessionId attaches to the owner's current Session; a
|
|
93
|
+
// target naming one attaches only while it is still current. A replaced
|
|
94
|
+
// Session is reported, never silently exchanged for the new current one.
|
|
95
95
|
async function open(ctl, target) {
|
|
96
96
|
dispose(ctl);
|
|
97
97
|
const generation = ctl.generation;
|
|
98
98
|
ctl.current = target;
|
|
99
|
-
ctl
|
|
99
|
+
showEmpty(ctl, null);
|
|
100
100
|
ctl.elements.cli.textContent = cliFor(target);
|
|
101
101
|
setState(ctl, "terminal.connecting", "connecting");
|
|
102
|
-
drawTargets(ctl);
|
|
103
102
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
const query = new URLSearchParams({ scope: target.scope, role: target.roleName });
|
|
104
|
+
if (target.scope === "task") query.set("task", target.taskId);
|
|
105
|
+
const facts = await requestJson("/api/conversation?" + query);
|
|
106
|
+
if (generation !== ctl.generation) return;
|
|
107
|
+
const currentId = facts.currentSessionId || null;
|
|
108
|
+
const id = target.nativeSessionId || currentId;
|
|
109
|
+
const session = (facts.sessions || []).find(function (s) { return s.nativeSessionId === id; }) || null;
|
|
110
|
+
if (ctl.hooks.onSession) ctl.hooks.onSession({ id: id, currentSessionId: currentId, session: session });
|
|
111
|
+
if (generation !== ctl.generation) return;
|
|
112
|
+
if (!id || id !== currentId) {
|
|
113
|
+
setState(ctl, "terminal.unavailable", "idle");
|
|
114
|
+
showEmpty(ctl, id ? "terminal.notCurrent" : "terminal.noSession");
|
|
115
|
+
return;
|
|
112
116
|
}
|
|
117
|
+
target = { ...target, nativeSessionId: id };
|
|
118
|
+
ctl.current = target;
|
|
113
119
|
} catch (error) {
|
|
114
120
|
if (generation === ctl.generation) { setState(ctl, "terminal.error", "error"); ctl.notify(error.message); }
|
|
115
121
|
return;
|
|
@@ -197,6 +203,9 @@ function ready(ctl, message, terminal, link) {
|
|
|
197
203
|
current: number.format(message.history.limit), target: number.format(message.history.target)
|
|
198
204
|
}));
|
|
199
205
|
}
|
|
200
|
-
|
|
206
|
+
// A keyboard user moving through the session page's Chat | Terminal switch
|
|
207
|
+
// keeps focus there (arrow keys must not become terminal input).
|
|
208
|
+
const active = document.activeElement;
|
|
209
|
+
if (!(active && active.closest && active.closest(".sp-modes"))) terminal.focus();
|
|
201
210
|
}
|
|
202
211
|
`;
|
|
@@ -4,7 +4,7 @@ export const OVERVIEW_SCRIPT = String.raw `
|
|
|
4
4
|
// then catalog-wide signals and the explicit Session read.
|
|
5
5
|
import { h, icon, clear } from "/assets/js/lib/dom.js";
|
|
6
6
|
import { byNewest, fill, formatDateTime, relativeTime } from "/assets/js/lib/format.js";
|
|
7
|
-
import { badge, button, dot, emptyState, note,
|
|
7
|
+
import { badge, button, dot, emptyState, note, panelToggle, topBar } from "/assets/js/ui/primitives.js";
|
|
8
8
|
import { card } from "/assets/js/ui/containers.js";
|
|
9
9
|
import { metricTile } from "/assets/js/ui/metrics.js";
|
|
10
10
|
import { label, sessionBadges, tone } from "/assets/js/domain/vocab.js";
|
|
@@ -21,7 +21,7 @@ export function renderOverview(container, state, t, locale, actions) {
|
|
|
21
21
|
h("span.crumb", null, t("manage.workbench")),
|
|
22
22
|
h("span.crumb-sep", { "aria-hidden": "true" }, "/"),
|
|
23
23
|
h("span.crumb-current", null, t("overview.title"))),
|
|
24
|
-
[
|
|
24
|
+
[panelToggle(t, actions.togglePanel)]));
|
|
25
25
|
page.append(overviewHead(state.counts, t, actions), metricStrip(state.counts, t));
|
|
26
26
|
page.append(h("div.card-stack", null,
|
|
27
27
|
inboxCard(state, t, actions),
|
|
@@ -4,7 +4,7 @@ export const TASK_PAGE_SCRIPT = String.raw `
|
|
|
4
4
|
// observation arrives separately from the Context snapshot, so every fact it
|
|
5
5
|
// feeds lives in a [data-slot] that updateObservation() redraws in place.
|
|
6
6
|
import { h, icon, clear } from "/assets/js/lib/dom.js";
|
|
7
|
-
import { badge, chip,
|
|
7
|
+
import { badge, chip, panelToggle, timeTag, topBar } from "/assets/js/ui/primitives.js";
|
|
8
8
|
import { label, statusBadge } from "/assets/js/domain/vocab.js";
|
|
9
9
|
import { totalOf } from "/assets/js/domain/context.js";
|
|
10
10
|
import { renderOverviewPanel } from "/assets/js/views/task/overview.js";
|
|
@@ -79,17 +79,7 @@ function taskBar(task, t, ctx) {
|
|
|
79
79
|
h("button.crumb", { type: "button", title: t("overview.title"), onclick: ctx.onBack }, t("manage.workbench")),
|
|
80
80
|
h("span.crumb-sep", { "aria-hidden": "true" }, "/"),
|
|
81
81
|
h("code.crumb-current", { title: task.title }, task.id)),
|
|
82
|
-
[
|
|
83
|
-
dockToggle("session", "terminal", t("dock.session"), t, ctx),
|
|
84
|
-
h("span.task-bar-sep", { "aria-hidden": "true" }),
|
|
85
|
-
operatorToggle(t, ctx.toggleOperator)]);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function dockToggle(mode, iconName, title, t, ctx) {
|
|
89
|
-
return h("button.btn.btn-ghost.dock-toggle", {
|
|
90
|
-
type: "button", dataset: { dockToggle: mode }, title: title,
|
|
91
|
-
onclick: function () { ctx.showDock(mode); }
|
|
92
|
-
}, icon(iconName), h("span", null, t("dock." + mode)));
|
|
82
|
+
[panelToggle(t, ctx.togglePanel)]);
|
|
93
83
|
}
|
|
94
84
|
|
|
95
85
|
function taskMeta(task, t, locale) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { DOCK } from "../shared/geometry.js";
|
|
2
2
|
import { iconSvg } from "../shared/icons.js";
|
|
3
|
-
// The dock divider and the dock region:
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// The dock divider and the dock region: one header row with the target menu
|
|
4
|
+
// (the only Role selector), the view tabs (icon-only until the dock is wide)
|
|
5
|
+
// and the dock actions; then the session page mount (chat or terminal, built
|
|
6
|
+
// by views/dock/session), the discussion mount and the preview.
|
|
6
7
|
// The string keeps its indentation inside .app and has no trailing newline.
|
|
7
8
|
export const DOCK_HTML = ` <div id="dock-divider" class="dock-divider" role="separator" tabindex="0" aria-orientation="vertical" aria-valuemin="${DOCK.min}" aria-valuemax="${DOCK.max}" aria-valuenow="${DOCK.initial}" aria-label="Resize session panel" data-i18n-aria-label="dock.resize" hidden><span aria-hidden="true"></span></div>
|
|
8
9
|
<aside id="dock" class="dock" aria-label="Session" data-i18n-aria-label="dock.label" hidden>
|
|
@@ -18,36 +19,17 @@ export const DOCK_HTML = ` <div id="dock-divider" class="dock-divider" role="
|
|
|
18
19
|
<button id="global-input-open" class="dock-target-item" type="button">${iconSvg("send")}<span data-i18n="global.openRole">Message another Role…</span></button>
|
|
19
20
|
</div>
|
|
20
21
|
</details>
|
|
22
|
+
<div class="seg seg-sm dock-tabs" role="tablist" aria-label="Session view" data-i18n-aria-label="dock.label">
|
|
23
|
+
<button id="dock-tab-conversation" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-conversation" aria-label="Conversation" data-i18n-aria-label="conversation.title" title="Conversation" data-i18n-title="conversation.title">${iconSvg("sparkle")}<span data-i18n="conversation.title">Conversation</span></button>
|
|
24
|
+
<button id="dock-tab-discussion" class="seg-btn" type="button" role="tab" aria-selected="true" aria-controls="dock-discussion" aria-label="Discussion" data-i18n-aria-label="dock.discussion" title="Discussion" data-i18n-title="dock.discussion">${iconSvg("chat")}<span data-i18n="dock.discussion">Discussion</span></button>
|
|
25
|
+
<button id="dock-tab-preview" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-preview" aria-label="Preview" data-i18n-aria-label="preview.title" title="Preview" data-i18n-title="preview.title" disabled>${iconSvg("eye")}<span data-i18n="preview.title">Preview</span></button>
|
|
26
|
+
</div>
|
|
21
27
|
<div class="dock-actions">
|
|
22
28
|
<button id="dock-swap" class="icon-btn" type="button" aria-label="Swap with task details" data-i18n-aria-label="dock.swap" title="Swap with task details">${iconSvg("swap")}</button>
|
|
23
29
|
<button id="dock-close" class="icon-btn" type="button" aria-label="Hide session panel" data-i18n-aria-label="dock.hide" title="Hide · D">${iconSvg("close")}</button>
|
|
24
30
|
</div>
|
|
25
31
|
</header>
|
|
26
|
-
<div class="dock-tabs">
|
|
27
|
-
<div class="seg seg-sm" role="tablist" aria-label="Session view" data-i18n-aria-label="dock.label">
|
|
28
|
-
<button id="dock-tab-conversation" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-conversation"><span data-i18n="conversation.title">Conversation</span></button>
|
|
29
|
-
<button id="dock-tab-discussion" class="seg-btn" type="button" role="tab" aria-selected="true" aria-controls="dock-discussion">${iconSvg("chat")}<span data-i18n="dock.discussion">Discussion</span></button>
|
|
30
|
-
<button id="dock-tab-session" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-session">${iconSvg("terminal")}<span data-i18n="dock.session">Live session</span></button>
|
|
31
|
-
<button id="dock-tab-preview" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-preview" disabled><span data-i18n="preview.title">Preview</span></button>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
32
|
<section id="dock-conversation" class="dock-pane dock-discussion" role="tabpanel" aria-labelledby="dock-tab-conversation" hidden></section>
|
|
35
33
|
<section id="dock-discussion" class="dock-pane dock-discussion" role="tabpanel" aria-labelledby="dock-tab-discussion"></section>
|
|
36
34
|
<section id="dock-preview" class="dock-pane dock-preview" role="tabpanel" aria-labelledby="dock-tab-preview" hidden></section>
|
|
37
|
-
<section id="dock-session" class="dock-pane dock-session" role="tabpanel" aria-labelledby="dock-tab-session" hidden>
|
|
38
|
-
<div class="session-bar">
|
|
39
|
-
<div id="session-targets" class="session-targets" role="group" aria-label="Session target" data-i18n-aria-label="dock.target"></div>
|
|
40
|
-
<span id="terminal-state" class="conn" data-state="idle"><i aria-hidden="true"></i><span data-i18n="terminal.idle">Not connected</span></span>
|
|
41
|
-
</div>
|
|
42
|
-
<div id="terminal-host" class="terminal-host"></div>
|
|
43
|
-
<div id="terminal-empty" class="terminal-empty">
|
|
44
|
-
${iconSvg("terminal", "icon icon-xl")}
|
|
45
|
-
<p class="terminal-empty-title" data-i18n="terminal.emptyTitle">Attach to a native Session</p>
|
|
46
|
-
<p class="terminal-empty-text" data-i18n="terminal.emptyText">Pick a Role above. The panel shows the Session's current screen; attaching never starts or restarts an Agent.</p>
|
|
47
|
-
</div>
|
|
48
|
-
<footer class="session-foot">
|
|
49
|
-
<span data-i18n="terminal.cliHint">Full terminal</span>
|
|
50
|
-
<code id="terminal-cli">yui operator enter</code>
|
|
51
|
-
</footer>
|
|
52
|
-
</section>
|
|
53
35
|
</aside>`;
|