@zq-silk/yui 2.3.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 +15 -1
- 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 +17 -8
- 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 +25 -2
- 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 +25 -20
- 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 +7 -12
- package/dist/integration/integrationSourceApplication.js +4 -2
- package/dist/job/httpPreview.js +61 -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 +48 -71
- package/dist/repository/gitWorktreeInventory.js +121 -0
- package/dist/repository/project.js +74 -25
- package/dist/resources/resourceDiscovery.js +2 -2
- package/dist/resources/resourceGc.js +7 -4
- package/dist/resources/resourceRegistrar.js +2 -1
- 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/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/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/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} +20 -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} +61 -51
- 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/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} +10 -10
- 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 +295 -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 +106 -8
- 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/release-workflow.md +28 -0
- package/docs/release-workflow.zh-CN.md +21 -0
- package/docs/storage-baseline.md +38 -1
- package/docs/storage-baseline.zh-CN.md +22 -0
- package/docs/task-discovery.md +28 -0
- package/docs/testing/verification-levels.md +2 -2
- package/i18n/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/dist/web/assets/client/app.js +0 -808
- package/dist/web/assets/client/components.js +0 -477
- package/dist/web/assets/client/detail.js +0 -379
- package/dist/web/assets/client/dock.js +0 -309
- package/dist/web/assets/client/evidence.js +0 -364
- package/dist/web/assets/client/forms.js +0 -230
- package/dist/web/assets/client/i18n.js +0 -1277
- package/dist/web/assets/client/markdown.js +0 -114
- package/dist/web/assets/client/records.js +0 -198
- package/dist/web/assets/client/sections.js +0 -349
- 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 -216
- package/dist/web/assets/styles/views.js +0 -309
- /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
|
@@ -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
|
+
`;
|