@zq-silk/yui 2.11.0 → 2.14.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 +5 -0
- package/dist/artifacts/artifactCapability.js +14 -8
- package/dist/artifacts/artifactCommitLock.js +7 -5
- package/dist/artifacts/jobArtifact.js +93 -0
- package/dist/artifacts/jobArtifactTransport.js +13 -0
- package/dist/artifacts/taskArtifactRepository.js +58 -10
- package/dist/cli/commandCatalog.js +45 -14
- package/dist/cli/commandDiscovery.js +1 -1
- package/dist/commands/durableJobCommands.js +31 -6
- package/dist/commands/globalRoleCommands.js +5 -0
- package/dist/commands/scheduleCommands.js +14 -3
- package/dist/commands/taskCommands.js +5 -2
- package/dist/context/taskCatalog.js +6 -1
- package/dist/controlPlaneCli.js +7 -2
- package/dist/controller/agentJobControl.js +391 -0
- package/dist/controller/controller.js +24 -3
- package/dist/controller/fileSchedulerStoreAdapter.js +12 -1
- package/dist/controller/globalInputDelivery.js +8 -1
- package/dist/controller/jobArtifactControl.js +111 -0
- package/dist/controller/jobControl.js +4 -0
- package/dist/controller/jobDefinitionControl.js +191 -19
- package/dist/controller/jobSupervisor.js +83 -3
- package/dist/controller/jobTaskDelegationControl.js +193 -0
- package/dist/controller/runtime.js +9 -1
- package/dist/controller/scheduleControl.js +83 -20
- package/dist/integration/gitIntegrationService.js +2 -0
- package/dist/job/agentJob.js +131 -0
- package/dist/job/agentJobObservation.js +61 -0
- package/dist/job/agentRoleScope.js +33 -0
- package/dist/job/durableJob.js +63 -28
- package/dist/job/jobAssignmentScope.js +1 -0
- package/dist/job/jobBusinessResult.js +72 -0
- package/dist/job/jobCleanupInspection.js +4 -0
- package/dist/job/jobDefinition.js +26 -2
- package/dist/job/jobRunner.js +4 -3
- package/dist/job/projectRefreshRunner.js +54 -18
- package/dist/job/projectSync.js +162 -0
- package/dist/job/scheduleService.js +55 -0
- package/dist/job/taskDelegation.js +48 -0
- package/dist/message/messageContinuation.js +1 -1
- package/dist/resources/projectResourceService.js +1 -1
- package/dist/resources/resourceGc.js +1 -1
- package/dist/role/role.js +12 -0
- package/dist/schedule/schedule.js +65 -15
- package/dist/scheduler/wakeReason.js +4 -0
- package/dist/storage/agentJobSchema.js +10 -0
- package/dist/storage/recordValidation.js +2 -0
- package/dist/storage/sqliteSchema.js +77 -6
- package/dist/storage/sqliteStore.js +53 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +3 -0
- package/dist/storage/taskCatalog.js +10 -2
- package/dist/storage/upgrade/jobBusinessResultMigration.js +20 -0
- package/dist/storage/upgrade/scheduleDispatchMigration.js +15 -0
- package/dist/task/archivePreflight.js +15 -0
- package/dist/task/completionReadiness.js +7 -0
- package/dist/task/task.js +17 -1
- package/dist/verification/verificationGateService.js +4 -0
- package/dist/web/assets/client/app/catalog.js +16 -0
- package/dist/web/assets/client/app/settings.js +4 -0
- package/dist/web/assets/client/i18n/en.js +75 -2
- package/dist/web/assets/client/i18n/zh-CN.js +75 -2
- package/dist/web/assets/client/index.js +2 -0
- package/dist/web/assets/client/layout/manageShell.js +9 -0
- package/dist/web/assets/client/layout/router.js +1 -0
- package/dist/web/assets/client/views/jobs.js +166 -37
- package/dist/web/assets/client/views/permissions.js +129 -0
- package/dist/web/assets/client/views/task/files.js +5 -4
- package/dist/web/assets/client/views/task/page.js +5 -1
- package/dist/web/assets/shared/manageNav.js +1 -0
- package/dist/web/assets/shell/settingsContent.js +4 -0
- package/dist/web/assets/styles/views/settings.js +13 -0
- package/dist/web/taskPreviews.js +2 -2
- package/dist/web/webJobs.js +36 -11
- package/dist/web/webMaterials.js +21 -9
- package/dist/web/webPermissions.js +97 -0
- package/dist/web/webServer.js +44 -5
- package/dist/web/webTaskSurface.js +1 -0
- package/docs/authorization.md +35 -0
- package/docs/job-runs.md +392 -17
- package/docs/release-workflow.md +77 -0
- package/docs/release-workflow.zh-CN.md +57 -0
- package/docs/schedules.md +69 -15
- package/docs/storage-baseline.md +38 -1
- package/docs/storage-baseline.zh-CN.md +27 -1
- package/docs/testing/verification-levels.md +2 -2
- package/docs/testing/verification-levels.zh-CN.md +2 -2
- package/i18n/README.zh-CN.md +4 -0
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const JOBS_SCRIPT = String.raw `
|
|
2
2
|
import { h } from "/assets/js/lib/dom.js";
|
|
3
3
|
import { requestJson, submitMutation } from "/assets/js/lib/api.js";
|
|
4
|
+
import { drawArtifact } from "/assets/js/views/task/files.js";
|
|
4
5
|
|
|
5
6
|
// One view for two server-fixed scopes. Refresh only reads facts; it never
|
|
6
7
|
// admits due work, derives nextDue or replaces a selected exact Run with latest.
|
|
@@ -11,7 +12,17 @@ export function mountJobs(root, t, taskId) {
|
|
|
11
12
|
const list = h("div.jobs-list");
|
|
12
13
|
const detail = h("section.jobs-detail", { "aria-label": copy("details") });
|
|
13
14
|
const editor = h("section");
|
|
14
|
-
|
|
15
|
+
const params = new URLSearchParams(location.search);
|
|
16
|
+
let jobs = [], schedules = [], selected = params.get("job") || undefined,
|
|
17
|
+
selectedRun = params.get("jobRun") || undefined, generation = 0, runGeneration = 0, pending = false, dirty = false;
|
|
18
|
+
const artifactState = {};
|
|
19
|
+
try { artifactState.reference = JSON.parse(params.get("artifact") || "null"); } catch {}
|
|
20
|
+
function pinSelection() {
|
|
21
|
+
const url = new URL(location.href);
|
|
22
|
+
if (selected) url.searchParams.set("job", selected); else url.searchParams.delete("job");
|
|
23
|
+
if (selectedRun) url.searchParams.set("jobRun", selectedRun); else url.searchParams.delete("jobRun");
|
|
24
|
+
history.replaceState(history.state, "", url);
|
|
25
|
+
}
|
|
15
26
|
const blocked = new Set();
|
|
16
27
|
const read = (family, input) => requestJson(base + "/" + family + "?" + new URLSearchParams(input));
|
|
17
28
|
const button = (text, action, primary = false) => h("button.btn" + (primary ? ".btn-primary" : ""), {
|
|
@@ -40,6 +51,11 @@ export function mountJobs(root, t, taskId) {
|
|
|
40
51
|
status(copy("submitting"));
|
|
41
52
|
try {
|
|
42
53
|
const result = await submitMutation(key, base + "/" + family, payload);
|
|
54
|
+
if (result.status === "saved-unregistered") {
|
|
55
|
+
status(copy("registerHelp") + "\n" + result.error, true);
|
|
56
|
+
await refresh(false);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
43
59
|
status(payload.op === "run"
|
|
44
60
|
? (result.reason === "overlap" ? copy("overlap") : copy("admitted")) + " · " + result.run.id + " · " + result.run.status
|
|
45
61
|
: payload.op === "run-cancel" ? copy("cancelRequested") : copy("saved"));
|
|
@@ -73,39 +89,53 @@ export function mountJobs(root, t, taskId) {
|
|
|
73
89
|
function editJob(job) {
|
|
74
90
|
const id = input(job?.id || "", { required: true, pattern: "[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}", disabled: !!job });
|
|
75
91
|
const title = input(job?.title || "", { required: true });
|
|
92
|
+
const kind = choose(taskId ? ["shell", "task", "agent"] : ["builtin", "task", "agent"], job?.configuration.kind);
|
|
93
|
+
kind.disabled = !!job;
|
|
76
94
|
const action = choose(["system.noop", "project.refresh"], job?.configuration.action);
|
|
77
95
|
const project = input(job?.configuration.projectId || "");
|
|
78
96
|
const configuration = h("textarea", { rows: 12, required: true });
|
|
79
97
|
configuration.value = JSON.stringify(job?.configuration || { kind: "shell", owner: { kind: "task" },
|
|
80
98
|
projectId: "", head: "", workspace: "", env: {}, steps: [{ name: "check", command: "true" }] }, null, 2);
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
action.
|
|
87
|
-
|
|
99
|
+
const controlled = h("div", null, field(copy("action"), action), field(copy("project"), project), h("p", null, copy("rebind")));
|
|
100
|
+
const configured = h("div", null, h("p", null, copy("taskConfig")), field(copy("configuration"), configuration));
|
|
101
|
+
const fields = [field("ID", id), field(copy("name"), title), field(copy("executor"), kind), controlled, configured];
|
|
102
|
+
const sync = () => {
|
|
103
|
+
controlled.hidden = kind.value !== "builtin"; configured.hidden = !controlled.hidden;
|
|
104
|
+
project.disabled = controlled.hidden || action.value !== "project.refresh"; project.required = !project.disabled;
|
|
105
|
+
configuration.required = !configured.hidden;
|
|
106
|
+
};
|
|
107
|
+
kind.addEventListener("change", () => {
|
|
108
|
+
configuration.value = JSON.stringify(kind.value === "agent"
|
|
109
|
+
? { kind: "agent", target: { kind: "job", agentId: "" }, input: "", concurrency: "serial" }
|
|
110
|
+
: kind.value === "task" ? { kind: "task", goal: "delivery", target: {
|
|
111
|
+
kind: "new", title: "", description: "", projectBindings: [] } }
|
|
112
|
+
: { kind: "shell", owner: { kind: "task" }, projectId: "", head: "", workspace: "", env: {},
|
|
113
|
+
steps: [{ name: "check", command: "true" }] }, null, 2);
|
|
114
|
+
sync();
|
|
115
|
+
});
|
|
116
|
+
action.addEventListener("change", sync); sync();
|
|
88
117
|
form(job ? copy("editJob") : copy("newJob"), fields, () => {
|
|
89
|
-
const config =
|
|
118
|
+
const config = kind.value !== "builtin" ? JSON.parse(configuration.value) : {
|
|
90
119
|
kind: "builtin", action: action.value, input: {},
|
|
91
120
|
...(action.value === "project.refresh" ? { projectId: project.value.trim() } : {})
|
|
92
121
|
};
|
|
93
122
|
void mutate("jobs", { op: job ? "update" : "define", id: id.value.trim(), title: title.value.trim(),
|
|
94
123
|
configuration: config, ...(job ? { expectedRevision: job.revision } : {}) }, result => {
|
|
95
|
-
|
|
124
|
+
selected = result.id; selectedRun = undefined; pinSelection(); closeEditor();
|
|
96
125
|
});
|
|
97
126
|
});
|
|
98
127
|
}
|
|
99
128
|
function editSchedule(schedule) {
|
|
100
129
|
const id = input(schedule?.id || "", { required: true, disabled: !!schedule });
|
|
101
|
-
const jobId = choose(jobs.map(j => j.id), schedule?.jobId || selected);
|
|
102
|
-
|
|
130
|
+
const jobId = choose(jobs.filter(j => !j.builtin || j.id === schedule?.jobId).map(j => j.id), schedule?.jobId || selected);
|
|
131
|
+
jobId.disabled = !!schedule?.builtin;
|
|
132
|
+
const kind = choose(schedule?.builtin ? ["interval", "cron"] : ["once", "interval", "cron"], schedule?.rule.kind || "interval");
|
|
103
133
|
const rule = schedule?.rule;
|
|
104
134
|
const at = input(rule?.at || rule?.anchor || "", { placeholder: "2026-10-17T09:00:00+08:00" });
|
|
105
|
-
const every = input(String(rule?.everyMs || 3600000), { type: "number", min: 1000, step: 1 });
|
|
135
|
+
const every = input(String(rule?.everyMs || 3600000), { type: "number", min: schedule?.builtin ? 60000 : 1000, step: 1 });
|
|
106
136
|
const expression = input(rule?.expression || "0 9 * * *");
|
|
107
137
|
const timezone = input(rule?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC");
|
|
108
|
-
const when = field(copy("instant"), at), interval = field(copy("every"), every);
|
|
138
|
+
const when = field(copy("instant"), at), interval = field(copy(schedule?.builtin ? "syncEvery" : "every"), every);
|
|
109
139
|
const cron = field(copy("cron"), expression), zone = field(copy("timezone"), timezone);
|
|
110
140
|
const sync = () => {
|
|
111
141
|
when.hidden = kind.value === "cron"; interval.hidden = kind.value !== "interval";
|
|
@@ -118,31 +148,119 @@ export function mountJobs(root, t, taskId) {
|
|
|
118
148
|
const rule = kind.value === "once" ? { kind: "once", at: at.value }
|
|
119
149
|
: kind.value === "interval" ? { kind: "interval", anchor: at.value, everyMs: Number(every.value) }
|
|
120
150
|
: { kind: "cron", expression: expression.value, timezone: timezone.value };
|
|
121
|
-
void mutate("schedules", { op: schedule ? "update" : "create", id: id.value.trim(),
|
|
151
|
+
void mutate("schedules", { op: schedule ? "update" : "create", id: id.value.trim(), rule,
|
|
152
|
+
...(schedule?.builtin ? {} : { jobId: jobId.value, ...(!taskId ? { authorization: "service" } : {}) }),
|
|
122
153
|
...(schedule ? { expectedRevision: schedule.revision } : {}) }, closeEditor);
|
|
123
154
|
});
|
|
124
155
|
}
|
|
125
156
|
async function showRun(jobId, runId, target) {
|
|
157
|
+
const epoch = ++runGeneration;
|
|
126
158
|
selectedRun = runId;
|
|
159
|
+
pinSelection();
|
|
127
160
|
target.replaceChildren(h("p", null, copy("loading")));
|
|
128
161
|
try {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
target.replaceChildren(h("h4", null, run.id), fact(copy("state"), run.status),
|
|
162
|
+
const run = await read("jobs", { op: "run-get", id: jobId, runId });
|
|
163
|
+
if (epoch !== runGeneration || selected !== jobId || selectedRun !== runId) return;
|
|
164
|
+
const evidence = h("section"), artifacts = h("section"), session = h("section");
|
|
165
|
+
target.replaceChildren(h("div.settings-stack", null, h("h4", null, run.id), fact(copy("state"), run.status),
|
|
166
|
+
fact(copy("executor"), run.executor || (run.delegation ? "task" : "process")),
|
|
167
|
+
run.delegation ? h("a.btn", { href: "/?task=" + encodeURIComponent(run.delegation.taskId) },
|
|
168
|
+
copy("executionTask") + " · " + run.delegation.taskId) : null,
|
|
169
|
+
...(run.businessResult ? [h("div", null, fact(copy("result"), run.businessResult.summary),
|
|
170
|
+
raw(copy("result"), run.businessResult))] : []),
|
|
171
|
+
run.cancelRequestedAt ? fact(copy("cancelRequested"), run.cancelRequestedAt) : null,
|
|
133
172
|
fact(copy("trigger"), run.trigger.source + " · " + run.trigger.key),
|
|
134
173
|
fact(copy("created"), run.createdAt), fact(copy("finished"), run.terminalAt),
|
|
135
|
-
raw(copy("snapshot"), run),
|
|
136
|
-
h("h4", null, copy("logs")), logs.length ? h("div", null, logs.map(log => h("section", null,
|
|
137
|
-
h("h5", null, log.step + (log.truncated ? " · " + copy("truncated") : "")),
|
|
138
|
-
h("pre.settings-observation", null, log.text)))) : h("p", null, copy("noLogs")));
|
|
174
|
+
raw(copy("snapshot"), run), artifacts, evidence, session));
|
|
139
175
|
if (["queued", "running", "unknown-needs-attention"].includes(run.status)) target.prepend(
|
|
140
|
-
button(copy("cancelRun"), () =>
|
|
141
|
-
|
|
176
|
+
button(copy("cancelRun"), () => {
|
|
177
|
+
if (window.confirm(copy("cancelBoundary") + "\n" + runId))
|
|
178
|
+
void mutate("jobs", { op: "run-cancel", id: jobId, runId });
|
|
179
|
+
}));
|
|
180
|
+
drawArtifacts(artifacts, jobId, run, epoch);
|
|
181
|
+
if (run.executor === "agent" && run.configuration.target.kind === "job") drawSession(session, jobId, runId);
|
|
182
|
+
try {
|
|
183
|
+
const value = await read("jobs", { op: "run-evidence", id: jobId, runId });
|
|
184
|
+
if (epoch !== runGeneration) return;
|
|
185
|
+
evidence.append(raw(copy("diagnostics"), value));
|
|
186
|
+
if (run.executor !== "agent" && !run.delegation) {
|
|
187
|
+
const logs = await read("jobs", { op: "logs", id: jobId, runId });
|
|
188
|
+
if (epoch !== runGeneration) return;
|
|
189
|
+
evidence.append(h("h4", null, copy("logs")), logs.length ? h("div", null, logs.map(log => h("section", null,
|
|
190
|
+
h("h5", null, log.step + (log.truncated ? " · " + copy("truncated") : "")),
|
|
191
|
+
h("pre.settings-observation", null, log.text)))) : h("p", null, copy("noLogs")));
|
|
192
|
+
}
|
|
193
|
+
} catch (error) { if (epoch === runGeneration) evidence.append(h("p.settings-error", null, copy("diagnostics") + ": " + error.message)); }
|
|
194
|
+
} catch (error) { if (epoch === runGeneration) target.replaceChildren(h("p.settings-error", null, error.message)); }
|
|
195
|
+
}
|
|
196
|
+
function drawArtifacts(target, jobId, run, epoch) {
|
|
197
|
+
const refs = run.artifacts || [];
|
|
198
|
+
const viewer = h("div.file-viewer");
|
|
199
|
+
const readRef = async (ref, offset = 0) => {
|
|
200
|
+
const value = await read("jobs", { op: "artifact-preview", id: jobId, runId: run.id,
|
|
201
|
+
reference: JSON.stringify(ref), offset: String(offset) });
|
|
202
|
+
return { ...value, relativePath: ref.relativePath, commit: ref.version };
|
|
203
|
+
};
|
|
204
|
+
const select = async (ref, offset = 0) => {
|
|
205
|
+
artifactState.reference = ref;
|
|
206
|
+
const url = new URL(location.href);
|
|
207
|
+
url.searchParams.set("artifact", JSON.stringify(ref));
|
|
208
|
+
history.replaceState(history.state, "", url);
|
|
209
|
+
const key = JSON.stringify(ref);
|
|
210
|
+
viewer.replaceChildren(h("p", null, copy("loading")));
|
|
211
|
+
try {
|
|
212
|
+
const value = await readRef(ref, offset);
|
|
213
|
+
if (epoch !== runGeneration || JSON.stringify(artifactState.reference) !== key) return;
|
|
214
|
+
drawArtifact(viewer, value, JSON.stringify(ref.owner), t, {
|
|
215
|
+
compare: false,
|
|
216
|
+
readArtifact: async (_owner, path, commit, page) => {
|
|
217
|
+
const exact = refs.find(r => r.relativePath === path && r.version === commit
|
|
218
|
+
&& JSON.stringify(r.owner) === JSON.stringify(ref.owner));
|
|
219
|
+
if (!exact) throw new Error(copy("registeredOnly"));
|
|
220
|
+
return readRef(exact, page);
|
|
221
|
+
}
|
|
222
|
+
}, (_path, _commit, page) => void select(ref, page), artifactState);
|
|
223
|
+
} catch (error) { if (epoch === runGeneration && JSON.stringify(artifactState.reference) === key)
|
|
224
|
+
viewer.replaceChildren(h("p.settings-error", null, error.message)); }
|
|
225
|
+
};
|
|
226
|
+
target.append(h("h4", null, copy("artifacts")), h("p", null, copy("registeredOnly")),
|
|
227
|
+
...refs.map(ref => button(ref.relativePath + " · " + ref.version.slice(0, 12), () => void select(ref))), viewer);
|
|
228
|
+
if (artifactState.reference && refs.some(ref => JSON.stringify(ref) === JSON.stringify(artifactState.reference)))
|
|
229
|
+
void select(artifactState.reference);
|
|
230
|
+
const recovery = h("div");
|
|
231
|
+
target.append(button(copy("inspectSaved"), async () => {
|
|
232
|
+
recovery.replaceChildren();
|
|
233
|
+
try {
|
|
234
|
+
const result = await read("jobs", { op: "artifact-list", id: jobId, runId: run.id });
|
|
235
|
+
const missing = result.repository.saved.filter(ref => !result.artifacts.some(r => JSON.stringify(r) === JSON.stringify(ref)));
|
|
236
|
+
recovery.append(raw(copy("artifacts"), result), h("p", null, copy("registerHelp")),
|
|
237
|
+
...missing.map(ref => button(copy("register") + " · " + ref.relativePath, () => {
|
|
238
|
+
if (window.confirm(copy("registerHelp") + "\n" + ref.version))
|
|
239
|
+
void mutate("jobs", { op: "artifact-register", id: jobId, runId: run.id, reference: ref });
|
|
240
|
+
})));
|
|
241
|
+
} catch (error) { recovery.append(h("p.settings-error", null, error.message)); }
|
|
242
|
+
}), recovery);
|
|
243
|
+
}
|
|
244
|
+
function drawSession(target, jobId, runId) {
|
|
245
|
+
target.append(button(copy("inspectSession"), async () => {
|
|
246
|
+
try {
|
|
247
|
+
const value = await read("jobs", { op: "session-inspect", id: jobId, runId });
|
|
248
|
+
const set = value.sessions, current = set?.sessions?.[set.activeAgentId];
|
|
249
|
+
target.replaceChildren(raw(copy("inspectSession"), value), h("p", null, copy("sessionBoundary")));
|
|
250
|
+
if (current) target.append(...["session-stop", "session-new"].map(op =>
|
|
251
|
+
button(copy(op === "session-stop" ? "stopSession" : "newSession"), () => {
|
|
252
|
+
if (window.confirm(copy("sessionBoundary") + "\n" + current.nativeSessionId))
|
|
253
|
+
void mutate("jobs", { op, id: jobId, runId, expectedSessionId: current.nativeSessionId });
|
|
254
|
+
})));
|
|
255
|
+
} catch (error) { target.append(h("p.settings-error", null, error.message)); }
|
|
256
|
+
}));
|
|
142
257
|
}
|
|
143
258
|
async function showDetail(epoch) {
|
|
144
259
|
const job = jobs.find(j => j.id === selected);
|
|
145
|
-
if (!job) {
|
|
260
|
+
if (!job) {
|
|
261
|
+
detail.replaceChildren(...(selected ? [h("p.settings-error", null, copy("unavailable") + " · " + selected)] : []));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
146
264
|
const [runs, plans] = await Promise.all([
|
|
147
265
|
read("jobs", { op: "runs", id: job.id }),
|
|
148
266
|
Promise.all(schedules.filter(s => s.jobId === job.id).map(s => read("schedules", { op: "show", id: s.id })))
|
|
@@ -150,38 +268,43 @@ export function mountJobs(root, t, taskId) {
|
|
|
150
268
|
if (epoch !== generation) return;
|
|
151
269
|
runs.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
152
270
|
const summary = run => run ? (run.id || run.runId) + " · " + run.status + " · "
|
|
153
|
-
+ (run.terminalAt || run.finishedAt || run.createdAt || run.attemptedAt)
|
|
271
|
+
+ (run.terminalAt || run.finishedAt || run.createdAt || run.attemptedAt)
|
|
272
|
+
+ (run.summary || run.businessResult?.summary ? " · " + (run.summary || run.businessResult.summary) : "") : null;
|
|
154
273
|
const runDetail = h("section.job-run");
|
|
155
274
|
detail.replaceChildren(h("div.settings-stack", null, h("h3", null, job.title), fact("ID", job.id),
|
|
156
275
|
fact(copy("scope"), taskId || "global"), fact(copy("project"), job.configuration.projectId),
|
|
276
|
+
job.builtin ? h("p", null, copy("builtinSync")) : null,
|
|
157
277
|
fact(copy("revision"), job.revision),
|
|
278
|
+
fact(copy("executor"), job.configuration.kind),
|
|
158
279
|
fact(copy("recentRun"), summary(runs[0])),
|
|
280
|
+
fact(copy("recentSuccess"), summary(runs.find(run => run.businessResult?.outcome === "succeeded"))),
|
|
159
281
|
job.refresh ? h("div", null,
|
|
160
282
|
fact(copy("refreshAttempt"), summary(job.refresh.lastAttempt)),
|
|
161
283
|
fact(copy("refreshSuccess"), summary(job.refresh.lastSuccessfulRefresh))) : null,
|
|
162
284
|
h("div.settings-actions", null,
|
|
163
285
|
button(copy("runNow"), () => void mutate("jobs", { op: "run", id: job.id, requestId: crypto.randomUUID() }, result => {
|
|
164
|
-
selectedRun = result.run.id;
|
|
286
|
+
selectedRun = result.run.id; pinSelection();
|
|
165
287
|
}), true),
|
|
166
|
-
button(copy("editJob"), () => editJob(job)),
|
|
167
|
-
button(copy("newSchedule"), () => editSchedule())),
|
|
288
|
+
!job.builtin ? button(copy("editJob"), () => editJob(job)) : null,
|
|
289
|
+
!job.builtin && !["agent", "task"].includes(job.configuration.kind) ? button(copy("newSchedule"), () => editSchedule()) : null),
|
|
168
290
|
raw(copy("configuration"), job.configuration),
|
|
169
291
|
job.refresh ? raw(copy("refreshFacts"), job.refresh) : null,
|
|
170
292
|
h("h3", null, copy("schedules")),
|
|
171
293
|
plans.length ? h("div.settings-stack", null, plans.map(({ schedule: s, lastSuccess }) => h("article.settings-card", null,
|
|
172
294
|
h("h4", null, s.id), fact(copy("state"), s.state),
|
|
295
|
+
s.builtin ? fact(copy("automatic"), copy(s.builtin.enabled ? "enabled" : "disabled")) : null,
|
|
173
296
|
s.state === "completed" ? h("p", null, copy("onceCompleted")) : null,
|
|
174
297
|
fact(copy("rule"), s.rule.kind === "cron" ? s.rule.expression : s.rule.kind === "once" ? s.rule.at : s.rule.anchor + " + " + s.rule.everyMs + " ms"),
|
|
175
298
|
fact(copy("timezone"), s.rule.timezone || "UTC / offset"),
|
|
176
|
-
fact(copy("nextDue"), s.nextDue), fact(copy("lastAttempt"), s.lastAttempt
|
|
299
|
+
fact(copy("nextDue"), s.state === "enabled" ? s.nextDue : null), fact(copy("lastAttempt"), s.lastAttempt
|
|
177
300
|
? s.lastAttempt.at + " · " + s.lastAttempt.outcome + " · " + (s.lastAttempt.runId || s.lastAttempt.reason || "") : null),
|
|
178
301
|
fact(copy("lastSuccess"), lastSuccess ? lastSuccess.id + " · " + (lastSuccess.terminalAt || lastSuccess.updatedAt) : null),
|
|
179
302
|
s.reason ? h("p.settings-error", null, s.reason) : null,
|
|
180
303
|
h("div.settings-actions", null,
|
|
181
304
|
button(copy("editSchedule"), () => editSchedule(s)),
|
|
182
|
-
button(copy(s.state === "enabled" ? "disable" : "enable"), () => void mutate("schedules", {
|
|
183
|
-
op: s.state === "enabled" ? "disable" : "enable", id: s.id, expectedRevision: s.revision
|
|
184
|
-
})), s.lastAttempt?.runId ?
|
|
305
|
+
button(copy((s.builtin ? s.builtin.enabled : s.state === "enabled") ? "disable" : "enable"), () => void mutate("schedules", {
|
|
306
|
+
op: (s.builtin ? s.builtin.enabled : s.state === "enabled") ? "disable" : "enable", id: s.id, expectedRevision: s.revision
|
|
307
|
+
})), s.lastAttempt?.runId ? h("a.btn", { href: jobLink(taskId, s.lastAttempt.jobId || job.id, s.lastAttempt.runId) }, copy("inspectRun")) : null),
|
|
185
308
|
raw(copy("details"), s)))) : h("p", null, copy("noSchedules")),
|
|
186
309
|
h("h3", null, copy("history")),
|
|
187
310
|
runs.length ? h("div.jobs-runs", null, runs.map(run =>
|
|
@@ -201,14 +324,14 @@ export function mountJobs(root, t, taskId) {
|
|
|
201
324
|
list.replaceChildren(...(jobs.length ? jobs.map(job => h("button.job-choice", {
|
|
202
325
|
type: "button", "aria-pressed": String(selected === job.id), onclick: () => {
|
|
203
326
|
if (pending || !guardDraft()) return;
|
|
204
|
-
closeEditor(); selected = job.id; selectedRun = undefined; void refresh();
|
|
327
|
+
closeEditor(); selected = job.id; selectedRun = undefined; pinSelection(); void refresh();
|
|
205
328
|
}
|
|
206
329
|
}, h("strong", null, job.title), h("code", null, job.id),
|
|
207
|
-
h("small", null, job.configuration.action || job.configuration.kind),
|
|
330
|
+
h("small", null, job.builtin ? copy("builtinSync") : job.configuration.action || job.configuration.kind),
|
|
208
331
|
h("small", null, job.configuration.projectId || "—"))) : [h("p", null, copy("empty"))]));
|
|
209
332
|
await showDetail(epoch);
|
|
210
333
|
if (announce && epoch === generation) status(copy("observed") + " · " + new Date().toISOString());
|
|
211
|
-
} catch (error) { if (epoch === generation) status(error.message, true); }
|
|
334
|
+
} catch (error) { if (epoch === generation) status(copy("stale") + " " + error.message, true); }
|
|
212
335
|
}
|
|
213
336
|
root.classList.add("jobs-manager");
|
|
214
337
|
root.replaceChildren(h("h2", null, copy("heading")),
|
|
@@ -219,4 +342,10 @@ export function mountJobs(root, t, taskId) {
|
|
|
219
342
|
void refresh();
|
|
220
343
|
return { isDirty: () => dirty, isBusy: () => pending, refresh };
|
|
221
344
|
}
|
|
345
|
+
|
|
346
|
+
export function jobLink(taskId, jobId, runId) {
|
|
347
|
+
const query = new URLSearchParams({ job: jobId, ...(runId ? { jobRun: runId } : {}),
|
|
348
|
+
...(taskId ? { task: taskId, section: "jobs" } : {}) });
|
|
349
|
+
return (taskId ? "/" : "/settings/jobs") + "?" + query;
|
|
350
|
+
}
|
|
222
351
|
`;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export const PERMISSIONS_SCRIPT = String.raw `
|
|
2
|
+
import { h } from "/assets/js/lib/dom.js";
|
|
3
|
+
import { requestJson } from "/assets/js/lib/api.js";
|
|
4
|
+
|
|
5
|
+
export function mountPermissions(root, t) {
|
|
6
|
+
let meta, generation = 0, timer;
|
|
7
|
+
let mode = "commands", role = "", category = "", q = "", cursor = "0", previous = [];
|
|
8
|
+
const label = r => r.name ? t("permissions.configured") + ": " + r.name : t("permissions.role." + r.id);
|
|
9
|
+
let status, results, prev, next, search, roleSelect, categorySelect;
|
|
10
|
+
function reset() { cursor = "0"; previous = []; load(); }
|
|
11
|
+
function controls() {
|
|
12
|
+
const modeSelect = h("select", { onchange: () => {
|
|
13
|
+
mode = modeSelect.value; roleSelect.disabled = categorySelect.disabled = mode !== "commands"; reset();
|
|
14
|
+
} }, ["commands", "capabilities"].map(id => h("option", { value: id }, t("permissions." + id))));
|
|
15
|
+
modeSelect.value = mode;
|
|
16
|
+
search = h("input", { type: "search", value: q, maxlength: 200, oninput: () => {
|
|
17
|
+
q = search.value; clearTimeout(timer);
|
|
18
|
+
// Invalidate any older result immediately, even during debounce.
|
|
19
|
+
generation++; prev.disabled = next.disabled = true;
|
|
20
|
+
timer = setTimeout(reset, 180);
|
|
21
|
+
} });
|
|
22
|
+
roleSelect = h("select", { onchange: () => { role = roleSelect.value; reset(); } },
|
|
23
|
+
h("option", { value: "" }, t("permissions.allRoles")),
|
|
24
|
+
meta.roles.map(r => h("option", { value: r.id }, label(r))));
|
|
25
|
+
roleSelect.value = role;
|
|
26
|
+
categorySelect = h("select", { onchange: () => { category = categorySelect.value; reset(); } },
|
|
27
|
+
h("option", { value: "" }, t("permissions.allCategories")),
|
|
28
|
+
meta.categories.map(c => h("option", { value: c }, c)));
|
|
29
|
+
categorySelect.value = category;
|
|
30
|
+
roleSelect.disabled = categorySelect.disabled = mode !== "commands";
|
|
31
|
+
status = h("p", { role: "status", "aria-live": "polite" });
|
|
32
|
+
results = h("div.permissions-results");
|
|
33
|
+
prev = h("button.btn", { type: "button", disabled: true, onclick: () => { cursor = previous.pop(); load(); } }, t("permissions.previous"));
|
|
34
|
+
next = h("button.btn", { type: "button", disabled: true }, t("permissions.next"));
|
|
35
|
+
const field = (key, input) => h("label.field", null, h("span", null, t("permissions." + key)), input);
|
|
36
|
+
root.replaceChildren(
|
|
37
|
+
h("div.card.settings-card.settings-stack", null,
|
|
38
|
+
h("p", null, t("permissions.notice")),
|
|
39
|
+
h("div.settings-grid", null, field("view", modeSelect), field("search", search),
|
|
40
|
+
field("role", roleSelect), field("category", categorySelect))),
|
|
41
|
+
status, results, h("div.settings-actions", null, prev, next),
|
|
42
|
+
h("details.settings-card", null, h("summary", null, t("permissions.legend")),
|
|
43
|
+
h("p", null, t("permissions.states")),
|
|
44
|
+
h("p", null, t("permissions.boundaries")),
|
|
45
|
+
h("p", null, t("permissions.sources") + ": " + meta.sources.join(" · "))));
|
|
46
|
+
}
|
|
47
|
+
function detailContent(data) {
|
|
48
|
+
return h("div.settings-stack", null,
|
|
49
|
+
h("p", null, t("permissions.unevaluated")),
|
|
50
|
+
h("p", null, t("permissions.boundaries")),
|
|
51
|
+
data.roles.map(r => h("div", null,
|
|
52
|
+
h("h3", null, label(meta.roles.find(item => item.id === r.role))),
|
|
53
|
+
h("p", null, t("permissions.reason." + (r.visible ? "visible" : "hidden"))),
|
|
54
|
+
h("p", null, t("permissions.boundary." + r.audience)),
|
|
55
|
+
r.usage.length ? h("pre", null, r.usage.join("\n")) : null)),
|
|
56
|
+
h("small", null, t("permissions.sources") + ": " + data.source));
|
|
57
|
+
}
|
|
58
|
+
function commandTable(page, version) {
|
|
59
|
+
const shown = role ? meta.roles.filter(r => r.id === role) : meta.roles.filter(r => !r.name);
|
|
60
|
+
const body = h("tbody");
|
|
61
|
+
page.items.forEach((item, index) => {
|
|
62
|
+
const detail = h("td", { colSpan: shown.length + 1 });
|
|
63
|
+
const detailRow = h("tr", { hidden: true, id: "permission-detail-" + version + "-" + index }, detail);
|
|
64
|
+
let loaded = false;
|
|
65
|
+
const button = h("button.permissions-command", { type: "button", "aria-expanded": "false",
|
|
66
|
+
"aria-controls": detailRow.id, onclick: async () => {
|
|
67
|
+
detailRow.hidden = !detailRow.hidden;
|
|
68
|
+
button.setAttribute("aria-expanded", String(!detailRow.hidden));
|
|
69
|
+
if (detailRow.hidden || loaded) return;
|
|
70
|
+
loaded = true; detail.textContent = t("permissions.loading");
|
|
71
|
+
try {
|
|
72
|
+
const params = new URLSearchParams({ id: item.id, ...(role ? { role } : {}) });
|
|
73
|
+
const data = await requestJson("/api/permissions/command?" + params);
|
|
74
|
+
if (version !== generation) return;
|
|
75
|
+
detail.replaceChildren(detailContent(data));
|
|
76
|
+
} catch (error) { loaded = false; detail.textContent = error.message; }
|
|
77
|
+
} }, item.id);
|
|
78
|
+
body.append(h("tr", null, h("th", { scope: "row" }, button, h("small", null, item.summary)),
|
|
79
|
+
item.roles.map(r => h("td", null, h("span", { class: r.visible ? "permissions-visible" : "permissions-hidden" },
|
|
80
|
+
t("permissions." + (r.visible ? "visible" : "hidden")))))), detailRow);
|
|
81
|
+
});
|
|
82
|
+
return h("div.permissions-scroll", { tabIndex: 0, role: "region", "aria-label": t("permissions.commands") },
|
|
83
|
+
h("table.permissions-table", null,
|
|
84
|
+
h("caption", null, t("permissions.tableHint")),
|
|
85
|
+
h("thead", null, h("tr", null, h("th", { scope: "col" }, t("permissions.command")),
|
|
86
|
+
shown.map(r => h("th", { scope: "col" }, label(r))))), body));
|
|
87
|
+
}
|
|
88
|
+
async function load() {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
const version = ++generation;
|
|
91
|
+
status.textContent = t("permissions.loading");
|
|
92
|
+
results.replaceChildren(); prev.disabled = next.disabled = true;
|
|
93
|
+
const params = new URLSearchParams({ q, cursor, ...(mode === "commands" ? { role, category } : {}) });
|
|
94
|
+
try {
|
|
95
|
+
const page = await requestJson("/api/permissions/" + mode + "?" + params);
|
|
96
|
+
if (version !== generation) return;
|
|
97
|
+
status.textContent = page.items.length ? (Number(cursor) + 1) + "–" + (Number(cursor) + page.items.length) + " / " + page.total : t("permissions.empty");
|
|
98
|
+
if (mode === "commands") results.append(commandTable(page, version));
|
|
99
|
+
else results.append(h("p", null, t("permissions.capabilityNotice")), ...page.items.map(item =>
|
|
100
|
+
h("details.settings-card", null, h("summary", null, item.name),
|
|
101
|
+
h("p", null, item.summary), h("p", null, t("permissions.unevaluated")),
|
|
102
|
+
h("p", null, t("permissions.effect") + ": " + item.effect),
|
|
103
|
+
h("p", null, t("permissions.requiredPermissions") + ": " + (item.requiredPermissions.join(", ") || "—")),
|
|
104
|
+
h("p", null, t("permissions.requiredInputs") + ": " + (item.requiredInputs.join(", ") || "—")),
|
|
105
|
+
h("p", null, t("permissions.inputs") + ": " + (item.inputs.join(", ") || "—")),
|
|
106
|
+
h("small", null, t("permissions.sources") + ": " + page.source + " → " + item.source))));
|
|
107
|
+
prev.disabled = previous.length === 0;
|
|
108
|
+
next.disabled = page.nextCursor === null;
|
|
109
|
+
next.onclick = () => { previous.push(cursor); cursor = page.nextCursor; load(); };
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (version !== generation) return;
|
|
112
|
+
status.textContent = error.message;
|
|
113
|
+
results.append(h("button.btn", { type: "button", onclick: load }, t("permissions.retry")));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function init() {
|
|
117
|
+
root.replaceChildren(h("p", { role: "status" }, t("permissions.loading")));
|
|
118
|
+
try {
|
|
119
|
+
meta = await requestJson("/api/permissions");
|
|
120
|
+
controls(); load();
|
|
121
|
+
} catch (error) {
|
|
122
|
+
root.replaceChildren(h("p", { role: "status" }, error.message),
|
|
123
|
+
h("button.btn", { type: "button", onclick: init }, t("permissions.retry")));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
init();
|
|
127
|
+
return { translate() { if (meta) { controls(); load(); } } };
|
|
128
|
+
}
|
|
129
|
+
`;
|
|
@@ -74,7 +74,7 @@ function artifactViewer(viewer, data, t, actions) {
|
|
|
74
74
|
return { element: viewer, read: read, draw: draw };
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function drawArtifact(viewer, value, taskId, t, actions, read, state = {}) {
|
|
77
|
+
export function drawArtifact(viewer, value, taskId, t, actions, read, state = {}) {
|
|
78
78
|
clear(viewer);
|
|
79
79
|
if (!value) return;
|
|
80
80
|
const selected = value.after || value;
|
|
@@ -121,12 +121,13 @@ function drawArtifact(viewer, value, taskId, t, actions, read, state = {}) {
|
|
|
121
121
|
viewer.append(h("p.viewer-ref", null, mono("sha256:" + selected.digest)));
|
|
122
122
|
if (value.before) viewer.append(h("p.viewer-ref", null, mono("← git:" + value.before.commit + ":" + value.before.relativePath + " · sha256:" + value.before.digest)));
|
|
123
123
|
viewer.append(h("p.faint.small", null, t("evidence.fixedNote")));
|
|
124
|
-
viewer.append(h("div.record-actions", null, value.kind === "image" ? null : copyText, download,
|
|
124
|
+
viewer.append(h("div.record-actions", null, value.kind === "image" ? null : copyText, download,
|
|
125
|
+
actions.openConversation ? feedback : null));
|
|
125
126
|
if (value.kind === "image") { viewer.append(imagePreview(value, t, undefined, preview)); return; }
|
|
126
127
|
const before = h("input", { placeholder: t("materials.before"), "aria-label": t("materials.before"), maxLength: 64 });
|
|
127
128
|
const compare = button(t("materials.compare"), { variant: "ghost" });
|
|
128
129
|
compare.addEventListener("click", function () { read(selected.relativePath, selected.commit, 0, before.value); });
|
|
129
|
-
viewer.append(h("div.record-actions", null, before, compare));
|
|
130
|
+
if (actions.compare !== false) viewer.append(h("div.record-actions", null, before, compare));
|
|
130
131
|
const source = h("pre.code-block.artifact-text", { tabIndex: 0, "aria-label": t("evidence.fixedText") }, value.content);
|
|
131
132
|
if (!value.before && /\.md$|\.markdown$/i.test(selected.relativePath)) {
|
|
132
133
|
viewer.append(markdownPreview(value, taskId, t, actions, preview));
|
|
@@ -207,7 +208,7 @@ function markdownPreview(value, taskId, t, actions, preview) {
|
|
|
207
208
|
feedback.addEventListener("click", function () { actions.openConversation([artifactRef(value), artifactRef(image)]); });
|
|
208
209
|
slot.append(imagePreview(image, t, entry.alt, saved), h("span.viewer-ref", null,
|
|
209
210
|
path + " · git:" + image.commit + " · sha256:" + image.digest),
|
|
210
|
-
h("span.record-actions", null, download, feedback));
|
|
211
|
+
h("span.record-actions", null, download, actions.openConversation ? feedback : null));
|
|
211
212
|
}
|
|
212
213
|
async function observeRead() {
|
|
213
214
|
load.disabled = true; body.dataset.reading = "true"; status.textContent = t("evidence.reading");
|
|
@@ -12,7 +12,7 @@ import { updateObservation } from "/assets/js/views/task/observation.js";
|
|
|
12
12
|
import { renderDelivery } from "/assets/js/views/task/delivery.js";
|
|
13
13
|
import { renderRuntime } from "/assets/js/views/task/runtime.js";
|
|
14
14
|
import { renderRecords } from "/assets/js/views/task/records.js";
|
|
15
|
-
import { mountJobs } from "/assets/js/views/jobs.js";
|
|
15
|
+
import { mountJobs, jobLink } from "/assets/js/views/jobs.js";
|
|
16
16
|
|
|
17
17
|
// Overview: where it stands. Delivery: what it produced. Runtime: what is
|
|
18
18
|
// live now. Records: what happened. Retired section names stay addressable.
|
|
@@ -94,6 +94,10 @@ function dockToggle(mode, iconName, title, t, ctx) {
|
|
|
94
94
|
|
|
95
95
|
function taskMeta(task, t, locale) {
|
|
96
96
|
return h("div.task-meta", null,
|
|
97
|
+
task.source?.kind === "job-run" ? h("a.btn", {
|
|
98
|
+
href: jobLink(task.source.run.scope.kind === "task" ? task.source.run.scope.taskId : undefined,
|
|
99
|
+
task.source.jobId, task.source.run.runId)
|
|
100
|
+
}, t("jobs.sourceJob") + " · " + task.source.jobId + " / " + task.source.run.runId) : null,
|
|
97
101
|
statusBadge(t, "task", "status", task.status),
|
|
98
102
|
h("span", { dataset: { slot: "exec-badge" } }),
|
|
99
103
|
task.priority ? badge(label(t, "priority", task.priority), task.priority === "urgent" || task.priority === "high" ? "accent" : "idle") : null,
|
|
@@ -8,6 +8,7 @@ export const MANAGE_SECTIONS = Object.freeze({
|
|
|
8
8
|
{ page: "settings", section: "preferences", key: "settings.section.preferences", label: "Preferences" },
|
|
9
9
|
{ page: "settings", section: "skills", key: "settings.section.skills", label: "Skills" },
|
|
10
10
|
{ page: "settings", section: "jobs", key: "settings.section.jobs", label: "Jobs & schedules" },
|
|
11
|
+
{ page: "settings", section: "permissions", key: "settings.section.permissions", label: "Command permissions" },
|
|
11
12
|
{ page: "settings", section: "server", key: "settings.section.server", label: "Agents & server" },
|
|
12
13
|
{ page: "models", section: "connection", key: "models.section.connection", label: "Model connection" }
|
|
13
14
|
]
|
|
@@ -30,4 +30,8 @@ export const SETTINGS_MAIN = `
|
|
|
30
30
|
<label class="field settings-search"><span data-i18n="settings.search">Find a setting</span><input id="settings-search" type="search" autocomplete="off"></label>
|
|
31
31
|
<p id="settings-status" role="status"></p>
|
|
32
32
|
<div id="settings-groups" class="settings-stack"></div>
|
|
33
|
+
</section>
|
|
34
|
+
<section class="manage-section" data-section="permissions" aria-labelledby="settings-permissions-title" hidden>
|
|
35
|
+
${manageHead("settings-permissions-title", "settings.section.permissions", "Command permissions", "permissions.intro", "Read-only discovery templates, not authorization for a specific call. No rules or Grants are changed.")}
|
|
36
|
+
<section id="permission-browser" class="settings-stack"></section>
|
|
33
37
|
</section>`;
|
|
@@ -33,6 +33,19 @@ export const SETTINGS_STYLES = `
|
|
|
33
33
|
.settings-search input{width:100%;max-width:520px}
|
|
34
34
|
#settings-status:empty,#browser-receipt:empty{display:none}
|
|
35
35
|
.mono-line{overflow-wrap:anywhere}
|
|
36
|
+
.permissions-results{display:grid;gap:12px;min-width:0}
|
|
37
|
+
.permissions-scroll{overflow:auto;border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface)}
|
|
38
|
+
.permissions-table{border-collapse:collapse;width:100%;font-size:var(--fs-xs)}
|
|
39
|
+
.permissions-table caption{text-align:left;padding:14px;color:var(--ink-2)}
|
|
40
|
+
.permissions-table th,.permissions-table td{text-align:left;vertical-align:top;padding:12px;border-bottom:1px solid var(--line)}
|
|
41
|
+
.permissions-table thead th{background:var(--surface-2);min-width:100px;font-weight:600}
|
|
42
|
+
.permissions-table th[scope=row]{min-width:240px;max-width:360px;font-weight:400}
|
|
43
|
+
.permissions-table small{display:block;color:var(--ink-3);margin-top:6px;overflow-wrap:anywhere}
|
|
44
|
+
.permissions-command{font-family:var(--font-mono);color:var(--accent);background:none;border:0;padding:4px 0;cursor:pointer;text-align:left;overflow-wrap:anywhere}
|
|
45
|
+
.permissions-command:hover{text-decoration:underline}
|
|
46
|
+
.permissions-table pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:var(--fs-xs);max-width:700px}
|
|
47
|
+
.permissions-visible{color:var(--ink);white-space:nowrap}
|
|
48
|
+
.permissions-hidden{color:var(--ink-3);white-space:nowrap}
|
|
36
49
|
@media(max-width:${BREAKPOINTS.small}px){
|
|
37
50
|
.settings-grid{grid-template-columns:minmax(0,1fr)}
|
|
38
51
|
.settings-card{padding:14px}
|
package/dist/web/taskPreviews.js
CHANGED
|
@@ -25,7 +25,7 @@ export function createTaskPreviews(store, home) {
|
|
|
25
25
|
};
|
|
26
26
|
const registered = (taskId, jobId) => {
|
|
27
27
|
const job = store.getDurableJob(taskId, jobId);
|
|
28
|
-
if (!store.getTask(taskId) || !job || job.taskId !== taskId || !isHttpPreview(job.env)) {
|
|
28
|
+
if (!store.getTask(taskId) || !job || job.delegation || job.taskId !== taskId || !isHttpPreview(job.env)) {
|
|
29
29
|
throw new WebRequestRejected("Registered Task preview not found.");
|
|
30
30
|
}
|
|
31
31
|
validateHttpPreview(job.env, job.steps);
|
|
@@ -69,7 +69,7 @@ export function createTaskPreviews(store, home) {
|
|
|
69
69
|
async list(taskId) {
|
|
70
70
|
if (!store.getTask(taskId))
|
|
71
71
|
throw new WebRequestRejected("Task not found.");
|
|
72
|
-
const jobs = store.listDurableJobs(taskId).filter(job => isHttpPreview(job.env)).reverse();
|
|
72
|
+
const jobs = store.listDurableJobs(taskId).filter((job) => !job.delegation && isHttpPreview(job.env)).reverse();
|
|
73
73
|
const services = await Promise.all(jobs.slice(0, 16).map(async (job) => {
|
|
74
74
|
let current = state(job);
|
|
75
75
|
let detail = job.result?.unknownReason ?? `Job: ${job.status}`;
|