@zq-silk/yui 2.17.0 → 2.19.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 +2 -1
- package/dist/commands/controllerCommands.js +14 -4
- package/dist/commands/durableJobCommands.js +50 -3
- package/dist/commands/globalRoleCommands.js +18 -12
- package/dist/commands/inboxCommands.js +1 -0
- package/dist/commands/operatorCommands.js +10 -2
- package/dist/controlPlaneCli.js +21 -4
- package/dist/controller/clientRuntime.js +34 -13
- package/dist/controller/controller.js +45 -22
- package/dist/controller/diagnosticJournal.js +214 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +143 -101
- package/dist/controller/globalInputDelivery.js +6 -2
- 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/messageHealth.js +354 -0
- package/dist/controller/runtime.js +75 -5
- package/dist/controller/taskHostTerminalRecovery.js +11 -0
- 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/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/runtime/builtinAgentErrorMappers.js +4 -0
- package/dist/runtime/globalSessionHealth.js +39 -0
- package/dist/scheduler/roleRunStall.js +10 -10
- package/dist/storage/contextRecords.js +16 -0
- package/dist/storage/persistenceWorker.js +41 -19
- package/dist/storage/sqliteSchema.js +35 -11
- package/dist/storage/sqliteStore.js +102 -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 +17 -6
- 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/i18n/en.js +203 -18
- package/dist/web/assets/client/i18n/zh-CN.js +202 -17
- package/dist/web/assets/client/index.js +2 -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/lib/prefs.js +1 -1
- package/dist/web/assets/client/views/jobs.js +1042 -160
- package/dist/web/assets/client/views/sidebar.js +3 -0
- package/dist/web/assets/shared/manageNav.js +3 -1
- package/dist/web/assets/shell/appRail.js +2 -1
- 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/jobs.js +301 -12
- package/dist/web/assets/styles/views/sidebar.js +1 -2
- package/dist/web/webServer.js +28 -1
- package/docs/global-shell-jobs.md +78 -0
- package/docs/job-runs.md +6 -3
- package/docs/managed-turn-and-session-runtime.md +170 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +116 -0
- package/docs/release-workflow.md +63 -0
- package/docs/release-workflow.zh-CN.md +44 -0
- package/docs/storage-baseline.md +14 -1
- package/docs/storage-baseline.zh-CN.md +6 -1
- package/docs/testing/verification-levels.md +2 -2
- package/docs/testing/verification-levels.zh-CN.md +2 -2
- package/package.json +1 -1
|
@@ -120,6 +120,9 @@ function taskRow(task, state, t, locale, onSelect) {
|
|
|
120
120
|
h("span.task-body", null,
|
|
121
121
|
h("span.task-line", null, h("span.task-name", null, task.title), h("time.task-time", { dateTime: task.updatedAt }, relativeTime(task.updatedAt, locale, t))),
|
|
122
122
|
h("span.task-line.task-sub", null, h("code.task-id", null, task.id),
|
|
123
|
+
// A Task a Job Run created is still an ordinary real Task; mark where it came from.
|
|
124
|
+
task.source && task.source.kind === "job-run"
|
|
125
|
+
? h("span.task-source", { title: t("jobs.sourceJob") + " · " + task.source.jobId }, t("catalog.sourceJob")) : null,
|
|
123
126
|
counts.workItems > 0 ? h("span.task-count", { title: t("sidebar.workItems") }, icon("layers", "icon-sm"), String(counts.workItems)) : null,
|
|
124
127
|
taskSignals(task, t))));
|
|
125
128
|
}
|
|
@@ -7,9 +7,11 @@ export const MANAGE_SECTIONS = Object.freeze({
|
|
|
7
7
|
settings: [
|
|
8
8
|
{ page: "settings", section: "preferences", key: "settings.section.preferences", label: "Preferences" },
|
|
9
9
|
{ page: "settings", section: "skills", key: "settings.section.skills", label: "Skills" },
|
|
10
|
-
{ page: "settings", section: "jobs", key: "settings.section.jobs", label: "Jobs & schedules" },
|
|
11
10
|
{ page: "settings", section: "permissions", key: "settings.section.permissions", label: "Command permissions" },
|
|
12
11
|
{ page: "settings", section: "server", key: "settings.section.server", label: "Agents & server" },
|
|
13
12
|
{ page: "models", section: "connection", key: "models.section.connection", label: "Model connection" }
|
|
13
|
+
],
|
|
14
|
+
jobs: [
|
|
15
|
+
{ page: "jobs", section: "jobs", key: "nav.jobs", label: "Jobs" }
|
|
14
16
|
]
|
|
15
17
|
});
|
|
@@ -6,7 +6,8 @@ export function appRail(current) {
|
|
|
6
6
|
return `<nav class="app-rail" aria-label="Main navigation" data-i18n-aria-label="manage.nav">
|
|
7
7
|
<a class="rail-brand" href="/"${brandNav} title="Yui"><span class="brand-mark" aria-hidden="true">${brandGlyphSvg()}</span><span class="sr-only">Yui</span></a>
|
|
8
8
|
<div class="rail-items">
|
|
9
|
-
${item("workbench", "/", "workbench", "manage.workbench", "
|
|
9
|
+
${item("workbench", "/", "workbench", "manage.workbench", "Tasks", current, current === "workbench" ? "rail-workbench" : undefined)}
|
|
10
|
+
${item("jobs", "/jobs", "clock", "nav.jobs", "Jobs", current)}
|
|
10
11
|
${item("models", "/models", "layers", "models.title", "Models", current)}
|
|
11
12
|
${item("settings", "/settings", "settings", "settings.title", "Settings", current, current === "workbench" ? "settings-open" : undefined)}
|
|
12
13
|
</div>
|
|
@@ -4,7 +4,7 @@ import { BRAND_ICONS } from "../shared/brand.js";
|
|
|
4
4
|
import { iconSvg } from "../shared/icons.js";
|
|
5
5
|
import { appRail } from "./appRail.js";
|
|
6
6
|
import { MANAGE_SECTIONS } from "../shared/manageNav.js";
|
|
7
|
-
// One frame and entry point for /models/* and /settings/*. The frame is the
|
|
7
|
+
// One frame and entry point for /jobs, /models/* and /settings/*. The frame is the
|
|
8
8
|
// workbench's own: the global rail, a collapsible list (here the current
|
|
9
9
|
// area's sections), and the center column under the shared top bar. Narrow
|
|
10
10
|
// screens open the rail and the list together as a drawer, exactly as on the
|
|
@@ -22,9 +22,14 @@ const area = (name, key, label) => `
|
|
|
22
22
|
const RAIL = `<aside class="manage-rail sidebar" aria-label="Sections" data-i18n-aria-label="manage.sections">
|
|
23
23
|
<header class="side-head">
|
|
24
24
|
<h2 id="manage-area-title" class="side-title"></h2>
|
|
25
|
+
<span id="jobs-sync" class="sync" data-area-only="jobs" hidden title="Last sync" data-i18n-title="sync.label"><i aria-hidden="true"></i><time id="jobs-last-sync">—</time></span>
|
|
26
|
+
<button id="jobs-refresh" class="icon-btn" type="button" data-area-only="jobs" hidden aria-label="Refresh" data-i18n-aria-label="actions.refresh" title="Refresh" data-i18n-title="actions.refresh">${iconSvg("refresh")}</button>
|
|
25
27
|
<button id="sidebar-collapse" class="icon-btn side-collapse" type="button" aria-label="Collapse sections" data-i18n-aria-label="manage.collapseSections" title="Collapse sections · [" data-i18n-title="manage.collapseSections">${iconSvg("sidebar")}</button>
|
|
26
28
|
</header>
|
|
27
29
|
<nav class="manage-nav" aria-label="Sections" data-i18n-aria-label="manage.sections">${area("models", "models.title", "Models")}${area("settings", "settings.title", "Settings")}
|
|
30
|
+
<div class="manage-area jobs-area" data-area-group="jobs">
|
|
31
|
+
<div id="jobs-index" class="jobs-index" role="region" aria-label="Jobs" data-i18n-aria-label="nav.jobs"></div>
|
|
32
|
+
</div>
|
|
28
33
|
</nav>
|
|
29
34
|
</aside>`;
|
|
30
35
|
const TOP_BAR = `<header class="top-bar">
|
|
@@ -47,7 +52,7 @@ export function manageDocument(options) {
|
|
|
47
52
|
</head>
|
|
48
53
|
<body class="manage-body ${options.page}-body">
|
|
49
54
|
<div class="app manage-app" data-page="${options.page}" hidden>
|
|
50
|
-
${appRail(options.page
|
|
55
|
+
${appRail(options.page)}
|
|
51
56
|
${RAIL}
|
|
52
57
|
<div id="sidebar-scrim" class="sidebar-scrim"></div>
|
|
53
58
|
<main id="manage-scroll" class="center manage-center" tabindex="-1">
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { manageDocument } from "./manage.js";
|
|
2
2
|
import { SETTINGS_MAIN } from "./settingsContent.js";
|
|
3
|
-
// One document for /models/* and /settings/*.
|
|
4
|
-
// across history navigation, preserving drafts, observations and locale.
|
|
3
|
+
// One document for /jobs, /models/* and /settings/*. The controllers stay
|
|
4
|
+
// mounted across history navigation, preserving drafts, observations and locale.
|
|
5
5
|
export const MODELS_HTML = manageDocument({ page: "models", title: "Models", main: `
|
|
6
6
|
<div id="settings-root" hidden>${SETTINGS_MAIN}</div>
|
|
7
|
-
<div id="models-root" class="models-root" hidden></div
|
|
7
|
+
<div id="models-root" class="models-root" hidden></div>
|
|
8
|
+
<div id="jobs-root" class="jobs-root" hidden></div>` });
|
|
@@ -22,9 +22,6 @@ export const SETTINGS_MAIN = `
|
|
|
22
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
23
|
<section id="skill-browser" class="card settings-card settings-stack"></section>
|
|
24
24
|
</section>
|
|
25
|
-
<section class="manage-section" data-section="jobs" hidden>
|
|
26
|
-
<section id="global-jobs"></section>
|
|
27
|
-
</section>
|
|
28
25
|
<section class="manage-section" data-section="server" aria-labelledby="settings-server-title" hidden>
|
|
29
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.")}
|
|
30
27
|
<label class="field settings-search"><span data-i18n="settings.search">Find a setting</span><input id="settings-search" type="search" autocomplete="off"></label>
|
|
@@ -24,7 +24,6 @@ export const SIDEBAR_HTML = ` <aside class="sidebar" aria-label="Tasks" data-
|
|
|
24
24
|
</label>
|
|
25
25
|
<button id="body-search-open" class="icon-btn side-search-body" type="button" aria-label="Search task contents" data-i18n-aria-label="bodySearch.title" title="Search task contents" data-i18n-title="bodySearch.title">${iconSvg("textSearch")}</button>
|
|
26
26
|
</div>
|
|
27
|
-
<div id="catalog-source" class="seg seg-sm source-seg" role="radiogroup" aria-label="Task category" data-i18n-aria-label="jobs.taskCategory"></div>
|
|
28
27
|
<div id="attention-bar" class="attention-bar" aria-live="polite"></div>
|
|
29
28
|
</div>
|
|
30
29
|
<div id="status-filters" class="status-tabs" role="group" aria-label="Filter by status" data-i18n-aria-label="filters.label"></div>
|
|
@@ -1,19 +1,308 @@
|
|
|
1
|
+
// Jobs: one calm, dense surface. Hairline separators instead of nested cards,
|
|
2
|
+
// the workbench's own list head and "new" action, a two-line index, a scannable Run table and a properties
|
|
3
|
+
// column. Only shared tokens (ink, line, surface, tone, radius, type scale).
|
|
1
4
|
export const JOBS_STYLES = `
|
|
2
|
-
.jobs-manager { min-width: 0; display: grid; gap:
|
|
3
|
-
.jobs-layout { display: grid; grid-template-columns: minmax(160px, 240px) minmax(0, 1fr); gap: 24px; align-items: start; }
|
|
4
|
-
.jobs-list, .jobs-detail, .jobs-runs { display: grid; gap: 12px; min-width: 0; }
|
|
5
|
-
.job-choice { display: grid; gap: 6px; text-align: left; padding: 14px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); color: inherit; cursor: pointer; overflow-wrap: anywhere; }
|
|
6
|
-
.job-choice[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); }
|
|
7
|
-
.job-choice:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
8
|
-
.jobs-manager .settings-actions { flex-wrap: wrap; }
|
|
9
|
-
.jobs-manager .settings-observation { max-width: 100%; max-height: 420px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
10
|
-
.jobs-manager .btn, .job-fact { overflow-wrap: anywhere; white-space: normal; text-align: left; }
|
|
11
|
-
.jobs-runs .btn { font-family: var(--font-mono); font-size: 12px; }
|
|
12
|
-
.jobs-manager textarea { width: 100%; font-family: var(--font-mono); }
|
|
5
|
+
.jobs-manager { min-width: 0; display: grid; gap: 14px; container-type: inline-size; }
|
|
13
6
|
.jobs-manager [hidden] { display: none !important; }
|
|
7
|
+
.jobs-manager textarea { width: 100%; font-family: var(--font-mono); font-size: var(--fs-xs); }
|
|
8
|
+
.jobs-manager .settings-observation { max-width: 100%; max-height: 420px; margin: 0; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--fs-xs); }
|
|
9
|
+
.jobs-manager .btn { height: 30px; padding: 0 11px; }
|
|
10
|
+
.jobs-layout { display: grid; grid-template-columns: minmax(180px, 240px) minmax(0, 1fr); gap: 20px; align-items: start; }
|
|
11
|
+
.jobs-list, .jobs-detail { display: grid; gap: 12px; min-width: 0; }
|
|
12
|
+
|
|
13
|
+
/* Page bar */
|
|
14
|
+
.jobs-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; min-width: 0; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
|
|
15
|
+
.jobs-bar-title { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
|
|
16
|
+
.jobs-bar-title h1, .jobs-bar-title h2 { font-size: var(--fs-lg); font-weight: 650; letter-spacing: -.01em; }
|
|
17
|
+
.jobs-bar-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 6px; min-width: 0; }
|
|
18
|
+
.jobs-bar-actions .settings-receipt { margin: 0 6px 0 0; color: var(--ink-4); font-size: var(--fs-xs); white-space: pre-line; }
|
|
19
|
+
.jobs-bar-actions .settings-receipt:empty { display: none; }
|
|
20
|
+
.jobs-bar-actions .settings-receipt.settings-error { order: 9; flex-basis: 100%; margin: 4px 0 0; padding: 8px 10px; border-radius: var(--r-sm); background: var(--bad-soft); color: var(--bad); text-align: left; }
|
|
21
|
+
.jobs-receipt .settings-receipt { margin: 0; color: var(--ink-3); font-size: var(--fs-xs); white-space: pre-line; }
|
|
22
|
+
.jobs-receipt .settings-receipt:empty, .jobs-receipt:has(.settings-receipt:empty) { display: none; }
|
|
23
|
+
.jobs-receipt .settings-receipt.settings-error { padding: 8px 10px; border-radius: var(--r-sm); background: var(--bad-soft); color: var(--bad); }
|
|
24
|
+
.job-notice { margin: 0; padding: 8px 12px; border-radius: var(--r-sm); background: var(--surface-3); color: var(--ink-2); font-size: var(--fs-sm); }
|
|
25
|
+
|
|
26
|
+
/* Job index (left rail): two lines per Job, status at the right edge. */
|
|
27
|
+
.jobs-area .jobs-index { display: grid; gap: 10px; min-width: 0; padding-top: 2px; }
|
|
28
|
+
.jobs-area .side-create { margin-bottom: 2px; }
|
|
29
|
+
/* The area title stays whole beside the Jobs sync; a long clock gives way. */
|
|
30
|
+
.manage-rail .side-head:has(#jobs-sync:not([hidden])) .side-title { flex: none; }
|
|
31
|
+
#jobs-sync { min-width: 0; overflow: hidden; white-space: nowrap; }
|
|
32
|
+
#jobs-sync time { overflow: hidden; text-overflow: ellipsis; }
|
|
33
|
+
.jobs-scope { display: grid; gap: 4px; padding: 0 2px; }
|
|
34
|
+
.jobs-scope label { color: var(--ink-4); font-size: var(--fs-2xs); font-weight: 600; text-transform: uppercase; letter-spacing: .07em; }
|
|
35
|
+
.jobs-scope select { width: 100%; min-width: 0; height: 32px; padding-top: 4px; padding-bottom: 4px; font-size: var(--fs-sm); }
|
|
36
|
+
.jobs-scope-help { margin: 0; color: var(--ink-4); font-size: var(--fs-2xs); line-height: 1.4; }
|
|
37
|
+
.jobs-scope-help.is-error, .jobs-index-empty.is-error { color: var(--bad); }
|
|
38
|
+
.jobs-index-body { display: grid; gap: 8px; min-width: 0; }
|
|
39
|
+
.jobs-index-tools { display: grid; gap: 6px; }
|
|
40
|
+
.jobs-index-tools .search-field input { width: 100%; height: 32px; font-size: var(--fs-sm); }
|
|
41
|
+
.jobs-index-count { margin: 0; padding: 0 2px; color: var(--ink-4); font-size: var(--fs-2xs); font-variant-numeric: tabular-nums; }
|
|
42
|
+
.jobs-index-empty { margin: 0; padding: 24px 12px; color: var(--ink-4); font-size: var(--fs-sm); text-align: center; }
|
|
43
|
+
.jobs-list { gap: 1px; }
|
|
44
|
+
.job-row { position: relative; display: flex; align-items: flex-start; gap: 9px; width: 100%; min-width: 0; padding: 7px 8px; border: 0; border-radius: var(--r-sm); background: transparent; color: inherit; text-align: left; cursor: pointer; transition: background var(--t-fast); }
|
|
45
|
+
.job-row > .dot { margin-top: 6px; }
|
|
46
|
+
.job-row:hover { background: var(--surface-3); }
|
|
47
|
+
.job-row[aria-current="true"] { background: var(--surface-3); }
|
|
48
|
+
.job-row[aria-current="true"] .job-row-name { font-weight: 600; }
|
|
49
|
+
.job-row-body { display: grid; gap: 1px; flex: 1; min-width: 0; }
|
|
50
|
+
.job-row-line { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
51
|
+
.job-row-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); font-size: var(--fs-sm); font-weight: 500; line-height: 1.45; }
|
|
52
|
+
.job-row-line > .badge { flex: none; height: 18px; padding: 0 6px; font-size: var(--fs-2xs); }
|
|
53
|
+
.job-row-sub { color: var(--ink-4); font-size: var(--fs-2xs); }
|
|
54
|
+
.job-row-sub > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
55
|
+
.job-row-type { flex: none; max-width: 50%; color: var(--ink-3); }
|
|
56
|
+
.job-row-plan { flex: 1; }
|
|
57
|
+
.job-row-plan::before { content: "·"; margin-right: 6px; color: var(--ink-4); }
|
|
58
|
+
.job-row-none { flex: none; color: var(--ink-4); font-size: var(--fs-2xs); }
|
|
59
|
+
|
|
60
|
+
/* Focus is always visible. */
|
|
61
|
+
.job-row:focus-visible, .job-run-row:focus-visible, .job-copy:focus-visible, .job-more:focus-visible,
|
|
62
|
+
.job-section-action:focus-visible, .job-raw > summary:focus-visible, .job-log > summary:focus-visible, .job-fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; box-shadow: none; }
|
|
63
|
+
|
|
64
|
+
/* Job header */
|
|
65
|
+
.job-page { display: grid; gap: 16px; min-width: 0; }
|
|
66
|
+
.job-header { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 10px 16px; min-width: 0; }
|
|
67
|
+
.job-title { display: grid; gap: 6px; flex: 1 1 360px; min-width: 0; }
|
|
68
|
+
.job-title h2 { font-size: var(--fs-xl); font-weight: 650; letter-spacing: -.015em; line-height: 1.25; overflow-wrap: anywhere; }
|
|
69
|
+
.job-badges { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
|
|
70
|
+
.job-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 0; color: var(--ink-3); font-size: var(--fs-xs); }
|
|
71
|
+
.job-meta-item { display: inline-flex; align-items: center; gap: 4px; min-width: 0; }
|
|
72
|
+
.job-meta-item + .job-meta-item::before { content: "·"; margin: 0 8px; color: var(--ink-4); }
|
|
73
|
+
.job-meta a { color: inherit; }
|
|
74
|
+
.job-actions { display: flex; flex-wrap: wrap; gap: 6px; flex: none; }
|
|
75
|
+
.job-note { margin: 0; color: var(--ink-4); font-size: var(--fs-xs); line-height: 1.5; }
|
|
76
|
+
.job-note.is-error { color: var(--bad); }
|
|
77
|
+
|
|
78
|
+
/* Overview strip: numbers first, the last 30 Runs as a status strip. */
|
|
79
|
+
.job-overview { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px 24px; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--r-md); background: var(--surface); }
|
|
80
|
+
.job-stats { display: flex; flex-wrap: wrap; gap: 10px 0; min-width: 0; }
|
|
81
|
+
.job-stat { display: grid; align-content: start; gap: 2px; min-width: 0; padding: 0 18px; border-left: 1px solid var(--line); }
|
|
82
|
+
.job-stat:first-child { padding-left: 0; border-left: 0; }
|
|
83
|
+
.job-stat-label { color: var(--ink-4); font-size: var(--fs-2xs); white-space: nowrap; }
|
|
84
|
+
.job-stat-value { color: var(--ink); font-size: var(--fs-lg); font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.3; }
|
|
85
|
+
.job-stat-sub { color: var(--ink-4); font-size: var(--fs-2xs); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
86
|
+
.job-stat-run { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; min-height: 26px; }
|
|
87
|
+
.job-stat-run code { font-size: var(--fs-xs); color: var(--ink-2); }
|
|
88
|
+
.job-stat-run .badge { height: 20px; padding: 0 7px; font-size: var(--fs-2xs); }
|
|
89
|
+
.job-spark { display: flex; align-items: stretch; gap: 2px; height: 26px; }
|
|
90
|
+
.job-spark-bar { width: 6px; border-radius: 2px; background: var(--tone, var(--line-3)); opacity: .9; }
|
|
91
|
+
.job-spark-bar.tone-idle { background: var(--line-3); }
|
|
92
|
+
|
|
93
|
+
/* About the Job: description and configuration | plans and definition */
|
|
94
|
+
.job-purpose { display: flex; align-items: flex-start; gap: 4px; min-width: 0; padding: 10px 6px 10px 12px; border-left: 2px solid var(--line-3); border-radius: 0 var(--r-sm) var(--r-sm) 0; background: var(--surface-3); }
|
|
95
|
+
.job-purpose .job-prose { flex: 1; max-height: 180px; line-height: 1.6; }
|
|
96
|
+
.job-purpose.is-note { display: block; margin: 0; padding: 8px 12px; color: var(--ink-2); font-size: var(--fs-sm); }
|
|
97
|
+
.job-about { display: grid; grid-template-columns: minmax(0, 1fr) minmax(240px, 300px); gap: 24px; align-items: start; min-width: 0; }
|
|
98
|
+
.job-primary, .job-aside { display: grid; gap: 22px; min-width: 0; }
|
|
99
|
+
.job-section { display: grid; gap: 8px; min-width: 0; }
|
|
100
|
+
.job-section-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 26px; }
|
|
101
|
+
.job-section-head h4 { color: var(--ink-2); font-size: var(--fs-sm); font-weight: 600; }
|
|
102
|
+
.job-section-count { color: var(--ink-4); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
|
|
103
|
+
.job-section-title { margin: 6px 0 0; color: var(--ink-3); font-size: var(--fs-xs); font-weight: 600; }
|
|
104
|
+
.job-section-action { display: inline-flex; align-items: center; gap: 4px; height: 26px; padding: 0 8px; border: 0; border-radius: var(--r-sm); background: transparent; color: var(--ink-3); font-size: var(--fs-xs); font-weight: 500; }
|
|
105
|
+
.job-section-action:hover { background: var(--surface-3); color: var(--ink); }
|
|
106
|
+
.job-section-action .icon { width: 14px; height: 14px; }
|
|
107
|
+
.job-empty-block { margin: 0; padding: 14px 12px; border: 1px dashed var(--line-2); border-radius: var(--r-md); color: var(--ink-4); font-size: var(--fs-sm); text-align: center; }
|
|
108
|
+
.job-blank { display: grid; justify-items: center; gap: 10px; padding: 64px 16px; color: var(--ink-4); text-align: center; }
|
|
109
|
+
|
|
110
|
+
/* Run history: a table on wide containers, two lines on narrow ones. */
|
|
111
|
+
.job-history { display: grid; gap: 8px; min-width: 0; }
|
|
112
|
+
.job-filters { gap: 4px; }
|
|
113
|
+
.job-filters .chip-tab { height: 26px; padding: 0 9px; }
|
|
114
|
+
.job-filters .count { color: inherit; opacity: .7; font-variant-numeric: tabular-nums; }
|
|
115
|
+
.job-history-body { border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; background: var(--surface); }
|
|
116
|
+
.job-runs-head, .job-run-row { display: grid; grid-template-columns: 104px minmax(0, 1fr) 112px 72px 16px; align-items: center; gap: 12px; }
|
|
117
|
+
.job-runs-head { padding: 6px 12px; border-bottom: 1px solid var(--line); color: var(--ink-4); font-size: var(--fs-2xs); font-weight: 500; }
|
|
118
|
+
.job-runs-head > :nth-child(n+3), .job-run-when, .job-run-span { text-align: right; }
|
|
119
|
+
.jobs-runs { display: grid; }
|
|
120
|
+
.job-run-row { scroll-margin-top: calc(var(--header-h, 56px) + 12px); width: 100%; min-width: 0; padding: 7px 12px; border: 0; border-top: 1px solid var(--line); background: transparent; color: inherit; text-align: left; cursor: pointer; transition: background var(--t-fast); }
|
|
121
|
+
.jobs-runs > .job-run-row:first-child { border-top: 0; }
|
|
122
|
+
.job-run-row:hover { background: var(--surface-3); }
|
|
123
|
+
.job-run-row[aria-current="true"] { background: var(--surface-3); box-shadow: inset 2px 0 0 var(--accent); }
|
|
124
|
+
.job-run-caret { display: inline-grid; place-items: center; color: var(--ink-4); }
|
|
125
|
+
.job-run-caret .icon { width: 14px; height: 14px; transition: transform var(--t-fast); }
|
|
126
|
+
.job-run-row[aria-expanded="true"] .job-run-caret .icon { transform: rotate(90deg); color: var(--ink-2); }
|
|
127
|
+
.job-run-status .badge { height: 20px; padding: 0 7px; font-size: var(--fs-2xs); }
|
|
128
|
+
.job-run-main { display: grid; gap: 1px; min-width: 0; }
|
|
129
|
+
.job-run-summary { overflow: hidden; color: var(--ink); font-size: var(--fs-sm); text-overflow: ellipsis; white-space: nowrap; }
|
|
130
|
+
.job-run-sub { display: flex; gap: 8px; min-width: 0; color: var(--ink-4); font-size: var(--fs-2xs); white-space: nowrap; }
|
|
131
|
+
.job-run-sub code { color: var(--ink-3); }
|
|
132
|
+
.job-run-when, .job-run-span { color: var(--ink-3); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
133
|
+
.job-more { width: 100%; padding: 8px 12px; border: 0; border-top: 1px solid var(--line); background: transparent; color: var(--ink-3); font-size: var(--fs-xs); font-weight: 500; }
|
|
134
|
+
.job-more:hover { background: var(--surface-3); color: var(--ink); }
|
|
135
|
+
|
|
136
|
+
/* Selected Run: opened in place, directly under its row */
|
|
137
|
+
.job-run-inline { padding: 4px 12px 14px; border-top: 1px dashed var(--line); background: var(--surface-3); box-shadow: inset 2px 0 0 var(--accent); }
|
|
138
|
+
.jobs-runs > .job-run-inline:first-child { border-top: 0; }
|
|
139
|
+
.job-run-inline .job-run-panel { border-color: var(--line); box-shadow: var(--shadow-1); }
|
|
140
|
+
.job-run-inline:empty, .job-run-panel > section:empty, .job-run-artifacts > div:empty { display: none; }
|
|
141
|
+
.job-run-panel > .job-kv { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 24px; }
|
|
142
|
+
.job-run-panel > .job-kv > .job-kv-row:nth-child(2) { border-top: 0; }
|
|
143
|
+
.job-run-panel { display: grid; gap: 12px; min-width: 0; padding: 14px; border: 1px solid var(--line-2); border-radius: var(--r-md); background: var(--surface); }
|
|
144
|
+
.job-run-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
|
|
145
|
+
.job-run-head > .job-badges { flex: none; flex-wrap: nowrap; }
|
|
146
|
+
.job-run-title { display: flex; flex: 1; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; min-height: 28px; }
|
|
147
|
+
.job-run-title h4 { font-size: var(--fs-sm); font-weight: 600; }
|
|
148
|
+
.job-run-close { width: 28px; height: 28px; }
|
|
149
|
+
.job-result { margin: 0; padding: 8px 10px; border-left: 2px solid var(--tone, var(--line-3)); border-radius: 0 var(--r-sm) var(--r-sm) 0; background: var(--tone-soft, var(--surface-3)); color: var(--ink); font-size: var(--fs-sm); overflow-wrap: anywhere; }
|
|
150
|
+
.job-result-label { margin-right: 8px; color: var(--ink-3); font-size: var(--fs-xs); font-weight: 600; }
|
|
151
|
+
.job-run-steps { display: grid; margin: 0; padding: 0; list-style: none; border: 1px solid var(--line); border-radius: var(--r-sm); }
|
|
152
|
+
.job-run-step { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; padding: 6px 10px; border-top: 1px solid var(--line); font-size: var(--fs-xs); }
|
|
153
|
+
.job-run-step:first-child { border-top: 0; }
|
|
154
|
+
.job-run-step .badge { height: 20px; font-size: var(--fs-2xs); }
|
|
155
|
+
.job-run-step-meta { margin-left: auto; color: var(--ink-4); font-variant-numeric: tabular-nums; }
|
|
156
|
+
.job-logs { display: grid; gap: 6px; }
|
|
157
|
+
.job-log { border: 1px solid var(--line); border-radius: var(--r-sm); }
|
|
158
|
+
.job-log > summary { display: flex; align-items: center; gap: 8px; padding: 6px 10px; font-size: var(--fs-xs); cursor: pointer; }
|
|
159
|
+
.job-log-text { max-height: 320px; margin: 0; padding: 10px; overflow: auto; border-top: 1px solid var(--line); background: var(--sunken); font-size: var(--fs-2xs); line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
160
|
+
.job-artifact-list { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
161
|
+
.job-inline-action { justify-self: start; height: 26px !important; padding: 0 8px !important; color: var(--ink-3); font-size: var(--fs-xs); }
|
|
162
|
+
.job-run-evidence, .job-run-artifacts { display: grid; gap: 6px; min-width: 0; }
|
|
163
|
+
.job-advanced { display: grid; gap: 6px; }
|
|
164
|
+
|
|
165
|
+
/* Label/value lists (configuration, definition, Run facts) */
|
|
166
|
+
.job-kv { display: grid; margin: 0; min-width: 0; }
|
|
167
|
+
.job-kv-row { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: 12px; padding: 6px 0; border-top: 1px solid var(--line); font-size: var(--fs-sm); }
|
|
168
|
+
.job-kv-row:first-child { border-top: 0; }
|
|
169
|
+
.job-kv dt { color: var(--ink-4); font-size: var(--fs-xs); line-height: 1.7; overflow-wrap: anywhere; }
|
|
170
|
+
.job-kv dd { margin: 0; min-width: 0; color: var(--ink); overflow-wrap: anywhere; }
|
|
171
|
+
.job-kv.is-compact .job-kv-row { grid-template-columns: 84px minmax(0, 1fr); padding: 5px 0; }
|
|
172
|
+
.job-kv.is-nested { padding-left: 10px; border-left: 2px solid var(--line); }
|
|
173
|
+
.job-kv.is-nested .job-kv-row { grid-template-columns: minmax(80px, 120px) minmax(0, 1fr); padding: 3px 0; border-top: 0; }
|
|
174
|
+
.job-key { color: var(--ink-3); font-size: var(--fs-xs); }
|
|
175
|
+
.job-empty { color: var(--ink-4); }
|
|
176
|
+
.job-muted { color: var(--ink-4); font-size: var(--fs-xs); }
|
|
177
|
+
.job-text { white-space: pre-wrap; }
|
|
178
|
+
.job-kind { font-weight: 600; }
|
|
179
|
+
.job-prose { max-height: 240px; margin: 0; overflow: auto; color: var(--ink); font-family: inherit; font-size: var(--fs-sm); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
180
|
+
.job-chips { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
181
|
+
.job-chip { padding: 1px 6px; border-radius: var(--r-xs); background: var(--surface-3); color: var(--ink-2); font-size: var(--fs-2xs); overflow-wrap: anywhere; }
|
|
182
|
+
.job-chip-key { color: var(--ink); font-weight: 600; }
|
|
183
|
+
.job-list { margin: 0; padding-left: 18px; }
|
|
184
|
+
.job-fold > summary { color: var(--ink-3); font-size: var(--fs-xs); cursor: pointer; }
|
|
185
|
+
.job-time { display: inline-flex; flex-wrap: wrap; gap: 0 8px; font-variant-numeric: tabular-nums; }
|
|
186
|
+
.job-time-ago { color: var(--ink-4); font-size: var(--fs-xs); }
|
|
187
|
+
.job-id, .job-path { display: inline-flex; align-items: center; gap: 2px; max-width: 100%; min-width: 0; }
|
|
188
|
+
.job-path code { min-width: 0; overflow-wrap: anywhere; font-size: var(--fs-xs); }
|
|
189
|
+
.job-copy { display: inline-grid; place-items: center; width: 22px; height: 22px; flex: none; padding: 0; border: 0; border-radius: var(--r-xs); background: transparent; color: var(--ink-4); }
|
|
190
|
+
.job-copy:hover { background: var(--surface-3); color: var(--ink); }
|
|
191
|
+
.job-copy .icon { width: 13px; height: 13px; }
|
|
192
|
+
.job-copy.is-copied { color: var(--ok); }
|
|
193
|
+
.job-copy-text { display: inline-flex; align-items: center; width: auto; gap: 4px; padding: 0 6px; font-size: var(--fs-2xs); white-space: nowrap; }
|
|
194
|
+
|
|
195
|
+
/* Configuration: steps as numbered rows with their command */
|
|
196
|
+
.job-config { display: grid; gap: 14px; min-width: 0; }
|
|
197
|
+
.job-steps { display: grid; margin: 0; padding: 0; list-style: none; border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; }
|
|
198
|
+
.job-step { display: grid; gap: 6px; padding: 10px 12px; border-top: 1px solid var(--line); min-width: 0; }
|
|
199
|
+
.job-step:first-child { border-top: 0; }
|
|
200
|
+
.job-step-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; min-width: 0; }
|
|
201
|
+
.job-step-index { display: inline-grid; place-items: center; width: 18px; height: 18px; border-radius: var(--r-pill); background: var(--surface-3); color: var(--ink-3); font-size: var(--fs-2xs); font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
202
|
+
.job-step-name { color: var(--ink); font-size: var(--fs-xs); font-weight: 600; }
|
|
203
|
+
.job-tag { max-width: 100%; padding: 0 6px; border: 1px solid var(--line-2); border-radius: var(--r-xs); color: var(--ink-3); font-size: var(--fs-2xs); line-height: 18px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
204
|
+
.job-command { display: flex; align-items: flex-start; gap: 4px; min-width: 0; padding: 6px 6px 6px 10px; border-radius: var(--r-sm); background: var(--sunken); }
|
|
205
|
+
.job-command pre { flex: 1; min-width: 0; margin: 0; padding-top: 2px; color: var(--ink); font-size: var(--fs-xs); line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
206
|
+
.job-command.is-prose pre { font-family: inherit; font-size: var(--fs-sm); }
|
|
207
|
+
.job-step-env { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
|
|
208
|
+
.job-step-label { color: var(--ink-4); font-size: var(--fs-2xs); }
|
|
209
|
+
|
|
210
|
+
/* Exact JSON, one disclosure away */
|
|
211
|
+
.job-raw { position: relative; min-width: 0; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--surface); overflow: hidden; }
|
|
212
|
+
.job-raw > summary { display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 7px 110px 7px 12px; color: var(--ink-3); font-size: var(--fs-xs); cursor: pointer; list-style: none; }
|
|
213
|
+
.job-raw > summary::before { content: "›"; font-size: 16px; line-height: 1; transform-origin: center; }
|
|
214
|
+
.job-raw[open] > summary::before { transform: rotate(90deg); }
|
|
215
|
+
.job-raw > summary:hover { color: var(--ink); }
|
|
216
|
+
.job-raw-hint { padding: 0 5px; border: 1px solid var(--line-2); border-radius: var(--r-xs); color: var(--ink-4); font-family: var(--font-mono); font-size: 10px; line-height: 16px; }
|
|
217
|
+
.job-raw-body { min-width: 0; border-top: 1px solid var(--line); }
|
|
218
|
+
.job-raw-tools { position: absolute; right: 8px; top: 4px; display: flex; }
|
|
219
|
+
.job-raw .settings-observation { display: block; margin: 0; padding: 14px; max-height: 320px; overflow: auto; background: var(--sunken); color: var(--ink-2); font: var(--fs-xs)/1.65 var(--font-mono); white-space: pre; tab-size: 2; }
|
|
220
|
+
.job-form-advanced { min-width: 0; color: var(--ink-3); font-size: var(--fs-xs); }
|
|
221
|
+
.job-form-advanced > summary { cursor: pointer; padding: 8px 0; }
|
|
222
|
+
.job-form-advanced[open] > :not(summary) { margin-top: 8px; }
|
|
223
|
+
.job-form-advanced .job-raw { margin-top: 10px; }
|
|
224
|
+
.job-form-advanced:has(:focus-visible), .job-raw > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
225
|
+
|
|
226
|
+
/* Plans */
|
|
227
|
+
.job-plans { display: grid; margin: 0; padding: 0; list-style: none; border: 1px solid var(--line); border-radius: var(--r-md); background: var(--surface); }
|
|
228
|
+
.job-plan { display: grid; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--line); min-width: 0; }
|
|
229
|
+
.job-plan:first-child { border-top: 0; }
|
|
230
|
+
.job-plan-main { display: grid; gap: 4px; min-width: 0; }
|
|
231
|
+
.job-plan-line { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; min-width: 0; }
|
|
232
|
+
.job-plan-line .badge { height: 20px; padding: 0 7px; font-size: var(--fs-2xs); }
|
|
233
|
+
.job-plan-rule { font-size: var(--fs-sm); font-weight: 600; }
|
|
234
|
+
.job-plan-exact { color: var(--ink-4); font-family: var(--font-mono); font-size: var(--fs-2xs); overflow-wrap: anywhere; }
|
|
235
|
+
.job-plan-sub { color: var(--ink-4); font-size: var(--fs-2xs); }
|
|
236
|
+
.job-plan-sub code { color: var(--ink-3); overflow-wrap: anywhere; }
|
|
237
|
+
.job-plan-actions { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
238
|
+
.job-plan-actions .btn { height: 26px; padding: 0 8px; font-size: var(--fs-xs); }
|
|
239
|
+
|
|
240
|
+
/* Editor (a modal dialog, as the workbench's new Task) */
|
|
241
|
+
.job-dialog { width: min(760px, calc(100vw - 32px)); }
|
|
242
|
+
.job-dialog .job-form { gap: 14px; }
|
|
243
|
+
.job-dialog .dialog-actions { position: sticky; bottom: -20px; margin: 0 -20px -20px; padding: 12px 20px; border-top: 1px solid var(--line); background: var(--surface); }
|
|
244
|
+
.job-dialog-receipt { margin: 0; color: var(--ink-3); font-size: var(--fs-xs); white-space: pre-line; }
|
|
245
|
+
.job-dialog-receipt:empty { display: none; }
|
|
246
|
+
.job-dialog-receipt.settings-error { padding: 8px 10px; border-radius: var(--r-sm); background: var(--bad-soft); color: var(--bad); }
|
|
247
|
+
.job-fieldset { display: grid; gap: 10px; min-width: 0; margin: 0; padding: 12px 0 0; border: 0; border-top: 1px solid var(--line); }
|
|
248
|
+
.job-fieldset > legend { padding: 0 8px 0 0; color: var(--ink-2); font-size: var(--fs-sm); font-weight: 600; }
|
|
249
|
+
.job-field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px 12px; }
|
|
250
|
+
.job-field-stack { display: grid; gap: 10px; }
|
|
251
|
+
.job-field-stack[hidden], .job-form [hidden] { display: none; }
|
|
252
|
+
.job-bound-target { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 10px 12px; border-radius: var(--r-sm); background: var(--sunken); color: var(--ink-3); font-size: var(--fs-sm); }
|
|
253
|
+
.job-bound-target strong { color: var(--ink); font-weight: 500; overflow-wrap: anywhere; }
|
|
254
|
+
.job-weekdays { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
255
|
+
.job-weekdays .btn { min-width: 38px; height: 34px; padding: 0 10px; }
|
|
256
|
+
.job-weekdays .btn[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--surface); }
|
|
257
|
+
.job-rule-preview { padding: 10px 12px; margin: 0; border-left: 2px solid var(--accent); background: var(--sunken); color: var(--ink-2); font-size: var(--fs-sm); overflow-wrap: anywhere; }
|
|
258
|
+
.job-rule-preview { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; border-radius: var(--r-sm); }
|
|
259
|
+
.job-rule-preview.is-error { border-color: var(--bad); color: var(--bad); background: var(--bad-soft); }
|
|
260
|
+
.job-plan-expression { color: var(--ink-3); font-size: var(--fs-xs); overflow-wrap: anywhere; }
|
|
261
|
+
.job-clock input { font-variant-numeric: tabular-nums; }
|
|
262
|
+
.job-time-shortcuts { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
263
|
+
.job-time-shortcuts .btn { height: 28px; padding: 0 10px; color: var(--ink-3); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
|
|
264
|
+
.job-cron-guide { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); margin: 0; padding: 12px; gap: 8px; border: 1px solid var(--line); border-radius: var(--r-sm); }
|
|
265
|
+
.job-cron-guide dt { color: var(--ink-3); font-size: var(--fs-xs); }
|
|
266
|
+
.job-cron-guide dd { display: grid; gap: 6px; margin: 6px 0 0; overflow-wrap: anywhere; }
|
|
267
|
+
.job-cron-guide dd span { color: var(--ink-4); font-size: var(--fs-2xs); }
|
|
268
|
+
@media (max-width: 480px) {
|
|
269
|
+
.job-cron-guide { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
270
|
+
}
|
|
271
|
+
.job-edit-row { display: grid; gap: 10px; padding: 12px; border: 1px solid var(--line); border-radius: var(--r-sm); min-width: 0; }
|
|
272
|
+
.job-edit-row > .btn, .job-field-stack > .btn { justify-self: start; }
|
|
273
|
+
.job-form textarea { width: 100%; resize: vertical; min-height: 88px; font-size: var(--fs-sm); }
|
|
274
|
+
.job-form input, .job-form select, .job-form textarea { min-width: 0; }
|
|
275
|
+
.jobs-manager form .field > span { color: var(--ink-3); font-size: var(--fs-xs); font-weight: 500; }
|
|
276
|
+
.jobs-manager form input, .jobs-manager form select { height: 32px; padding-top: 4px; padding-bottom: 4px; font-size: var(--fs-sm); }
|
|
277
|
+
.job-config-preview { display: grid; gap: 8px; padding: 12px; border: 1px dashed var(--line-2); border-radius: var(--r-md); }
|
|
278
|
+
.job-preview-label { color: var(--ink-4); font-size: var(--fs-2xs); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
|
|
279
|
+
|
|
14
280
|
@media (max-width: 720px) { .jobs-layout { grid-template-columns: minmax(0, 1fr); } }
|
|
281
|
+
@container (max-width: 860px) {
|
|
282
|
+
.job-about { grid-template-columns: minmax(0, 1fr); }
|
|
283
|
+
}
|
|
15
284
|
@container (max-width: 640px) {
|
|
16
285
|
.jobs-layout { grid-template-columns: minmax(0, 1fr); }
|
|
17
|
-
.jobs-
|
|
286
|
+
.jobs-bar-refresh > span { display: none; }
|
|
287
|
+
.job-actions { flex-wrap: wrap; }
|
|
288
|
+
.job-overview { padding: 10px 12px; }
|
|
289
|
+
.job-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 12px; width: 100%; }
|
|
290
|
+
.job-stat { padding: 0; border-left: 0; }
|
|
291
|
+
.job-spark { width: 100%; }
|
|
292
|
+
.job-spark-bar { flex: 1; width: auto; max-width: 8px; }
|
|
293
|
+
.job-runs-head { display: none; }
|
|
294
|
+
.job-run-row { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "main status" "main when"; gap: 2px 10px; padding: 8px 10px; }
|
|
295
|
+
.job-run-caret { display: none; }
|
|
296
|
+
.job-run-inline { padding: 2px 8px 10px; }
|
|
297
|
+
.job-run-status { grid-area: status; justify-self: end; }
|
|
298
|
+
.job-run-main { grid-area: main; }
|
|
299
|
+
.job-run-when { grid-area: when; }
|
|
300
|
+
.job-run-span { display: none; }
|
|
301
|
+
.job-run-summary { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
|
302
|
+
.job-kv-row, .job-kv.is-compact .job-kv-row { grid-template-columns: 92px minmax(0, 1fr); gap: 10px; }
|
|
303
|
+
.job-kv.is-nested .job-kv-row { grid-template-columns: minmax(0, 1fr); }
|
|
304
|
+
.job-run-panel { padding: 12px; }
|
|
305
|
+
.job-run-panel > .job-kv { grid-template-columns: minmax(0, 1fr); }
|
|
306
|
+
.job-run-panel > .job-kv > .job-kv-row:nth-child(2) { border-top: 1px solid var(--line); }
|
|
18
307
|
}
|
|
19
308
|
`;
|
|
@@ -30,8 +30,6 @@ export const SIDEBAR_STYLES = `
|
|
|
30
30
|
.search-field:focus-within kbd{opacity:0}
|
|
31
31
|
.side-search-body{width:34px;height:34px;border:1px solid var(--line-2);border-radius:var(--r-md);background:var(--surface-2)}
|
|
32
32
|
.side-search-body:hover:not(:disabled){border-color:var(--line-3)}
|
|
33
|
-
.source-seg{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));width:100%}
|
|
34
|
-
.source-seg .seg-btn{justify-content:center;min-width:0;overflow:hidden;text-overflow:ellipsis}
|
|
35
33
|
.attention-bar:empty{display:none}
|
|
36
34
|
.attention-list{display:flex;flex-wrap:wrap;gap:6px}
|
|
37
35
|
.attention-pill{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 9px;border:1px solid transparent;border-radius:var(--r-pill);background:var(--tone-soft);color:var(--tone);font-size:var(--fs-xs);font-weight:500}
|
|
@@ -63,6 +61,7 @@ export const SIDEBAR_STYLES = `
|
|
|
63
61
|
.task-time{flex:none;color:var(--ink-4);font-size:var(--fs-2xs);font-variant-numeric:tabular-nums}
|
|
64
62
|
.task-sub{gap:8px;height:18px;color:var(--ink-4);font-size:var(--fs-2xs)}
|
|
65
63
|
.task-id{font-size:var(--fs-2xs);line-height:1;color:var(--ink-3)}
|
|
64
|
+
.task-source{flex:none;padding:0 5px;border:1px solid var(--line-2);border-radius:var(--r-pill);color:var(--ink-3);font-size:10px;line-height:15px}
|
|
66
65
|
.task-count{display:inline-flex;align-items:center;gap:3px;line-height:1;white-space:nowrap}
|
|
67
66
|
.task-count .icon{width:11px;height:11px}
|
|
68
67
|
.task-signals{display:inline-flex;gap:4px;margin-left:auto}
|
package/dist/web/webServer.js
CHANGED
|
@@ -720,7 +720,7 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
720
720
|
try {
|
|
721
721
|
const assetVersion = new URL(request.url, "http://localhost").searchParams.get("v");
|
|
722
722
|
const asset = findWebAsset(pathname, assetVersion);
|
|
723
|
-
const managementPage = /^\/(?:models(?:\/(?:providers|catalog|quota))?|settings(?:\/(?:preferences|skills|jobs|permissions|server|connection))
|
|
723
|
+
const managementPage = /^\/(?:models(?:\/(?:providers|catalog|quota))?|settings(?:\/(?:preferences|skills|jobs|permissions|server|connection))?|jobs)\/?$/.test(pathname);
|
|
724
724
|
if (pathname === "/" || pathname === "/index.html" || managementPage) {
|
|
725
725
|
sendText(response, 200, "text/html; charset=utf-8", prepareWebDocument(managementPage ? MODELS_HTML.replace("__YUI_WEB_TOKEN__", token) : dashboardHtml(token), managementPage ? "/assets/js/app/manage.js" : "/assets/app.js"), method === "HEAD");
|
|
726
726
|
}
|
|
@@ -735,6 +735,11 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
735
735
|
...[...query].filter(([key]) => key !== "query").flatMap(([key, value]) => [`--${key}`, value])]);
|
|
736
736
|
sendJson(response, 200, store.transaction(reader => searchTaskBodies(reader, options)), method === "HEAD");
|
|
737
737
|
}
|
|
738
|
+
else if (pathname === "/api/jobs/scopes") {
|
|
739
|
+
if (new URL(request.url, "http://localhost").search !== "")
|
|
740
|
+
throw usageError("Job scope discovery takes no options.");
|
|
741
|
+
sendJson(response, 200, store.readTransaction(reader => readWebJobScopes(reader)), method === "HEAD");
|
|
742
|
+
}
|
|
738
743
|
else if (pathname === "/api/dashboard" || pathname === "/api/dashboard/sessions") {
|
|
739
744
|
const query = new URL(request.url, "http://localhost").searchParams;
|
|
740
745
|
const options = parseTaskCatalogOptions([...query].flatMap(([key, value]) => key === "all"
|
|
@@ -1281,6 +1286,28 @@ function rejectUpgrade(socket, status, message) {
|
|
|
1281
1286
|
+ "Connection: close\r\n"
|
|
1282
1287
|
+ "Content-Length: 0\r\n\r\n");
|
|
1283
1288
|
}
|
|
1289
|
+
/** Bounded owner discovery for the Jobs entry: counts per owning scope only.
|
|
1290
|
+
* Records are still listed through the scope's own Job/Schedule reads, with
|
|
1291
|
+
* their existing authority checks; this never lists a definition itself. */
|
|
1292
|
+
const JOB_SCOPE_TASK_LIMIT = 200;
|
|
1293
|
+
export function readWebJobScopes(reader) {
|
|
1294
|
+
const empty = { definitions: 0, schedules: 0, enabledSchedules: 0 };
|
|
1295
|
+
let global = empty;
|
|
1296
|
+
const tasks = [];
|
|
1297
|
+
for (const summary of reader.listJobScopeSummaries()) {
|
|
1298
|
+
const counts = { definitions: summary.definitions, schedules: summary.schedules, enabledSchedules: summary.enabledSchedules };
|
|
1299
|
+
if (summary.scope.kind === "global") {
|
|
1300
|
+
global = counts;
|
|
1301
|
+
continue;
|
|
1302
|
+
}
|
|
1303
|
+
const task = reader.getTask(summary.scope.taskId);
|
|
1304
|
+
if (task !== null)
|
|
1305
|
+
tasks.push({ taskId: task.id, title: task.title, status: task.status, ...counts });
|
|
1306
|
+
}
|
|
1307
|
+
tasks.sort((a, b) => Number(b.taskId.slice(5)) - Number(a.taskId.slice(5)));
|
|
1308
|
+
return { global, tasks: tasks.slice(0, JOB_SCOPE_TASK_LIMIT), total: tasks.length,
|
|
1309
|
+
omitted: Math.max(0, tasks.length - JOB_SCOPE_TASK_LIMIT), scope: "owner-counts-only; records use each scope's authorized reads" };
|
|
1310
|
+
}
|
|
1284
1311
|
function dashboardHtml(token) {
|
|
1285
1312
|
return DASHBOARD_HTML.replace('content="__YUI_WEB_TOKEN__"', `content="${escapeHtmlAttribute(token)}"`);
|
|
1286
1313
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Global Shell Jobs
|
|
2
|
+
|
|
3
|
+
A global Shell Job owns its command steps, environment and text script files.
|
|
4
|
+
It needs neither a Task nor a Project. The Web Jobs editor and CLI edit the
|
|
5
|
+
same revisioned definition. Saving never starts a Run.
|
|
6
|
+
|
|
7
|
+
Commands execute as the local Yui user, **not in a security sandbox**. Do not
|
|
8
|
+
store credentials in commands, scripts or environment variables. Task-scoped
|
|
9
|
+
Shell Jobs retain their existing managed Project/workspace authority.
|
|
10
|
+
|
|
11
|
+
## CLI
|
|
12
|
+
|
|
13
|
+
Create a command-only Job:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
yui job define --global --job hello --title "Hello" --command 'printf "hello\n"'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Import a local script into a Job-owned relative path (the file contents are
|
|
20
|
+
copied, not linked to the mutable source file):
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
yui job define --global --job check --title "Daily check" \
|
|
24
|
+
--command 'sh scripts/check.sh' --script scripts/check.sh=./check.sh
|
|
25
|
+
yui job show --global --job check --json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Edit the local script and upload a replacement using the revision returned by
|
|
29
|
+
`show`. Existing commands and other script files are preserved:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
yui job update --global --job check --revision 1 \
|
|
33
|
+
--script scripts/check.sh=./check.sh
|
|
34
|
+
yui job update --global --job hello --revision 1 --command 'printf "updated\n"'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`--script` can be repeated for different paths. To edit multiple steps,
|
|
38
|
+
environment variables, or remove files, use `--input-file config.json` with
|
|
39
|
+
the complete configuration:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"kind": "shell",
|
|
44
|
+
"env": {},
|
|
45
|
+
"steps": [{"name": "check", "command": "sh scripts/check.sh", "timeoutMs": 60000}],
|
|
46
|
+
"files": [{"path": "scripts/check.sh", "content": "printf 'ok\\n'\n"}]
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Pass `--title` with complete-configuration define/update. Re-read the current
|
|
51
|
+
revision after a conflict instead of blindly retrying. A full configuration
|
|
52
|
+
replaces the previous one; shorthand edits preserve unrelated fields.
|
|
53
|
+
|
|
54
|
+
Run explicitly and inspect the exact returned Run:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
yui job run --global --job check --request-id check-20261023-1
|
|
58
|
+
yui job runs --global --job check
|
|
59
|
+
yui job logs --global --job check --run <returned-run-id>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Files, history and schedules
|
|
63
|
+
|
|
64
|
+
Each admitted Run captures the exact configuration and script contents. The
|
|
65
|
+
runner materializes files in a fresh `artifacts/jobs/global/<run-id>/workspace`
|
|
66
|
+
directory inside the selected Yui Home. Earlier Runs are not changed by later
|
|
67
|
+
edits. Script paths are relative, cannot traverse directories, and are limited
|
|
68
|
+
to 64 files / 512 KiB total. Use `sh scripts/check.sh` or another installed
|
|
69
|
+
interpreter; script names do not choose an interpreter automatically.
|
|
70
|
+
|
|
71
|
+
Schedules use the existing explicit service authorization, bound to the Job's
|
|
72
|
+
revision. Editing commands or scripts invalidates the old schedule binding;
|
|
73
|
+
inspect and explicitly re-enable/re-authorize the schedule before future runs.
|
|
74
|
+
Saving a new script never silently changes an already authorized command.
|
|
75
|
+
|
|
76
|
+
Storage 1.23 declares this additive payload change. Upgrade an existing Home
|
|
77
|
+
through the normal authorized upgrade path; do not run a new build against an
|
|
78
|
+
older shared Home or rewrite its records by hand.
|
package/docs/job-runs.md
CHANGED
|
@@ -176,9 +176,12 @@ identities. Ordinary readers use only the current contract.
|
|
|
176
176
|
start is not confirmation. The snapshot is derived exclusively from the immutable
|
|
177
177
|
Run fields, never a mutable current Job definition. Scope, executor (`shell` or
|
|
178
178
|
controlled `builtin`, or `task`), target and trigger are separate concepts. Task
|
|
179
|
-
delegation is available in either management scope;
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
delegation is available in either management scope; builtin remains global-only.
|
|
180
|
+
Global Shell Jobs own revisioned command steps and script files, without a Task
|
|
181
|
+
or Project. Task Shell Jobs retain managed-workspace authority.
|
|
182
|
+
See [Global Shell Jobs](global-shell-jobs.md) for Web/CLI editing, per-Run file
|
|
183
|
+
snapshots and schedule reauthorization. There is no extensible empty executor
|
|
184
|
+
or plugin configuration.
|
|
182
185
|
|
|
183
186
|
## Delegating delivery to a Task
|
|
184
187
|
|