@zq-silk/yui 2.17.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.
Files changed (70) hide show
  1. package/dist/cli/commandCatalog.js +2 -1
  2. package/dist/commands/controllerCommands.js +13 -4
  3. package/dist/commands/durableJobCommands.js +50 -3
  4. package/dist/commands/globalRoleCommands.js +18 -12
  5. package/dist/commands/inboxCommands.js +1 -0
  6. package/dist/commands/operatorCommands.js +10 -2
  7. package/dist/controlPlaneCli.js +21 -4
  8. package/dist/controller/clientRuntime.js +34 -13
  9. package/dist/controller/controller.js +35 -22
  10. package/dist/controller/diagnosticJournal.js +214 -0
  11. package/dist/controller/fileSchedulerStoreAdapter.js +139 -101
  12. package/dist/controller/globalInputDelivery.js +6 -2
  13. package/dist/controller/globalRuntimeAttention.js +11 -3
  14. package/dist/controller/jobControl.js +1 -1
  15. package/dist/controller/jobDefinitionControl.js +24 -13
  16. package/dist/controller/jobSupervisor.js +4 -0
  17. package/dist/controller/runtime.js +66 -4
  18. package/dist/controller/workerSchedulerStore.js +24 -0
  19. package/dist/core/boundedRpc.js +4 -0
  20. package/dist/core/controllerServer.js +1 -1
  21. package/dist/core/controllerTelemetry.js +92 -19
  22. package/dist/job/globalShell.js +51 -0
  23. package/dist/job/jobBusinessResult.js +4 -3
  24. package/dist/job/jobDefinition.js +6 -1
  25. package/dist/job/jobRunner.js +9 -0
  26. package/dist/job/scheduleService.js +2 -0
  27. package/dist/message/globalInboxRecovery.js +30 -19
  28. package/dist/message/globalProviderRetry.js +7 -1
  29. package/dist/runtime/builtinAgentErrorMappers.js +4 -0
  30. package/dist/runtime/globalSessionHealth.js +39 -0
  31. package/dist/scheduler/roleRunStall.js +10 -10
  32. package/dist/storage/persistenceWorker.js +41 -19
  33. package/dist/storage/sqliteSchema.js +35 -11
  34. package/dist/storage/sqliteStore.js +86 -8
  35. package/dist/storage/storageVersions.js +1 -1
  36. package/dist/storage/storeRpc.js +25 -11
  37. package/dist/storage/taskReadRevisionSchema.js +39 -0
  38. package/dist/web/assets/client/app/catalog.js +0 -29
  39. package/dist/web/assets/client/app/jobs.js +115 -0
  40. package/dist/web/assets/client/app/main.js +17 -6
  41. package/dist/web/assets/client/app/manage.js +1 -0
  42. package/dist/web/assets/client/app/settings.js +1 -5
  43. package/dist/web/assets/client/i18n/en.js +203 -18
  44. package/dist/web/assets/client/i18n/zh-CN.js +202 -17
  45. package/dist/web/assets/client/index.js +2 -0
  46. package/dist/web/assets/client/layout/manageShell.js +61 -15
  47. package/dist/web/assets/client/layout/router.js +2 -1
  48. package/dist/web/assets/client/lib/prefs.js +1 -1
  49. package/dist/web/assets/client/views/jobs.js +1042 -160
  50. package/dist/web/assets/client/views/sidebar.js +3 -0
  51. package/dist/web/assets/shared/manageNav.js +3 -1
  52. package/dist/web/assets/shell/appRail.js +2 -1
  53. package/dist/web/assets/shell/manage.js +7 -2
  54. package/dist/web/assets/shell/models.js +4 -3
  55. package/dist/web/assets/shell/settingsContent.js +0 -3
  56. package/dist/web/assets/shell/sidebar.js +0 -1
  57. package/dist/web/assets/styles/views/jobs.js +301 -12
  58. package/dist/web/assets/styles/views/sidebar.js +1 -2
  59. package/dist/web/webServer.js +28 -1
  60. package/docs/global-shell-jobs.md +78 -0
  61. package/docs/job-runs.md +6 -3
  62. package/docs/managed-turn-and-session-runtime.md +74 -0
  63. package/docs/managed-turn-and-session-runtime.zh-CN.md +51 -0
  64. package/docs/release-workflow.md +43 -0
  65. package/docs/release-workflow.zh-CN.md +29 -0
  66. package/docs/storage-baseline.md +14 -1
  67. package/docs/storage-baseline.zh-CN.md +6 -1
  68. package/docs/testing/verification-levels.md +2 -2
  69. package/docs/testing/verification-levels.zh-CN.md +2 -2
  70. package/package.json +1 -1
@@ -16,22 +16,30 @@ export function manageArea(page, section) {
16
16
  export function pageSections(page) {
17
17
  return Object.values(SECTIONS).flat().filter(item => item.page === page).map(item => item.section);
18
18
  }
19
+ const TASK_ID = /^task-\d+$/;
19
20
  export function readManageRoute(url) {
20
21
  const parts = url.pathname.split("/").filter(Boolean);
22
+ // Jobs has its own entry. The retired Settings section keeps its links
23
+ // working: it opens the same Job (canonicalized without a Back step).
24
+ const legacyJobs = parts[0] === "settings" && (parts[1] || url.searchParams.get("section")) === "jobs";
25
+ if (parts[0] === "jobs" || legacyJobs) {
26
+ const task = url.searchParams.get("task");
27
+ return { page: "jobs", section: "jobs", task: task && TASK_ID.test(task) ? task : null,
28
+ job: url.searchParams.get("job"), jobRun: url.searchParams.get("jobRun"),
29
+ artifact: url.searchParams.get("artifact"), ...(legacyJobs ? { moved: true } : {}) };
30
+ }
21
31
  const area = parts[0] === "settings" ? "settings" : "models";
22
32
  const requested = parts[1] || url.searchParams.get("section");
23
33
  const section = (SECTIONS[area].find(item => item.section === requested) ||
24
34
  (area === "models" && requested === "connection" ? SECTIONS.settings.at(-1) : SECTIONS[area][0])).section;
25
35
  return { page: section === "connection" ? "models" : area, section,
26
- ...(section === "jobs" ? { job: url.searchParams.get("job"), jobRun: url.searchParams.get("jobRun"),
27
- artifact: url.searchParams.get("artifact") } : {}),
28
36
  source: url.searchParams.get("source") === "command-code" ? "command-code" : "factory" };
29
37
  }
30
38
  export function manageHref(next) {
31
- if (next.section === "jobs") {
39
+ if (next.page === "jobs") {
32
40
  const query = new URLSearchParams();
33
- for (const key of ["job", "jobRun", "artifact"]) if (next[key]) query.set(key, next[key]);
34
- return "/settings/jobs" + (query.size ? "?" + query : "");
41
+ for (const key of ["task", "job", "jobRun", "artifact"]) if (next[key]) query.set(key, next[key]);
42
+ return "/jobs" + (query.size ? "?" + query : "");
35
43
  }
36
44
  return "/" + manageArea(next.page, next.section) + "/" + next.section +
37
45
  (next.section === "quota" && next.source === "command-code" ? "?source=command-code" : "");
@@ -53,11 +61,15 @@ function render() {
53
61
  if (link.dataset.area === area) link.setAttribute("aria-current", "page"); else link.removeAttribute("aria-current");
54
62
  });
55
63
  // The list head and the top bar name where the reader is.
56
- const areaTitle = i18n.t(area + ".title");
57
- const sectionTitle = i18n.t((route.page === "models" ? "models" : "settings") + ".section." + route.section);
64
+ const areaTitle = i18n.t(area === "jobs" ? "nav.jobs" : area + ".title");
65
+ const page = pages.get(route.page);
66
+ const sectionTitle = page && page.crumb ? page.crumb()
67
+ : i18n.t((route.page === "models" ? "models" : "settings") + ".section." + route.section);
58
68
  document.querySelector("#manage-area-title").textContent = areaTitle;
59
69
  document.querySelector("#manage-crumb-area").textContent = areaTitle;
60
70
  document.querySelector("#manage-crumb-section").textContent = sectionTitle;
71
+ // Area-only controls in the shared list head (the Jobs sync and refresh).
72
+ rail.querySelectorAll("[data-area-only]").forEach(node => { node.hidden = node.dataset.areaOnly !== area; });
61
73
  rail.querySelectorAll("[data-area-group]").forEach(group => {
62
74
  if (group.dataset.areaGroup === area) group.dataset.active = "true"; else delete group.dataset.active;
63
75
  });
@@ -65,27 +77,60 @@ function render() {
65
77
  document.title = sectionTitle + " · " + areaTitle + " · Yui";
66
78
  }
67
79
  function apply(next) {
80
+ // The page being left is told first, so it can set aside what must not
81
+ // outlive its visibility (a modal editor) without discarding it.
82
+ const previous = route && route.page !== next.page ? pages.get(route.page) : null;
83
+ if (previous && previous.hide) previous.hide();
68
84
  route = next;
69
85
  rememberPlace(manageArea(next.page, next.section), manageHref(next));
70
86
  rememberPlace("manage", manageHref(next));
71
- document.querySelector("#models-root").hidden = next.page !== "models";
72
- document.querySelector("#settings-root").hidden = next.page !== "settings";
87
+ for (const name of ["models", "settings", "jobs"]) document.querySelector("#" + name + "-root").hidden = next.page !== name;
73
88
  pages.get(next.page).show(next);
74
89
  render();
75
90
  }
76
91
  // A section the reader switched to rises in (never on the first paint).
77
92
  function revealSection() {
78
93
  if (typeof enter !== "function") return;
79
- enter(document.querySelector("#models-root:not([hidden]), #settings-root:not([hidden])"), "rise");
94
+ enter(document.querySelector("#models-root:not([hidden]), #settings-root:not([hidden]), #jobs-root:not([hidden])"), "rise");
95
+ }
96
+ // Every route change (link, control, Back/Forward) first asks the page it
97
+ // leaves and the page it enters; either may keep the current view.
98
+ function mayNavigate(next) {
99
+ for (const name of new Set([route.page, next.page])) {
100
+ const page = pages.get(name);
101
+ if (page && page.canNavigate && !page.canNavigate(next)) return false;
102
+ }
103
+ return true;
80
104
  }
105
+ // Returns false when a page kept the current view (nothing changed).
81
106
  export function navigateManage(next) {
82
107
  const normalized = readManageRoute(new URL(manageHref(next), location.origin));
83
- if (manageHref(normalized) === location.pathname + location.search) return;
108
+ if (manageHref(normalized) === location.pathname + location.search) return true;
109
+ if (!mayNavigate(normalized)) return false;
84
110
  history.replaceState({ ...history.state, scroll: scroller().scrollTop }, "");
85
111
  history.pushState({ scroll: 0 }, "", manageHref(normalized));
86
112
  apply(normalized);
87
113
  scroller().scrollTop = 0;
88
114
  revealSection();
115
+ return true;
116
+ }
117
+ // A page settled on a more exact address for the current view (for example
118
+ // the Job it opened): record it in place, without a history step.
119
+ export function replaceManageRoute(next) {
120
+ route = next;
121
+ rememberPlace(manageArea(next.page, next.section), manageHref(next));
122
+ rememberPlace("manage", manageHref(next));
123
+ history.replaceState(history.state, "", manageHref(next));
124
+ render();
125
+ }
126
+ // A page already shows a selection the reader chose in place (for example a
127
+ // Run opened under its row): record it as a history step without applying it
128
+ // again, keeping the reading position for Back.
129
+ export function pushManageRoute(next) {
130
+ if (manageHref(next) === location.pathname + location.search) return;
131
+ history.replaceState({ ...history.state, scroll: scroller().scrollTop }, "");
132
+ history.pushState({ scroll: scroller().scrollTop }, "", manageHref(next));
133
+ replaceManageRoute(next);
89
134
  }
90
135
  export function createManageShell(options) {
91
136
  return {
@@ -108,14 +153,15 @@ export function startManagement() {
108
153
  if (!link || link.target || link.hasAttribute("download") || event.defaultPrevented ||
109
154
  event.button > 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
110
155
  const url = new URL(link.href);
111
- // Exact Job/Run links initialize a new pinned view, not just a section switch.
112
- if (url.searchParams.has("job")) return;
113
- if (url.origin !== location.origin || !/^\/(models|settings)(\/|$)/.test(url.pathname)) return;
156
+ if (url.origin !== location.origin || !/^\/(models|settings|jobs)(\/|$)/.test(url.pathname)) return;
114
157
  event.preventDefault();
115
158
  navigateManage(readManageRoute(url));
116
159
  });
117
160
  window.addEventListener("popstate", () => {
118
- apply(readManageRoute(new URL(location.href)));
161
+ const next = readManageRoute(new URL(location.href));
162
+ // A kept view restores its own address as the newest entry.
163
+ if (!mayNavigate(next)) { history.pushState({ scroll: scroller().scrollTop }, "", manageHref(route)); return; }
164
+ apply(next);
119
165
  scroller().scrollTop = history.state?.scroll || 0;
120
166
  revealSection();
121
167
  });
@@ -23,7 +23,8 @@ export function syncUrl(state, replace) {
23
23
  if (state.query) params.set("q", state.query); else params.delete("q");
24
24
  if (state.attentionFilter) params.set("attention", state.attentionFilter); else params.delete("attention");
25
25
  if (state.catalogAll) params.set("all", "true"); else params.delete("all");
26
- if (state.source && state.source !== "ordinary") params.set("source", state.source); else params.delete("source");
26
+ // Tasks have no category filter: every real Task, Job-created ones marked.
27
+ params.delete("source");
27
28
  applyUrl(params, replace);
28
29
  }
29
30
 
@@ -18,7 +18,7 @@ export function clearPreference(key) {
18
18
  // switching back restores the filters, Task or section the user left. Only
19
19
  // same-origin paths of the expected area are accepted; anything else falls back.
20
20
  const PLACES = { workbench: /^\/(?:\?|$)/, models: /^\/models(?:[/?]|$)/, settings: /^\/settings(?:[/?]|$)/,
21
- manage: /^\/(?:models|settings)(?:[/?]|$)/ };
21
+ jobs: /^\/jobs(?:[/?]|$)/, manage: /^\/(?:jobs|models|settings)(?:[/?]|$)/ };
22
22
 
23
23
  export function rememberPlace(area, href) {
24
24
  if (!PLACES[area] || !PLACES[area].test(href)) return;