@zq-silk/yui 2.1.0 → 2.3.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/README.md +8 -2
- package/dist/cli/commandCatalog.js +32 -6
- package/dist/cli/commandDiscovery.js +4 -1
- package/dist/cli/interactionPolicy.js +4 -0
- package/dist/cli.js +23 -3078
- package/dist/commands/taskCommands.js +84 -28
- package/dist/commands/taskIntegrationCommands.js +8 -17
- package/dist/commands/taskUpstreamCommands.js +3 -1
- package/dist/context/runContextPack.js +132 -5
- package/dist/context/sourceRunContext.js +4 -2
- package/dist/context/taskContext.js +6 -1
- package/dist/controlPlaneCli.js +3099 -0
- package/dist/controller/clientRuntime.js +3 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +19 -6
- package/dist/controller/jobControl.js +21 -0
- package/dist/executor/agentExecutor.js +1 -1
- package/dist/executor/effectiveLaunch.js +18 -5
- package/dist/executor/fileRoleLaunchPlanner.js +15 -19
- package/dist/integration/gitIntegrationService.js +58 -82
- package/dist/job/jobCleanupInspection.js +67 -0
- package/dist/message/messageContinuation.js +7 -2
- package/dist/nativeAgent/agent.js +176 -74
- package/dist/nativeAgent/cliDemo.js +37 -0
- package/dist/nativeAgent/codingTools.js +11 -0
- package/dist/nativeAgent/commandTool.js +215 -0
- package/dist/nativeAgent/compactionDemo.js +158 -0
- package/dist/nativeAgent/composition.js +71 -0
- package/dist/nativeAgent/context/budget.js +44 -0
- package/dist/nativeAgent/context/index.js +339 -0
- package/dist/nativeAgent/context/providerCompressor.js +103 -0
- package/dist/nativeAgent/demo.js +12 -1
- package/dist/nativeAgent/evaluation/cases.js +38 -0
- package/dist/nativeAgent/evaluation/checks.js +91 -0
- package/dist/nativeAgent/evaluation/demo.js +19 -0
- package/dist/nativeAgent/evaluation/files.js +54 -0
- package/dist/nativeAgent/evaluation/fixture.js +36 -0
- package/dist/nativeAgent/evaluation/index.js +239 -0
- package/dist/nativeAgent/executionOwner.js +209 -0
- package/dist/nativeAgent/filePatterns.js +170 -0
- package/dist/nativeAgent/fileToolsSupport.js +202 -0
- package/dist/nativeAgent/index.js +13 -0
- package/dist/nativeAgent/interaction/cli.js +358 -0
- package/dist/nativeAgent/interaction/contracts.js +1 -0
- package/dist/nativeAgent/interaction/index.js +3 -0
- package/dist/nativeAgent/interaction/memoryDemo.js +115 -0
- package/dist/nativeAgent/interaction/renderer.js +34 -0
- package/dist/nativeAgent/localSafety.js +258 -0
- package/dist/nativeAgent/model/anthropicMessages.js +204 -0
- package/dist/nativeAgent/model/chatCompletions.js +210 -0
- package/dist/nativeAgent/model/errors.js +47 -0
- package/dist/nativeAgent/model/gateway.js +423 -0
- package/dist/nativeAgent/model/index.js +7 -0
- package/dist/nativeAgent/model/observationAdapter.js +21 -0
- package/dist/nativeAgent/model/protocols.js +19 -0
- package/dist/nativeAgent/model/responses.js +263 -0
- package/dist/nativeAgent/model/types.js +1 -0
- package/dist/nativeAgent/model/wire.js +73 -0
- package/dist/nativeAgent/observability/index.js +220 -0
- package/dist/nativeAgent/product/catalog.js +82 -0
- package/dist/nativeAgent/product/config.js +295 -0
- package/dist/nativeAgent/product/facts.js +30 -0
- package/dist/nativeAgent/product/index.js +62 -0
- package/dist/nativeAgent/product/location.js +44 -0
- package/dist/nativeAgent/product/runtime.js +276 -0
- package/dist/nativeAgent/product/storage.js +49 -0
- package/dist/nativeAgent/product/tools.js +47 -0
- package/dist/nativeAgent/product/transport.js +54 -0
- package/dist/nativeAgent/projectGuidance/index.js +425 -0
- package/dist/nativeAgent/searchTools.js +305 -0
- package/dist/nativeAgent/session/backends.js +293 -0
- package/dist/nativeAgent/session/catalog.js +97 -0
- package/dist/nativeAgent/session/catalogDemo.js +87 -0
- package/dist/nativeAgent/session/contracts.js +1 -0
- package/dist/nativeAgent/session/format.js +269 -0
- package/dist/nativeAgent/session/index.js +5 -0
- package/dist/nativeAgent/session/location.js +36 -0
- package/dist/nativeAgent/session/sqliteFormat.js +134 -0
- package/dist/nativeAgent/session/store.js +248 -0
- package/dist/nativeAgent/textTools.js +270 -133
- package/dist/nativeAgent/toolManager/executor.js +290 -0
- package/dist/nativeAgent/toolManager/index.js +4 -0
- package/dist/nativeAgent/validation.js +2 -2
- package/dist/repository/gitWorkspace.js +41 -12
- package/dist/repository/taskWorkspaceCoordinator.js +72 -145
- package/dist/repository/taskWorkspacePreparer.js +39 -43
- package/dist/repository/workspaceCleanupInspection.js +88 -7
- package/dist/resources/resourceRegistrar.js +16 -10
- package/dist/runtime/integrationRuntimeIsolation.js +21 -0
- package/dist/runtime/taskRuntimeIsolation.js +91 -21
- package/dist/task/archiveDiagnostics.js +26 -8
- package/dist/task/archivePreflight.js +26 -9
- package/dist/task/leaderArchive.js +9 -15
- package/dist/task/taskAuthority.js +56 -0
- package/dist/web/assets/client/api.js +8 -0
- package/dist/web/assets/client/app.js +74 -9
- package/dist/web/assets/client/components.js +95 -36
- package/dist/web/assets/client/detail.js +110 -64
- package/dist/web/assets/client/dock.js +95 -11
- package/dist/web/assets/client/evidence.js +286 -94
- package/dist/web/assets/client/i18n.js +182 -62
- package/dist/web/assets/client/overview.js +9 -10
- package/dist/web/assets/client/records.js +142 -7
- package/dist/web/assets/client/sections.js +234 -198
- package/dist/web/assets/client/sidebar.js +31 -2
- package/dist/web/assets/icons.js +4 -0
- package/dist/web/assets/shell.js +2 -1
- package/dist/web/assets/styles/components.js +64 -15
- package/dist/web/assets/styles/layout.js +11 -4
- package/dist/web/assets/styles/responsive.js +16 -6
- package/dist/web/assets/styles/tokens.js +1 -1
- package/dist/web/assets/styles/views.js +62 -42
- package/dist/web/webServer.js +12 -1
- package/dist/web/webTaskSurface.js +8 -1
- package/docs/agent-result-consumption.md +16 -0
- package/docs/agent-result-consumption.zh-CN.md +13 -0
- package/docs/examples/agent-offline.mjs +194 -0
- package/docs/native-agent.md +283 -0
- package/docs/observability/README.md +13 -3
- package/docs/observability/README.zh-CN.md +10 -3
- package/docs/release-workflow.md +72 -9
- package/docs/release-workflow.zh-CN.md +55 -6
- package/docs/roles-and-configuration.md +32 -0
- package/docs/roles-and-configuration.zh-CN.md +26 -0
- package/docs/task-delivery.md +36 -8
- package/docs/task-delivery.zh-CN.md +24 -7
- package/i18n/README.zh-CN.md +3 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +9 -0
- package/skills/yui-leader/references/execution.md +2 -1
- package/skills/yui-leader/references/integration.md +20 -0
- package/skills/yui-operator/SKILL.md +4 -0
- package/skills/yui-reviewer/SKILL.md +8 -0
- package/skills/yui-runtime/SKILL.md +11 -0
- package/skills/yui-runtime/references/publication.md +20 -4
- package/skills/yui-worker/SKILL.md +3 -0
|
@@ -51,6 +51,62 @@ export function assertTaskDeliveryAuthority(store, environment, taskId) {
|
|
|
51
51
|
}
|
|
52
52
|
return actor;
|
|
53
53
|
}
|
|
54
|
+
/** Project effects consume the current Session's captured scope, never the
|
|
55
|
+
* Role's desired configuration or the mere presence of a writable directory.
|
|
56
|
+
* Task management (including abort/inspection) remains a separate authority. */
|
|
57
|
+
export function assertTaskProjectWriteAuthority(store, environment, taskId, projectId) {
|
|
58
|
+
const actor = assertTaskDeliveryAuthority(store, environment, taskId);
|
|
59
|
+
if (actor !== "leader")
|
|
60
|
+
return actor;
|
|
61
|
+
const caller = currentManagedRuntime(store, environment, taskId, LEADER_ROLE);
|
|
62
|
+
const session = store.getTaskRoleSessionSet(taskId, LEADER_ROLE)?.sessions[caller.agentId];
|
|
63
|
+
const effective = session?.effective;
|
|
64
|
+
const task = store.getTask(taskId);
|
|
65
|
+
const main = store.getTaskWorkspace(taskId);
|
|
66
|
+
const entry = main?.entries.find(entry => entry.projectId === projectId);
|
|
67
|
+
if (task?.status !== "active" || task.executionGate.state !== "enabled"
|
|
68
|
+
|| !task.projectBindings.some(binding => binding.projectId === projectId)
|
|
69
|
+
|| effective?.profileAccess !== "write" || !effective.writeProjectIds.includes(projectId)
|
|
70
|
+
|| main?.owner.type !== "task" || main.owner.taskId !== taskId
|
|
71
|
+
|| effective.workspace.root !== main.root || entry?.access !== "write"
|
|
72
|
+
|| !effective.workspace.entries.some(captured => captured.projectId === projectId
|
|
73
|
+
&& captured.access === "write" && captured.path === entry.path)) {
|
|
74
|
+
throw usageError("Project write requires this Task's current delivery Session with a captured writable Project in its Task-main workspace. Read the Session scope; use formal Session replacement to adopt a new boundary.");
|
|
75
|
+
}
|
|
76
|
+
return actor;
|
|
77
|
+
}
|
|
78
|
+
/** A managed Leader integrates into its captured Task-main, not any branch
|
|
79
|
+
* reachable through the same Git repository. The executor also verifies that
|
|
80
|
+
* this ref is physically checked out here immediately before target effects. */
|
|
81
|
+
export function taskIntegrationTargetCheckout(store, environment, taskId, projectId, targetRef) {
|
|
82
|
+
if (assertTaskProjectWriteAuthority(store, environment, taskId, projectId) !== "leader") {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
return taskIntegrationSettlementCheckout(store, environment, taskId, projectId, targetRef);
|
|
86
|
+
}
|
|
87
|
+
/** Confirming an already-applied CAS is Task management, not a Project write
|
|
88
|
+
* grant. Keep the current delivery identity and captured target ownership, but
|
|
89
|
+
* do not require or change its declared write scope. Never use this for CAS. */
|
|
90
|
+
export function taskIntegrationSettlementCheckout(store, environment, taskId, projectId, targetRef) {
|
|
91
|
+
if (assertTaskDeliveryAuthority(store, environment, taskId) !== "leader")
|
|
92
|
+
return undefined;
|
|
93
|
+
const task = store.getTask(taskId);
|
|
94
|
+
const main = store.getTaskWorkspace(taskId);
|
|
95
|
+
const entry = main?.entries.find(entry => entry.projectId === projectId);
|
|
96
|
+
const caller = currentManagedRuntime(store, environment, taskId, LEADER_ROLE);
|
|
97
|
+
const effective = store.getTaskRoleSessionSet(taskId, LEADER_ROLE)?.sessions[caller.agentId]?.effective;
|
|
98
|
+
const captured = effective?.workspace.entries.find(entry => entry.projectId === projectId);
|
|
99
|
+
const branchRef = (ref) => ref.startsWith("refs/heads/") ? ref : `refs/heads/${ref}`;
|
|
100
|
+
if (task?.status !== "active" || !task.projectBindings.some(binding => binding.projectId === projectId)
|
|
101
|
+
|| main?.owner.type !== "task" || main.owner.taskId !== taskId
|
|
102
|
+
|| effective?.workspace.root !== main.root || entry?.access !== "write"
|
|
103
|
+
|| captured?.path !== entry.path || entry.branch === undefined || captured.branch === undefined
|
|
104
|
+
|| branchRef(entry.branch) !== branchRef(captured.branch)
|
|
105
|
+
|| branchRef(targetRef) !== branchRef(captured.branch)) {
|
|
106
|
+
throw usageError("Managed Leader Integration target must be its captured Task-main branch and checkout.");
|
|
107
|
+
}
|
|
108
|
+
return entry.path;
|
|
109
|
+
}
|
|
54
110
|
/**
|
|
55
111
|
* Authority for a live steer/interrupt of an exact current native Turn
|
|
56
112
|
* (decision-3 §1/§9, message-5 gap B). The legal evidence is the native
|
|
@@ -84,6 +84,14 @@ export const api = {
|
|
|
84
84
|
result = await requestJson(task(taskId) + "/inspect?" + query);
|
|
85
85
|
}
|
|
86
86
|
},
|
|
87
|
+
// One page of a Context family: summaries and exact references, no bodies.
|
|
88
|
+
list: function (taskId, store, options) {
|
|
89
|
+
const query = new URLSearchParams({ store: store });
|
|
90
|
+
["status", "cursor", "limit"].forEach(function (key) {
|
|
91
|
+
if (options && options[key] !== undefined && options[key] !== null) query.set(key, String(options[key]));
|
|
92
|
+
});
|
|
93
|
+
return requestJson(task(taskId) + "/list?" + query);
|
|
94
|
+
},
|
|
87
95
|
artifacts: function (taskId) { return requestJson(task(taskId) + "/artifacts"); },
|
|
88
96
|
artifact: function (taskId, path, commit) {
|
|
89
97
|
return requestJson(task(taskId) + "/artifacts?" + new URLSearchParams({ path, commit }));
|
|
@@ -9,13 +9,13 @@ import { formatClock } from "/assets/js/format.js";
|
|
|
9
9
|
import { h } from "/assets/js/dom.js";
|
|
10
10
|
import { renderAttentionBar, renderFilters, renderTasks, STATUS_FILTERS, ATTENTION_KINDS } from "/assets/js/sidebar.js";
|
|
11
11
|
import { renderOverview } from "/assets/js/overview.js";
|
|
12
|
-
import { renderTaskDetail, selectTab, updateObservation, TABS } from "/assets/js/detail.js";
|
|
12
|
+
import { renderTaskDetail, selectTab, updateObservation, TABS, TAB_ALIASES } from "/assets/js/detail.js";
|
|
13
13
|
import { renderDiscussion, discussionHasUnsent, createTerminalController, sameTarget } from "/assets/js/dock.js";
|
|
14
14
|
import { entriesOf } from "/assets/js/records.js";
|
|
15
15
|
|
|
16
16
|
const $ = function (selector) { return document.querySelector(selector); };
|
|
17
17
|
const el = {
|
|
18
|
-
sidebar: $(".sidebar"), center: $("#center"), detail: $("#detail"),
|
|
18
|
+
sidebar: $(".sidebar"), sidebarDivider: $("#sidebar-divider"), center: $("#center"), detail: $("#detail"),
|
|
19
19
|
search: $("#search"), filters: $("#status-filters"), attention: $("#attention-bar"), tasks: $("#task-list"),
|
|
20
20
|
catalogNext: $("#catalog-next"), catalogReset: $("#catalog-reset"), catalogCount: $("#catalog-count"),
|
|
21
21
|
catalogAttentionReset: $("#catalog-attention-reset"),
|
|
@@ -43,6 +43,9 @@ function readPreference(key, fallback) {
|
|
|
43
43
|
function writePreference(key, value) {
|
|
44
44
|
try { localStorage.setItem(key, value); } catch {}
|
|
45
45
|
}
|
|
46
|
+
function clearPreference(key) {
|
|
47
|
+
try { localStorage.removeItem(key); } catch {}
|
|
48
|
+
}
|
|
46
49
|
const dock = {
|
|
47
50
|
open: readPreference("yui.dock.open", "true") !== "false",
|
|
48
51
|
mode: "discussion",
|
|
@@ -52,6 +55,10 @@ const dock = {
|
|
|
52
55
|
// Task, never restored from the desktop preference.
|
|
53
56
|
sheet: false
|
|
54
57
|
};
|
|
58
|
+
// Task list width: null keeps the CSS default (--sidebar-w); otherwise the width
|
|
59
|
+
// the user dragged to. It is a preference; the width actually applied is clamped
|
|
60
|
+
// to the current window.
|
|
61
|
+
const sidebar = { width: Number(readPreference("yui.sidebar.width", "")) || null, resizing: false };
|
|
55
62
|
const narrow = window.matchMedia("(max-width: 900px)");
|
|
56
63
|
|
|
57
64
|
const i18n = createI18n(el.locale);
|
|
@@ -111,6 +118,7 @@ function detailContext() {
|
|
|
111
118
|
openSession: function (roleName) { openSession({ scope: "task", taskId: state.selected, roleName: roleName }); },
|
|
112
119
|
answerInput: answerInput,
|
|
113
120
|
inspect: api.inspect,
|
|
121
|
+
list: api.list,
|
|
114
122
|
artifacts: api.artifacts,
|
|
115
123
|
readArtifact: api.artifact,
|
|
116
124
|
evidence: api.evidence,
|
|
@@ -125,6 +133,7 @@ function dockActions() {
|
|
|
125
133
|
return {
|
|
126
134
|
sendMessage: api.sendMessage,
|
|
127
135
|
inspect: api.inspect,
|
|
136
|
+
list: api.list,
|
|
128
137
|
afterWrite: function () { refreshDashboard({ quiet: true }); }
|
|
129
138
|
};
|
|
130
139
|
}
|
|
@@ -223,15 +232,17 @@ function switchTab(tab, options) {
|
|
|
223
232
|
if (!(options && options.keepScroll)) el.center.scrollTop = 0;
|
|
224
233
|
setSectionParam(tab);
|
|
225
234
|
}
|
|
226
|
-
// A section may name a tab or any anchored block inside
|
|
227
|
-
// "reviews" → the
|
|
235
|
+
// A section may name a tab, a retired tab name, or any anchored block inside
|
|
236
|
+
// one (for example "reviews" → the Delivery tab, scrolled to #detail-reviews).
|
|
228
237
|
function applySection(section) {
|
|
229
|
-
|
|
230
|
-
|
|
238
|
+
const tab = (section && TAB_ALIASES[section]) || section;
|
|
239
|
+
if (!tab || TABS.includes(tab)) { switchTab(tab || "overview"); return; }
|
|
240
|
+
const anchor = el.detail.querySelector("#detail-" + CSS.escape(tab));
|
|
231
241
|
const panel = anchor && anchor.closest(".tab-panel");
|
|
232
242
|
if (!panel) { switchTab("overview"); return; }
|
|
233
243
|
state.activeTab = panel.id.replace(/^panel-/, "");
|
|
234
244
|
selectTab(el.detail, state.activeTab);
|
|
245
|
+
if (anchor.tagName === "DETAILS") anchor.open = true;
|
|
235
246
|
anchor.scrollIntoView({ block: "start" });
|
|
236
247
|
}
|
|
237
248
|
|
|
@@ -510,7 +521,7 @@ function updateLayout() {
|
|
|
510
521
|
el.dockTabSession.setAttribute("aria-selected", String(dock.mode === "session"));
|
|
511
522
|
el.dockTabDiscussion.disabled = !state.selected;
|
|
512
523
|
el.dockSwap.setAttribute("aria-pressed", String(dock.center));
|
|
513
|
-
|
|
524
|
+
applySidebarWidth();
|
|
514
525
|
syncDockButtons();
|
|
515
526
|
}
|
|
516
527
|
function syncDockButtons() {
|
|
@@ -598,9 +609,63 @@ el.divider.addEventListener("keydown", function (event) {
|
|
|
598
609
|
event.preventDefault();
|
|
599
610
|
setDockWidth(event.key === "Home" ? 320 : event.key === "End" ? maxDockWidth() : dock.width + delta, true);
|
|
600
611
|
});
|
|
601
|
-
window.addEventListener("resize", function () { setDockWidth(dock.width, false); });
|
|
602
612
|
narrow.addEventListener("change", updateLayout);
|
|
603
613
|
|
|
614
|
+
// --- Sidebar width ----------------------------------------------------------------
|
|
615
|
+
const SIDEBAR_MIN = 240;
|
|
616
|
+
const SIDEBAR_MAX = 560;
|
|
617
|
+
function maxSidebarWidth() {
|
|
618
|
+
// An open dock can shrink to its minimum, so it reserves only that much.
|
|
619
|
+
const reserve = dockVisible() ? 320 + 9 : 0;
|
|
620
|
+
return Math.max(SIDEBAR_MIN, Math.min(SIDEBAR_MAX, window.innerWidth - reserve - 420));
|
|
621
|
+
}
|
|
622
|
+
function applySidebarWidth() {
|
|
623
|
+
const root = document.documentElement.style;
|
|
624
|
+
if (sidebar.width === null) root.removeProperty("--sidebar-w");
|
|
625
|
+
else root.setProperty("--sidebar-w", Math.max(SIDEBAR_MIN, Math.min(maxSidebarWidth(), sidebar.width)) + "px");
|
|
626
|
+
// A wider task list leaves the dock less room; the dock keeps its stored width.
|
|
627
|
+
setDockWidth(dock.width, false);
|
|
628
|
+
el.sidebarDivider.setAttribute("aria-valuemax", String(maxSidebarWidth()));
|
|
629
|
+
el.sidebarDivider.setAttribute("aria-valuenow", String(el.sidebar.offsetWidth));
|
|
630
|
+
}
|
|
631
|
+
function setSidebarWidth(width, persist) {
|
|
632
|
+
sidebar.width = width === null ? null : Math.max(SIDEBAR_MIN, Math.min(maxSidebarWidth(), Math.round(width)));
|
|
633
|
+
if (persist) {
|
|
634
|
+
if (sidebar.width === null) clearPreference("yui.sidebar.width");
|
|
635
|
+
else writePreference("yui.sidebar.width", String(sidebar.width));
|
|
636
|
+
}
|
|
637
|
+
applySidebarWidth();
|
|
638
|
+
}
|
|
639
|
+
el.sidebarDivider.addEventListener("pointerdown", function (event) {
|
|
640
|
+
if (event.button !== 0 || window.innerWidth <= 900) return;
|
|
641
|
+
sidebar.resizing = true;
|
|
642
|
+
el.sidebarDivider.setPointerCapture(event.pointerId);
|
|
643
|
+
document.body.classList.add("sidebar-resizing");
|
|
644
|
+
event.preventDefault();
|
|
645
|
+
});
|
|
646
|
+
el.sidebarDivider.addEventListener("pointermove", function (event) {
|
|
647
|
+
if (!sidebar.resizing) return;
|
|
648
|
+
setSidebarWidth(event.clientX - el.sidebar.getBoundingClientRect().left, false);
|
|
649
|
+
});
|
|
650
|
+
function finishSidebarResize() {
|
|
651
|
+
if (!sidebar.resizing) return;
|
|
652
|
+
sidebar.resizing = false;
|
|
653
|
+
document.body.classList.remove("sidebar-resizing");
|
|
654
|
+
if (sidebar.width !== null) writePreference("yui.sidebar.width", String(sidebar.width));
|
|
655
|
+
}
|
|
656
|
+
el.sidebarDivider.addEventListener("pointerup", finishSidebarResize);
|
|
657
|
+
el.sidebarDivider.addEventListener("pointercancel", finishSidebarResize);
|
|
658
|
+
el.sidebarDivider.addEventListener("dblclick", function () { setSidebarWidth(null, true); });
|
|
659
|
+
el.sidebarDivider.addEventListener("keydown", function (event) {
|
|
660
|
+
const current = el.sidebar.offsetWidth;
|
|
661
|
+
const next = event.key === "ArrowRight" ? current + 16 : event.key === "ArrowLeft" ? current - 16
|
|
662
|
+
: event.key === "Home" ? SIDEBAR_MIN : event.key === "End" ? maxSidebarWidth() : null;
|
|
663
|
+
if (next === null) return;
|
|
664
|
+
event.preventDefault();
|
|
665
|
+
setSidebarWidth(next, true);
|
|
666
|
+
});
|
|
667
|
+
window.addEventListener("resize", applySidebarWidth);
|
|
668
|
+
|
|
604
669
|
// --- Sidebar controls -------------------------------------------------------------
|
|
605
670
|
let searchTimer = null;
|
|
606
671
|
el.search.addEventListener("input", function () {
|
|
@@ -696,7 +761,7 @@ document.addEventListener("keydown", function (event) {
|
|
|
696
761
|
else if (key === "r") refreshDashboard();
|
|
697
762
|
else if (key === "o") openOperator();
|
|
698
763
|
else if (key === "d") toggleDock();
|
|
699
|
-
else if (/^[1-
|
|
764
|
+
else if (/^[1-9]$/.test(key) && Number(key) <= TABS.length && state.detail) switchTab(TABS[Number(key) - 1]);
|
|
700
765
|
});
|
|
701
766
|
|
|
702
767
|
// --- Boot -------------------------------------------------------------------------
|
|
@@ -39,7 +39,9 @@ const TONES = {
|
|
|
39
39
|
stopped: "bad", idle: "idle", background: "warn" },
|
|
40
40
|
role: { running: "ok", waiting: "warn", idle: "idle", unknown: "idle", failed: "bad", exited: "idle", detached: "idle" },
|
|
41
41
|
decision: { active: "ok", superseded: "idle" },
|
|
42
|
-
input: { open: "warn", answered: "ok", cancelled: "idle", "auto-resolved": "ok" }
|
|
42
|
+
input: { open: "warn", answered: "ok", cancelled: "idle", "auto-resolved": "ok" },
|
|
43
|
+
integration: { running: "info", validating: "info", committed: "ok", conflicted: "warn", blocked: "bad",
|
|
44
|
+
failed: "bad", superseded: "idle" }
|
|
43
45
|
};
|
|
44
46
|
export function tone(kind, value) {
|
|
45
47
|
return (TONES[kind] && TONES[kind][value]) || "idle";
|
|
@@ -81,17 +83,19 @@ export function timeTag(iso, locale, t, options) {
|
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
// --- Containers -------------------------------------------------------------------
|
|
86
|
+
// One panel of the single reading column: a heading (icon tile, title, count
|
|
87
|
+
// and hint) with optional actions, then the body. A "list-card" body holds a
|
|
88
|
+
// .row-stack whose rows are divided by hairlines instead of nested boxes.
|
|
84
89
|
export function card(options) {
|
|
85
90
|
const opts = options || {};
|
|
86
91
|
const element = h("section.card" + (opts.className ? "." + opts.className.split(" ").join(".") : ""));
|
|
87
92
|
if (opts.id) element.id = opts.id;
|
|
88
93
|
if (opts.title || opts.actions) {
|
|
89
|
-
const
|
|
90
|
-
h("h3.card-title", null, opts.icon ? icon(opts.icon) : null, h("span", null, opts.title),
|
|
94
|
+
const heading = h("div.card-heading", null,
|
|
95
|
+
h("h3.card-title", null, opts.icon ? h("span.card-icon", null, icon(opts.icon)) : null, h("span", null, opts.title),
|
|
91
96
|
opts.count !== undefined && opts.count !== null ? h("span.count", null, String(opts.count)) : null),
|
|
92
|
-
opts.
|
|
93
|
-
|
|
94
|
-
element.append(head);
|
|
97
|
+
opts.hint ? h("p.card-hint", null, opts.hint) : null);
|
|
98
|
+
element.append(h("header.card-head", null, heading, opts.actions ? h("div.card-actions", null, opts.actions) : null));
|
|
95
99
|
}
|
|
96
100
|
const body = h("div.card-body");
|
|
97
101
|
element.append(body);
|
|
@@ -99,6 +103,39 @@ export function card(options) {
|
|
|
99
103
|
return element;
|
|
100
104
|
}
|
|
101
105
|
|
|
106
|
+
// A panel whose body is a divided list of rows.
|
|
107
|
+
export function listCard(options) {
|
|
108
|
+
const element = card({ ...options, className: "list-card" + (options.className ? " " + options.className : "") });
|
|
109
|
+
element.list = h("div.row-stack");
|
|
110
|
+
element.body.append(element.list);
|
|
111
|
+
return element;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Update a card heading count after a later read; null removes it.
|
|
115
|
+
export function setCardCount(element, value) {
|
|
116
|
+
const title = element.querySelector(".card-title");
|
|
117
|
+
let count = title && title.querySelector(".count");
|
|
118
|
+
if (value === undefined || value === null) { if (count) count.remove(); return; }
|
|
119
|
+
if (!title) return;
|
|
120
|
+
if (!count) { count = h("span.count"); title.append(count); }
|
|
121
|
+
count.textContent = String(value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Local filter chips; selecting one redraws the list in place.
|
|
125
|
+
export function filterChips(ariaLabel, options, selected, onSelect) {
|
|
126
|
+
const row = h("div.chip-tabs", { role: "group", "aria-label": ariaLabel });
|
|
127
|
+
options.forEach(function (option) {
|
|
128
|
+
row.append(h("button.chip-tab", {
|
|
129
|
+
type: "button", "aria-pressed": String(option.value === selected), dataset: { filter: option.value },
|
|
130
|
+
onclick: function () {
|
|
131
|
+
row.querySelectorAll(".chip-tab").forEach(function (b) { b.setAttribute("aria-pressed", String(b.dataset.filter === option.value)); });
|
|
132
|
+
onSelect(option.value);
|
|
133
|
+
}
|
|
134
|
+
}, option.label, option.count === undefined || option.count === null ? null : h("span.count", null, String(option.count))));
|
|
135
|
+
});
|
|
136
|
+
return row;
|
|
137
|
+
}
|
|
138
|
+
|
|
102
139
|
export function disclosure(title, viewKey, options) {
|
|
103
140
|
const opts = options || {};
|
|
104
141
|
const element = h("details.disclosure" + (opts.className ? "." + opts.className : ""));
|
|
@@ -112,12 +149,6 @@ export function disclosure(title, viewKey, options) {
|
|
|
112
149
|
return element;
|
|
113
150
|
}
|
|
114
151
|
|
|
115
|
-
export function sectionTitle(text, count, actions) {
|
|
116
|
-
return h("div.section-title", null, h("h2", null, text,
|
|
117
|
-
count !== undefined && count !== null ? h("span.count", null, String(count)) : null),
|
|
118
|
-
actions ? h("div.section-actions", null, actions) : null);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
152
|
export function kv(rows) {
|
|
122
153
|
const list = h("dl.kv");
|
|
123
154
|
rows.forEach(function (row) {
|
|
@@ -206,9 +237,24 @@ export function metricTile(labelText, value, options) {
|
|
|
206
237
|
return tile;
|
|
207
238
|
}
|
|
208
239
|
|
|
240
|
+
function pageLocale() { return typeof document === "undefined" ? undefined : document.documentElement.lang || undefined; }
|
|
241
|
+
|
|
242
|
+
// Seconds as the largest whole unit plus the next one ("2d 22h", "2小时48分钟").
|
|
243
|
+
const DURATION_UNITS = [["day", 86400], ["hour", 3600], ["minute", 60], ["second", 1]];
|
|
244
|
+
function durationText(seconds) {
|
|
245
|
+
const total = Math.max(0, Math.round(seconds));
|
|
246
|
+
const found = DURATION_UNITS.findIndex(function (unit) { return total >= unit[1]; });
|
|
247
|
+
const first = found < 0 ? DURATION_UNITS.length - 1 : found;
|
|
248
|
+
return DURATION_UNITS.slice(first, first + 2).map(function (unit, index) {
|
|
249
|
+
const count = index ? Math.floor((total % DURATION_UNITS[first][1]) / unit[1]) : Math.floor(total / unit[1]);
|
|
250
|
+
return index && !count ? "" : new Intl.NumberFormat(pageLocale(), { style: "unit", unit: unit[0], unitDisplay: "narrow" }).format(count);
|
|
251
|
+
}).filter(Boolean).join(" ");
|
|
252
|
+
}
|
|
253
|
+
|
|
209
254
|
export function usageMetricText(metric, t, suffix) {
|
|
210
255
|
if (!metric || metric.value == null) return t("detail.unobserved");
|
|
211
|
-
|
|
256
|
+
const value = suffix === "s" ? durationText(metric.value) : metric.value + (suffix || "");
|
|
257
|
+
return value + (metric.status === "partial" ? " · " + t("detail.partial") : "");
|
|
212
258
|
}
|
|
213
259
|
|
|
214
260
|
function usageTile(labelText, metric, t, suffix) {
|
|
@@ -237,7 +283,7 @@ export function observabilityMetricCard(observability, t) {
|
|
|
237
283
|
const meta = node("p", "usage-meta");
|
|
238
284
|
meta.append(node("span", "", t("usage.scope")));
|
|
239
285
|
meta.append(node("span", "", t("usage.observedThrough") + " "
|
|
240
|
-
+ (cost.observedThrough ? formatDateTime(cost.observedThrough) : t("detail.unobserved"))));
|
|
286
|
+
+ (cost.observedThrough ? formatDateTime(cost.observedThrough, pageLocale()) : t("detail.unobserved"))));
|
|
241
287
|
meta.append(node("span", "", t("usage.contextBytes") + " "
|
|
242
288
|
+ (context.totalBytes == null ? t("detail.partial") : context.totalBytes + " B")));
|
|
243
289
|
wrap.append(meta);
|
|
@@ -300,30 +346,35 @@ export function workItemCard(item, t, locale, titles) {
|
|
|
300
346
|
const settled = item.status !== "open";
|
|
301
347
|
const element = h(settled ? "details.work-card.is-settled" : "article.work-card");
|
|
302
348
|
element.dataset.viewKey = "work:" + item.id;
|
|
303
|
-
|
|
349
|
+
element.append(h(settled ? "summary.work-head" : "header.work-head", null,
|
|
304
350
|
settled ? icon("chevron", "disclosure-chevron") : null,
|
|
305
351
|
dot(tone("work", item.status)),
|
|
306
352
|
h("span.work-title", null, item.title),
|
|
307
|
-
statusBadge(t, "work", "work", item.status));
|
|
308
|
-
element.append(
|
|
309
|
-
|
|
310
|
-
|
|
353
|
+
statusBadge(t, "work", "work", item.status)));
|
|
354
|
+
element.append(h("div.work-body", null, workItemBody(item, t, locale, titles)));
|
|
355
|
+
return element;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// The body of one work item, shared by the open card and a lazily read row.
|
|
359
|
+
export function workItemBody(item, t, locale, titles) {
|
|
360
|
+
const parts = [];
|
|
361
|
+
parts.push(h("div.meta-line", null,
|
|
311
362
|
item.assignee ? h("span.meta-strong", null, icon("user", "icon-sm"), item.assignee) : null,
|
|
312
363
|
mono(item.id), timeTag(item.updatedAt, locale, t)));
|
|
313
|
-
if (item.objective && item.objective !== item.title)
|
|
364
|
+
if (item.objective && item.objective !== item.title) parts.push(richText(t("work.objective"), item.objective, t));
|
|
314
365
|
if (item.acceptance && item.acceptance.length) {
|
|
315
|
-
|
|
366
|
+
parts.push(h("div.prose-block", null, h("h4.prose-label", null, t("work.acceptance")),
|
|
316
367
|
h("ul.checklist", null, item.acceptance.map(function (entry) {
|
|
317
368
|
return h("li" + (item.status === "accepted" ? ".is-done" : ""), null, icon(item.status === "accepted" ? "check" : "target", "icon-sm"), h("span", null, entry));
|
|
318
369
|
}))));
|
|
319
370
|
}
|
|
320
371
|
const deps = (item.dependsOn || []).map(function (id) { return (titles && titles[id]) || id; });
|
|
321
|
-
if (deps.length)
|
|
372
|
+
if (deps.length) parts.push(h("div.meta-line", null, h("span.meta-label", null, t("work.dependsOn")), deps.map(function (d) { return chip(d); })));
|
|
322
373
|
if (item.writeProjectIds && item.writeProjectIds.length) {
|
|
323
|
-
|
|
374
|
+
parts.push(h("div.meta-line", null, h("span.meta-label", null, t("work.writeProjects")), item.writeProjectIds.map(function (d) { return chip(d); })));
|
|
324
375
|
}
|
|
325
376
|
if (item.candidates && item.candidates.length) {
|
|
326
|
-
|
|
377
|
+
parts.push(h("div.prose-block", null, h("h4.prose-label", null, t("work.candidates")),
|
|
327
378
|
h("ol.candidates", null, item.candidates.slice().sort(function (a, b) { return b.sequence - a.sequence; }).map(function (candidate) {
|
|
328
379
|
return h("li", null, h("span.candidate-seq", null, "#" + candidate.sequence), h("span", null, candidate.summary),
|
|
329
380
|
h("span.faint", null, candidate.source && candidate.source.type === "run" ? candidate.source.runId : t("work.direct")),
|
|
@@ -331,18 +382,16 @@ export function workItemCard(item, t, locale, titles) {
|
|
|
331
382
|
}))));
|
|
332
383
|
}
|
|
333
384
|
if (item.disposition) {
|
|
334
|
-
|
|
385
|
+
parts.push(note(t("work.disposition") + ": " + item.disposition.summary
|
|
335
386
|
+ (item.disposition.replacementWorkItemId ? " → " + item.disposition.replacementWorkItemId : "")));
|
|
336
387
|
}
|
|
337
|
-
if (item.outcome)
|
|
338
|
-
|
|
339
|
-
return element;
|
|
388
|
+
if (item.outcome) parts.push(richText(t("work.outcome"), item.outcome, t, { className: "callout" }));
|
|
389
|
+
return parts.filter(Boolean);
|
|
340
390
|
}
|
|
341
391
|
|
|
342
392
|
export function runCard(run, t, locale) {
|
|
343
393
|
const element = h("article.run-card");
|
|
344
394
|
element.dataset.status = run.status;
|
|
345
|
-
const input = run.inputs && run.inputs.length ? run.inputs[0].input : null;
|
|
346
395
|
element.append(h("header.run-head", null,
|
|
347
396
|
dot(tone("run", run.status)),
|
|
348
397
|
h("span.run-role", null, run.roleName),
|
|
@@ -350,20 +399,27 @@ export function runCard(run, t, locale) {
|
|
|
350
399
|
run.purpose ? chip(label(t, "run.purpose", run.purpose)) : null,
|
|
351
400
|
run.workItemId ? chip(run.workItemId) : null,
|
|
352
401
|
h("span.spacer"),
|
|
353
|
-
statusBadge(t, "run", "run", run.status)));
|
|
354
|
-
element
|
|
402
|
+
statusBadge(t, "run", "run", run.status)), runBody(run, t, locale));
|
|
403
|
+
return element;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// What one execution record says: delivery, instruction, report, failure.
|
|
407
|
+
export function runBody(run, t, locale) {
|
|
408
|
+
const parts = [];
|
|
409
|
+
const input = run.inputs && run.inputs.length ? run.inputs[0].input : null;
|
|
410
|
+
parts.push(h("div.meta-line", null,
|
|
355
411
|
h("span", null, t("run.delivery") + ": " + label(t, "run.delivery", (run.execution && run.execution.delivery) || "unobserved")),
|
|
356
412
|
run.mode ? h("span", null, label(t, "mode", run.mode)) : null,
|
|
357
413
|
timeTag((run.result && run.result.completedAt) || run.updatedAt, locale, t),
|
|
358
414
|
agentChips(run.effective || (run.agentId ? run : null))));
|
|
359
415
|
const directive = input && (input.directive || input.action);
|
|
360
|
-
if (directive)
|
|
361
|
-
if (run.result && run.result.output)
|
|
362
|
-
if (run.result && run.result.diagnostic)
|
|
416
|
+
if (directive) parts.push(richText(t("run.instruction"), directive, t, { threshold: 320 }));
|
|
417
|
+
if (run.result && run.result.output) parts.push(richText(t("run.output"), run.result.output, t, { threshold: 320, className: "callout" }));
|
|
418
|
+
if (run.result && run.result.diagnostic) parts.push(richText(t("run.failure"), run.result.diagnostic, t, { threshold: 320, className: "callout.tone-bad" }));
|
|
363
419
|
if (run.executionGroupId) {
|
|
364
|
-
|
|
420
|
+
parts.push(h("p.faint.mono-line", null, t("run.lineage") + " " + run.executionGroupId + (run.executionLaneId ? "/" + run.executionLaneId : "")));
|
|
365
421
|
}
|
|
366
|
-
return
|
|
422
|
+
return parts.filter(Boolean);
|
|
367
423
|
}
|
|
368
424
|
|
|
369
425
|
export function reviewCard(round, t, locale) {
|
|
@@ -377,6 +433,9 @@ export function reviewCard(round, t, locale) {
|
|
|
377
433
|
statusBadge(t, "review", "review", round.status)));
|
|
378
434
|
element.append(h("div.meta-line", null,
|
|
379
435
|
round.workItemId ? mono(round.workItemId + (round.candidateId ? " · " + round.candidateId : "")) : null,
|
|
436
|
+
round.taskCandidate && round.taskCandidate.projects ? round.taskCandidate.projects.map(function (project) {
|
|
437
|
+
return mono(project.projectId + " @ " + String(project.commit).slice(0, 12));
|
|
438
|
+
}) : null,
|
|
380
439
|
round.reviewBaseCommit ? h("span", null, t("review.base") + " ", mono(String(round.reviewBaseCommit).slice(0, 12))) : null,
|
|
381
440
|
round.reviewerRunId ? h("span", null, t("review.run") + " ", mono(round.reviewerRunId)) : null,
|
|
382
441
|
timeTag(round.createdAt, locale, t)));
|