@zq-silk/yui 2.0.0 → 2.2.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 +4 -0
- package/dist/cli/commandCatalog.js +318 -289
- package/dist/cli/commandDiscovery.js +72 -0
- package/dist/cli/completion.js +22 -227
- package/dist/cli/dynamicCompletion.js +12 -6
- package/dist/cli/invocationAuthority.js +2 -2
- package/dist/cli/invocationRouter.js +18 -7
- package/dist/cli.js +23 -3030
- package/dist/commands/capabilityCommands.js +5 -5
- package/dist/commands/globalRoleCommands.js +2 -1
- package/dist/commands/taskCommands.js +90 -21
- package/dist/commands/taskIntegrationCommands.js +3 -1
- 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 +12 -3
- package/dist/controlPlaneCli.js +3092 -0
- 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 +50 -15
- package/dist/kernel/accessAssessment.js +1 -0
- package/dist/kernel/builtinCapabilities.js +28 -1
- package/dist/kernel/capabilityRegistry.js +64 -19
- package/dist/message/messageContinuation.js +7 -2
- package/dist/nativeAgent/agent.js +275 -0
- 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/contracts.js +1 -0
- package/dist/nativeAgent/demo.js +40 -0
- 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 +16 -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/mockProvider.js +43 -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 +293 -0
- package/dist/nativeAgent/toolManager/executor.js +290 -0
- package/dist/nativeAgent/toolManager/index.js +4 -0
- package/dist/nativeAgent/validation.js +95 -0
- package/dist/plugins/pluginService.js +98 -8
- package/dist/resources/projectResourceService.js +23 -1
- package/dist/runtime/managedIdentity.js +6 -0
- package/dist/surface/surfaceContributions.js +3 -0
- package/dist/task/taskAuthority.js +61 -10
- package/dist/web/assets/assetManifest.js +38 -22
- package/dist/web/assets/client/api.js +115 -0
- package/dist/web/assets/client/app.js +615 -862
- package/dist/web/assets/client/components.js +323 -897
- package/dist/web/assets/client/detail.js +333 -0
- package/dist/web/assets/client/dock.js +225 -0
- package/dist/web/assets/client/dom.js +55 -3
- package/dist/web/assets/client/evidence.js +172 -0
- package/dist/web/assets/client/format.js +25 -14
- package/dist/web/assets/client/forms.js +230 -0
- package/dist/web/assets/client/i18n.js +1024 -791
- package/dist/web/assets/client/overview.js +118 -0
- package/dist/web/assets/client/records.js +63 -0
- package/dist/web/assets/client/sections.js +313 -0
- package/dist/web/assets/client/sidebar.js +146 -0
- package/dist/web/assets/client/theme.js +61 -22
- package/dist/web/assets/icons.js +39 -0
- package/dist/web/assets/shell.js +125 -118
- package/dist/web/assets/styles/base.js +41 -0
- package/dist/web/assets/styles/components.js +169 -0
- package/dist/web/assets/styles/layout.js +56 -77
- package/dist/web/assets/styles/markdown.js +16 -24
- package/dist/web/assets/styles/responsive.js +41 -44
- package/dist/web/assets/styles/tokens.js +65 -89
- package/dist/web/assets/styles/views.js +292 -0
- package/dist/web/webServer.js +2 -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/release-workflow.md +61 -0
- package/docs/release-workflow.zh-CN.md +46 -0
- package/docs/roles-and-configuration.md +32 -0
- package/docs/roles-and-configuration.zh-CN.md +26 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +9 -0
- package/skills/yui-reviewer/SKILL.md +5 -0
- package/skills/yui-runtime/SKILL.md +33 -0
- package/dist/web/assets/client/taskSummary.js +0 -350
- package/dist/web/assets/client/taskSurface.js +0 -616
- package/dist/web/assets/client/view.js +0 -608
- package/dist/web/assets/styles/cards.js +0 -247
- package/dist/web/assets/styles/widgets.js +0 -168
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
export const EVIDENCE_SCRIPT = String.raw `
|
|
2
|
+
// Results and evidence. Files are read on request at a fixed revision; the
|
|
3
|
+
// selected revision survives refreshes. Reading never accepts, publishes or
|
|
4
|
+
// executes a result.
|
|
5
|
+
import { h, icon, clear } from "/assets/js/dom.js";
|
|
6
|
+
import { formatBytes } from "/assets/js/format.js";
|
|
7
|
+
import { card, button, note, mono, emptyState, richText, badge, observabilityMetricCard } from "/assets/js/components.js";
|
|
8
|
+
|
|
9
|
+
export function renderEvidence(container, data, t, locale, actions) {
|
|
10
|
+
const state = data.viewState;
|
|
11
|
+
const files = card({ title: t("evidence.files"), icon: "file", hint: t("evidence.filesHint") });
|
|
12
|
+
const list = h("div.file-list");
|
|
13
|
+
const viewer = h("div.file-viewer");
|
|
14
|
+
const load = button(state.artifactList ? t("evidence.reload") : t("evidence.load"), { icon: "refresh", variant: "ghost" });
|
|
15
|
+
files.querySelector(".card-head").append(h("div.card-actions", null, load));
|
|
16
|
+
let readSequence = 0;
|
|
17
|
+
async function read(path, commit) {
|
|
18
|
+
const sequence = ++readSequence;
|
|
19
|
+
viewer.dataset.reading = "true";
|
|
20
|
+
clear(viewer);
|
|
21
|
+
viewer.append(h("p.faint", null, t("evidence.reading")));
|
|
22
|
+
try {
|
|
23
|
+
const result = await actions.readArtifact(data.task.id, path, commit);
|
|
24
|
+
if (sequence !== readSequence) return;
|
|
25
|
+
state.artifact = result;
|
|
26
|
+
drawViewer();
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (sequence === readSequence) { clear(viewer); viewer.append(note(t("evidence.unavailable") + " " + error.message, "bad")); }
|
|
29
|
+
} finally { if (sequence === readSequence) viewer.dataset.reading = "false"; }
|
|
30
|
+
}
|
|
31
|
+
function drawViewer() {
|
|
32
|
+
clear(viewer);
|
|
33
|
+
const value = state.artifact;
|
|
34
|
+
if (!value) return;
|
|
35
|
+
const ref = "git:" + value.commit + ":" + value.relativePath;
|
|
36
|
+
const copy = button(t("evidence.copy"), { icon: "copy", variant: "ghost" });
|
|
37
|
+
copy.addEventListener("click", async function () {
|
|
38
|
+
try { await navigator.clipboard.writeText(data.task.id + " " + ref); copy.lastChild.textContent = t("evidence.copied"); }
|
|
39
|
+
catch { copy.lastChild.textContent = t("evidence.copyManual"); }
|
|
40
|
+
});
|
|
41
|
+
viewer.append(h("header.viewer-head", null, icon("file"), h("strong", null, value.relativePath), h("span.spacer"), copy));
|
|
42
|
+
viewer.append(h("p.viewer-ref", null, mono(ref)));
|
|
43
|
+
viewer.append(h("p.faint.small", null, t("evidence.fixedNote")));
|
|
44
|
+
const content = h("pre.code-block.artifact-text", { tabIndex: 0, "aria-label": t("evidence.fixedText") }, value.content);
|
|
45
|
+
viewer.append(content);
|
|
46
|
+
}
|
|
47
|
+
function drawList() {
|
|
48
|
+
clear(list);
|
|
49
|
+
const current = state.artifactList;
|
|
50
|
+
if (!current) return;
|
|
51
|
+
if (state.artifact && current.commit !== state.artifact.commit) list.append(note(t("evidence.differentRevision")));
|
|
52
|
+
if (!current.entries.length) { list.append(emptyState(t("evidence.noFiles"), "file")); return; }
|
|
53
|
+
current.entries.forEach(function (entry) {
|
|
54
|
+
list.append(h("button.file-row", {
|
|
55
|
+
type: "button",
|
|
56
|
+
"aria-current": String(!!state.artifact && state.artifact.relativePath === entry.relativePath && state.artifact.commit === current.commit),
|
|
57
|
+
onclick: function () { read(entry.relativePath, current.commit); }
|
|
58
|
+
}, icon("file", "icon-sm"), h("span.file-name", null, entry.relativePath), h("span.faint.small", null, formatBytes(entry.size))));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
load.addEventListener("click", async function () {
|
|
62
|
+
load.disabled = true;
|
|
63
|
+
list.dataset.reading = "true";
|
|
64
|
+
try {
|
|
65
|
+
state.artifactList = await actions.artifacts(data.task.id);
|
|
66
|
+
drawList();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
clear(list);
|
|
69
|
+
list.append(note(t("evidence.listUnavailable") + " " + error.message, "bad"));
|
|
70
|
+
} finally { load.disabled = false; list.dataset.reading = "false"; }
|
|
71
|
+
});
|
|
72
|
+
// Explicit frozen references stay attached to their original revision.
|
|
73
|
+
const sources = [data.task.completionArtifactRefs || [],
|
|
74
|
+
data.core.records.filter(function (entry) { return !entry.omitted && ["task-brief", "task-decision"].includes(entry.ref.store); })
|
|
75
|
+
.map(function (entry) { return JSON.stringify(entry.value); })].flat().join(" ");
|
|
76
|
+
const refs = Array.from(sources.matchAll(/git:([0-9a-f]{40}):([^\s"\\]+?)(?=[\s"\\]|$)/g));
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
const referenced = h("div.file-list");
|
|
79
|
+
refs.forEach(function (match) {
|
|
80
|
+
if (seen.has(match[0])) return;
|
|
81
|
+
seen.add(match[0]);
|
|
82
|
+
referenced.append(h("button.file-row", { type: "button", onclick: function () { read(match[2], match[1]); } },
|
|
83
|
+
icon("flag", "icon-sm"), h("span.file-name", null, match[2]), badge(t("evidence.referenced"), "accent")));
|
|
84
|
+
});
|
|
85
|
+
if (!state.artifactList && !seen.size) files.body.append(emptyState(t("evidence.notLoaded"), "file"));
|
|
86
|
+
files.body.append(referenced, list, viewer);
|
|
87
|
+
drawList();
|
|
88
|
+
drawViewer();
|
|
89
|
+
container.append(files);
|
|
90
|
+
|
|
91
|
+
const delivery = card({ title: t("evidence.delivery"), icon: "layers", hint: t("evidence.deliveryHint") });
|
|
92
|
+
const output = h("div.stack");
|
|
93
|
+
const open = button(t("evidence.readDelivery"), { icon: "eye", variant: "ghost" });
|
|
94
|
+
delivery.querySelector(".card-head").append(h("div.card-actions", null, open));
|
|
95
|
+
delivery.body.append(output);
|
|
96
|
+
open.addEventListener("click", function () {
|
|
97
|
+
open.disabled = true;
|
|
98
|
+
drawDelivery(output, data, t, actions).finally(function () { open.disabled = false; });
|
|
99
|
+
});
|
|
100
|
+
container.append(delivery);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function drawDelivery(output, data, t, actions) {
|
|
104
|
+
clear(output);
|
|
105
|
+
const runtime = data.runtimeStatus === "available" ? data.runtime : null;
|
|
106
|
+
if (!runtime) output.append(note(t("evidence.observationUnavailable")));
|
|
107
|
+
const remote = runtime && runtime.remoteDelivery;
|
|
108
|
+
if (remote) {
|
|
109
|
+
output.append(h("h4.sub-head", null, t("evidence.remote"), badge(t("remoteDelivery." + remote.status, remote.status), remote.status === "merged" ? "ok" : "idle")));
|
|
110
|
+
remote.projects.forEach(function (project) {
|
|
111
|
+
output.append(h("article.record", null,
|
|
112
|
+
h("header.record-head", null, h("strong", null, project.directory), h("span.faint.small", null, project.coverage)),
|
|
113
|
+
h("p.small", null, project.reason),
|
|
114
|
+
h("p.faint.small", null, t("evidence.expectedCommit") + " ", mono(project.expectedLocalCommit || t("common.unknown"))),
|
|
115
|
+
h("p.faint.small", null, "PR/MR · " + (project.state || "none") + " · " + t("evidence.verification") + " " + (project.verification || "unverified")),
|
|
116
|
+
project.adoption ? h("p.faint.small", null, t("evidence.adoption") + " ", mono(project.adoption.id)) : null));
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const original = h("div.stack", null, h("p.faint", null, t("evidence.readingOriginal")));
|
|
120
|
+
output.append(original);
|
|
121
|
+
try {
|
|
122
|
+
const evidence = await actions.evidence(data.task.id);
|
|
123
|
+
clear(original);
|
|
124
|
+
original.append(h("h4.sub-head", null, t("evidence.integrations")));
|
|
125
|
+
if (!evidence.integrations.length) original.append(note(t("evidence.noIntegrations")));
|
|
126
|
+
evidence.integrations.forEach(function (attempt) {
|
|
127
|
+
const element = h("article.record", null,
|
|
128
|
+
h("header.record-head", null, mono(attempt.id), h("span.spacer"), badge(attempt.status, attempt.status === "succeeded" ? "ok" : "idle")),
|
|
129
|
+
h("p.faint.small", null, t("evidence.candidate") + " ", mono(attempt.candidateCommit || t("evidence.noCandidate"))));
|
|
130
|
+
if (!attempt.checks || !attempt.checks.length) element.append(h("p.faint.small", null, t("evidence.noChecks")));
|
|
131
|
+
(attempt.checks || []).forEach(function (check) {
|
|
132
|
+
element.append(h("p.small", null, check.name + " · " + check.outcome));
|
|
133
|
+
if (check.details) element.append(h("p.faint.small", null, check.details));
|
|
134
|
+
});
|
|
135
|
+
original.append(element);
|
|
136
|
+
});
|
|
137
|
+
original.append(h("h4.sub-head", null, t("evidence.reviews")));
|
|
138
|
+
if (!evidence.reviews.length) original.append(note(t("evidence.noReviews")));
|
|
139
|
+
evidence.reviews.forEach(function (round) {
|
|
140
|
+
const element = h("article.record", null,
|
|
141
|
+
h("header.record-head", null, mono(round.id), h("span.spacer"), badge(round.status, round.status === "completed" ? "ok" : "idle")),
|
|
142
|
+
h("p.faint.small.mono-line", null, round.taskCandidate
|
|
143
|
+
? round.taskCandidate.projects.map(function (p) { return p.projectId + " · " + p.commit; }).join(" ")
|
|
144
|
+
: [round.workItemId, round.candidateId, round.reviewBaseCommit].filter(Boolean).join(" · ")));
|
|
145
|
+
if (round.reviewerRunId) {
|
|
146
|
+
const report = button(t("evidence.readReport"), { icon: "eye", variant: "link" });
|
|
147
|
+
report.addEventListener("click", async function () {
|
|
148
|
+
report.disabled = true;
|
|
149
|
+
try {
|
|
150
|
+
const source = await actions.inspect(data.task.id, { store: "run", refId: round.reviewerRunId });
|
|
151
|
+
element.append(richText(round.reviewerRunId, (source.value.result && source.value.result.output) || t("evidence.noReport"), t));
|
|
152
|
+
report.remove();
|
|
153
|
+
} catch (error) { element.append(note(error.message, "bad")); report.disabled = false; }
|
|
154
|
+
});
|
|
155
|
+
element.append(report);
|
|
156
|
+
}
|
|
157
|
+
original.append(element);
|
|
158
|
+
});
|
|
159
|
+
original.append(h("h4.sub-head", null, t("evidence.workspaces")));
|
|
160
|
+
if (!evidence.workspaces.length) original.append(note(t("evidence.noWorkspaces")));
|
|
161
|
+
evidence.workspaces.forEach(function (workspace) {
|
|
162
|
+
original.append(h("p.small.mono-line", null, workspace.owner.type + " · " + workspace.root));
|
|
163
|
+
});
|
|
164
|
+
original.append(note(t("evidence.cleanupNote")), h("pre.code-block", null, "yui task archive-preflight " + data.task.id + " --integrated"));
|
|
165
|
+
} catch (error) {
|
|
166
|
+
clear(original);
|
|
167
|
+
original.append(note(t("evidence.originalUnavailable") + " " + error.message, "bad"));
|
|
168
|
+
}
|
|
169
|
+
const metrics = runtime && observabilityMetricCard(runtime.observability, t);
|
|
170
|
+
if (metrics) output.append(h("h4.sub-head", null, t("evidence.usage")), metrics);
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
export const FORMAT_SCRIPT = `
|
|
2
|
-
// Date /
|
|
1
|
+
export const FORMAT_SCRIPT = String.raw `
|
|
2
|
+
// Date / number formatting shared by every view.
|
|
3
3
|
export function relativeTime(iso, locale, t) {
|
|
4
4
|
const seconds = Math.round((Date.parse(iso) - Date.now()) / 1000);
|
|
5
|
-
if (!Number.isFinite(seconds)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
["month", 2592000],
|
|
9
|
-
["day", 86400],
|
|
10
|
-
["hour", 3600],
|
|
11
|
-
["minute", 60]
|
|
12
|
-
];
|
|
5
|
+
if (!Number.isFinite(seconds)) return "—";
|
|
6
|
+
if (Math.abs(seconds) < 45) return t("time.justNow");
|
|
7
|
+
const units = [["year", 31536000], ["month", 2592000], ["day", 86400], ["hour", 3600], ["minute", 60]];
|
|
13
8
|
const selected = units.find(function (entry) { return Math.abs(seconds) >= entry[1]; }) || ["second", 1];
|
|
14
|
-
return new Intl.RelativeTimeFormat(locale, { numeric: "auto" })
|
|
15
|
-
Math.round(seconds / selected[1]),
|
|
16
|
-
selected[0]
|
|
17
|
-
);
|
|
9
|
+
return new Intl.RelativeTimeFormat(locale, { numeric: "auto", style: "short" })
|
|
10
|
+
.format(Math.round(seconds / selected[1]), selected[0]);
|
|
18
11
|
}
|
|
19
12
|
|
|
20
13
|
export function formatDateTime(iso, locale) {
|
|
@@ -22,6 +15,24 @@ export function formatDateTime(iso, locale) {
|
|
|
22
15
|
return new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short" }).format(new Date(iso));
|
|
23
16
|
}
|
|
24
17
|
|
|
18
|
+
export function formatClock(iso, locale) {
|
|
19
|
+
if (!iso || !Number.isFinite(Date.parse(iso))) return "—";
|
|
20
|
+
return new Intl.DateTimeFormat(locale, { hour: "2-digit", minute: "2-digit", second: "2-digit" }).format(new Date(iso));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatShort(iso, locale) {
|
|
24
|
+
if (!iso || !Number.isFinite(Date.parse(iso))) return "";
|
|
25
|
+
return new Intl.DateTimeFormat(locale, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" })
|
|
26
|
+
.format(new Date(iso));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function formatBytes(value) {
|
|
30
|
+
if (!Number.isFinite(value)) return "—";
|
|
31
|
+
if (value < 1024) return value + " B";
|
|
32
|
+
if (value < 1024 * 1024) return (value / 1024).toFixed(1) + " KB";
|
|
33
|
+
return (value / 1024 / 1024).toFixed(1) + " MB";
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
export function byNewest(left, right) {
|
|
26
37
|
return Date.parse(right.updatedAt || right.createdAt) - Date.parse(left.updatedAt || left.createdAt);
|
|
27
38
|
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
export const FORMS_SCRIPT = String.raw `
|
|
2
|
+
// Write forms. Each submission carries a fresh requestId, marks itself unsent
|
|
3
|
+
// until a receipt settles, and reports an unknown outcome as unknown: nothing
|
|
4
|
+
// here ever replays a write automatically.
|
|
5
|
+
import { h, icon, clear } from "/assets/js/dom.js";
|
|
6
|
+
import { button } from "/assets/js/components.js";
|
|
7
|
+
|
|
8
|
+
function receiptLine(text, state) {
|
|
9
|
+
const line = h("p.receipt", { role: "status" }, text);
|
|
10
|
+
if (state) line.dataset.state = state;
|
|
11
|
+
return line;
|
|
12
|
+
}
|
|
13
|
+
function setReceipt(line, text, state) {
|
|
14
|
+
line.textContent = text;
|
|
15
|
+
line.dataset.state = state || "";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function segmented(name, options, value, onChange) {
|
|
19
|
+
const group = h("div.seg.seg-sm", { role: "radiogroup" });
|
|
20
|
+
options.forEach(function (option) {
|
|
21
|
+
const item = h("button.seg-btn", {
|
|
22
|
+
type: "button", role: "radio", title: option.title || null,
|
|
23
|
+
"aria-checked": String(option.value === value),
|
|
24
|
+
dataset: { value: option.value },
|
|
25
|
+
onclick: function () {
|
|
26
|
+
group.querySelectorAll(".seg-btn").forEach(function (other) { other.setAttribute("aria-checked", String(other === item)); });
|
|
27
|
+
onChange(option.value);
|
|
28
|
+
}
|
|
29
|
+
}, option.label);
|
|
30
|
+
group.append(item);
|
|
31
|
+
});
|
|
32
|
+
group.dataset.name = name;
|
|
33
|
+
return group;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function messageComposer(task, t, actions) {
|
|
37
|
+
let intent = "discuss";
|
|
38
|
+
const form = h("form.composer");
|
|
39
|
+
const message = h("textarea.composer-input", {
|
|
40
|
+
required: true, maxLength: 8000, rows: 3, placeholder: t("composer.placeholder"),
|
|
41
|
+
"aria-label": t("composer.label")
|
|
42
|
+
});
|
|
43
|
+
const open = ["active", "draft"].includes(task.status);
|
|
44
|
+
const send = h("button.btn.btn-primary.composer-send", { type: "submit", disabled: !open, title: t("composer.sendHint") },
|
|
45
|
+
icon("send"), h("span", null, t("composer.send")));
|
|
46
|
+
const intents = segmented("intent", [
|
|
47
|
+
{ value: "discuss", label: t("intent.discuss"), title: t("intent.discuss.hint") },
|
|
48
|
+
{ value: "record", label: t("intent.record"), title: t("intent.record.hint") },
|
|
49
|
+
{ value: "develop", label: t("intent.develop"), title: t("intent.develop.hint") }
|
|
50
|
+
], intent, function (value) { intent = value; });
|
|
51
|
+
const receipt = receiptLine(open ? t("receipt.notSubmitted") : t("composer.closed"));
|
|
52
|
+
const facets = h("div.facets");
|
|
53
|
+
form.append(h("div.composer-box", null, message, h("div.composer-bar", null, intents, h("span.spacer"), send)), receipt, facets);
|
|
54
|
+
form.addEventListener("input", function () { form.dataset.unsent = message.value ? "true" : "false"; });
|
|
55
|
+
message.addEventListener("keydown", function (event) {
|
|
56
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) { event.preventDefault(); form.requestSubmit(); }
|
|
57
|
+
});
|
|
58
|
+
form.addEventListener("submit", async function (event) {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
if (send.disabled || !message.value.trim()) return;
|
|
61
|
+
send.disabled = true;
|
|
62
|
+
form.dataset.unsent = "true";
|
|
63
|
+
const requestId = crypto.randomUUID();
|
|
64
|
+
clear(facets);
|
|
65
|
+
setReceipt(receipt, t("receipt.waiting") + " · " + requestId, "pending");
|
|
66
|
+
try {
|
|
67
|
+
// requestId is the submission key: the same key retried returns the
|
|
68
|
+
// original Message and routing, never a second submission.
|
|
69
|
+
const result = await actions.sendMessage(task.id, message.value, requestId, intent);
|
|
70
|
+
setReceipt(receipt, t("receipt.saved") + " · " + result.record.id, "ok");
|
|
71
|
+
renderSubmissionFacets(facets, result.submission, t);
|
|
72
|
+
message.value = "";
|
|
73
|
+
form.dataset.unsent = "false";
|
|
74
|
+
send.disabled = false;
|
|
75
|
+
if (actions.afterWrite) actions.afterWrite();
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error.disposition === "not-submitted") {
|
|
78
|
+
setReceipt(receipt, t("receipt.notSubmittedBecause") + " " + error.message, "bad");
|
|
79
|
+
send.disabled = false;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
setReceipt(receipt, t("receipt.unknownMessage") + " · " + requestId, "warn");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return form;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Each submission facet is reported on its own line, never collapsed into a
|
|
89
|
+
// single "started". A missing facet block is a contract violation and is
|
|
90
|
+
// reported as such rather than reinterpreted.
|
|
91
|
+
export function renderSubmissionFacets(container, submission, t) {
|
|
92
|
+
clear(container);
|
|
93
|
+
if (!submission) { container.append(h("p.faint.small", null, t("facet.missing"))); return; }
|
|
94
|
+
const rows = [
|
|
95
|
+
["facet.phase", "phase." + submission.phase, submission.phase],
|
|
96
|
+
["facet.planning", "planningFacet." + submission.planning, submission.planning],
|
|
97
|
+
["facet.activation", "activation." + submission.activation, submission.activation],
|
|
98
|
+
["facet.delivery", "deliveryFacet." + submission.delivery, submission.delivery]
|
|
99
|
+
];
|
|
100
|
+
container.append(h("dl.kv.kv-compact", null, rows.map(function (row) {
|
|
101
|
+
return [h("dt", null, t(row[0])), h("dd", null, t(row[1], row[2]))];
|
|
102
|
+
})));
|
|
103
|
+
const step = submission.nextStep;
|
|
104
|
+
if (!step) return;
|
|
105
|
+
const text = step.kind === "activate-manually" ? t("next.activateManually").replace("{task}", step.taskId)
|
|
106
|
+
: step.kind === "start-execution" ? t("next.startExecution").replace("{task}", step.taskId)
|
|
107
|
+
: step.kind === "await-pending-activation" ? t("next.awaitActivation").replace("{ref}", step.activationRef)
|
|
108
|
+
: step.kind === "resolve-failed-activation" ? t("next.resolveFailed").replace("{failure}", step.failure).replace("{ref}", step.activationRef)
|
|
109
|
+
: "";
|
|
110
|
+
if (text) container.append(h("p.next-step", { dataset: { nextStep: step.kind } }, icon("flag", "icon-sm"), h("span", null, text)));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Queue / steer / interrupt — the same three application-layer actions the
|
|
114
|
+
// CLI drives. Steer and interrupt name an exact current Turn; a mismatch is a
|
|
115
|
+
// visible failure receipt, never a silent downgrade to a queue.
|
|
116
|
+
export function controlForm(task, t, actions, roleNames) {
|
|
117
|
+
let kind = "queue";
|
|
118
|
+
const form = h("form.stack");
|
|
119
|
+
const role = h("input", { list: "control-roles-" + task.id, placeholder: t("control.rolePlaceholder") });
|
|
120
|
+
const roles = h("datalist", { id: "control-roles-" + task.id }, (roleNames || []).map(function (name) { return h("option", { value: name }); }));
|
|
121
|
+
const body = h("textarea", { maxLength: 8000, rows: 3 });
|
|
122
|
+
const expected = h("input.mono", { placeholder: t("control.expectedPlaceholder") });
|
|
123
|
+
const workItem = h("input.mono");
|
|
124
|
+
const then = h("input.mono", { placeholder: t("control.thenPlaceholder") });
|
|
125
|
+
const field = function (text, control, hint) {
|
|
126
|
+
return h("label.field", null, h("span", null, text), control, hint ? h("small.faint", null, hint) : null);
|
|
127
|
+
};
|
|
128
|
+
const roleField = field(t("control.role"), role);
|
|
129
|
+
const bodyField = field(t("control.message"), body);
|
|
130
|
+
const expectedField = field(t("control.expectedTurn"), expected, t("control.expectedHint"));
|
|
131
|
+
const workItemField = field(t("control.workItem"), workItem);
|
|
132
|
+
const thenField = field(t("control.then"), then);
|
|
133
|
+
const open = ["active", "draft"].includes(task.status);
|
|
134
|
+
const submit = button(t("control.submit"), { type: "submit", variant: "primary", icon: "send", disabled: !open });
|
|
135
|
+
const receipt = receiptLine(t("receipt.notSubmitted"));
|
|
136
|
+
function apply() {
|
|
137
|
+
bodyField.hidden = kind === "interrupt";
|
|
138
|
+
expectedField.hidden = kind === "queue";
|
|
139
|
+
thenField.hidden = kind !== "interrupt";
|
|
140
|
+
workItemField.hidden = kind === "interrupt";
|
|
141
|
+
role.required = kind !== "queue";
|
|
142
|
+
body.required = kind !== "interrupt";
|
|
143
|
+
expected.required = kind !== "queue";
|
|
144
|
+
}
|
|
145
|
+
const actionsSeg = segmented("action", [
|
|
146
|
+
{ value: "queue", label: t("control.queue"), title: t("control.queue.hint") },
|
|
147
|
+
{ value: "steer", label: t("control.steer"), title: t("control.steer.hint") },
|
|
148
|
+
{ value: "interrupt", label: t("control.interrupt"), title: t("control.interrupt.hint") }
|
|
149
|
+
], kind, function (value) { kind = value; apply(); });
|
|
150
|
+
form.append(h("div.field", null, h("span", null, t("control.action")), actionsSeg),
|
|
151
|
+
h("div.field-row", null, roleField, workItemField), roles, bodyField, expectedField, thenField,
|
|
152
|
+
h("div.form-actions", null, submit), receipt);
|
|
153
|
+
apply();
|
|
154
|
+
form.addEventListener("input", function () { form.dataset.unsent = "true"; });
|
|
155
|
+
form.addEventListener("submit", async function (event) {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
if (submit.disabled) return;
|
|
158
|
+
submit.disabled = true;
|
|
159
|
+
form.dataset.unsent = "true";
|
|
160
|
+
const requestId = crypto.randomUUID();
|
|
161
|
+
const payload = { action: kind, requestId };
|
|
162
|
+
if (kind === "interrupt") {
|
|
163
|
+
payload.role = role.value.trim();
|
|
164
|
+
payload.expectedTarget = expected.value.trim();
|
|
165
|
+
if (then.value.trim()) payload.thenMessage = then.value.trim();
|
|
166
|
+
} else {
|
|
167
|
+
payload.body = body.value;
|
|
168
|
+
if (kind === "steer") { payload.to = role.value.trim(); payload.expectedTarget = expected.value.trim(); }
|
|
169
|
+
else if (role.value.trim()) payload.to = role.value.trim();
|
|
170
|
+
if (workItem.value.trim()) payload.workItem = workItem.value.trim();
|
|
171
|
+
}
|
|
172
|
+
setReceipt(receipt, t("receipt.waiting") + " · " + requestId, "pending");
|
|
173
|
+
try {
|
|
174
|
+
// The receipt states exactly what settled; none of these states implies
|
|
175
|
+
// the Provider executed the input.
|
|
176
|
+
const result = await actions.control(task.id, payload);
|
|
177
|
+
const settled = result.steer || result.interrupt || result.delivery || { state: result.disposition };
|
|
178
|
+
setReceipt(receipt, t("receipt.receipt") + " " + result.action + " · " + settled.state
|
|
179
|
+
+ (settled.outcome ? " · " + settled.outcome : "") + (settled.detail ? " · " + settled.detail : "")
|
|
180
|
+
+ (result.record ? " · " + result.record.id : ""), "ok");
|
|
181
|
+
form.dataset.unsent = "false";
|
|
182
|
+
submit.disabled = false;
|
|
183
|
+
if (actions.afterWrite) actions.afterWrite();
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (error.disposition === "not-submitted") {
|
|
186
|
+
setReceipt(receipt, t("receipt.notSubmittedBecause") + " " + error.message, "bad");
|
|
187
|
+
submit.disabled = false;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
setReceipt(receipt, t("receipt.unknownControl") + " · " + requestId, "warn");
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return form;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function titleForm(task, t, actions) {
|
|
197
|
+
const form = h("form.inline-form");
|
|
198
|
+
const archived = task.status === "archived";
|
|
199
|
+
const input = h("input", { value: task.title, required: true, maxLength: 500, disabled: archived, "aria-label": t("details.title") });
|
|
200
|
+
const save = button(t("actions.save"), { type: "submit", variant: "primary", disabled: archived });
|
|
201
|
+
const receipt = receiptLine(t("receipt.notSubmitted"));
|
|
202
|
+
form.append(h("div.inline-row", null, input, save), receipt);
|
|
203
|
+
form.addEventListener("input", function () { form.dataset.unsent = input.value !== task.title ? "true" : "false"; });
|
|
204
|
+
form.addEventListener("submit", async function (event) {
|
|
205
|
+
event.preventDefault();
|
|
206
|
+
if (save.disabled) return;
|
|
207
|
+
save.disabled = true;
|
|
208
|
+
form.dataset.unsent = "true";
|
|
209
|
+
const requestId = crypto.randomUUID();
|
|
210
|
+
setReceipt(receipt, t("receipt.waiting") + " · " + requestId, "pending");
|
|
211
|
+
try {
|
|
212
|
+
const result = await actions.updateTask(task.id, { title: input.value }, requestId);
|
|
213
|
+
setReceipt(receipt, t("receipt.savedRevision") + " " + result.revision, "ok");
|
|
214
|
+
input.value = result.record.title;
|
|
215
|
+
form.dataset.unsent = "false";
|
|
216
|
+
save.disabled = false;
|
|
217
|
+
if (actions.afterWrite) actions.afterWrite();
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (error.disposition === "not-submitted") {
|
|
220
|
+
setReceipt(receipt, t("receipt.notSavedBecause") + " " + error.message, "bad");
|
|
221
|
+
save.disabled = false;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// A lost response must never automatically replay a write.
|
|
225
|
+
setReceipt(receipt, t("receipt.unknownSave") + " · " + requestId, "warn");
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
return form;
|
|
229
|
+
}
|
|
230
|
+
`;
|