@zq-silk/yui 2.2.0 → 2.4.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 +19 -3
- package/dist/artifacts/artifactCapability.js +39 -10
- package/dist/artifacts/imageMetadata.js +120 -0
- package/dist/artifacts/managedGit.js +7 -4
- package/dist/artifacts/taskArtifactRepository.js +6 -0
- package/dist/cli/commandCatalog.js +25 -11
- package/dist/cli/interactionPolicy.js +4 -0
- package/dist/cli/invocationAuthority.js +1 -1
- package/dist/commands/executionAuditCommands.js +5 -0
- package/dist/commands/globalRoleCommands.js +41 -3
- package/dist/commands/projectCommands.js +89 -94
- package/dist/commands/roleSkillValidation.js +4 -8
- package/dist/commands/taskCommands.js +30 -9
- package/dist/commands/taskIntegrationCommands.js +5 -16
- package/dist/commands/taskUsageCommand.js +38 -0
- package/dist/context/roleSessionContext.js +18 -2
- package/dist/context/runContextPack.js +5 -1
- package/dist/context/sessionBootstrapManifest.js +10 -1
- package/dist/context/skillCatalog.js +108 -0
- package/dist/context/skillPackage.js +161 -0
- package/dist/context/taskContext.js +3 -2
- package/dist/context/taskSearch.js +127 -0
- package/dist/controlPlaneCli.js +51 -39
- package/dist/controller/clientRuntime.js +3 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
- package/dist/controller/jobControl.js +2 -0
- package/dist/controller/nativeHostInput.js +37 -0
- package/dist/controller/runtime.js +93 -2
- package/dist/controller/runtimeEventInbox.js +2 -2
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/doctor/doctor.js +10 -0
- package/dist/execution/reviewMainRun.js +1 -0
- package/dist/execution/workItemMainRun.js +1 -0
- package/dist/executor/codexConfigConflict.js +6 -4
- package/dist/executor/effectiveLaunch.js +24 -1
- package/dist/executor/fileRoleLaunchPlanner.js +13 -5
- package/dist/integration/gitIntegrationService.js +15 -79
- package/dist/integration/integrationSourceApplication.js +4 -2
- package/dist/job/httpPreview.js +61 -0
- package/dist/job/jobCleanupInspection.js +67 -0
- package/dist/job/jobRunner.js +12 -2
- package/dist/kernel/builtinCapabilities.js +3 -1
- package/dist/message/message.js +15 -1
- package/dist/output/rolePresentation.js +4 -1
- package/dist/repository/executionLaneGitSnapshot.js +2 -1
- package/dist/repository/gitCompatibility.js +62 -0
- package/dist/repository/gitWorkspace.js +86 -80
- package/dist/repository/gitWorktreeInventory.js +121 -0
- package/dist/repository/project.js +74 -25
- package/dist/repository/taskWorkspaceCoordinator.js +72 -145
- package/dist/repository/taskWorkspacePreparer.js +39 -43
- package/dist/repository/workspaceCleanupInspection.js +88 -7
- package/dist/resources/resourceDiscovery.js +2 -2
- package/dist/resources/resourceGc.js +7 -4
- package/dist/resources/resourceRegistrar.js +18 -11
- package/dist/runtime/agentEndpoint.js +8 -0
- package/dist/runtime/agentHost.js +170 -3
- package/dist/runtime/codexAppServerRuntime.js +1 -1
- package/dist/runtime/codexNativeAccess.js +175 -0
- package/dist/runtime/controlledCodexTui.js +116 -0
- package/dist/runtime/integrationRuntimeIsolation.js +21 -0
- package/dist/runtime/publicReply.js +23 -0
- package/dist/runtime/runtimeObservation.js +48 -0
- package/dist/runtime/structuredProviderHost.js +16 -7
- package/dist/runtime/taskCostMetrics.js +133 -0
- package/dist/runtime/taskRuntimeIsolation.js +91 -21
- package/dist/runtime/taskUsageMetrics.js +11 -2
- package/dist/runtime/taskUsageQuery.js +38 -0
- package/dist/scheduler/taskExecutionProjection.js +2 -0
- package/dist/scheduler/taskObservabilityProjection.js +9 -1
- package/dist/storage/homeLayout.js +13 -0
- package/dist/storage/sqliteSchema.js +44 -0
- package/dist/storage/sqliteStore.js +53 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskSearch.js +35 -0
- package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
- package/dist/task/archiveDiagnostics.js +26 -8
- package/dist/task/archivePreflight.js +26 -9
- package/dist/task/leaderArchive.js +9 -15
- package/dist/task/ordinaryArchive.js +42 -0
- package/dist/tmux/tmuxManager.js +8 -8
- package/dist/web/assets/assetManifest.js +11 -51
- package/dist/web/assets/client/app/catalog.js +215 -0
- package/dist/web/assets/client/app/main.js +148 -0
- package/dist/web/assets/client/app/selection.js +139 -0
- package/dist/web/assets/client/app/settings.js +326 -0
- package/dist/web/assets/client/app/taskView.js +280 -0
- package/dist/web/assets/client/domain/context.js +96 -0
- package/dist/web/assets/client/domain/records.js +126 -0
- package/dist/web/assets/client/domain/runs.js +119 -0
- package/dist/web/assets/client/domain/taskForms.js +197 -0
- package/dist/web/assets/client/domain/vocab.js +69 -0
- package/dist/web/assets/client/domain/work.js +116 -0
- package/dist/web/assets/client/i18n/en.js +900 -0
- package/dist/web/assets/client/i18n/zh-CN.js +898 -0
- package/dist/web/assets/client/index.js +114 -0
- package/dist/web/assets/client/layout/resizer.js +37 -0
- package/dist/web/assets/client/layout/router.js +35 -0
- package/dist/web/assets/client/layout/shortcuts.js +45 -0
- package/dist/web/assets/client/layout/sizing.js +95 -0
- package/dist/web/assets/client/layout/workspace.js +247 -0
- package/dist/web/assets/client/{api.js → lib/api.js} +28 -4
- package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
- package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
- package/dist/web/assets/client/lib/i18n.js +54 -0
- package/dist/web/assets/client/lib/markdown.js +167 -0
- package/dist/web/assets/client/lib/prefs.js +28 -0
- package/dist/web/assets/client/lib/theme.js +68 -0
- package/dist/web/assets/client/ui/containers.js +101 -0
- package/dist/web/assets/client/ui/controls.js +51 -0
- package/dist/web/assets/client/ui/forms.js +48 -0
- package/dist/web/assets/client/ui/metrics.js +137 -0
- package/dist/web/assets/client/ui/primitives.js +92 -0
- package/dist/web/assets/client/ui/text.js +33 -0
- package/dist/web/assets/client/ui/toast.js +12 -0
- package/dist/web/assets/client/views/bodySearch.js +172 -0
- package/dist/web/assets/client/views/createTask.js +85 -0
- package/dist/web/assets/client/views/dock/conversation.js +612 -0
- package/dist/web/assets/client/views/dock/discussion.js +173 -0
- package/dist/web/assets/client/views/dock/preview.js +129 -0
- package/dist/web/assets/client/views/dock/terminal.js +199 -0
- package/dist/web/assets/client/views/globalInput.js +67 -0
- package/dist/web/assets/client/{overview.js → views/overview.js} +62 -56
- package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
- package/dist/web/assets/client/views/skills.js +152 -0
- package/dist/web/assets/client/views/task/actions.js +36 -0
- package/dist/web/assets/client/views/task/delivery.js +185 -0
- package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
- package/dist/web/assets/client/views/task/files.js +272 -0
- package/dist/web/assets/client/views/task/observation.js +153 -0
- package/dist/web/assets/client/views/task/overview.js +161 -0
- package/dist/web/assets/client/views/task/page.js +131 -0
- package/dist/web/assets/client/views/task/records.js +163 -0
- package/dist/web/assets/client/views/task/runtime.js +130 -0
- package/dist/web/assets/client/views/task/timeline.js +190 -0
- package/dist/web/assets/shared/geometry.js +19 -0
- package/dist/web/assets/{icons.js → shared/icons.js} +4 -0
- package/dist/web/assets/shared/taskTabs.js +8 -0
- package/dist/web/assets/shared/themes.js +93 -0
- package/dist/web/assets/shell/dialogs.js +63 -0
- package/dist/web/assets/shell/dock.js +40 -0
- package/dist/web/assets/shell/index.js +40 -0
- package/dist/web/assets/shell/settings.js +34 -0
- package/dist/web/assets/shell/sidebar.js +45 -0
- package/dist/web/assets/styles/index.js +41 -0
- package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +15 -13
- package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
- package/dist/web/assets/styles/markdown.js +22 -3
- package/dist/web/assets/styles/tokens.js +10 -63
- package/dist/web/assets/styles/ui/controls.js +76 -0
- package/dist/web/assets/styles/ui/forms.js +46 -0
- package/dist/web/assets/styles/ui/surfaces.js +122 -0
- package/dist/web/assets/styles/views/delivery.js +29 -0
- package/dist/web/assets/styles/views/dock.js +71 -0
- package/dist/web/assets/styles/views/page.js +32 -0
- package/dist/web/assets/styles/views/records.js +70 -0
- package/dist/web/assets/styles/views/settings.js +26 -0
- package/dist/web/assets/styles/views/sidebar.js +74 -0
- package/dist/web/assets/styles/views/task.js +80 -0
- package/dist/web/assets/styles/views/terminal.js +38 -0
- package/dist/web/taskPreviews.js +205 -0
- package/dist/web/tmuxWebTerminal.js +16 -9
- package/dist/web/webConversation.js +373 -0
- package/dist/web/webMaterials.js +155 -0
- package/dist/web/webServer.js +306 -18
- package/dist/web/webSettings.js +331 -0
- package/dist/web/webSkills.js +86 -0
- package/dist/web/webSnapshot.js +40 -8
- package/dist/web/webTaskSurface.js +114 -9
- package/dist/workspace/gitChangeSetCapture.js +3 -1
- package/docs/managed-turn-and-session-runtime.md +9 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
- package/docs/observability/README.md +13 -3
- package/docs/observability/README.zh-CN.md +10 -3
- package/docs/release-workflow.md +53 -0
- package/docs/release-workflow.zh-CN.md +40 -0
- package/docs/storage-baseline.md +38 -1
- package/docs/storage-baseline.zh-CN.md +22 -0
- package/docs/task-delivery.md +36 -8
- package/docs/task-delivery.zh-CN.md +24 -7
- package/docs/task-discovery.md +28 -0
- package/docs/testing/verification-levels.md +2 -2
- package/i18n/README.zh-CN.md +4 -3
- package/package.json +1 -1
- 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 +3 -0
- package/skills/yui-runtime/SKILL.md +4 -0
- package/skills/yui-runtime/references/publication.md +20 -4
- package/skills/yui-worker/SKILL.md +3 -0
- package/dist/web/assets/client/app.js +0 -804
- package/dist/web/assets/client/components.js +0 -418
- package/dist/web/assets/client/detail.js +0 -333
- package/dist/web/assets/client/dock.js +0 -225
- package/dist/web/assets/client/evidence.js +0 -172
- package/dist/web/assets/client/forms.js +0 -230
- package/dist/web/assets/client/i18n.js +0 -1161
- package/dist/web/assets/client/markdown.js +0 -114
- package/dist/web/assets/client/records.js +0 -63
- package/dist/web/assets/client/sections.js +0 -313
- package/dist/web/assets/client/theme.js +0 -71
- package/dist/web/assets/shell.js +0 -161
- package/dist/web/assets/styles/components.js +0 -169
- package/dist/web/assets/styles/views.js +0 -292
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export const METRICS_SCRIPT = String.raw `
|
|
2
|
+
// Metric tiles and the usage card. DOM is built with node() only, so the
|
|
3
|
+
// usage test can execute this module with node() and formatDateTime() alone.
|
|
4
|
+
import { node } from "/assets/js/lib/dom.js";
|
|
5
|
+
import { formatDateTime } from "/assets/js/lib/format.js";
|
|
6
|
+
import { requestJson } from "/assets/js/lib/api.js";
|
|
7
|
+
|
|
8
|
+
export function metricTile(labelText, value, options) {
|
|
9
|
+
const variant = options && options.tone ? " tone-" + options.tone : "";
|
|
10
|
+
const tile = node("div", "metric" + variant);
|
|
11
|
+
tile.append(node("span", "metric-label", labelText), node("strong", "metric-value", String(value)));
|
|
12
|
+
return tile;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function pageLocale() { return typeof document === "undefined" ? undefined : document.documentElement.lang || undefined; }
|
|
16
|
+
|
|
17
|
+
// Seconds as the largest whole unit plus the next one ("2d 22h", "2小时48分钟").
|
|
18
|
+
const DURATION_UNITS = [["day", 86400], ["hour", 3600], ["minute", 60], ["second", 1]];
|
|
19
|
+
function durationText(seconds) {
|
|
20
|
+
const total = Math.max(0, Math.round(seconds));
|
|
21
|
+
const found = DURATION_UNITS.findIndex(function (unit) { return total >= unit[1]; });
|
|
22
|
+
const first = found < 0 ? DURATION_UNITS.length - 1 : found;
|
|
23
|
+
return DURATION_UNITS.slice(first, first + 2).map(function (unit, index) {
|
|
24
|
+
const count = index ? Math.floor((total % DURATION_UNITS[first][1]) / unit[1]) : Math.floor(total / unit[1]);
|
|
25
|
+
return index && !count ? "" : new Intl.NumberFormat(pageLocale(), { style: "unit", unit: unit[0], unitDisplay: "narrow" }).format(count);
|
|
26
|
+
}).filter(Boolean).join(" ");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function usageMetricText(metric, t, suffix) {
|
|
30
|
+
if (!metric || metric.value == null) return t("detail.unobserved");
|
|
31
|
+
const value = suffix === "s" ? durationText(metric.value) : metric.value + (suffix || "");
|
|
32
|
+
return value + (metric.status === "partial" ? " · " + t("detail.partial") : "");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function usageTile(labelText, metric, t, suffix) {
|
|
36
|
+
const tile = metricTile(labelText, usageMetricText(metric, t, suffix));
|
|
37
|
+
if (metric && metric.reasons && metric.reasons.length) {
|
|
38
|
+
tile.append(node("small", "metric-note", metric.reasons.map(function (reason) {
|
|
39
|
+
return t("usage.reason." + reason);
|
|
40
|
+
}).join("; ")));
|
|
41
|
+
}
|
|
42
|
+
return tile;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function observabilityMetricCard(observability, t, viewState) {
|
|
46
|
+
if (!observability) return null;
|
|
47
|
+
const wrap = node("div", "usage");
|
|
48
|
+
const grid = node("div", "metric-grid");
|
|
49
|
+
const cost = observability.cost || {};
|
|
50
|
+
const context = observability.context || {};
|
|
51
|
+
grid.append(usageTile(t("usage.tokens"), cost.tokens, t));
|
|
52
|
+
grid.append(usageTile(t("usage.toolCalls"), cost.toolCalls, t));
|
|
53
|
+
grid.append(usageTile(t("usage.elapsed"), cost.elapsedSeconds, t, "s"));
|
|
54
|
+
grid.append(usageTile(t("usage.executionSum"), cost.executionSeconds, t, "s"));
|
|
55
|
+
["actual", "estimated"].forEach(function (kind) {
|
|
56
|
+
const money = cost.costs && cost.costs[kind];
|
|
57
|
+
grid.append(metricTile(t("usage." + kind), !money || !money.amounts.length
|
|
58
|
+
? t("detail.unobserved")
|
|
59
|
+
: money.amounts.map(function (amount) { return usageMetricText(amount, t) + " " + amount.currency; }).join("; ")));
|
|
60
|
+
});
|
|
61
|
+
grid.append(metricTile(t("usage.ready"), observability.readyCount === undefined
|
|
62
|
+
? ((observability.dag && observability.dag.readyIds) || []).length : observability.readyCount));
|
|
63
|
+
grid.append(metricTile(t("usage.contextSnapshots"), context.snapshotCount == null ? t("detail.unobserved") : context.snapshotCount));
|
|
64
|
+
wrap.append(grid);
|
|
65
|
+
const meta = node("p", "usage-meta");
|
|
66
|
+
meta.append(node("span", "", t("usage.scope")));
|
|
67
|
+
meta.append(node("span", "", t("usage.observedThrough") + " "
|
|
68
|
+
+ (cost.observedThrough ? formatDateTime(cost.observedThrough, pageLocale()) : t("detail.unobserved"))));
|
|
69
|
+
meta.append(node("span", "", t("usage.contextBytes") + " "
|
|
70
|
+
+ (context.totalBytes == null ? t("detail.partial") : context.totalBytes + " B")));
|
|
71
|
+
wrap.append(meta);
|
|
72
|
+
wrap.append(node("p", "usage-meta", t("usage.notBill")));
|
|
73
|
+
if (cost.history && !cost.history.complete) wrap.append(node("p", "metric-note", t("usage.reason.usage-history-limited")));
|
|
74
|
+
if (cost.taskId && typeof document !== "undefined") {
|
|
75
|
+
const details = node("details", "usage-details");
|
|
76
|
+
details.dataset.viewKey = "usage-sources";
|
|
77
|
+
details.append(node("summary", "", t("usage.sources")));
|
|
78
|
+
const body = node("div", "stack");
|
|
79
|
+
const more = node("button", "", t("usage.loadDetails"));
|
|
80
|
+
more.type = "button";
|
|
81
|
+
const cache = viewState || {};
|
|
82
|
+
const state = cache.usageDetails || (cache.usageDetails = { pages: [], offset: 0, reading: false });
|
|
83
|
+
function drawPages() {
|
|
84
|
+
body.replaceChildren();
|
|
85
|
+
state.pages.forEach(function (page) {
|
|
86
|
+
body.append(node("p", "metric-note", t("usage.observedThrough") + " "
|
|
87
|
+
+ (page.observedThrough ? formatDateTime(page.observedThrough, pageLocale()) : t("detail.unobserved"))));
|
|
88
|
+
["actual", "estimated"].forEach(function (kind) {
|
|
89
|
+
page.costs[kind].evidence.forEach(function (entry) {
|
|
90
|
+
const basis = entry.basis;
|
|
91
|
+
body.append(node("p", "small mono-line", t("usage." + kind) + " · "
|
|
92
|
+
+ usageMetricText(entry.amount, t) + " " + entry.currency + " · " + entry.source
|
|
93
|
+
+ " · " + entry.roleName + "/" + entry.nativeSessionId + " · " + entry.semantics
|
|
94
|
+
+ (basis ? " · " + basis.model + " · " + basis.source + "@" + basis.version
|
|
95
|
+
+ " · " + basis.scope + " · " + t("usage.excluded") + ": " + basis.excluded.join(", ") : "")));
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
page.sessions.forEach(function (entry) {
|
|
99
|
+
body.append(node("p", "small mono-line", entry.identity.roleName + "/" + entry.identity.nativeSessionId
|
|
100
|
+
+ " · " + usageMetricText(entry.tokens, t) + " · " + entry.sources.join(", ")
|
|
101
|
+
+ " · " + entry.semantics.join(", ")));
|
|
102
|
+
});
|
|
103
|
+
if (!page.details.sessionTotal && !page.details.actualTotal && !page.details.estimatedTotal) {
|
|
104
|
+
body.append(node("p", "small", t("usage.unavailable")));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
more.hidden = state.offset === null;
|
|
108
|
+
more.disabled = state.reading;
|
|
109
|
+
details.dataset.reading = String(state.reading);
|
|
110
|
+
}
|
|
111
|
+
async function load() {
|
|
112
|
+
if (state.reading || state.offset === null) return;
|
|
113
|
+
state.reading = true;
|
|
114
|
+
drawPages();
|
|
115
|
+
try {
|
|
116
|
+
const page = await requestJson("/api/tasks/" + encodeURIComponent(cost.taskId)
|
|
117
|
+
+ "/usage?limit=20&offset=" + state.offset);
|
|
118
|
+
state.pages.push(page);
|
|
119
|
+
state.offset = page.details.nextOffset;
|
|
120
|
+
drawPages();
|
|
121
|
+
} catch (error) {
|
|
122
|
+
body.append(node("p", "metric-note", String(error.message || error)));
|
|
123
|
+
} finally {
|
|
124
|
+
state.reading = false;
|
|
125
|
+
more.disabled = false;
|
|
126
|
+
details.dataset.reading = "false";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
drawPages();
|
|
130
|
+
more.addEventListener("click", load);
|
|
131
|
+
details.addEventListener("toggle", function () { if (details.open && !state.pages.length) load(); });
|
|
132
|
+
details.append(body, more);
|
|
133
|
+
wrap.append(details);
|
|
134
|
+
}
|
|
135
|
+
return wrap;
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export const PRIMITIVES_SCRIPT = String.raw `
|
|
2
|
+
// Leaf builders: data in, one small DOM element out. Nothing here reads
|
|
3
|
+
// application state, translates domain values or performs a request.
|
|
4
|
+
import { h, icon } from "/assets/js/lib/dom.js";
|
|
5
|
+
import { formatDateTime, relativeTime } from "/assets/js/lib/format.js";
|
|
6
|
+
|
|
7
|
+
export function badge(text, toneName, options) {
|
|
8
|
+
const element = h("span.badge.tone-" + (toneName || "idle"), null,
|
|
9
|
+
options && options.dot ? h("i.badge-dot", { "aria-hidden": "true" }) : null, text);
|
|
10
|
+
if (options && options.title) element.title = options.title;
|
|
11
|
+
return element;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function dot(toneName, title) {
|
|
15
|
+
const element = h("span.dot.tone-" + (toneName || "idle"), { "aria-hidden": title ? null : "true" });
|
|
16
|
+
if (title) { element.title = title; element.setAttribute("role", "img"); element.setAttribute("aria-label", title); }
|
|
17
|
+
return element;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function chip(text, extraClass) {
|
|
21
|
+
return h("span.chip" + (extraClass ? "." + extraClass : ""), null, text);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function mono(text, extraClass) {
|
|
25
|
+
return h("code.id" + (extraClass ? "." + extraClass : ""), null, text);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function timeTag(iso, locale, t, options) {
|
|
29
|
+
if (!iso) return null;
|
|
30
|
+
const element = h("time.when", { dateTime: iso }, options && options.absolute
|
|
31
|
+
? formatDateTime(iso, locale) : relativeTime(iso, locale, t));
|
|
32
|
+
element.title = formatDateTime(iso, locale);
|
|
33
|
+
return element;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function button(text, options) {
|
|
37
|
+
const opts = options || {};
|
|
38
|
+
const element = h("button.btn" + (opts.variant ? ".btn-" + opts.variant : ""), {
|
|
39
|
+
type: opts.type || "button", onclick: opts.onClick, disabled: !!opts.disabled, title: opts.title
|
|
40
|
+
}, opts.icon ? icon(opts.icon) : null, text ? h("span", null, text) : null);
|
|
41
|
+
if (opts.dataset) Object.assign(element.dataset, opts.dataset);
|
|
42
|
+
return element;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function note(text, toneName) {
|
|
46
|
+
return h("p.note" + (toneName ? ".tone-" + toneName : ""), null, text);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function emptyState(text, iconName, extra) {
|
|
50
|
+
return h("div.empty", null, iconName ? icon(iconName) : null, h("p", null, text), extra || null);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function loadingBlock(text) {
|
|
54
|
+
return h("div.loading-block", null, h("span.spinner"), h("span", null, text));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function kv(rows) {
|
|
58
|
+
const list = h("dl.kv");
|
|
59
|
+
rows.forEach(function (row) {
|
|
60
|
+
if (!row || row[1] === undefined || row[1] === null || row[1] === "") return;
|
|
61
|
+
list.append(h("dt", null, row[0]), h("dd", null, row[1]));
|
|
62
|
+
});
|
|
63
|
+
return list;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function bulletList(items, className) {
|
|
67
|
+
const list = (items || []).filter(Boolean);
|
|
68
|
+
if (!list.length) return null;
|
|
69
|
+
return h("ul.bullets" + (className ? "." + className : ""), null, list.map(function (item) { return h("li", null, item); }));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Verbatim text (a command, an exact record) in a scrollable block.
|
|
73
|
+
export function codeBlock(text) {
|
|
74
|
+
return h("pre.code-block", null, text);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function jsonBlock(value) {
|
|
78
|
+
return codeBlock(JSON.stringify(value, null, 2));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Only http(s) links without credentials leave the page.
|
|
82
|
+
export function safeUrl(value) {
|
|
83
|
+
try {
|
|
84
|
+
const url = new URL(value);
|
|
85
|
+
return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password ? url.href : null;
|
|
86
|
+
} catch { return null; }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function externalLink(href, text) {
|
|
90
|
+
return h("a.link", { href: href, rel: "noopener noreferrer", target: "_blank" }, text);
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const TEXT_SCRIPT = String.raw `
|
|
2
|
+
// Agent and user prose: rendered Markdown that collapses when long.
|
|
3
|
+
import { h } from "/assets/js/lib/dom.js";
|
|
4
|
+
import { renderMarkdown } from "/assets/js/lib/markdown.js";
|
|
5
|
+
|
|
6
|
+
function shouldCollapse(text, threshold) {
|
|
7
|
+
const value = String(text);
|
|
8
|
+
if (value.length > (threshold || 700)) return true;
|
|
9
|
+
let lines = 0;
|
|
10
|
+
for (let index = 0; index < value.length; index += 1) if (value[index] === "\n") lines += 1;
|
|
11
|
+
return lines > 12;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function richText(title, text, t, options) {
|
|
15
|
+
if (!text) return null;
|
|
16
|
+
const opts = options || {};
|
|
17
|
+
const block = h("div.prose-block" + (opts.className ? "." + opts.className : ""));
|
|
18
|
+
if (title) block.append(h("h4.prose-label", null, title));
|
|
19
|
+
const body = h("div.md" + (opts.muted ? ".muted" : ""));
|
|
20
|
+
body.innerHTML = renderMarkdown(text);
|
|
21
|
+
block.append(body);
|
|
22
|
+
if (shouldCollapse(text, opts.threshold)) {
|
|
23
|
+
block.classList.add("is-collapsed");
|
|
24
|
+
const toggle = h("button.link-btn", { type: "button" }, t("actions.showMore"));
|
|
25
|
+
toggle.addEventListener("click", function () {
|
|
26
|
+
const collapsed = block.classList.toggle("is-collapsed");
|
|
27
|
+
toggle.textContent = t(collapsed ? "actions.showMore" : "actions.showLess");
|
|
28
|
+
});
|
|
29
|
+
block.append(toggle);
|
|
30
|
+
}
|
|
31
|
+
return block;
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const TOAST_SCRIPT = String.raw `
|
|
2
|
+
// A transient status message; a newer message replaces the current one.
|
|
3
|
+
export function createToast(element) {
|
|
4
|
+
let timer = null;
|
|
5
|
+
return function showToast(message) {
|
|
6
|
+
element.textContent = message;
|
|
7
|
+
element.classList.add("show");
|
|
8
|
+
window.clearTimeout(timer);
|
|
9
|
+
timer = window.setTimeout(function () { element.classList.remove("show"); }, 3200);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
export const BODY_SEARCH_SCRIPT = String.raw `
|
|
2
|
+
import { h } from "/assets/js/lib/dom.js";
|
|
3
|
+
import { button } from "/assets/js/ui/primitives.js";
|
|
4
|
+
|
|
5
|
+
// Textareas normalize CR/CRLF to LF. Map their UTF-16 selections back to the
|
|
6
|
+
// unchanged source before computing the search contract's code-point offset.
|
|
7
|
+
export function selectedSearchText(page, body) {
|
|
8
|
+
if (body.selectionStart === body.selectionEnd) return { excerpt: page.content, offset: page.offset };
|
|
9
|
+
let visible = 0, start, end;
|
|
10
|
+
for (let index = 0; index <= page.content.length;) {
|
|
11
|
+
if (visible === body.selectionStart) start = index;
|
|
12
|
+
if (visible === body.selectionEnd) { end = index; break; }
|
|
13
|
+
if (index === page.content.length) break;
|
|
14
|
+
const point = String.fromCodePoint(page.content.codePointAt(index));
|
|
15
|
+
index += point === "\r" && page.content[index + 1] === "\n" ? 2 : point.length;
|
|
16
|
+
visible += point.length;
|
|
17
|
+
}
|
|
18
|
+
if (start === undefined || end === undefined) throw new Error("Select complete characters before quoting.");
|
|
19
|
+
return { excerpt: page.content.slice(start, end),
|
|
20
|
+
offset: page.offset + Array.from(page.content.slice(0, start)).length };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Search never sends input or starts a Task. The user/Operator chooses excerpts
|
|
24
|
+
// to reuse through the normal Task input path with its own authority.
|
|
25
|
+
export function bindBodySearch(options) {
|
|
26
|
+
const t = options.t, api = options.api;
|
|
27
|
+
const dialog = h("dialog.dialog.body-search-dialog", { "aria-labelledby": "body-search-title" });
|
|
28
|
+
const close = button(t("actions.close"));
|
|
29
|
+
close.dataset.i18n = "actions.close";
|
|
30
|
+
close.addEventListener("click", function () { dialog.close(); });
|
|
31
|
+
const query = h("input", { type: "search", required: true, maxLength: 256 });
|
|
32
|
+
const scope = h("select", { "aria-label": t("bodySearch.scope") },
|
|
33
|
+
h("option", { value: "" }, t("bodySearch.allTasks")),
|
|
34
|
+
h("option", { value: "current" }, t("bodySearch.currentTask")));
|
|
35
|
+
const kind = h("select");
|
|
36
|
+
["", "brief", "message", "decision", "result", "completion"].forEach(function (value) {
|
|
37
|
+
const key = "bodySearch.kind." + (value || "all");
|
|
38
|
+
kind.append(h("option", { value: value, dataset: { i18n: key } }, t(key)));
|
|
39
|
+
});
|
|
40
|
+
const submit = button(t("bodySearch.search"), { variant: "primary" });
|
|
41
|
+
submit.dataset.i18n = "bodySearch.search";
|
|
42
|
+
submit.type = "submit";
|
|
43
|
+
const status = h("p.receipt", { role: "status" });
|
|
44
|
+
const destination = h("p.dialog-sub");
|
|
45
|
+
const results = h("div.row-stack");
|
|
46
|
+
const next = button(t("catalog.next"));
|
|
47
|
+
next.dataset.i18n = "catalog.next";
|
|
48
|
+
next.disabled = true;
|
|
49
|
+
const form = h("form.dialog-body", null,
|
|
50
|
+
h("header.dialog-head", null, h("h2", { id: "body-search-title", dataset: { i18n: "bodySearch.title" } }, t("bodySearch.title")), close),
|
|
51
|
+
h("p.dialog-sub", { dataset: { i18n: "bodySearch.help" } }, t("bodySearch.help")),
|
|
52
|
+
h("label.field", null, h("span", { dataset: { i18n: "bodySearch.query" } }, t("bodySearch.query")), query),
|
|
53
|
+
h("label.field", null, h("span", { dataset: { i18n: "bodySearch.kind" } }, t("bodySearch.kind")), kind),
|
|
54
|
+
h("label.field", null, h("span", null, t("bodySearch.scope")), scope),
|
|
55
|
+
h("div.dialog-actions", null, submit, next), destination, status, results);
|
|
56
|
+
dialog.append(form);
|
|
57
|
+
document.body.append(dialog);
|
|
58
|
+
let cursor = null, searched = null, generation = 0, target = null, currentTask = null;
|
|
59
|
+
function invalidate() {
|
|
60
|
+
generation += 1; cursor = null; searched = null; next.disabled = true;
|
|
61
|
+
submit.disabled = false; results.replaceChildren(); status.textContent = "";
|
|
62
|
+
}
|
|
63
|
+
query.addEventListener("input", invalidate);
|
|
64
|
+
kind.addEventListener("change", invalidate);
|
|
65
|
+
scope.addEventListener("change", invalidate);
|
|
66
|
+
dialog.addEventListener("close", invalidate);
|
|
67
|
+
async function load(more) {
|
|
68
|
+
const ticket = ++generation;
|
|
69
|
+
const parameters = more ? new URLSearchParams(searched) : new URLSearchParams({ query: query.value.trim(), limit: "20" });
|
|
70
|
+
if (!more && kind.value) parameters.set("kind", kind.value);
|
|
71
|
+
if (!more && scope.value === "current") {
|
|
72
|
+
if (!currentTask) { status.textContent = t("bodySearch.noTask"); return; }
|
|
73
|
+
parameters.set("task", currentTask);
|
|
74
|
+
}
|
|
75
|
+
if (more && cursor) parameters.set("cursor", cursor);
|
|
76
|
+
submit.disabled = true; next.disabled = true; status.textContent = t("bodySearch.loading");
|
|
77
|
+
// One page in view: browsing large histories never accumulates all bodies.
|
|
78
|
+
results.replaceChildren();
|
|
79
|
+
try {
|
|
80
|
+
const page = await api.searchBodies(parameters);
|
|
81
|
+
if (ticket !== generation) return;
|
|
82
|
+
cursor = page.nextCursor;
|
|
83
|
+
parameters.delete("cursor"); searched = parameters.toString();
|
|
84
|
+
status.textContent = page.items.length ? t("bodySearch.results") + " " + page.items.length : t("bodySearch.empty");
|
|
85
|
+
page.items.forEach(function (item) {
|
|
86
|
+
const source = item.taskId + " / " + t("bodySearch.kind." + item.kind) + " / " + item.ref.refId + " / " + item.field;
|
|
87
|
+
const original = button(t("bodySearch.original"));
|
|
88
|
+
original.dataset.i18n = "bodySearch.original";
|
|
89
|
+
const locate = button(t("bodySearch.locate"));
|
|
90
|
+
locate.dataset.i18n = "bodySearch.locate";
|
|
91
|
+
locate.addEventListener("click", function () { dialog.close(); options.selectTask(item.taskId); });
|
|
92
|
+
const quote = button(t("bodySearch.quoteSnippet"));
|
|
93
|
+
quote.disabled = !target;
|
|
94
|
+
const feedback = h("p.receipt", { role: "status" });
|
|
95
|
+
const detail = h("div");
|
|
96
|
+
const row = h("article.card", null, h("div.card-body", null,
|
|
97
|
+
h("h3.card-title", null, source), h("pre.code-block.search-snippet", null, item.snippet),
|
|
98
|
+
h("p.faint.small", null, item.ref.revision + " · " + item.ref.digest),
|
|
99
|
+
h("div.dialog-actions", null, original, locate, quote), feedback, detail));
|
|
100
|
+
async function insert(excerpt, offset, control) {
|
|
101
|
+
control.disabled = true;
|
|
102
|
+
try {
|
|
103
|
+
const page = await api.searchSource(item, offset);
|
|
104
|
+
if (ticket !== generation) return;
|
|
105
|
+
if (!page.content.startsWith(excerpt)) throw new Error(t("bodySearch.sourceChanged"));
|
|
106
|
+
const ref = JSON.stringify({ authority: "reference-only", taskId: item.taskId,
|
|
107
|
+
kind: item.kind, ref: item.ref, field: item.field, offset: offset,
|
|
108
|
+
length: Array.from(excerpt).length, offsetUnit: "unicode-code-points" });
|
|
109
|
+
options.insertReference(target, t("bodySearch.referenceOnly") + "\n" + ref
|
|
110
|
+
+ "\n\n" + excerpt.split("\n").map(function (line) { return "> " + line; }).join("\n"));
|
|
111
|
+
feedback.textContent = t("bodySearch.inserted");
|
|
112
|
+
} catch (error) { if (ticket === generation) feedback.textContent = error.message; }
|
|
113
|
+
finally { if (ticket === generation) control.disabled = !target; }
|
|
114
|
+
}
|
|
115
|
+
quote.addEventListener("click", function () { insert(item.snippet, item.offset, quote); });
|
|
116
|
+
async function read(offset) {
|
|
117
|
+
original.disabled = true;
|
|
118
|
+
detail.textContent = t("bodySearch.loading");
|
|
119
|
+
try {
|
|
120
|
+
const page = await api.searchSource(item, offset);
|
|
121
|
+
if (ticket !== generation) return;
|
|
122
|
+
const body = h("textarea.search-source", { readOnly: true, rows: 12,
|
|
123
|
+
"aria-label": t("bodySearch.sourceText") });
|
|
124
|
+
body.value = page.content;
|
|
125
|
+
const stage = button(t("bodySearch.quoteSelection"));
|
|
126
|
+
stage.disabled = !target || !page.content;
|
|
127
|
+
stage.addEventListener("click", function () {
|
|
128
|
+
try {
|
|
129
|
+
const selection = selectedSearchText(page, body);
|
|
130
|
+
insert(selection.excerpt, selection.offset, stage);
|
|
131
|
+
} catch (error) { feedback.textContent = error.message; }
|
|
132
|
+
});
|
|
133
|
+
const previous = button(t("bodySearch.previous"));
|
|
134
|
+
previous.disabled = page.offset === 0;
|
|
135
|
+
previous.addEventListener("click", function () { read(Math.max(0, page.offset - 4000)); });
|
|
136
|
+
const more = button(t("catalog.next"));
|
|
137
|
+
more.disabled = page.nextOffset === null;
|
|
138
|
+
more.addEventListener("click", function () { read(page.nextOffset); });
|
|
139
|
+
detail.replaceChildren(h("p.faint.small", null,
|
|
140
|
+
page.offset + "–" + (page.offset + Array.from(page.content).length) + " / " + page.totalCharacters),
|
|
141
|
+
body, h("div.dialog-actions", null, previous, more, stage));
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (ticket === generation) detail.textContent = t("bodySearch.unavailable") + " " + error.message;
|
|
144
|
+
} finally { if (ticket === generation) original.disabled = false; }
|
|
145
|
+
}
|
|
146
|
+
original.addEventListener("click", function () { read(0); });
|
|
147
|
+
results.append(row);
|
|
148
|
+
});
|
|
149
|
+
next.disabled = !cursor;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (ticket !== generation) return;
|
|
152
|
+
cursor = null; searched = null;
|
|
153
|
+
status.textContent = t("bodySearch.failed") + " " + error.message;
|
|
154
|
+
} finally { if (ticket === generation) submit.disabled = false; }
|
|
155
|
+
}
|
|
156
|
+
form.addEventListener("submit", function (event) { event.preventDefault(); if (!submit.disabled) load(false); });
|
|
157
|
+
next.addEventListener("click", function () { if (cursor && searched) load(true); });
|
|
158
|
+
document.querySelector("#body-search-open").addEventListener("click", function () {
|
|
159
|
+
invalidate();
|
|
160
|
+
target = options.captureDraftTarget();
|
|
161
|
+
currentTask = options.currentTask();
|
|
162
|
+
if (!target) destination.textContent = t("bodySearch.noTarget");
|
|
163
|
+
else {
|
|
164
|
+
const identity = JSON.parse(target);
|
|
165
|
+
destination.textContent = t("bodySearch.destination") + " "
|
|
166
|
+
+ (identity[0].taskId || identity[0].scope) + " / " + identity[0].roleName + " / " + identity[1];
|
|
167
|
+
}
|
|
168
|
+
dialog.showModal(); query.focus();
|
|
169
|
+
});
|
|
170
|
+
return { dialog: dialog };
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const CREATE_TASK_SCRIPT = String.raw `
|
|
2
|
+
import { h } from "/assets/js/lib/dom.js";
|
|
3
|
+
import { button } from "/assets/js/ui/primitives.js";
|
|
4
|
+
import { field, receiptLine, setReceipt, submitWrite } from "/assets/js/ui/forms.js";
|
|
5
|
+
import { renderSubmissionFacets } from "/assets/js/domain/taskForms.js";
|
|
6
|
+
|
|
7
|
+
export function bindCreateTask(options) {
|
|
8
|
+
const t = options.t;
|
|
9
|
+
let dialog = null;
|
|
10
|
+
window.addEventListener("beforeunload", function (event) {
|
|
11
|
+
if (dialog && dialog.querySelector('[data-unsent="true"]')) {
|
|
12
|
+
event.preventDefault(); event.returnValue = "";
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
document.querySelector("#create-task").addEventListener("click", async function () {
|
|
16
|
+
if (dialog && dialog.dataset.saved === "true") { dialog.remove(); dialog = null; }
|
|
17
|
+
if (dialog) { dialog.showModal(); return; }
|
|
18
|
+
dialog = h("dialog.dialog", { "aria-label": t("create.title") });
|
|
19
|
+
const creationDialog = dialog;
|
|
20
|
+
const form = h("form.dialog-body");
|
|
21
|
+
const title = h("input", { required: true, maxLength: 200 });
|
|
22
|
+
const requirements = h("textarea", { required: true, rows: 6, maxLength: 8000 });
|
|
23
|
+
const projects = h("fieldset.stack", null, h("legend", null, t("create.projects")));
|
|
24
|
+
const plan = h("input", { type: "checkbox", checked: true });
|
|
25
|
+
const receipt = receiptLine(t("receipt.notSubmitted"));
|
|
26
|
+
const facets = h("div");
|
|
27
|
+
const submit = button(t("create.plan"), { type: "submit", variant: "primary", disabled: true });
|
|
28
|
+
const close = button(t("actions.close"), { onClick: function () { dialog.close(); } });
|
|
29
|
+
form.append(h("h2", null, t("create.title")), field(t("create.name"), title),
|
|
30
|
+
field(t("goal.requirements"), requirements), projects, field(t("create.plan"), plan, t("create.hint")),
|
|
31
|
+
submit, close, receipt, facets);
|
|
32
|
+
dialog.append(form);
|
|
33
|
+
document.body.append(dialog);
|
|
34
|
+
dialog.showModal();
|
|
35
|
+
const choices = [];
|
|
36
|
+
let projectsReady = false;
|
|
37
|
+
let cursor;
|
|
38
|
+
const more = button(t("catalog.next"), { onClick: function () { loadProjects(); } });
|
|
39
|
+
projects.append(more);
|
|
40
|
+
async function loadProjects() {
|
|
41
|
+
more.disabled = true;
|
|
42
|
+
try {
|
|
43
|
+
const result = await options.api.projects(cursor);
|
|
44
|
+
result.projects.forEach(function (project) {
|
|
45
|
+
const control = h("input", { type: "checkbox", value: project.id, disabled: creationDialog.dataset.saved === "true" });
|
|
46
|
+
choices.push(control);
|
|
47
|
+
projects.insertBefore(field(project.name + " · " + project.id, control), more);
|
|
48
|
+
});
|
|
49
|
+
cursor = result.nextCursor;
|
|
50
|
+
more.hidden = !cursor;
|
|
51
|
+
more.disabled = creationDialog.dataset.saved === "true";
|
|
52
|
+
if (!projectsReady) { submit.disabled = false; projectsReady = true; }
|
|
53
|
+
} catch (error) { setReceipt(receipt, error.message, "bad"); more.disabled = creationDialog.dataset.saved === "true"; }
|
|
54
|
+
}
|
|
55
|
+
await loadProjects();
|
|
56
|
+
plan.addEventListener("change", function () { submit.textContent = t(plan.checked ? "create.plan" : "create.only"); });
|
|
57
|
+
form.addEventListener("input", function () { form.dataset.unsent = "true"; });
|
|
58
|
+
form.addEventListener("submit", function (event) {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
if (submit.disabled) return;
|
|
61
|
+
submitWrite({
|
|
62
|
+
control: submit, receipt: receipt, form: form, t: t,
|
|
63
|
+
send: function (requestId) {
|
|
64
|
+
return options.api.createTask({ title: title.value.trim(), requirements: requirements.value.trim(),
|
|
65
|
+
projectIds: choices.filter(function (control) { return control.checked; }).map(function (control) { return control.value; }),
|
|
66
|
+
plan: plan.checked, requestId: requestId });
|
|
67
|
+
},
|
|
68
|
+
saved: function (result) {
|
|
69
|
+
creationDialog.dataset.saved = "true";
|
|
70
|
+
[title, requirements, plan, more].concat(choices).forEach(function (control) { control.disabled = true; });
|
|
71
|
+
setReceipt(receipt, t("receipt.saved") + " · " + result.task.id, "ok");
|
|
72
|
+
renderSubmissionFacets(facets, result.submission, t);
|
|
73
|
+
facets.append(button(t("create.open"), { onClick: function () {
|
|
74
|
+
dialog.close(); dialog.remove(); dialog = null;
|
|
75
|
+
options.selectTask(result.task.id);
|
|
76
|
+
} }));
|
|
77
|
+
options.afterWrite();
|
|
78
|
+
return true;
|
|
79
|
+
},
|
|
80
|
+
rejectedKey: "receipt.notSubmittedBecause", unknownKey: "workbench.unknown"
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
`;
|