@zq-silk/yui 2.5.0 → 2.6.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 +3 -2
- package/dist/commands/taskCommands.js +8 -3
- package/dist/commands/taskContextCommand.js +3 -2
- package/dist/context/taskContext.js +7 -4
- package/dist/kernel/builtinCapabilities.js +2 -1
- package/dist/runtime/builtinAgentErrorMappers.js +9 -5
- package/dist/runtime/providerRetry.js +38 -0
- package/dist/runtime/taskSessions.js +240 -0
- package/dist/scheduler/taskExecutionProjection.js +78 -6
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app/taskView.js +1 -0
- package/dist/web/assets/client/domain/records.js +70 -2
- package/dist/web/assets/client/domain/runs.js +3 -2
- package/dist/web/assets/client/domain/vocab.js +1 -1
- package/dist/web/assets/client/i18n/en.js +17 -1
- package/dist/web/assets/client/i18n/zh-CN.js +17 -1
- package/dist/web/assets/client/lib/api.js +4 -1
- package/dist/web/assets/client/lib/theme.js +31 -10
- package/dist/web/assets/client/views/task/actions.js +38 -21
- package/dist/web/assets/client/views/task/observation.js +12 -3
- package/dist/web/assets/client/views/task/overview.js +7 -40
- package/dist/web/assets/client/views/task/runtime.js +71 -34
- package/dist/web/assets/shared/icons.js +2 -0
- package/dist/web/assets/shell/index.js +1 -0
- package/dist/web/assets/shell/settings.js +1 -0
- package/dist/web/assets/styles/views/task.js +14 -0
- package/dist/web/webServer.js +12 -0
- package/dist/web/webSessions.js +2 -165
- package/dist/web/webSnapshot.js +9 -5
- package/dist/web/webTaskActivity.js +54 -0
- package/dist/web/webTaskSurface.js +1 -0
- package/docs/observability/README.md +25 -1
- package/docs/observability/README.zh-CN.md +18 -1
- package/docs/provider-retry.md +11 -0
- package/docs/release-workflow.md +27 -0
- package/docs/release-workflow.zh-CN.md +22 -0
- package/package.json +1 -1
- package/skills/yui-runtime/references/recovery.md +9 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { APP_SCRIPT, CLIENT_MODULES } from "./client/index.js";
|
|
7
|
+
import { THEME_BOOTSTRAP_SCRIPT } from "./client/lib/theme.js";
|
|
7
8
|
import { FONT_WOFF2_BASE64 } from "./fontData.js";
|
|
8
9
|
import { DASHBOARD_HTML } from "./shell/index.js";
|
|
9
10
|
import { STYLESHEETS } from "./styles/index.js";
|
|
@@ -13,6 +14,7 @@ const JS = "text/javascript; charset=utf-8";
|
|
|
13
14
|
export const WEB_ASSETS = Object.freeze({
|
|
14
15
|
...Object.fromEntries(STYLESHEETS.map(({ name, body }) => [`/assets/css/${name}.css`, { contentType: CSS, body }])),
|
|
15
16
|
...Object.fromEntries(Object.entries(CLIENT_MODULES).map(([path, body]) => [`/assets/js/${path}.js`, { contentType: JS, body }])),
|
|
17
|
+
"/assets/theme.js": { contentType: JS, body: THEME_BOOTSTRAP_SCRIPT },
|
|
16
18
|
"/assets/app.js": { contentType: JS, body: APP_SCRIPT },
|
|
17
19
|
...fontAssets(),
|
|
18
20
|
"/assets/vendor/xterm.mjs": vendorAsset("@xterm/xterm/lib/xterm.mjs", JS),
|
|
@@ -4,9 +4,77 @@ export const RECORDS_SCRIPT = String.raw `
|
|
|
4
4
|
// never acknowledges or accepts anything.
|
|
5
5
|
import { h, icon } from "/assets/js/lib/dom.js";
|
|
6
6
|
import { button, jsonBlock, mono, note } from "/assets/js/ui/primitives.js";
|
|
7
|
-
import { rawDisclosure } from "/assets/js/ui/containers.js";
|
|
7
|
+
import { rawDisclosure, listCard, setCardCount } from "/assets/js/ui/containers.js";
|
|
8
8
|
import { richText } from "/assets/js/ui/text.js";
|
|
9
|
-
import { cachedExact, readExact, summaryFields } from "/assets/js/domain/context.js";
|
|
9
|
+
import { cachedExact, readExact, summaryFields, entriesOf, totalOf, exactCache } from "/assets/js/domain/context.js";
|
|
10
|
+
|
|
11
|
+
// Current-family counts and pages use exactly the Context current filter.
|
|
12
|
+
// A changed Context revalidates already-loaded membership with one bounded
|
|
13
|
+
// page. Only the family's own digest invalidates its pages, not unrelated
|
|
14
|
+
// runtime events. Loading a list does not eagerly inspect its bodies.
|
|
15
|
+
export function currentRecordCard(data, store, t, actions, options) {
|
|
16
|
+
const view = data.viewState;
|
|
17
|
+
exactCache(view);
|
|
18
|
+
view.openRows = view.openRows || {};
|
|
19
|
+
view.currentLists = view.currentLists || {};
|
|
20
|
+
let state = view.currentLists[store];
|
|
21
|
+
if (!state || (!state.started && state.key !== data.core.coreCursor)) {
|
|
22
|
+
state = view.currentLists[store] = {
|
|
23
|
+
key: data.core.coreCursor, items: entriesOf(data.core, store),
|
|
24
|
+
total: totalOf(data.core, store), started: false, cursor: null, error: null, pending: false
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const revalidate = state.key !== data.core.coreCursor;
|
|
28
|
+
state.key = data.core.coreCursor;
|
|
29
|
+
const element = listCard({ id: options.id, title: options.title, icon: options.icon || "pulse", count: state.total });
|
|
30
|
+
const rows = h("div.stack"), footer = h("div.stack", { "aria-live": "polite" });
|
|
31
|
+
element.list.append(rows, footer);
|
|
32
|
+
function draw(updateRows) {
|
|
33
|
+
setCardCount(element, state.total);
|
|
34
|
+
if (updateRows !== false) {
|
|
35
|
+
rows.replaceChildren();
|
|
36
|
+
state.items.forEach(function (item) {
|
|
37
|
+
rows.append(!item.omitted && item.value && options.render ? options.render(item.value, null, item)
|
|
38
|
+
: lazyRow(item, data.task.id, t, actions, {
|
|
39
|
+
cache: view.exact,
|
|
40
|
+
openRows: view.openRows,
|
|
41
|
+
head: h("span", null, item.ref.refId + (item.summary ? " · " + item.summary : "")),
|
|
42
|
+
render: options.render
|
|
43
|
+
}));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
footer.replaceChildren(note(t("activity.loaded") + " " + state.items.length + " / " + state.total));
|
|
47
|
+
if (!state.total) footer.append(note(t("activity.none")));
|
|
48
|
+
if (state.error) footer.append(note(state.error + " · " + t("activity.refreshHint"), "bad"));
|
|
49
|
+
if ((!state.started && state.items.length < state.total) || state.cursor) {
|
|
50
|
+
footer.append(button(t("activity.more") + " (" + Math.max(0, state.total - state.items.length) + ")", {
|
|
51
|
+
disabled: state.pending, onClick: function () { load(state.started); }
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
footer.append(button(t("activity.refresh"), { disabled: state.pending, onClick: function () { load(false); } }));
|
|
55
|
+
element.dataset.reading = String(state.pending);
|
|
56
|
+
}
|
|
57
|
+
async function load(more) {
|
|
58
|
+
if (state.pending) return;
|
|
59
|
+
const busy = function () { return !!rows.querySelector('[data-unsent="true"], [data-reading="true"]'); };
|
|
60
|
+
if (busy()) { state.error = t("activity.busy"); draw(false); return; }
|
|
61
|
+
state.pending = true;
|
|
62
|
+
draw(false);
|
|
63
|
+
try {
|
|
64
|
+
const page = await actions.list(data.task.id, store, { view: "current", limit: 20, cursor: more ? state.cursor : null });
|
|
65
|
+
if (busy()) throw new Error(t("activity.busy"));
|
|
66
|
+
if (more || !state.started || state.digest !== page.digest) {
|
|
67
|
+
state.items = more ? state.items.concat(page.items) : page.items;
|
|
68
|
+
state.cursor = page.nextCursor;
|
|
69
|
+
}
|
|
70
|
+
state.digest = page.digest; state.total = page.total; state.started = true; state.error = null;
|
|
71
|
+
} catch (error) { state.error = error.message; }
|
|
72
|
+
finally { state.pending = false; draw(!state.error); }
|
|
73
|
+
}
|
|
74
|
+
draw();
|
|
75
|
+
if (revalidate) load(false);
|
|
76
|
+
return element;
|
|
77
|
+
}
|
|
10
78
|
|
|
11
79
|
// A row drawn from its exact value: synchronously when cached, otherwise the
|
|
12
80
|
// summary card until the read returns and replaces it in place.
|
|
@@ -16,7 +16,7 @@ export function runCard(run, t, locale) {
|
|
|
16
16
|
run.purpose ? chip(label(t, "run.purpose", run.purpose)) : null,
|
|
17
17
|
run.workItemId ? chip(run.workItemId) : null,
|
|
18
18
|
h("span.spacer"),
|
|
19
|
-
statusBadge(t, "run", "run", run.status)), runBody(run, t, locale));
|
|
19
|
+
statusBadge(t, "run", "run", run.status)), ...runBody(run, t, locale));
|
|
20
20
|
return element;
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -107,7 +107,8 @@ export function roleCard(role, t, locale, options) {
|
|
|
107
107
|
function roleFacts(role, t, locale, opts) {
|
|
108
108
|
const session = opts.runtimeRole && opts.runtimeRole.runtimeSession;
|
|
109
109
|
const rows = [];
|
|
110
|
-
rows.push([t("role.session"), session && session.nativeSessionId ? mono(session.nativeSessionId)
|
|
110
|
+
rows.push([t("role.session"), session && session.nativeSessionId ? mono(session.nativeSessionId)
|
|
111
|
+
: !opts.runtimeRole ? t("activity.notLoaded") : t("role.noSession")]);
|
|
111
112
|
if (role.launchRevision !== undefined) rows.push([t("role.desired"), "r" + role.launchRevision + (opts.launchDrift ? " · " + t("role.drift") : "")]);
|
|
112
113
|
if (role.defaultAccess !== undefined) rows.push([t("role.access"), role.defaultAccess]);
|
|
113
114
|
if (opts.retry) rows.push([t("role.providerRetry"), label(t, "retry", opts.retry.status) + " · "
|
|
@@ -22,7 +22,7 @@ export function label(t, prefix, value) {
|
|
|
22
22
|
const TONES = {
|
|
23
23
|
task: { draft: "warn", active: "info", completed: "ok", cancelled: "idle", archived: "idle" },
|
|
24
24
|
exec: {
|
|
25
|
-
"needs-leader-action": "accent", "waiting-on-agents": "info", "waiting-user": "warn",
|
|
25
|
+
"needs-leader-action": "accent", "waiting-on-agents": "info", "waiting-on-jobs": "info", "waiting-user": "warn",
|
|
26
26
|
recovering: "warn", attention: "bad", "progressing-with-attention": "warn", blocked: "bad",
|
|
27
27
|
conflicted: "warn", working: "info", completed: "ok", retired: "idle", cancelled: "idle",
|
|
28
28
|
accepted: "ok", open: "info", archived: "idle"
|
|
@@ -58,7 +58,10 @@ export const EN = {
|
|
|
58
58
|
"create.hint": "Planning saves a Draft and queues the Leader. It does not start development. Uncheck to save only.",
|
|
59
59
|
"create.open": "Open saved task",
|
|
60
60
|
"workbench.next": "Next step",
|
|
61
|
-
"workbench.continue": "
|
|
61
|
+
"workbench.continue": "Talk to Leader",
|
|
62
|
+
"workbench.prompt.running": "Talk to the current Leader to adjust direction or add information.",
|
|
63
|
+
"workbench.prompt.activate": "Start the Task once the draft and plan are confirmed, or keep discussing with the Leader.",
|
|
64
|
+
"workbench.prompt.archive": "This Task has ended. Archive it, or follow up with the Leader.",
|
|
62
65
|
"workbench.activate": "Start task",
|
|
63
66
|
"workbench.activateConfirm": "Have you confirmed the Draft and plan? Request activation now?",
|
|
64
67
|
"workbench.activateHint": "Uses the existing activation process. A saved request is not an active Task.",
|
|
@@ -700,6 +703,19 @@ export const EN = {
|
|
|
700
703
|
"priority.low": "Low",
|
|
701
704
|
"exec.status.needs-leader-action": "Needs Leader action",
|
|
702
705
|
"exec.status.waiting-on-agents": "Waiting on agents",
|
|
706
|
+
"exec.status.waiting-on-jobs": "Waiting on background jobs",
|
|
707
|
+
"exec.action.wait-for-jobs": "Wait for background jobs",
|
|
708
|
+
"activity.loaded": "Loaded",
|
|
709
|
+
"activity.none": "No current items in this source.",
|
|
710
|
+
"activity.notLoaded": "Not loaded. Use the current activity list to read more.",
|
|
711
|
+
"activity.more": "Load more",
|
|
712
|
+
"activity.refresh": "Refresh list",
|
|
713
|
+
"activity.refreshHint": "If the source changed, refresh before continuing.",
|
|
714
|
+
"activity.busy": "Finish or clear the unsent answer, or wait for the current read before replacing this list.",
|
|
715
|
+
"activity.reviews": "Unfinished reviews",
|
|
716
|
+
"activity.native": "Native activity",
|
|
717
|
+
"activity.openSession": "Open current Role Session",
|
|
718
|
+
"activity.scope": "Partial coverage: selected native Sessions and retained, exactly identified child observations. Not a live process or complete historical Session inventory. Native facts may describe an AgentRun above; counts must not be added together.",
|
|
703
719
|
"exec.status.waiting-user": "Waiting on you",
|
|
704
720
|
"exec.status.recovering": "Recovering",
|
|
705
721
|
"exec.status.attention": "Needs attention",
|
|
@@ -56,7 +56,10 @@ export const ZH_CN = {
|
|
|
56
56
|
"create.hint": "保存 Draft 并通知 Leader 规划,不启动开发。取消勾选则仅保存任务。",
|
|
57
57
|
"create.open": "打开已保存任务",
|
|
58
58
|
"workbench.next": "下一步",
|
|
59
|
-
"workbench.continue": "
|
|
59
|
+
"workbench.continue": "与 Leader 对话",
|
|
60
|
+
"workbench.prompt.running": "需要调整方向或补充信息时,直接与当前 Leader 对话。",
|
|
61
|
+
"workbench.prompt.activate": "确认草稿与方案后启动任务,或继续与 Leader 讨论。",
|
|
62
|
+
"workbench.prompt.archive": "任务已结束。可以归档,或继续与 Leader 跟进。",
|
|
60
63
|
"workbench.activate": "启动任务",
|
|
61
64
|
"workbench.activateConfirm": "已确认草稿和方案,申请激活此任务?",
|
|
62
65
|
"workbench.activateHint": "复用现有激活流程。请求已保存不代表任务已激活。",
|
|
@@ -698,6 +701,19 @@ export const ZH_CN = {
|
|
|
698
701
|
"priority.low": "低",
|
|
699
702
|
"exec.status.needs-leader-action": "待 Leader 处理",
|
|
700
703
|
"exec.status.waiting-on-agents": "等待智能体",
|
|
704
|
+
"exec.status.waiting-on-jobs": "等待后台任务",
|
|
705
|
+
"exec.action.wait-for-jobs": "等待后台任务结果",
|
|
706
|
+
"activity.loaded": "已加载",
|
|
707
|
+
"activity.none": "此来源没有当前事项。",
|
|
708
|
+
"activity.notLoaded": "尚未加载;可通过当前活动列表继续读取。",
|
|
709
|
+
"activity.more": "继续加载",
|
|
710
|
+
"activity.refresh": "刷新列表",
|
|
711
|
+
"activity.refreshHint": "若来源已变化,请刷新后继续。",
|
|
712
|
+
"activity.busy": "请先完成或清空未发送的回答,或等待当前读取结束,再替换列表。",
|
|
713
|
+
"activity.reviews": "未结束的审查",
|
|
714
|
+
"activity.native": "原生活动",
|
|
715
|
+
"activity.openSession": "打开该角色的当前会话",
|
|
716
|
+
"activity.scope": "部分覆盖:选中的原生会话,以及身份明确、尚未结束的保留子活动观测;不是实时进程或完整历史会话清单。原生事实可能与上方 AgentRun 描述同一执行,计数不可直接相加。",
|
|
701
717
|
"exec.status.waiting-user": "等待你回答",
|
|
702
718
|
"exec.status.recovering": "恢复中",
|
|
703
719
|
"exec.status.attention": "需要关注",
|
|
@@ -70,6 +70,9 @@ export const api = {
|
|
|
70
70
|
context: function (taskId) { return requestJson(task(taskId) + "/context"); },
|
|
71
71
|
workbench: function (taskId) { return requestJson(task(taskId) + "/workbench"); },
|
|
72
72
|
observation: function (taskId) { return requestJson(task(taskId) + "?compact=true", { signal: AbortSignal.timeout(1500) }); },
|
|
73
|
+
activity: function (taskId, options) {
|
|
74
|
+
return requestJson(task(taskId) + "/activity?" + new URLSearchParams(options || {}));
|
|
75
|
+
},
|
|
73
76
|
projects: function (after) { return requestJson("/api/projects" + (after ? "?after=" + encodeURIComponent(after) : "")); },
|
|
74
77
|
createTask: function (input) { return submitMutation("task/create", "/api/tasks", input); },
|
|
75
78
|
taskAction: function (taskId, action, requestId) {
|
|
@@ -99,7 +102,7 @@ export const api = {
|
|
|
99
102
|
// One page of a Context family: summaries and exact references, no bodies.
|
|
100
103
|
list: function (taskId, store, options) {
|
|
101
104
|
const query = new URLSearchParams({ store: store });
|
|
102
|
-
["status", "cursor", "limit"].forEach(function (key) {
|
|
105
|
+
["view", "status", "cursor", "limit"].forEach(function (key) {
|
|
103
106
|
if (options && options[key] !== undefined && options[key] !== null) query.set(key, String(options[key]));
|
|
104
107
|
});
|
|
105
108
|
return requestJson(task(taskId) + "/list?" + query);
|
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
import { DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, THEME_OPTIONS } from "../../shared/themes.js";
|
|
2
|
+
// Shared by the parser-blocking head script and the later settings controller.
|
|
3
|
+
// Both resolve the same browser preference without publishing global state.
|
|
4
|
+
const THEME_PREFERENCE_SCRIPT = String.raw `
|
|
5
|
+
const IDS = ${JSON.stringify(THEME_OPTIONS.map(theme => theme.id))};
|
|
6
|
+
const DEFAULT_DARK = ${JSON.stringify(DEFAULT_DARK_THEME.id)};
|
|
7
|
+
const DEFAULT_LIGHT = ${JSON.stringify(DEFAULT_LIGHT_THEME.id)};
|
|
8
|
+
|
|
9
|
+
function storedPreference() {
|
|
10
|
+
try {
|
|
11
|
+
const saved = localStorage.getItem("yui.theme");
|
|
12
|
+
return IDS.includes(saved) ? saved : "system";
|
|
13
|
+
} catch { return "system"; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function resolveTheme(preference, media) {
|
|
17
|
+
return preference === "system" ? (media.matches ? DEFAULT_LIGHT : DEFAULT_DARK) : preference;
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
// A same-origin classic script runs before styles/body, unlike the deferred
|
|
21
|
+
// application module graph. No inline script or weaker CSP is needed.
|
|
22
|
+
export const THEME_BOOTSTRAP_SCRIPT = String.raw `
|
|
23
|
+
(function () {
|
|
24
|
+
${THEME_PREFERENCE_SCRIPT}
|
|
25
|
+
document.documentElement.dataset.theme = resolveTheme(
|
|
26
|
+
storedPreference(), window.matchMedia("(prefers-color-scheme: light)")
|
|
27
|
+
);
|
|
28
|
+
})();
|
|
29
|
+
`;
|
|
2
30
|
export const THEME_SCRIPT = String.raw `
|
|
3
31
|
// Theme preference. "system" follows prefers-color-scheme and resolves to the
|
|
4
32
|
// default dark or light theme; the resolved id is stamped on <html> so its
|
|
5
33
|
// token block applies. Options and swatches come from shared/themes.ts.
|
|
6
34
|
import { h, markSelected } from "/assets/js/lib/dom.js";
|
|
7
|
-
import {
|
|
35
|
+
import { writePreference } from "/assets/js/lib/prefs.js";
|
|
8
36
|
|
|
9
37
|
const THEMES = Object.freeze(${JSON.stringify(THEME_OPTIONS)});
|
|
10
|
-
|
|
11
|
-
const DEFAULT_DARK = ${JSON.stringify(DEFAULT_DARK_THEME.id)};
|
|
12
|
-
const DEFAULT_LIGHT = ${JSON.stringify(DEFAULT_LIGHT_THEME.id)};
|
|
13
|
-
|
|
14
|
-
function storedPreference() {
|
|
15
|
-
const saved = readPreference("yui.theme", "system");
|
|
16
|
-
return IDS.includes(saved) ? saved : "system";
|
|
17
|
-
}
|
|
38
|
+
${THEME_PREFERENCE_SCRIPT}
|
|
18
39
|
|
|
19
40
|
function themeOption(theme, t, onPick) {
|
|
20
41
|
const swatch = h("span.theme-swatch", { "aria-hidden": "true" }, theme.swatch.map(function (color, index) {
|
|
@@ -36,7 +57,7 @@ export function createThemeController(container, t) {
|
|
|
36
57
|
const listeners = new Set();
|
|
37
58
|
|
|
38
59
|
function resolved() {
|
|
39
|
-
return preference
|
|
60
|
+
return resolveTheme(preference, media);
|
|
40
61
|
}
|
|
41
62
|
function apply() {
|
|
42
63
|
document.documentElement.dataset.theme = resolved();
|
|
@@ -1,36 +1,53 @@
|
|
|
1
1
|
export const TASK_ACTIONS_SCRIPT = String.raw `
|
|
2
|
+
// Next step: one compact action bar. A short prompt on the left, the actions
|
|
3
|
+
// on the right (secondary first, primary last), and a single status line
|
|
4
|
+
// below that appears only when it carries a condition, a block or a receipt.
|
|
2
5
|
import { h } from "/assets/js/lib/dom.js";
|
|
3
|
-
import { button
|
|
6
|
+
import { button } from "/assets/js/ui/primitives.js";
|
|
4
7
|
import { card } from "/assets/js/ui/containers.js";
|
|
5
8
|
import { receiptLine, setReceipt, submitWrite } from "/assets/js/ui/forms.js";
|
|
6
9
|
|
|
7
10
|
export function taskActions(task, t, ctx) {
|
|
8
|
-
const element = card({ title: t("workbench.next"), icon: "chat" });
|
|
9
|
-
element.body.append(button(t("workbench.continue"), { variant: "primary", onClick: function () { ctx.openConversation(); } }));
|
|
10
11
|
const action = task.status === "draft" ? "activate"
|
|
11
12
|
: ["completed", "cancelled"].includes(task.status) ? "archive" : null;
|
|
13
|
+
const element = card({ className: "next-card" });
|
|
14
|
+
const continueButton = button(t("workbench.continue"), {
|
|
15
|
+
icon: "chat", variant: action ? "" : "primary", onClick: function () { ctx.openConversation(); }
|
|
16
|
+
});
|
|
17
|
+
const buttons = h("div.next-actions", null, continueButton);
|
|
18
|
+
const lead = h("div.next-lead", null,
|
|
19
|
+
h("p.next-kicker", null, t("workbench.next")),
|
|
20
|
+
h("p.next-prompt", null, t("workbench.prompt." + (action || "running"))));
|
|
21
|
+
element.body.append(h("div.next-row", null, lead, buttons));
|
|
12
22
|
if (!action) return element;
|
|
23
|
+
|
|
13
24
|
const activation = task.activationRequest;
|
|
14
25
|
const blocked = action === "activate" && (task.executionGate.state !== "enabled"
|
|
15
26
|
|| activation && ["pending", "failed"].includes(activation.disposition));
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
// Idle receipts stay empty (hidden); only real state is shown.
|
|
28
|
+
const receipt = receiptLine(blocked ? (activation && activation.outcome || t("workbench.activationBlocked")) : "", blocked ? "warn" : "");
|
|
29
|
+
const control = button(t("workbench." + action), {
|
|
30
|
+
icon: action === "activate" ? "play" : "archive", variant: "primary", disabled: !!blocked,
|
|
31
|
+
title: t("workbench." + action + "Hint"),
|
|
32
|
+
onClick: function () {
|
|
33
|
+
if (!window.confirm(t("workbench." + action + "Confirm"))) return;
|
|
34
|
+
submitWrite({
|
|
35
|
+
control: control, receipt: receipt, form: element.body, t: t,
|
|
36
|
+
send: function (requestId) { return ctx.taskAction(task.id, action, requestId); },
|
|
37
|
+
saved: function (value) {
|
|
38
|
+
const result = value.result;
|
|
39
|
+
const text = action === "activate" ? result.operationRef + " · " + result.request.disposition
|
|
40
|
+
: result.task.id + " · " + result.task.status;
|
|
41
|
+
setReceipt(receipt, text, "ok");
|
|
42
|
+
return true;
|
|
43
|
+
},
|
|
44
|
+
rejectedKey: "receipt.notSubmittedBecause", unknownKey: "workbench.unknown",
|
|
45
|
+
afterWrite: ctx.afterWrite
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
buttons.append(control);
|
|
50
|
+
element.body.append(h("div.next-foot", null, h("p.next-hint", null, t("workbench." + action + "Hint")), receipt));
|
|
34
51
|
return element;
|
|
35
52
|
}
|
|
36
53
|
`;
|
|
@@ -73,6 +73,14 @@ function nowHead(data, execution, t, locale) {
|
|
|
73
73
|
: h("span.now-owner", null, label(t, "exec.owner", execution.owner) + " · " + label(t, "exec.action", execution.action)));
|
|
74
74
|
const activeCount = execution.activeRunCount === undefined ? (execution.activeRuns || []).length : execution.activeRunCount;
|
|
75
75
|
if (activeCount) head.append(badge(activeCount + " " + t("now.activeRuns"), "info"));
|
|
76
|
+
if (execution.activity) {
|
|
77
|
+
const jobs = execution.activity.jobs, native = execution.activity.native;
|
|
78
|
+
head.append(badge(t("jobs.title") + " · " + t("job.running") + " " + jobs.running
|
|
79
|
+
+ " / " + t("job.queued") + " " + jobs.queued + " / " + t("detail.unobserved") + " " + jobs.unknown, jobs.unknown ? "warn" : "info"));
|
|
80
|
+
head.append(badge(t("activity.native") + " · " + (native.active + native.waiting)
|
|
81
|
+
+ " / " + t("sessions.background") + " " + native.background
|
|
82
|
+
+ " / " + t("detail.unobserved") + " " + native.unknown, native.unknown ? "warn" : "info"));
|
|
83
|
+
}
|
|
76
84
|
if (execution.monitoring === "stopped") head.append(badge(t("now.monitoringStopped"), "warn"));
|
|
77
85
|
if (execution.failClosed) head.append(badge(t("now.failClosed"), "bad"));
|
|
78
86
|
head.append(h("span.spacer"), h("span.faint.small", { title: formatDateTime(data.runtimeObservedAt, locale) },
|
|
@@ -123,7 +131,8 @@ function drawSessions(container, observation, t, locale) {
|
|
|
123
131
|
function drawRoleSession(slot, session, observation, t, locale) {
|
|
124
132
|
clear(slot);
|
|
125
133
|
if (!session) {
|
|
126
|
-
slot.append(h("span.faint.small", null, observation ? t("
|
|
134
|
+
slot.append(h("span.faint.small", null, !observation ? t("sessions.unavailable")
|
|
135
|
+
: observation.total > observation.sessions.length ? t("activity.notLoaded") : t("session.noneRecorded")));
|
|
127
136
|
return;
|
|
128
137
|
}
|
|
129
138
|
slot.append(h("div.role-session-head", null, dot(tone("session", session.group)),
|
|
@@ -143,8 +152,8 @@ function drawSessionFacts(facts, observation, sessions, t, locale) {
|
|
|
143
152
|
[t("sessions.role"), session.roleName],
|
|
144
153
|
[t("sessions.lastActivity"), session.lastActivityAt ? formatDateTime(session.lastActivityAt, locale) : t("sessions.unobserved")],
|
|
145
154
|
[t("sessions.inputUpdated"), formatDateTime(session.sourceUpdatedAt, locale)],
|
|
146
|
-
[t("sessions.operations"), session.operations.length
|
|
147
|
-
[t("sessions.background"), session.background.length
|
|
155
|
+
[t("sessions.operations"), (session.operationCount ?? session.operations.length) + " · " + session.operations.join(", ")],
|
|
156
|
+
[t("sessions.background"), (session.backgroundCount ?? session.background.length) + " · " + session.background.map(function (item) { return item.id + " · " + item.execution; }).join(", ")],
|
|
148
157
|
[t("sessions.identity"), mono([session.nativeSessionId, session.nativeTurnId, session.attemptId].filter(Boolean).join(" / "))]
|
|
149
158
|
]));
|
|
150
159
|
});
|
|
@@ -7,8 +7,8 @@ import { formatDateTime } from "/assets/js/lib/format.js";
|
|
|
7
7
|
import { bulletList, button, codeBlock, dot, emptyState, note, timeTag } from "/assets/js/ui/primitives.js";
|
|
8
8
|
import { card, disclosure } from "/assets/js/ui/containers.js";
|
|
9
9
|
import { richText } from "/assets/js/ui/text.js";
|
|
10
|
-
import { entriesOf,
|
|
11
|
-
import {
|
|
10
|
+
import { entriesOf, totalOf, valuesOf } from "/assets/js/domain/context.js";
|
|
11
|
+
import { currentRecordCard, recordCard } from "/assets/js/domain/records.js";
|
|
12
12
|
import { inputCard } from "/assets/js/domain/work.js";
|
|
13
13
|
import { taskActions } from "/assets/js/views/task/actions.js";
|
|
14
14
|
|
|
@@ -30,45 +30,12 @@ export function renderOverviewPanel(panel, data, t, locale, ctx) {
|
|
|
30
30
|
// Needs you: open InputRequests with their answer controls. Open questions
|
|
31
31
|
// the bounded read did not carry are listed by reference.
|
|
32
32
|
function needsCard(data, t, locale, ctx) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
return currentRecordCard(data, "input-request", t, ctx, {
|
|
34
|
+
id: "detail-attention", title: t("overview.needsYou"), icon: "inbox",
|
|
35
|
+
render: function (value) {
|
|
36
|
+
return value.status === "open" ? inputCard(value, t, locale, ctx.answerInput) : note(value.id + " · " + value.status);
|
|
37
|
+
}
|
|
38
38
|
});
|
|
39
|
-
const needs = card({ title: t("overview.needsYou"), icon: "inbox", count: openInputs.count, className: "needs-card" });
|
|
40
|
-
needs.id = "detail-attention";
|
|
41
|
-
inputs.forEach(function (entry) { needs.body.append(inputCard(entry.value, t, locale, ctx.answerInput)); });
|
|
42
|
-
const seen = new Set(inputs.map(function (entry) { return entry.ref.refId; }));
|
|
43
|
-
function deferredInput(item) {
|
|
44
|
-
seen.add(item.ref.refId);
|
|
45
|
-
return lazyRow(item, task.id, t, ctx, {
|
|
46
|
-
head: h("span", null, item.summary || item.ref.refId),
|
|
47
|
-
cache: exactCache(data.viewState),
|
|
48
|
-
render: function (value) {
|
|
49
|
-
return value.status === "open" ? inputCard(value, t, locale, ctx.answerInput)
|
|
50
|
-
: note(value.id + " · " + value.status);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
missing.forEach(function (ref) {
|
|
55
|
-
const entry = entriesOf(data.core, "input-request").find(function (entry) { return entry.ref.refId === ref.refId; });
|
|
56
|
-
needs.body.append(deferredInput({ ref: ref, summary: entry && entry.summary }));
|
|
57
|
-
});
|
|
58
|
-
if (openInputs.count > inputs.length + missing.length) {
|
|
59
|
-
let cursor;
|
|
60
|
-
const more = button(t("overview.moreInputs"), { onClick: async function () {
|
|
61
|
-
more.disabled = true;
|
|
62
|
-
try {
|
|
63
|
-
const page = await ctx.list(task.id, "input-request", { status: "open", limit: 20, cursor: cursor });
|
|
64
|
-
page.items.forEach(function (item) { if (!seen.has(item.ref.refId)) needs.body.insertBefore(deferredInput(item), more); });
|
|
65
|
-
cursor = page.nextCursor;
|
|
66
|
-
more.disabled = !cursor;
|
|
67
|
-
} catch (error) { needs.body.append(note(error.message, "bad")); more.disabled = false; }
|
|
68
|
-
} });
|
|
69
|
-
needs.body.append(more);
|
|
70
|
-
}
|
|
71
|
-
return needs;
|
|
72
39
|
}
|
|
73
40
|
|
|
74
41
|
function briefOf(data) {
|
|
@@ -3,38 +3,36 @@ export const TASK_RUNTIME_SCRIPT = String.raw `
|
|
|
3
3
|
// control, open execution, open operations, usage and diagnostics. Session
|
|
4
4
|
// lines, counts and raw facts are [data-slot]s the observation fills in.
|
|
5
5
|
import { h } from "/assets/js/lib/dom.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { card, cardDisclosure, listCard } from "/assets/js/ui/containers.js";
|
|
6
|
+
import { dot, button, emptyState, mono, note, timeTag } from "/assets/js/ui/primitives.js";
|
|
7
|
+
import { card, cardDisclosure } from "/assets/js/ui/containers.js";
|
|
9
8
|
import { observabilityMetricCard } from "/assets/js/ui/metrics.js";
|
|
10
9
|
import { statusBadge, tone } from "/assets/js/domain/vocab.js";
|
|
11
|
-
import { cachedExact, entriesOf, exactCache
|
|
12
|
-
import {
|
|
10
|
+
import { cachedExact, entriesOf, exactCache } from "/assets/js/domain/context.js";
|
|
11
|
+
import { currentRecordCard } from "/assets/js/domain/records.js";
|
|
13
12
|
import { roleCard, runCard } from "/assets/js/domain/runs.js";
|
|
14
13
|
import { controlForm } from "/assets/js/domain/taskForms.js";
|
|
15
14
|
|
|
16
15
|
export function renderRuntime(panel, data, t, locale, ctx) {
|
|
17
16
|
exactCache(data.viewState);
|
|
18
17
|
const roleEntries = entriesOf(data.core, "role");
|
|
19
|
-
panel.append(rolesCard(data,
|
|
18
|
+
panel.append(rolesCard(data, t, locale, ctx));
|
|
20
19
|
panel.append(controlCard(data.task, roleEntries, t, ctx));
|
|
21
20
|
panel.append(runsCard(data, t, locale, ctx));
|
|
22
21
|
panel.append(jobsCard(data, t, locale, ctx));
|
|
22
|
+
panel.append(currentRecordCard(data, "review-round", t, ctx, { id: "detail-reviews", title: t("activity.reviews") }));
|
|
23
|
+
panel.append(nativeCard(data, t, ctx));
|
|
23
24
|
panel.append(usageCard(data, t));
|
|
24
25
|
panel.append(diagnosticsCard(data.core, t));
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
// --- Roles -------------------------------------------------------------------
|
|
28
|
-
function rolesCard(data,
|
|
29
|
+
function rolesCard(data, t, locale, ctx) {
|
|
29
30
|
const runs = knownRuns(data);
|
|
30
|
-
const roles =
|
|
31
|
-
id: "detail-roles", title: t("roles.title"), icon: "user",
|
|
32
|
-
|
|
31
|
+
const roles = currentRecordCard(data, "role", t, ctx, {
|
|
32
|
+
id: "detail-roles", title: t("roles.title"), icon: "user",
|
|
33
|
+
render: function (role, result, entry) { return roleRow({ ...entry, value: role }, data, runs, t, locale, ctx); }
|
|
33
34
|
});
|
|
34
|
-
|
|
35
|
-
roles.list.append(entry.omitted ? recordCard(entry, data.task.id, t, ctx) : roleRow(entry, data, runs, t, locale, ctx));
|
|
36
|
-
});
|
|
37
|
-
if (!roleEntries.length) roles.list.append(emptyState(t("records.none")));
|
|
35
|
+
roles.list.prepend(h("div.chip-row", { dataset: { slot: "session-counts" } }));
|
|
38
36
|
return roles;
|
|
39
37
|
}
|
|
40
38
|
|
|
@@ -72,31 +70,70 @@ function controlCard(task, roleEntries, t, ctx) {
|
|
|
72
70
|
|
|
73
71
|
// --- Execution and operations --------------------------------------------------
|
|
74
72
|
function runsCard(data, t, locale, ctx) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!runEntries.length) runList.list.append(emptyState(t("runs.none"), "pulse"));
|
|
79
|
-
runEntries.forEach(function (entry) {
|
|
80
|
-
runList.list.append(exactRow(entry, data.task.id, t, ctx, data.viewState.exact, function (value, result) {
|
|
81
|
-
return runCard({ ...value, execution: (result && result.execution) || entry.execution }, t, locale);
|
|
82
|
-
}));
|
|
73
|
+
return currentRecordCard(data, "run", t, ctx, {
|
|
74
|
+
id: "detail-exec", title: t("runs.title"),
|
|
75
|
+
render: function (value, result) { return runCard({ ...value, execution: result && result.execution }, t, locale); }
|
|
83
76
|
});
|
|
84
|
-
if (runTotal > runEntries.length) runList.list.append(note(fill(t("runs.more"), { count: runTotal - runEntries.length })));
|
|
85
|
-
return runList;
|
|
86
77
|
}
|
|
87
78
|
|
|
88
79
|
function jobsCard(data, t, locale, ctx) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
id: "detail-operations", title: t("jobs.title"), icon: "refresh", count: jobTotal || null,
|
|
93
|
-
hint: jobTotal ? t("jobs.hint") : null
|
|
80
|
+
return currentRecordCard(data, "job", t, ctx, {
|
|
81
|
+
id: "detail-operations", title: t("jobs.title"), icon: "refresh",
|
|
82
|
+
render: function (job) { return jobRecord(job, data.task, t, locale); }
|
|
94
83
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
});
|
|
99
|
-
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function nativeCard(data, t, ctx) {
|
|
87
|
+
const element = card({ id: "detail-native-activity", title: t("activity.native"), icon: "pulse" });
|
|
88
|
+
let state = data.viewState.nativeActivity;
|
|
89
|
+
if (!state) {
|
|
90
|
+
state = data.viewState.nativeActivity = { key: data.core.coreCursor, items: [], page: null, pending: false, error: null };
|
|
91
|
+
}
|
|
92
|
+
const revalidate = state.page && state.key !== data.core.coreCursor;
|
|
93
|
+
state.key = data.core.coreCursor;
|
|
94
|
+
function draw() {
|
|
95
|
+
element.body.replaceChildren(note(t("activity.scope")));
|
|
96
|
+
if (!state.page) element.body.append(note(t("activity.notLoaded")));
|
|
97
|
+
else {
|
|
98
|
+
element.body.append(note(t("activity.loaded") + " " + state.items.length + " / " + state.page.total
|
|
99
|
+
+ " · " + state.page.coverage + " · " + state.page.readAt));
|
|
100
|
+
if (!state.page.total) element.body.append(note(t("activity.none")));
|
|
101
|
+
}
|
|
102
|
+
state.items.forEach(function (item) {
|
|
103
|
+
element.body.append(h("article.record", null,
|
|
104
|
+
h("header.record-head", null, mono(item.roleName + " / " + item.kind), h("span.spacer"), item.status),
|
|
105
|
+
h("p.small", null, item.summary),
|
|
106
|
+
h("p.faint.small.mono-line", null, [item.nativeSessionId, item.runId, item.sourceUpdatedAt].filter(Boolean).join(" · ")),
|
|
107
|
+
button(t("activity.openSession"), { onClick: function () { ctx.openSession(item.roleName); } })));
|
|
108
|
+
});
|
|
109
|
+
if (state.error) element.body.append(note(state.error + " · " + t("activity.refreshHint"), "bad"));
|
|
110
|
+
const controls = h("div.chip-row");
|
|
111
|
+
if (!state.page || state.page.nextCursor) controls.append(button(t("activity.more"), {
|
|
112
|
+
disabled: state.pending, onClick: function () { load(!!state.page); }
|
|
113
|
+
}));
|
|
114
|
+
controls.append(button(t("activity.refresh"), { disabled: state.pending, onClick: function () { load(false); } }));
|
|
115
|
+
element.body.append(controls);
|
|
116
|
+
element.dataset.reading = String(state.pending);
|
|
117
|
+
}
|
|
118
|
+
async function load(more) {
|
|
119
|
+
if (state.pending) return;
|
|
120
|
+
state.pending = true; draw();
|
|
121
|
+
try {
|
|
122
|
+
const page = await ctx.activity(data.task.id, { limit: 20, ...(more ? { cursor: state.page.nextCursor } : {}) });
|
|
123
|
+
if (!more && state.page && state.page.digest === page.digest) {
|
|
124
|
+
// Refresh observation metadata without discarding unchanged loaded pages.
|
|
125
|
+
state.page = { ...page, nextCursor: state.page.nextCursor };
|
|
126
|
+
} else {
|
|
127
|
+
state.items = more ? state.items.concat(page.items) : page.items;
|
|
128
|
+
state.page = page;
|
|
129
|
+
}
|
|
130
|
+
state.error = null;
|
|
131
|
+
} catch (error) { state.error = error.message; }
|
|
132
|
+
finally { state.pending = false; draw(); }
|
|
133
|
+
}
|
|
134
|
+
draw();
|
|
135
|
+
if (revalidate) load(false);
|
|
136
|
+
return element;
|
|
100
137
|
}
|
|
101
138
|
|
|
102
139
|
function jobRecord(job, task, t, locale) {
|
|
@@ -32,6 +32,8 @@ export const ICON_PATHS = Object.freeze({
|
|
|
32
32
|
user: ["M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8z", "M4 20a8 8 0 0 1 16 0"],
|
|
33
33
|
eye: ["M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12z", "M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"],
|
|
34
34
|
plus: ["M12 5v14", "M5 12h14"],
|
|
35
|
+
play: ["M8 5v14l11-7z"],
|
|
36
|
+
archive: ["M3 5h18v4H3z", "M5 9v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9", "M10 13h4"],
|
|
35
37
|
target: ["M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18z", "M12 16a4 4 0 1 0 0-8 4 4 0 0 0 0 8z", "M12 12h.01"],
|
|
36
38
|
flag: ["M5 21V4", "M5 4h11l-2 4 2 4H5"],
|
|
37
39
|
sparkle: ["M12 3v4", "M12 17v4", "M3 12h4", "M17 12h4", "M6 6l2.5 2.5", "M15.5 15.5L18 18", "M18 6l-2.5 2.5", "M8.5 15.5L6 18"]
|
|
@@ -17,6 +17,7 @@ export const DASHBOARD_HTML = `<!doctype html>
|
|
|
17
17
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
18
18
|
<meta name="color-scheme" content="dark light">
|
|
19
19
|
<meta name="yui-web-token" content="__YUI_WEB_TOKEN__">
|
|
20
|
+
<script src="/assets/theme.js"></script>
|
|
20
21
|
<link rel="icon" href="data:,">
|
|
21
22
|
<title>Yui</title>
|
|
22
23
|
<link rel="preload" href="/assets/fonts/inter-500.woff2" as="font" type="font/woff2" crossorigin>
|