@zq-silk/yui 2.7.1 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/cli/commandCatalog.js +8 -7
  2. package/dist/cli/updateCommand.js +3 -0
  3. package/dist/cli/updatePorts.js +20 -0
  4. package/dist/commands/configCommands.js +1 -0
  5. package/dist/commands/globalRoleCommands.js +42 -35
  6. package/dist/commands/inboxCommands.js +60 -40
  7. package/dist/commands/taskCommands.js +7 -3
  8. package/dist/commands/taskRoleRuntimeStatus.js +2 -0
  9. package/dist/context/runContextPack.js +48 -3
  10. package/dist/context/taskContext.js +36 -12
  11. package/dist/controlPlaneCli.js +64 -17
  12. package/dist/controller/controller.js +5 -0
  13. package/dist/controller/fileSchedulerStoreAdapter.js +9 -4
  14. package/dist/controller/globalInputDelivery.js +17 -5
  15. package/dist/controller/globalRuntimeAttention.js +135 -5
  16. package/dist/controller/runtime.js +4 -0
  17. package/dist/controller/structuredProviderObservation.js +51 -15
  18. package/dist/controller/taskHostTerminalRecovery.js +33 -0
  19. package/dist/executor/fileRoleLaunchPlanner.js +4 -9
  20. package/dist/message/inboxReceipt.js +30 -0
  21. package/dist/message/taskInboxOwnership.js +25 -3
  22. package/dist/models/modelManagement.js +469 -0
  23. package/dist/models/modelManagementConfig.js +46 -0
  24. package/dist/models/modelManagementFiles.js +90 -0
  25. package/dist/models/modelManagementHttp.js +90 -0
  26. package/dist/models/modelManagementTypes.js +1 -0
  27. package/dist/output/boundedRead.js +43 -14
  28. package/dist/runtime/agentEndpointIdentity.js +8 -4
  29. package/dist/runtime/agentEndpointOwnership.js +5 -6
  30. package/dist/runtime/agentHost.js +62 -10
  31. package/dist/runtime/agentHostImplementation.js +18 -0
  32. package/dist/runtime/managedCaller.js +25 -1
  33. package/dist/runtime/managedIdentity.js +1 -1
  34. package/dist/runtime/providerRetry.js +19 -1
  35. package/dist/runtime/sessionCaller.js +53 -0
  36. package/dist/storage/contextRecords.js +28 -0
  37. package/dist/storage/maintenanceCaller.js +69 -0
  38. package/dist/storage/sqliteSchema.js +12 -2
  39. package/dist/storage/sqliteStore.js +9 -5
  40. package/dist/storage/storageVersions.js +1 -1
  41. package/dist/storage/taskStore.js +4 -0
  42. package/dist/task/completionReadiness.js +3 -3
  43. package/dist/web/assets/assetManifest.js +28 -2
  44. package/dist/web/assets/client/app/manage.js +6 -0
  45. package/dist/web/assets/client/app/models.js +20 -0
  46. package/dist/web/assets/client/app/settings.js +41 -9
  47. package/dist/web/assets/client/i18n/en.js +180 -2
  48. package/dist/web/assets/client/i18n/zh-CN.js +178 -2
  49. package/dist/web/assets/client/index.js +8 -0
  50. package/dist/web/assets/client/layout/manageShell.js +94 -0
  51. package/dist/web/assets/client/lib/api.js +13 -1
  52. package/dist/web/assets/client/lib/i18n.js +5 -3
  53. package/dist/web/assets/client/views/dock/conversation.js +231 -80
  54. package/dist/web/assets/client/views/models.js +777 -0
  55. package/dist/web/assets/client/views/skills.js +22 -15
  56. package/dist/web/assets/shared/icons.js +2 -0
  57. package/dist/web/assets/shared/manageNav.js +13 -0
  58. package/dist/web/assets/shell/manage.js +54 -0
  59. package/dist/web/assets/shell/models.js +7 -0
  60. package/dist/web/assets/shell/settings.js +1 -35
  61. package/dist/web/assets/shell/settingsContent.js +30 -0
  62. package/dist/web/assets/shell/sidebar.js +1 -0
  63. package/dist/web/assets/styles/index.js +6 -0
  64. package/dist/web/assets/styles/views/conversation.js +133 -0
  65. package/dist/web/assets/styles/views/manage.js +71 -0
  66. package/dist/web/assets/styles/views/models.js +129 -0
  67. package/dist/web/assets/styles/views/settings.js +28 -14
  68. package/dist/web/assets/styles/views/sidebar.js +1 -0
  69. package/dist/web/webServer.js +59 -9
  70. package/docs/cli-information-contract.md +42 -11
  71. package/docs/cli-information-contract.zh-CN.md +28 -7
  72. package/docs/managed-turn-and-session-runtime.md +47 -1
  73. package/docs/managed-turn-and-session-runtime.zh-CN.md +32 -0
  74. package/docs/release-workflow.md +80 -7
  75. package/docs/release-workflow.zh-CN.md +55 -4
  76. package/docs/storage-baseline.md +6 -1
  77. package/docs/storage-baseline.zh-CN.md +4 -1
  78. package/docs/testing/review-evaluation.md +148 -0
  79. package/docs/testing/review-evaluation.zh-CN.md +117 -0
  80. package/docs/testing/verification-levels.md +6 -2
  81. package/docs/testing/verification-levels.zh-CN.md +3 -0
  82. package/package.json +1 -1
  83. package/skills/yui-leader/VERSION +1 -1
  84. package/skills/yui-leader/references/execution.md +29 -0
  85. package/skills/yui-leader/references/replicated-execution.md +7 -0
  86. package/skills/yui-operator/SKILL.md +5 -1
  87. package/skills/yui-operator/VERSION +1 -1
  88. package/skills/yui-reviewer/SKILL.md +35 -0
  89. package/skills/yui-reviewer/VERSION +1 -1
  90. package/skills/yui-runtime/SKILL.md +53 -7
  91. package/skills/yui-runtime/VERSION +1 -1
  92. package/skills/yui-runtime/references/recovery.md +24 -5
@@ -2,11 +2,14 @@ export const SKILLS_SCRIPT = String.raw `
2
2
  import { h } from "/assets/js/lib/dom.js";
3
3
  import { requestJson, submitMutation } from "/assets/js/lib/api.js";
4
4
 
5
- export function mountSkills(root, t) {
5
+ // options: heading (default true) renders the card's own title; onDirty(dirty)
6
+ // reports whether unsaved bindings exist, for the page navigation.
7
+ export function mountSkills(root, t, options = {}) {
6
8
  if (!root) return;
7
9
  const tr = (key, fallback) => t("skills.browser." + key, fallback);
8
- const scope = h("select", null, h("option", { value: "global" }, tr("global", "Global Role")),
9
- h("option", { value: "task" }, tr("task", "Task Role")));
10
+ const translated = key => ({ "data-i18n": "skills.browser." + key });
11
+ const scope = h("select", null, h("option", { value: "global", ...translated("global") }, tr("global", "Global Role")),
12
+ h("option", { value: "task", ...translated("task") }, tr("task", "Task Role")));
10
13
  const task = h("input", { placeholder: "task-123", disabled: true });
11
14
  const roles = h("select");
12
15
  const status = h("p", { role: "status", "aria-live": "polite" });
@@ -16,20 +19,21 @@ export function mountSkills(root, t) {
16
19
  const catalog = h("div.settings-stack");
17
20
  const reader = h("div.settings-stack", { tabindex: "-1" });
18
21
  const acknowledge = h("input", { type: "checkbox" });
19
- const acknowledgement = h("label.settings-reset", null, acknowledge, tr("ack", "Keep any live global Session on its existing version."));
20
- const save = h("button.btn.btn-primary", { type: "button", disabled: true }, tr("save", "Save bindings"));
21
- const reload = h("button.btn", { type: "button" }, tr("reload", "Load / reload Role"));
22
- const more = h("button.btn", { type: "button", hidden: true }, tr("more", "More Skills"));
23
- const search = h("button.btn", { type: "button" }, tr("search", "Search"));
22
+ const acknowledgement = h("label.settings-reset", null, acknowledge, h("span", translated("ack"), tr("ack", "Keep any live global Session on its existing version.")));
23
+ const save = h("button.btn.btn-primary", { type: "button", disabled: true, ...translated("save") }, tr("save", "Save bindings"));
24
+ const reload = h("button.btn", { type: "button", ...translated("reload") }, tr("reload", "Load / reload Role"));
25
+ const more = h("button.btn", { type: "button", hidden: true, ...translated("more") }, tr("more", "More Skills"));
26
+ const search = h("button.btn", { type: "button", ...translated("search") }, tr("search", "Search"));
24
27
  let state = null, draft = [], busy = false, uncertain = false, generation = 0, catalogGeneration = 0, readerGeneration = 0;
25
28
  const selected = () => ({ scope: scope.value, role: roles.value, ...(scope.value === "task" ? { task: task.value.trim() } : {}) });
26
29
  const params = object => new URLSearchParams(object).toString();
27
- const label = (text, control) => h("label.field", null, h("span", null, text), control);
30
+ const label = (text, control, key) => h("label.field", null, h("span", key ? translated(key) : null, text), control);
28
31
  function dirty() { return !!state && JSON.stringify(draft) !== JSON.stringify(state.skills); }
29
32
  function invalidate() {
30
33
  generation++; state = null; draft = []; bindings.replaceChildren(); frozen.replaceChildren(); save.disabled = true;
31
34
  scope.disabled = roles.disabled = false; task.disabled = scope.value !== "task";
32
35
  acknowledgement.hidden = scope.value !== "global";
36
+ options.onDirty?.(false);
33
37
  }
34
38
  function renderBindings() {
35
39
  // A draft remains attached to the exact Role baseline until saved or explicitly reloaded.
@@ -41,6 +45,7 @@ export function mountSkills(root, t) {
41
45
  h("button.btn", { type: "button", onclick: () => read({ id }) }, tr("readSource", "Read current source")))));
42
46
  if (!draft.length) bindings.append(h("p", null, tr("none", "No additional bindings. Built-in Role Skills are loaded automatically.")));
43
47
  save.disabled = busy || uncertain || !dirty();
48
+ options.onDirty?.(dirty());
44
49
  }
45
50
  function renderEvidence(data) {
46
51
  frozen.replaceChildren(h("h3", null, tr("frozen", "Current Session · frozen evidence")));
@@ -106,8 +111,8 @@ export function mountSkills(root, t) {
106
111
  h("h3", null, item.id), h("p", null, item.description || item.error),
107
112
  h("small.mono-line", null, item.source.kind + " · " + item.source.path),
108
113
  h("div.settings-actions", null,
109
- h("button.btn", { type: "button", onclick: () => read({ id: item.id }) }, tr("readSource", "Read current source")),
110
- h("button.btn", { type: "button", disabled: !!item.error, onclick: () => {
114
+ h("button.btn", { type: "button", ...translated("readSource"), onclick: () => read({ id: item.id }) }, tr("readSource", "Read current source")),
115
+ h("button.btn", { type: "button", ...translated("add"), disabled: !!item.error, onclick: () => {
111
116
  if (!state || busy || uncertain) { status.textContent = tr("choose", "Load an explicit Role before editing bindings."); return; }
112
117
  if (!draft.includes(item.id)) draft.push(item.id);
113
118
  renderBindings(); status.textContent = tr("unsaved", "Unsaved bindings. Review the selected Role and save.");
@@ -141,12 +146,14 @@ export function mountSkills(root, t) {
141
146
  search.addEventListener("click", () => loadCatalog());
142
147
  query.addEventListener("keydown", event => { if (event.key === "Enter") { event.preventDefault(); void loadCatalog(); } });
143
148
  window.addEventListener("beforeunload", event => { if (dirty()) { event.preventDefault(); event.returnValue = ""; } });
144
- root.replaceChildren(h("h2", null, tr("title", "Skills · browse & bind")),
145
- h("p", null, tr("intro", "Read existing packages and choose additional bindings for one explicit Role. Scripts are displayed as data, never run.")),
146
- h("div.settings-grid", null, label(tr("scope", "Scope"), scope), label(tr("taskId", "Task ID"), task), label("Role", roles)),
149
+ const heading = options.heading === false ? [] : [h("h2", null, tr("title", "Skills · browse & bind")),
150
+ h("p", null, tr("intro", "Read existing packages and choose additional bindings for one explicit Role. Scripts are displayed as data, never run."))];
151
+ root.replaceChildren(...heading,
152
+ h("div.settings-grid", null, label(tr("scope", "Scope"), scope, "scope"), label(tr("taskId", "Task ID"), task, "taskId"), label("Role", roles)),
147
153
  h("div.settings-actions", null, reload), status, bindings, acknowledgement,
148
- h("div.settings-actions", null, save), frozen, label(tr("query", "Find an existing Skill by name or purpose"), query),
154
+ h("div.settings-actions", null, save), frozen, label(tr("query", "Find an existing Skill by name or purpose"), query, "query"),
149
155
  h("div.settings-actions", null, search), catalog, more, reader);
150
156
  void loadRole(); void loadCatalog();
157
+ return { translate: () => { if (state) { renderBindings(); renderEvidence(state); } } };
151
158
  }
152
159
  `;
@@ -15,6 +15,8 @@ export const ICON_PATHS = Object.freeze({
15
15
  broadcast: ["M12 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2z", "M8.5 8.5a5 5 0 0 0 0 7", "M15.5 8.5a5 5 0 0 1 0 7", "M5.6 5.6a9 9 0 0 0 0 12.8", "M18.4 5.6a9 9 0 0 1 0 12.8"],
16
16
  chevron: ["M9 6l6 6-6 6"],
17
17
  down: ["M6 9l6 6 6-6"],
18
+ up: ["M12 19V5", "M6 11l6-6 6 6"],
19
+ stop: ["M8 8h8v8H8z"],
18
20
  check: ["M5 12.5l4.5 4.5L19 7.5"],
19
21
  alert: ["M12 4l9 16H3z", "M12 10v4", "M12 17v.5"],
20
22
  clock: ["M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18z", "M12 7v5l3 2"],
@@ -0,0 +1,13 @@
1
+ export const MANAGE_SECTIONS = Object.freeze({
2
+ models: [
3
+ { page: "models", section: "providers", key: "models.section.providers", label: "Access" },
4
+ { page: "models", section: "catalog", key: "models.section.catalog", label: "Model catalog" },
5
+ { page: "models", section: "quota", key: "models.section.quota", label: "Quota" }
6
+ ],
7
+ settings: [
8
+ { page: "settings", section: "preferences", key: "settings.section.preferences", label: "Preferences" },
9
+ { page: "settings", section: "skills", key: "settings.section.skills", label: "Skills" },
10
+ { page: "settings", section: "server", key: "settings.section.server", label: "Agents & server" },
11
+ { page: "models", section: "connection", key: "models.section.connection", label: "Model connection" }
12
+ ]
13
+ });
@@ -0,0 +1,54 @@
1
+ import { STYLESHEETS } from "../styles/index.js";
2
+ import { DEFAULT_DARK_THEME } from "../shared/themes.js";
3
+ import { iconSvg } from "../shared/icons.js";
4
+ import { MANAGE_SECTIONS } from "../shared/manageNav.js";
5
+ // One frame and entry point for /models/* and /settings/*. The shared router
6
+ // changes sections without unloading their controllers or drafts. Leaving
7
+ // the management document still uses the browser's unsaved-draft prompt.
8
+ const escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
9
+ const sectionLink = (item) => `<a class="manage-link" href="/${item.section === "connection" ? "settings" : item.page}/${item.section}" data-page="${item.page}" data-section="${item.section}"><span data-i18n="${item.key}">${escapeHtml(item.label)}</span><i class="manage-dirty" aria-hidden="true"></i></a>`;
10
+ const area = (name, icon, key, label) => `
11
+ <div class="manage-area" data-area-group="${name}">
12
+ <a class="manage-primary" href="/${name}" data-area="${name}">${iconSvg(icon)}<span data-i18n="${key}">${label}</span></a>
13
+ <div class="manage-sections" role="group" aria-label="${label}" data-i18n-aria-label="${key}">
14
+ ${MANAGE_SECTIONS[name].map(sectionLink).join("\n ")}
15
+ </div>
16
+ </div>`;
17
+ const RAIL = `<aside class="manage-rail">
18
+ <header class="manage-rail-head">
19
+ <a class="brand" href="/" title="Workbench" data-i18n-title="manage.workbench">
20
+ <span class="brand-mark" aria-hidden="true">結</span><span class="brand-name">Yui</span>
21
+ </a>
22
+ <label class="manage-locale"><span class="sr-only" data-i18n="settings.language">Language</span><select id="locale-select"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
23
+ </header>
24
+ <nav class="manage-nav" aria-label="Main navigation" data-i18n-aria-label="manage.nav">
25
+ <div class="manage-area" data-area-group="workbench">
26
+ <a class="manage-primary" href="/" data-area="workbench">${iconSvg("inbox")}<span data-i18n="manage.workbench">Workbench</span></a>
27
+ </div>${area("models", "layers", "models.title", "Models")}${area("settings", "settings", "settings.title", "Settings")}
28
+ </nav>
29
+ </aside>`;
30
+ // One management document. `main` is the page's reading column markup.
31
+ export function manageDocument(options) {
32
+ return `<!doctype html>
33
+ <html lang="en" data-theme="${DEFAULT_DARK_THEME.id}">
34
+ <head>
35
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
36
+ <meta name="yui-web-token" content="__YUI_WEB_TOKEN__">
37
+ <script src="/assets/theme.js"></script>
38
+ <link rel="icon" href="data:,"><title>Yui · ${options.title}</title>
39
+ ${STYLESHEETS.map(s => `<link rel="stylesheet" href="/assets/css/${s.name}.css">`).join("\n ")}
40
+ </head>
41
+ <body class="manage-body ${options.page}-body">
42
+ <div class="manage-app" data-page="${options.page}" hidden>
43
+ ${RAIL}
44
+ <main class="manage-main" id="manage-main">
45
+ ${options.main}
46
+ </main>
47
+ </div>
48
+ <script type="module" src="/assets/js/app/manage.js"></script>
49
+ </body></html>`;
50
+ }
51
+ // The common section heading: title and one explanatory line.
52
+ export function manageHead(id, key, label, hintKey, hint) {
53
+ return `<header class="manage-head"><div class="manage-heading"><h1 id="${id}" data-i18n="${key}">${escapeHtml(label)}</h1><p data-i18n="${hintKey}">${escapeHtml(hint)}</p></div></header>`;
54
+ }
@@ -0,0 +1,7 @@
1
+ import { manageDocument } from "./manage.js";
2
+ import { SETTINGS_MAIN } from "./settingsContent.js";
3
+ // One document for /models/* and /settings/*. Both controllers stay mounted
4
+ // across history navigation, preserving drafts, observations and locale.
5
+ export const MODELS_HTML = manageDocument({ page: "models", title: "Models", main: `
6
+ <div id="settings-root" hidden>${SETTINGS_MAIN}</div>
7
+ <div id="models-root" class="models-root" hidden></div>` });
@@ -1,35 +1 @@
1
- import { STYLESHEETS } from "../styles/index.js";
2
- import { DEFAULT_DARK_THEME } from "../shared/themes.js";
3
- export const SETTINGS_HTML = `<!doctype html>
4
- <html lang="en" data-theme="${DEFAULT_DARK_THEME.id}">
5
- <head>
6
- <meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
7
- <meta name="yui-web-token" content="__YUI_WEB_TOKEN__">
8
- <script src="/assets/theme.js"></script>
9
- <link rel="icon" href="data:,"><title>Yui · Settings</title>
10
- ${STYLESHEETS.map(s => `<link rel="stylesheet" href="/assets/css/${s.name}.css">`).join("\n")}
11
- </head>
12
- <body class="settings-body">
13
- <main class="settings-page">
14
- <header class="settings-header"><a class="btn" href="/">← Yui</a><h1 data-i18n="settings.title">Settings</h1></header>
15
- <p data-i18n="settings.scope">Browser preferences apply here immediately. Server settings are desired configuration, not proof that existing Sessions adopted them.</p>
16
- <section class="settings-card">
17
- <h2 data-i18n="settings.browser">This browser</h2>
18
- <div class="settings-grid">
19
- <fieldset class="field"><legend data-i18n="settings.theme">Theme</legend><div id="theme-options" class="theme-options"></div></fieldset>
20
- <div class="settings-stack">
21
- <label class="field"><span data-i18n="settings.language">Language</span><select id="locale-select"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
22
- <label class="field"><span data-i18n="settings.accessMode">Default session access</span><select id="access-mode"><option value="native" data-i18n="settings.native">Native terminal</option><option value="structured" data-i18n="settings.structured">Structured view</option></select></label>
23
- <p data-i18n="settings.accessHelp">Preference for subsequent session selection where supported. It does not switch a live Session.</p>
24
- <button id="reset-layout" type="button" class="btn" data-i18n="settings.resetLayout">Reset saved workspace layout</button>
25
- <p id="browser-receipt" role="status"></p>
26
- </div>
27
- </div>
28
- </section>
29
- <section id="skill-browser" class="settings-card settings-stack"></section>
30
- <label class="field settings-search"><span data-i18n="settings.search">Find a setting</span><input id="settings-search" type="search" autocomplete="off"></label>
31
- <p id="settings-status" role="status"></p>
32
- <div id="settings-groups" class="settings-stack"></div>
33
- </main>
34
- <script type="module" src="/assets/js/app/settings.js"></script>
35
- </body></html>`;
1
+ export { MODELS_HTML as SETTINGS_HTML } from "./models.js";
@@ -0,0 +1,30 @@
1
+ import { manageHead } from "./manage.js";
2
+ // Settings content in the shared management document. Sections stay mounted
3
+ // across /settings/* and /models/* navigation.
4
+ export const SETTINGS_MAIN = `
5
+ <section class="manage-section" data-section="preferences" aria-labelledby="settings-preferences-title">
6
+ ${manageHead("settings-preferences-title", "settings.section.preferences", "Preferences", "settings.hint.preferences", "Theme, language and workspace layout for this browser. Changes apply here immediately and are never sent to the server.")}
7
+ <section class="card settings-card">
8
+ <h2 data-i18n="settings.browser">This browser</h2>
9
+ <div class="settings-grid">
10
+ <fieldset class="field"><legend data-i18n="settings.theme">Theme</legend><div id="theme-options" class="theme-options"></div></fieldset>
11
+ <div class="settings-stack">
12
+ <label class="field"><span data-i18n="settings.language">Language</span><select id="settings-locale"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
13
+ <label class="field"><span data-i18n="settings.accessMode">Default session access</span><select id="access-mode"><option value="native" data-i18n="settings.native">Native terminal</option><option value="structured" data-i18n="settings.structured">Structured view</option></select></label>
14
+ <p data-i18n="settings.accessHelp">Preference for subsequent session selection where supported. It does not switch a live Session.</p>
15
+ <button id="reset-layout" type="button" class="btn" data-i18n="settings.resetLayout">Reset saved workspace layout</button>
16
+ <p id="browser-receipt" role="status"></p>
17
+ </div>
18
+ </div>
19
+ </section>
20
+ </section>
21
+ <section class="manage-section" data-section="skills" aria-labelledby="settings-skills-title" hidden>
22
+ ${manageHead("settings-skills-title", "settings.section.skills", "Skills", "skills.browser.intro", "Read existing packages and choose additional bindings for one explicit Role. Scripts are displayed as data, never run.")}
23
+ <section id="skill-browser" class="card settings-card settings-stack"></section>
24
+ </section>
25
+ <section class="manage-section" data-section="server" aria-labelledby="settings-server-title" hidden>
26
+ ${manageHead("settings-server-title", "settings.section.server", "Agents & server", "settings.hint.server", "Desired configuration for Yui, Roles, Agents and Profiles. Saving targets later launches; it is not proof that existing Sessions adopted it.")}
27
+ <label class="field settings-search"><span data-i18n="settings.search">Find a setting</span><input id="settings-search" type="search" autocomplete="off"></label>
28
+ <p id="settings-status" role="status"></p>
29
+ <div id="settings-groups" class="settings-stack"></div>
30
+ </section>`;
@@ -39,6 +39,7 @@ export const SIDEBAR_HTML = ` <aside class="sidebar" aria-label="Tasks" data-
39
39
  <button id="global-input-open" class="foot-btn" type="button" title="Global input" data-i18n-title="global.open">
40
40
  ${iconSvg("broadcast")}<span data-i18n="global.open">Global input</span>
41
41
  </button>
42
+ <a id="models-open" class="icon-btn" href="/models" aria-label="Models" data-i18n-aria-label="models.title" title="Models" data-i18n-title="models.title">${iconSvg("layers")}</a>
42
43
  <button id="settings-open" class="icon-btn" type="button" aria-label="Settings" data-i18n-aria-label="settings.title" title="Settings">${iconSvg("settings")}</button>
43
44
  </footer>
44
45
  </aside>
@@ -8,13 +8,16 @@ import { CONTROL_STYLES } from "./ui/controls.js";
8
8
  import { FORM_STYLES } from "./ui/forms.js";
9
9
  import { SURFACE_STYLES } from "./ui/surfaces.js";
10
10
  import { DELIVERY_STYLES } from "./views/delivery.js";
11
+ import { CONVERSATION_STYLES } from "./views/conversation.js";
11
12
  import { DOCK_STYLES } from "./views/dock.js";
12
13
  import { PAGE_STYLES } from "./views/page.js";
13
14
  import { RECORD_STYLES } from "./views/records.js";
14
15
  import { SIDEBAR_STYLES } from "./views/sidebar.js";
15
16
  import { TASK_STYLES } from "./views/task.js";
16
17
  import { TERMINAL_STYLES } from "./views/terminal.js";
18
+ import { MANAGE_STYLES } from "./views/manage.js";
17
19
  import { SETTINGS_STYLES } from "./views/settings.js";
20
+ import { MODELS_STYLES } from "./views/models.js";
18
21
  // The stylesheet registry, in cascade order: foundations (fonts, tokens,
19
22
  // element defaults), the workspace frame, shared UI primitives, then one
20
23
  // sheet per view, rendered prose, and breakpoints last so they override
@@ -34,8 +37,11 @@ export const STYLESHEETS = Object.freeze([
34
37
  { name: "views/records", body: RECORD_STYLES },
35
38
  { name: "views/delivery", body: DELIVERY_STYLES },
36
39
  { name: "views/dock", body: DOCK_STYLES },
40
+ { name: "views/conversation", body: CONVERSATION_STYLES },
37
41
  { name: "views/terminal", body: TERMINAL_STYLES },
42
+ { name: "views/manage", body: MANAGE_STYLES },
38
43
  { name: "views/settings", body: SETTINGS_STYLES },
44
+ { name: "views/models", body: MODELS_STYLES },
39
45
  { name: "markdown", body: MARKDOWN_STYLES },
40
46
  { name: "layout/responsive", body: RESPONSIVE_STYLES }
41
47
  ]);
@@ -0,0 +1,133 @@
1
+ /*
2
+ * CONVERSATION VIEW — the right-dock Conversation pane: a slim header, a
3
+ * borderless feed (bubble for the user, plain prose for the assistant, one-line
4
+ * activity rows) and a rounded composer with popovers. Colours come from
5
+ * theme tokens only; the Discussion pane keeps its own sheet (views/dock).
6
+ */
7
+ export const CONVERSATION_STYLES = `
8
+ #dock-conversation{min-width:0}
9
+ .cv-head{flex:none;display:flex;align-items:center;gap:6px;padding:8px 10px 4px 8px}
10
+ .cv-picker{flex:1;min-width:0;display:flex;align-items:center;gap:6px}
11
+ /* background-color only: the shorthand would drop the global select arrow */
12
+ .cv-session{flex:1;min-width:0;max-width:100%;height:30px;padding:0 28px 0 8px;border:0;background-color:transparent;color:var(--ink);font-weight:600;font-size:var(--fs-md);text-overflow:ellipsis;cursor:pointer;border-radius:var(--r-sm)}
13
+ .cv-session:hover,.cv-session:focus{background-color:var(--surface-3);border:0;box-shadow:none}
14
+ /* The global select:focus rule resets the whole background, so the arrow is restated for every state */
15
+ .cv-session,.cv-session:hover,.cv-session:focus{background-image:linear-gradient(45deg,transparent 50%,var(--ink-3) 50%),linear-gradient(135deg,var(--ink-3) 50%,transparent 50%);background-position:calc(100% - 15px) 50%,calc(100% - 11px) 50%;background-size:4px 4px;background-repeat:no-repeat}
16
+ .cv-status{flex:none;display:flex;align-items:center;gap:6px;margin:0;padding:0 16px 6px;color:var(--ink-3);font-size:var(--fs-xs)}
17
+ .cv-status::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--idle);flex:none}
18
+ .cv-status.is-active::before{background:var(--ok)}
19
+ .cv-status.is-waiting::before{background:var(--warn)}
20
+ .cv-status.is-failed::before,.cv-status.is-unknown::before{background:var(--bad)}
21
+ .cv-status:empty{display:none}
22
+ .cv-older{flex:none;display:flex;justify-content:center}
23
+ .cv-older:empty{display:none}
24
+ .cv-stage{position:relative;flex:1;min-height:0;display:flex;flex-direction:column}
25
+ .cv-stage .feed{gap:18px;padding:12px 16px 20px}
26
+ .cv-jump{position:absolute;left:50%;bottom:10px;transform:translateX(-50%);width:32px;height:32px;display:grid;place-items:center;padding:0;border:1px solid var(--line-2);border-radius:50%;background:var(--surface-2);color:var(--ink);box-shadow:var(--shadow-1);cursor:pointer}
27
+ .cv-jump:hover{background:var(--surface-3)}
28
+ .cv-jump[hidden]{display:none}
29
+ .cv-note{margin:0;color:var(--ink-3);font-size:var(--fs-xs);overflow-wrap:anywhere}
30
+ .cv-link{padding:2px 4px;border:0;background:none;color:var(--ink-3);font-size:var(--fs-xs);cursor:pointer;border-radius:var(--r-xs)}
31
+ .cv-link:hover{color:var(--ink);background:var(--surface-3)}
32
+ .cv-link[hidden],.cv-stop[hidden],.cv-chip[hidden]{display:none}
33
+ .cv-code{margin:6px 0 0;padding:8px 10px;max-height:200px;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;border-radius:var(--r-sm);background:var(--sunken);font:var(--fs-xs)/1.5 var(--font-mono)}
34
+ .cv-identity{margin:0;color:var(--ink-4);font-size:var(--fs-2xs);overflow-wrap:anywhere}
35
+ /* Messages */
36
+ .cv-msg{min-width:0;overflow-wrap:anywhere}
37
+ .cv-user{align-self:flex-end;max-width:88%;display:flex;flex-direction:column;align-items:flex-end;gap:4px}
38
+ .cv-bubble{padding:9px 14px;border-radius:18px;background:var(--info-soft);color:var(--ink);font-size:var(--fs-md);line-height:1.5}
39
+ .cv-tag{color:var(--ink-4);font-size:var(--fs-2xs)}
40
+ .cv-assistant{position:relative;font-size:var(--fs-md);line-height:1.6}
41
+ /* Copy: always visible under the newest message, a hover badge on older ones */
42
+ .cv-actions{display:flex;gap:2px;position:absolute;top:-6px;right:0;opacity:0;transition:opacity var(--t-fast)}
43
+ .cv-assistant:hover .cv-actions,.cv-assistant:focus-within .cv-actions{opacity:1}
44
+ .cv-assistant:last-child .cv-actions{position:static;margin-top:4px;opacity:1}
45
+ .cv-actions .cv-iconbtn{background:var(--surface-2)}
46
+ .cv-assistant:last-child .cv-actions .cv-iconbtn{background:transparent}
47
+ /* Activity: a single muted line that expands to its detail */
48
+ .cv-activity{color:var(--ink-3);font-size:var(--fs-sm);min-width:0}
49
+ .cv-activity>summary{display:flex;align-items:center;gap:8px;min-width:0;padding:2px 0;list-style:none;cursor:pointer;border-radius:var(--r-xs)}
50
+ .cv-activity>summary::-webkit-details-marker{display:none}
51
+ .cv-activity>summary:hover{color:var(--ink)}
52
+ .cv-activity .icon{width:15px;height:15px;flex:none}
53
+ .cv-act-label{flex:none}
54
+ .cv-act-detail{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink-4)}
55
+ .cv-activity[open]>summary .cv-act-detail{white-space:normal}
56
+ .cv-group>summary .cv-act-label{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
57
+ .cv-chevron{width:13px;height:13px;flex:none;transition:transform var(--t-fast)}
58
+ .cv-group[open]>summary .cv-chevron{transform:rotate(180deg)}
59
+ .cv-group-body{display:grid;gap:4px;padding:6px 0 2px 23px}
60
+ .cv-step{color:var(--ink-2);font-size:var(--fs-sm);line-height:1.55;min-width:0;overflow-wrap:anywhere}
61
+ /* Composer */
62
+ .cv-composer{flex:none;display:grid;gap:6px;padding:8px 12px 12px;min-width:0}
63
+ .cv-box{display:grid;gap:4px;padding:10px 10px 8px 14px;border:1px solid var(--line-2);border-radius:20px;background:var(--surface-2);box-shadow:var(--shadow-1);transition:border-color var(--t-fast),box-shadow var(--t-fast);min-width:0}
64
+ .cv-box:focus-within{border-color:var(--line-3)}
65
+ .cv-input{display:block;width:100%;box-sizing:border-box;min-height:44px;max-height:240px;padding:0;border:0;background:transparent;box-shadow:none;resize:none;color:var(--ink);font-size:var(--fs-md);line-height:1.5}
66
+ .cv-input:hover,.cv-input:focus{border:0;box-shadow:none;background:transparent;outline:none}
67
+ .cv-tools{display:flex;align-items:center;gap:6px;min-width:0}
68
+ .cv-iconbtn{width:30px;height:30px;display:inline-grid;place-items:center;padding:0;border:0;border-radius:50%;background:transparent;color:var(--ink-2);cursor:pointer;list-style:none;flex:none}
69
+ .cv-iconbtn::-webkit-details-marker{display:none}
70
+ .cv-iconbtn:hover{background:var(--surface-3);color:var(--ink)}
71
+ .cv-iconbtn .icon{width:17px;height:17px}
72
+ .cv-send,.cv-stop{width:32px;height:32px;display:inline-grid;place-items:center;padding:0;border:0;border-radius:50%;background:var(--ink);color:var(--canvas);cursor:pointer;flex:none}
73
+ .cv-send .icon,.cv-stop .icon{width:16px;height:16px}
74
+ .cv-send:hover,.cv-stop:hover{opacity:.85}
75
+ /* An empty draft reads as inactive, like the reference composers */
76
+ .cv-box:has(.cv-input:placeholder-shown) .cv-send{background:var(--surface-3);color:var(--ink-4)}
77
+ .cv-send:disabled{background:var(--surface-3);color:var(--ink-4);cursor:default;opacity:1}
78
+ .cv-chip{display:inline-flex;align-items:center;gap:5px;min-width:0;padding:3px 8px;border-radius:var(--r-pill);background:var(--tone-soft,var(--warn-soft));color:var(--tone,var(--warn));font-size:var(--fs-xs)}
79
+ .cv-chip .icon{width:13px;height:13px;flex:none}
80
+ .cv-chip span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
81
+ .cv-pill{display:inline-flex;align-items:center;gap:4px;max-width:180px;height:28px;padding:0 8px;border-radius:var(--r-pill);color:var(--ink-3);font-size:var(--fs-sm);list-style:none;cursor:pointer}
82
+ .cv-pill::-webkit-details-marker{display:none}
83
+ .cv-pill:hover,.cv-pop[open]>.cv-pill{background:var(--surface-3);color:var(--ink)}
84
+ .cv-pill .icon{width:13px;height:13px;flex:none}
85
+ .cv-model-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
86
+ .cv-hint{display:flex;flex-wrap:wrap;align-items:center;gap:2px 8px;padding:0 6px;min-width:0}
87
+ .cv-hint:empty{display:none}
88
+ .cv-hint .cv-note:empty{display:none}
89
+ .conversation-slash{padding:0 2px}
90
+ .cv-cmd{padding:3px 10px;border:1px solid var(--line-2);border-radius:var(--r-pill);background:var(--surface-2);color:var(--ink-2);font-size:var(--fs-xs);cursor:pointer}
91
+ .cv-cmd:hover{background:var(--surface-3);color:var(--ink)}
92
+ .cv-file{display:flex;align-items:center;gap:8px;padding:4px 8px;border-radius:var(--r-sm);background:var(--surface-3);font-size:var(--fs-xs);min-width:0}
93
+ .cv-file .small{flex:1;min-width:0;overflow-wrap:anywhere}
94
+ .cv-x{width:22px;height:22px;display:grid;place-items:center;padding:0;border:0;border-radius:50%;background:transparent;color:var(--ink-3);cursor:pointer}
95
+ .cv-x:hover{background:var(--line-2);color:var(--ink)}
96
+ .cv-x .icon{width:13px;height:13px}
97
+ /* Popovers: attach and model open upward, the header menu opens downward */
98
+ .cv-pop{position:relative;flex:none}
99
+ .cv-pop>summary{list-style:none}
100
+ .cv-pop>summary::-webkit-details-marker{display:none}
101
+ .cv-popbody{position:absolute;z-index:20;display:grid;gap:8px;width:min(300px,calc(100vw - 32px));max-height:min(420px,60vh);overflow:auto;padding:10px;border:1px solid var(--line-2);border-radius:var(--r-lg);background:var(--surface-2);box-shadow:var(--shadow-2);box-sizing:border-box}
102
+ .cv-attach>.cv-popbody{left:0;bottom:calc(100% + 8px)}
103
+ .cv-model>.cv-popbody{right:0;bottom:calc(100% + 8px)}
104
+ .cv-menu>.cv-popbody{right:0;top:calc(100% + 6px)}
105
+ .cv-pop-title{margin:0;padding:0 4px;color:var(--ink-3);font-size:var(--fs-xs)}
106
+ .cv-pop-actions{display:flex;align-items:center;justify-content:space-between;gap:8px}
107
+ .cv-options{display:grid;gap:1px}
108
+ .cv-option{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px;border:0;border-radius:var(--r-sm);background:transparent;color:var(--ink);text-align:left;font-size:var(--fs-md);cursor:pointer}
109
+ .cv-option:hover{background:var(--surface-3)}
110
+ .cv-option-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
111
+ .cv-option-id{display:none}
112
+ .cv-option .icon{width:15px;height:15px;flex:none;visibility:hidden}
113
+ .cv-option.is-selected .icon{visibility:visible}
114
+ .cv-field{display:grid;gap:4px;font-size:var(--fs-xs);color:var(--ink-3)}
115
+ .cv-field select{min-width:0;max-width:100%}
116
+ .cv-sub>summary{cursor:pointer;color:var(--ink-3);font-size:var(--fs-xs)}
117
+ /* Native requests and leader questions */
118
+ .cv-requests{flex:0 1 auto;max-height:35%;overflow-y:auto;overflow-wrap:anywhere;padding:0 12px}
119
+ .cv-requests:empty{display:none}
120
+ .cv-card{display:grid;gap:8px;margin:6px 0;padding:12px;border:1px solid var(--line-2);border-radius:var(--r-lg);background:var(--surface-2)}
121
+ .cv-card-title{margin:0;font-size:var(--fs-sm);font-weight:600}
122
+ .cv-requests>.cv-card-title{margin:8px 4px 0}
123
+ .cv-card label{display:grid;gap:4px}
124
+ .cv-card input,.cv-card select,.cv-card textarea{min-width:0;max-width:100%;box-sizing:border-box}
125
+ /* Keyboard focus */
126
+ .cv-iconbtn:focus-visible,.cv-send:focus-visible,.cv-stop:focus-visible,.cv-jump:focus-visible,.cv-pill:focus-visible,.cv-option:focus-visible,.cv-link:focus-visible,.cv-cmd:focus-visible,.cv-x:focus-visible,.cv-activity>summary:focus-visible,.cv-sub>summary:focus-visible{outline:none;box-shadow:var(--focus)}
127
+ .cv-session:focus-visible{box-shadow:var(--focus)}
128
+ .cv-input:focus-visible{outline:none;box-shadow:none}
129
+ @media(max-width:760px){
130
+ .cv-user{max-width:94%}
131
+ .cv-composer{padding-bottom:max(12px,env(safe-area-inset-bottom,0px))}
132
+ }
133
+ `;
@@ -0,0 +1,71 @@
1
+ import { BREAKPOINTS } from "../../shared/geometry.js";
2
+ /*
3
+ * MANAGE VIEW — the frame shared by the management pages (/models and
4
+ * /settings): a rail in the dashboard sidebar's language (brand, the fixed
5
+ * Workbench / Models / Settings navigation with every section listed under
6
+ * its area, the language control) beside one reading column whose sections
7
+ * share a heading (area kicker, title, explanation). Page sheets add only
8
+ * their own content. Below the narrow breakpoint the rail becomes a top bar:
9
+ * brand and language, the three main destinations, then the current area's
10
+ * sections, wrapping so every entry stays visible.
11
+ */
12
+ export const MANAGE_STYLES = `
13
+ .manage-body{display:block;height:auto;min-height:100vh;min-height:100dvh;overflow:auto}
14
+ .manage-app{display:grid;grid-template-columns:240px minmax(0,1fr);min-height:100vh;min-height:100dvh}
15
+ .manage-app[hidden],#models-root[hidden],#settings-root[hidden]{display:none}
16
+
17
+ /* Rail */
18
+ .manage-rail{position:sticky;top:0;display:flex;flex-direction:column;height:100vh;height:100dvh;min-width:0;overflow-y:auto;background:var(--surface);border-right:1px solid var(--line)}
19
+ .manage-rail-head{flex:none;display:flex;align-items:center;gap:10px;height:var(--header-h);padding:0 12px 0 16px;border-bottom:1px solid var(--line)}
20
+ .manage-rail-head .brand{flex:1;color:var(--ink);text-decoration:none}
21
+ .manage-locale{flex:none;min-width:0}
22
+ .manage-locale select{height:30px;width:auto;max-width:124px;padding:0 26px 0 9px;line-height:28px;font-size:var(--fs-xs)}
23
+ .manage-nav{display:flex;flex-direction:column;gap:6px;padding:12px 8px 20px;min-width:0}
24
+ .manage-area{display:grid;gap:2px;min-width:0}
25
+ .manage-primary,.manage-link{position:relative;display:flex;align-items:center;min-width:0;border-radius:var(--r-md);text-decoration:none;white-space:nowrap;transition:background var(--t-fast),color var(--t-fast)}
26
+ .manage-primary span,.manage-link span{overflow:hidden;text-overflow:ellipsis}
27
+ .manage-primary{gap:9px;height:34px;padding:0 10px;color:var(--ink-2);font-size:var(--fs-md);font-weight:550}
28
+ .manage-primary .icon{width:16px;height:16px;flex:none;color:var(--ink-4)}
29
+ .manage-primary:hover,.manage-link:hover{background:var(--surface-3);color:var(--ink)}
30
+ .manage-primary[aria-current]{color:var(--ink)}
31
+ .manage-primary[aria-current] .icon{color:var(--accent)}
32
+ .manage-sections{display:grid;gap:1px;margin:0 0 4px 18px;padding-left:8px;border-left:1px solid var(--line);min-width:0}
33
+ .manage-link{gap:8px;height:30px;padding:0 10px;color:var(--ink-3);font-size:var(--fs-sm);font-weight:500}
34
+ .manage-link[aria-current="page"]{background:var(--surface-3);color:var(--ink);box-shadow:inset 0 0 0 1px var(--line-2)}
35
+ .manage-link[aria-current="page"]::before{content:"";position:absolute;left:-9px;top:6px;bottom:6px;width:2px;border-radius:2px;background:var(--accent)}
36
+ .manage-dirty{display:none;width:7px;height:7px;margin-left:auto;flex:none;border-radius:50%;background:var(--warn);box-shadow:0 0 0 3px var(--warn-soft)}
37
+ .manage-link[data-dirty="true"] .manage-dirty{display:inline-block}
38
+
39
+ /* Reading column and the shared section heading */
40
+ .manage-main{min-width:0;padding:28px var(--gutter) 72px}
41
+ .manage-main>*{max-width:1120px;margin-left:auto;margin-right:auto}
42
+ .manage-scope{margin-bottom:14px;color:var(--ink-3);font-size:var(--fs-xs);line-height:1.5;overflow-wrap:anywhere}
43
+ .manage-section{display:grid;gap:14px;min-width:0}
44
+ .manage-section[hidden]{display:none}
45
+ .manage-head{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:12px 20px;min-width:0}
46
+ .manage-heading{display:grid;gap:4px;flex:1 1 320px;min-width:0}
47
+ .manage-heading .kicker{margin:0}
48
+ .manage-heading h1{font-size:var(--fs-2xl);font-weight:650;letter-spacing:-.02em;line-height:1.2;overflow-wrap:anywhere}
49
+ .manage-heading p:not(.kicker){color:var(--ink-3);font-size:var(--fs-sm);line-height:1.55;overflow-wrap:anywhere}
50
+ .manage-body input[type=checkbox]{width:16px;height:16px;padding:0;accent-color:var(--accent)}
51
+
52
+ @media(max-width:${BREAKPOINTS.narrow}px){
53
+ .manage-app{grid-template-columns:minmax(0,1fr)}
54
+ .manage-rail{position:static;height:auto;overflow:visible;border-right:0;border-bottom:1px solid var(--line)}
55
+ .manage-rail-head{height:52px}
56
+ .manage-nav{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:4px;padding:8px 10px}
57
+ .manage-area{display:contents}
58
+ .manage-primary{justify-content:center;gap:7px;height:36px;padding:0 6px;font-size:var(--fs-sm);background:var(--surface-2);box-shadow:inset 0 0 0 1px var(--line)}
59
+ .manage-primary[aria-current]{background:var(--surface-3);box-shadow:inset 0 0 0 1px var(--line-2),inset 0 -2px 0 var(--accent)}
60
+ /* Only the current area's sections, as a wrapping strip under the tabs. */
61
+ .manage-sections{display:none;grid-column:1/-1;grid-row:2;margin:4px 0 0;padding:0;border:0}
62
+ .manage-area[data-active="true"] .manage-sections{display:flex;flex-wrap:wrap;gap:4px}
63
+ .manage-link{flex:0 1 auto;max-width:100%;height:32px;padding:0 10px;border:1px solid var(--line)}
64
+ .manage-link[aria-current="page"]{border-color:var(--line-2)}
65
+ .manage-link[aria-current="page"]::before{left:10px;right:10px;top:auto;bottom:2px;width:auto;height:2px}
66
+ .manage-main{padding:18px 14px 56px}
67
+ }
68
+ @media(max-width:${BREAKPOINTS.small}px){
69
+ .manage-heading h1{font-size:var(--fs-xl)}
70
+ }
71
+ `;