@zq-silk/yui 2.16.0 → 2.18.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 +19 -12
- package/dist/cli/interactionCandidates.js +20 -5
- package/dist/cli/interactionPolicy.js +5 -4
- package/dist/cli/roleWizard.js +7 -0
- package/dist/commands/controllerCommands.js +13 -4
- package/dist/commands/durableJobCommands.js +50 -3
- package/dist/commands/globalRoleCommands.js +31 -14
- package/dist/commands/inboxCommands.js +1 -0
- package/dist/commands/operatorCommands.js +12 -2
- 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/controlPlaneCli.js +21 -4
- package/dist/controller/agentJobControl.js +9 -1
- package/dist/controller/clientRuntime.js +34 -13
- package/dist/controller/controller.js +35 -22
- package/dist/controller/diagnosticJournal.js +214 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +174 -105
- package/dist/controller/globalInputDelivery.js +19 -9
- package/dist/controller/globalRuntimeAttention.js +11 -3
- package/dist/controller/jobControl.js +1 -1
- package/dist/controller/jobDefinitionControl.js +24 -13
- package/dist/controller/jobSupervisor.js +4 -0
- package/dist/controller/providerRetryAdmission.js +9 -0
- package/dist/controller/providerRetryDelivery.js +16 -0
- package/dist/controller/runtime.js +80 -4
- package/dist/controller/runtimeLaunchCoordinator.js +3 -1
- package/dist/controller/workerSchedulerStore.js +24 -0
- package/dist/core/boundedRpc.js +4 -0
- package/dist/core/controllerServer.js +1 -1
- package/dist/core/controllerTelemetry.js +92 -19
- package/dist/executor/fileRoleLaunchPlanner.js +3 -0
- package/dist/job/globalShell.js +51 -0
- package/dist/job/jobBusinessResult.js +4 -3
- package/dist/job/jobDefinition.js +6 -1
- package/dist/job/jobRunner.js +9 -0
- package/dist/job/scheduleService.js +2 -0
- package/dist/message/globalInboxRecovery.js +30 -19
- package/dist/message/globalProviderRetry.js +7 -1
- 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/builtinAgentErrorMappers.js +4 -0
- package/dist/runtime/codexNativeAccess.js +21 -4
- package/dist/runtime/globalSessionHealth.js +39 -0
- 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/scheduler/roleRunStall.js +10 -10
- package/dist/storage/persistenceWorker.js +41 -19
- package/dist/storage/sqliteSchema.js +43 -9
- package/dist/storage/sqliteStore.js +86 -8
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +25 -11
- package/dist/storage/taskReadRevisionSchema.js +39 -0
- package/dist/web/assets/client/app/catalog.js +0 -29
- package/dist/web/assets/client/app/jobs.js +115 -0
- package/dist/web/assets/client/app/main.js +32 -28
- package/dist/web/assets/client/app/manage.js +1 -0
- package/dist/web/assets/client/app/settings.js +1 -5
- package/dist/web/assets/client/app/taskView.js +7 -11
- package/dist/web/assets/client/i18n/en.js +265 -36
- package/dist/web/assets/client/i18n/zh-CN.js +264 -35
- package/dist/web/assets/client/index.js +4 -0
- package/dist/web/assets/client/layout/manageShell.js +61 -15
- package/dist/web/assets/client/layout/router.js +2 -1
- 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/lib/prefs.js +1 -1
- 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/jobs.js +1042 -160
- package/dist/web/assets/client/views/overview.js +2 -2
- package/dist/web/assets/client/views/sidebar.js +3 -0
- package/dist/web/assets/client/views/task/page.js +2 -12
- package/dist/web/assets/shared/manageNav.js +3 -1
- package/dist/web/assets/shell/appRail.js +2 -1
- package/dist/web/assets/shell/dock.js +9 -27
- package/dist/web/assets/shell/manage.js +7 -2
- package/dist/web/assets/shell/models.js +4 -3
- package/dist/web/assets/shell/settingsContent.js +0 -3
- package/dist/web/assets/shell/sidebar.js +0 -1
- 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/jobs.js +301 -12
- package/dist/web/assets/styles/views/sidebar.js +1 -2
- 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/webServer.js +28 -1
- package/dist/web/webSettings.js +10 -2
- package/docs/global-shell-jobs.md +78 -0
- package/docs/job-runs.md +6 -3
- package/docs/managed-turn-and-session-runtime.md +84 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +57 -0
- package/docs/release-workflow.md +67 -0
- package/docs/release-workflow.zh-CN.md +48 -0
- package/docs/roles-and-configuration.md +45 -1
- package/docs/roles-and-configuration.zh-CN.md +35 -0
- package/docs/storage-baseline.md +19 -1
- package/docs/storage-baseline.zh-CN.md +10 -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
|
`;
|