@zq-silk/yui 2.4.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 +4 -3
- package/dist/commands/taskCommands.js +48 -15
- package/dist/commands/taskContextCommand.js +3 -2
- package/dist/context/sessionBootstrapManifest.js +8 -2
- package/dist/context/taskContext.js +7 -4
- package/dist/controller/fileSchedulerStoreAdapter.js +7 -2
- package/dist/controller/globalInputDelivery.js +2 -4
- package/dist/controller/providerRetryDelivery.js +4 -2
- package/dist/executor/agentAdapter.js +3 -5
- package/dist/executor/fileRoleLaunchPlanner.js +2 -2
- package/dist/interaction/notificationPresentation.js +85 -0
- package/dist/interaction/operatorPresentation.js +1 -2
- 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/leaderWakeupProcessor.js +1 -9
- 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/webConversation.js +2 -2
- 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/cli-information-contract.md +1 -1
- package/docs/cli-information-contract.zh-CN.md +1 -1
- package/docs/managed-turn-and-session-runtime.md +22 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +15 -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 +53 -0
- package/docs/release-workflow.zh-CN.md +40 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +5 -4
- package/skills/yui-leader/references/execution.md +2 -1
- package/skills/yui-operator/SKILL.md +3 -0
- package/skills/yui-reviewer/SKILL.md +4 -2
- package/skills/yui-runtime/SKILL.md +43 -2
- package/skills/yui-runtime/references/recovery.md +9 -0
- package/skills/yui-worker/SKILL.md +3 -2
|
@@ -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>
|
|
@@ -5,6 +5,7 @@ export const SETTINGS_HTML = `<!doctype html>
|
|
|
5
5
|
<head>
|
|
6
6
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
7
|
<meta name="yui-web-token" content="__YUI_WEB_TOKEN__">
|
|
8
|
+
<script src="/assets/theme.js"></script>
|
|
8
9
|
<link rel="icon" href="data:,"><title>Yui · Settings</title>
|
|
9
10
|
${STYLESHEETS.map(s => `<link rel="stylesheet" href="/assets/css/${s.name}.css">`).join("\n")}
|
|
10
11
|
</head>
|
|
@@ -53,6 +53,20 @@ export const TASK_STYLES = `
|
|
|
53
53
|
.next-line .icon{color:var(--accent)}
|
|
54
54
|
.next-line strong{color:var(--ink)}
|
|
55
55
|
|
|
56
|
+
/* Next step: a compact action bar — prompt left, actions right; the foot
|
|
57
|
+
(condition + receipt) is one quiet line under a hairline. */
|
|
58
|
+
.next-card>.card-body{gap:0;padding:14px 16px 14px 20px}
|
|
59
|
+
.next-row{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px 20px}
|
|
60
|
+
.next-lead{display:grid;gap:3px;flex:1 1 280px;min-width:0}
|
|
61
|
+
.next-kicker{color:var(--ink-4);font-size:var(--fs-2xs);font-weight:600;letter-spacing:.08em;text-transform:uppercase}
|
|
62
|
+
.next-prompt{color:var(--ink-2);font-size:var(--fs-sm);line-height:1.5}
|
|
63
|
+
.next-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px;flex:none}
|
|
64
|
+
.next-actions .btn{height:34px;padding:0 14px}
|
|
65
|
+
.next-foot{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 16px;margin-top:12px;padding-top:10px;border-top:1px dashed var(--line)}
|
|
66
|
+
.next-hint{flex:1 1 320px;min-width:0;color:var(--ink-4);font-size:var(--fs-xs);line-height:1.5}
|
|
67
|
+
.next-foot .receipt:empty{display:none}
|
|
68
|
+
@media(max-width:560px){.next-actions{flex:1 1 100%}.next-actions .btn{flex:1 1 0}}
|
|
69
|
+
|
|
56
70
|
/* Needs you / input requests */
|
|
57
71
|
.needs-card{border-color:var(--warn);box-shadow:0 0 0 3px var(--warn-soft)}
|
|
58
72
|
.needs-card .card-icon{background:var(--warn-soft);color:var(--warn)}
|
|
@@ -170,7 +170,7 @@ export function createWebConversationSurface(store, surface, plan, readPage = re
|
|
|
170
170
|
return [item];
|
|
171
171
|
let messages = [];
|
|
172
172
|
if (owner.scope === "task") {
|
|
173
|
-
const ref = /^Yui Task notification: task=([A-Za-z0-9_-]+) wake=([A-Za-z0-9_-]+)
|
|
173
|
+
const ref = /^Yui Task notification: task=([A-Za-z0-9_-]+) wake=([A-Za-z0-9_-]+)\.$/m.exec(item.text);
|
|
174
174
|
if (ref?.[1] === owner.taskId) {
|
|
175
175
|
const wake = store.listTaskWakes(owner.taskId).find(w => w.id === ref[2] && w.status === "consumed");
|
|
176
176
|
const ids = new Set(wake?.refs?.filter(r => r.type === "message").map(r => r.id));
|
|
@@ -178,7 +178,7 @@ export function createWebConversationSurface(store, surface, plan, readPage = re
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
else {
|
|
181
|
-
const ref = /^Yui Global Message: role=([A-Za-z0-9_-]+) message=([A-Za-z0-9_-]+)
|
|
181
|
+
const ref = /^Yui Global Message: role=([A-Za-z0-9_-]+) message=([A-Za-z0-9_-]+)\.$/m.exec(item.text);
|
|
182
182
|
if (ref?.[1] === owner.roleName)
|
|
183
183
|
messages = store.listGlobalRoleMessages(owner.roleName)
|
|
184
184
|
.filter(m => m.id === ref[2] && m.delivery?.via === "provider" && m.deliveryTarget?.nativeSessionId === id);
|
package/dist/web/webServer.js
CHANGED
|
@@ -5,6 +5,7 @@ import { CliError, usageError } from "../errors/cliError.js";
|
|
|
5
5
|
import { parseTaskCatalogOptions } from "../context/taskCatalog.js";
|
|
6
6
|
import { parseTaskSearchOptions, searchTaskBodies } from "../context/taskSearch.js";
|
|
7
7
|
import { readTaskUsage } from "../runtime/taskUsageQuery.js";
|
|
8
|
+
import { readWebTaskActivity } from "./webTaskActivity.js";
|
|
8
9
|
import { WebRequestRejected } from "./webMutation.js";
|
|
9
10
|
import { SETTINGS_HTML } from "./assets/shell/settings.js";
|
|
10
11
|
import { TASK_SUBMISSION_INTENTS } from "../message/message.js";
|
|
@@ -484,6 +485,7 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
484
485
|
throw new WebRequestRejected("Invalid list limit.");
|
|
485
486
|
value = dependencies.surface.list(taskId, {
|
|
486
487
|
store: query.get("store") ?? "",
|
|
488
|
+
...(query.has("view") ? { view: query.get("view") } : {}),
|
|
487
489
|
...(query.has("status") ? { status: query.get("status") } : {}),
|
|
488
490
|
...(query.has("cursor") ? { cursor: query.get("cursor") } : {}),
|
|
489
491
|
...(limit === null ? {} : { limit: Number(limit) })
|
|
@@ -552,6 +554,16 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
552
554
|
: buildWebTaskCatalog(store, options);
|
|
553
555
|
sendJson(response, 200, snapshot, method === "HEAD");
|
|
554
556
|
}
|
|
557
|
+
else if (/^\/api\/tasks\/[^/]+\/activity$/u.test(pathname)) {
|
|
558
|
+
const taskId = decodeURIComponent(pathname.split("/")[3]);
|
|
559
|
+
const query = new URL(request.url, "http://localhost").searchParams;
|
|
560
|
+
if ([...query.keys()].some(key => !["limit", "cursor"].includes(key)))
|
|
561
|
+
throw usageError("Invalid activity option.");
|
|
562
|
+
sendJson(response, 200, readWebTaskActivity(store, taskId, {
|
|
563
|
+
...(query.has("limit") ? { limit: Number(query.get("limit")) } : {}),
|
|
564
|
+
...(query.has("cursor") ? { cursor: query.get("cursor") } : {})
|
|
565
|
+
}, now()), method === "HEAD");
|
|
566
|
+
}
|
|
555
567
|
else if (/^\/api\/tasks\/[^/]+\/usage$/u.test(pathname)) {
|
|
556
568
|
const taskId = decodeURIComponent(pathname.split("/")[3]);
|
|
557
569
|
const query = new URL(request.url, "http://localhost").searchParams;
|