@zq-silk/yui 0.16.0 → 0.99.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.
Potentially problematic release.
This version of @zq-silk/yui might be problematic. Click here for more details.
- package/ARCHITECTURE.md +12 -0
- package/ARCHITECTURE.zh-CN.md +8 -0
- package/README.md +3 -0
- package/dist/agent/managedRuntimeEnvironment.js +0 -4
- package/dist/agentRun/agentRun.js +7 -17
- package/dist/cli/agentConfigurationPicker.js +24 -21
- package/dist/cli/commandCatalog.js +18 -10
- package/dist/cli/roleWizard.js +41 -30
- package/dist/cli/updateCommand.js +35 -13
- package/dist/cli/updateOrchestrator.js +73 -33
- package/dist/cli/updatePorts.js +51 -20
- package/dist/cli.js +35 -26
- package/dist/commands/controllerCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/projectCommands.js +1 -1
- package/dist/commands/releaseCommands.js +9 -38
- package/dist/commands/taskCommands.js +84 -81
- package/dist/commands/taskCompletionGate.js +0 -117
- package/dist/commands/taskRoleRuntimeStatus.js +1 -1
- package/dist/commands/taskUpstreamCommands.js +93 -54
- package/dist/context/runContextPack.js +30 -54
- package/dist/context/runInputContract.js +9 -0
- package/dist/context/sessionBootstrapManifest.js +11 -19
- package/dist/controller/agentHostObservation.js +4 -2
- package/dist/controller/controller.js +8 -6
- package/dist/controller/fileSchedulerStoreAdapter.js +85 -18
- package/dist/controller/globalInputDelivery.js +28 -7
- package/dist/controller/globalRuntimeAttention.js +34 -0
- package/dist/controller/operatorNotification.js +31 -0
- package/dist/controller/providerRetryAdmission.js +3 -1
- package/dist/controller/providerRetryDelivery.js +10 -3
- package/dist/controller/runtime.js +1 -27
- package/dist/controller/sessionOwnerReconciliation.js +42 -24
- package/dist/controller/structuredProviderObservation.js +27 -0
- package/dist/controller/updateReconciliation.js +72 -20
- package/dist/doctor/doctor.js +2 -1
- package/dist/executor/agentAdapter.js +28 -96
- package/dist/executor/agentConfigurationCatalog.js +13 -68
- package/dist/executor/agentConfigurationFields.js +120 -0
- package/dist/executor/agentConfigurationProbe.js +55 -80
- package/dist/executor/agentExecutor.js +5 -2
- package/dist/executor/executorRegistry.js +0 -9
- package/dist/executor/fileRoleLaunchPlanner.js +12 -11
- package/dist/interaction/operatorPresentation.js +2 -1
- package/dist/message/messageContinuation.js +8 -9
- package/dist/observability/executionAudit.js +1 -10
- package/dist/observability/runtimeIdentity.js +0 -23
- package/dist/output/agentConfigurationPresentation.js +4 -2
- package/dist/release/releaseHandover.js +2 -2
- package/dist/release/releaseIdempotencyStore.js +0 -23
- package/dist/release/releaseWorkflowPorts.js +8 -4
- package/dist/repository/gitWorkspace.js +66 -19
- package/dist/repository/taskWorkspacePreparer.js +75 -68
- package/dist/resources/resourceRegistry.js +90 -44
- package/dist/resources/sqliteResourceRegistry.js +3 -5
- package/dist/runtime/acpSession.js +21 -5
- package/dist/runtime/agentEndpoint.js +4 -1
- package/dist/runtime/agentHost.js +22 -62
- package/dist/runtime/agentHostCleanup.js +85 -0
- package/dist/runtime/agentHostProtocol.js +1 -1
- package/dist/runtime/builtinAgentDrivers.js +46 -119
- package/dist/runtime/builtinTranscriptObserver.js +10 -6
- package/dist/runtime/codexAppServerRuntime.js +55 -89
- package/dist/runtime/jsonLineChannel.js +35 -7
- package/dist/runtime/providerContinuationReconciliationService.js +42 -3
- package/dist/runtime/providerRetry.js +15 -0
- package/dist/runtime/providerRuntimeReconciler.js +12 -2
- package/dist/runtime/structuredProviderHost.js +106 -81
- package/dist/scheduler/activeRoleRunDelivery.js +5 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
- package/dist/storage/upgrade/upgradeOrchestrator.js +0 -10
- package/dist/task/nextAction.js +33 -23
- package/dist/task/task.js +2 -1
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +72 -3
- package/dist/web/assets/client/components.js +2 -1
- package/dist/web/assets/client/i18n.js +2 -0
- package/dist/web/assets/client/taskSummary.js +345 -0
- package/dist/web/assets/client/taskSurface.js +44 -26
- package/dist/web/assets/client/view.js +33 -0
- package/dist/web/assets/shell.js +1 -0
- package/dist/web/assets/styles/cards.js +21 -0
- package/dist/web/webServer.js +39 -5
- package/dist/web/webSessions.js +165 -0
- package/dist/web/webSnapshot.js +23 -0
- package/dist/web/webTaskSurface.js +23 -0
- package/docs/managed-turn-and-session-runtime.md +31 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +23 -0
- package/docs/observability/README.md +47 -0
- package/docs/observability/README.zh-CN.md +37 -0
- package/docs/project-refresh.md +9 -0
- package/docs/project-refresh.zh-CN.md +8 -0
- package/docs/provider-retry.md +35 -0
- package/docs/release-workflow.md +128 -20
- package/docs/release-workflow.zh-CN.md +107 -14
- package/docs/roles-and-configuration.md +30 -0
- package/docs/roles-and-configuration.zh-CN.md +20 -0
- package/docs/task-delivery.md +109 -0
- package/docs/task-delivery.zh-CN.md +80 -0
- package/docs/task-discovery.md +9 -0
- package/docs/task-discovery.zh-CN.md +6 -0
- package/docs/testing/verification-levels.md +33 -12
- package/docs/testing/verification-levels.zh-CN.md +28 -8
- package/i18n/README.zh-CN.md +2 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +11 -0
- package/skills/yui-leader/references/execution.md +22 -0
- package/skills/yui-leader/references/planning.md +6 -2
- package/skills/yui-operator/SKILL.md +45 -13
- package/skills/yui-operator/references/task-delivery.md +99 -0
- package/skills/yui-runtime/SKILL.md +5 -3
- package/skills/yui-runtime/references/publication.md +56 -5
- package/skills/yui-runtime/references/recovery.md +12 -2
- package/dist/runtime/agentHostCompatibility.js +0 -127
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const TASK_SURFACE_SCRIPT = `
|
|
2
2
|
import { node, clear } from "/assets/js/dom.js";
|
|
3
3
|
import { anchorSection, sectionHead, richText, inputCard, roleCard, runCard, pill } from "/assets/js/components.js";
|
|
4
|
+
import { renderTaskSummary, renderEvidence } from "/assets/js/task-summary.js";
|
|
4
5
|
|
|
5
6
|
// All business facts below retain their Context reference. Expanded values
|
|
6
7
|
// are current reads, not mutations of a historical Context snapshot.
|
|
@@ -16,6 +17,10 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
16
17
|
const scaffold = node("div", "detail-scaffold task-surface");
|
|
17
18
|
const summary = node("div", "section-body");
|
|
18
19
|
summary.append(node("span", "detail-kicker", task.id), node("h2", "detail-title", task.title), pill(t, "status", task.status));
|
|
20
|
+
renderTaskSummary(summary, data, t, locale, actions, recordCard);
|
|
21
|
+
const discuss = node("details", "record-card");
|
|
22
|
+
discuss.dataset.viewKey = "discussion";
|
|
23
|
+
discuss.append(node("summary", "", say("Discuss this Task", "讨论此任务")));
|
|
19
24
|
const conversation = node("button", "record-open", say("View Leader Session (read-only)", "查看 Leader Session(只读)"));
|
|
20
25
|
conversation.type = "button";
|
|
21
26
|
// A Draft's planning Session is a real Leader Session, so it is viewable once it
|
|
@@ -26,7 +31,7 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
26
31
|
const hasLeaderSession = Boolean(leaderRole?.runtimeSession?.nativeSessionId);
|
|
27
32
|
conversation.disabled = task.status === "archived" || !hasLeaderSession;
|
|
28
33
|
conversation.addEventListener("click", () => actions.openTerminal({ scope: "task", taskId: task.id, roleName: "leader" }));
|
|
29
|
-
|
|
34
|
+
discuss.append(conversation);
|
|
30
35
|
const chat = node("form", "record-card");
|
|
31
36
|
const chatLabel = node("label", "", say("Message to Task Leader", "发送给 Task Leader"));
|
|
32
37
|
const message = node("textarea", "");
|
|
@@ -90,13 +95,15 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
90
95
|
"提交结果未知。请重新加载并检查已保存消息,不要盲目重发 · ") + requestId;
|
|
91
96
|
}
|
|
92
97
|
});
|
|
93
|
-
|
|
98
|
+
discuss.append(chat);
|
|
99
|
+
summary.append(discuss);
|
|
94
100
|
// decision-3 three-action input control (queue / steer / interrupt). This is
|
|
95
101
|
// the same application-layer path the CLI drives; the Web surface adds no
|
|
96
102
|
// fourth action and no auto-fallback. Steer/interrupt name an exact current
|
|
97
103
|
// Turn (expected-target); a mismatch or missing Turn is a visible failure
|
|
98
104
|
// receipt, never a silent downgrade to a queue.
|
|
99
105
|
const control = node("details", "record-card");
|
|
106
|
+
control.dataset.viewKey = "control";
|
|
100
107
|
control.append(node("summary", "", say("Redirect a running Role (queue / steer / interrupt)",
|
|
101
108
|
"改向运行中的 Role(排队/即时插话/打断)")));
|
|
102
109
|
const controlForm = node("form", "record-block");
|
|
@@ -207,8 +214,8 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
207
214
|
// durable read and the one that drives re-rendering. The runtime observation
|
|
208
215
|
// is explicitly optional (it may time out and only ever enriches a live
|
|
209
216
|
// Session), so it must not decide whether a planning Turn is reported.
|
|
210
|
-
const turnEntries = records("
|
|
211
|
-
const planningTurns = values("
|
|
217
|
+
const turnEntries = records("run");
|
|
218
|
+
const planningTurns = values("run").filter((turn) =>
|
|
212
219
|
turn.roleName === "leader" && turn.purpose === "planning");
|
|
213
220
|
// The latest planning Turn, whatever state it reached. A completed planning
|
|
214
221
|
// Turn is still the real observed fact; reporting only active ones would call
|
|
@@ -224,19 +231,19 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
224
231
|
? planningTurn.status
|
|
225
232
|
: withheldTurns ? "withheld" : "not-dispatched";
|
|
226
233
|
planning.append(node("p", "muted", withheldTurns
|
|
227
|
-
? say("This bounded snapshot withholds
|
|
228
|
-
"当前受限快照未展开
|
|
234
|
+
? say("This bounded snapshot withholds AgentRun detail; read the original planning Run.",
|
|
235
|
+
"当前受限快照未展开 AgentRun 详情;请读取原始 planning Run。")
|
|
229
236
|
: planningTurn === null
|
|
230
|
-
? say("No planning
|
|
231
|
-
"
|
|
237
|
+
? say("No planning AgentRun is included. Direct Session activity is shown separately.",
|
|
238
|
+
"本次读取未包含 planning AgentRun;直接 Session 活动单独展示。")
|
|
232
239
|
: planningTurn.status === "active"
|
|
233
|
-
? say("
|
|
234
|
-
"planning
|
|
235
|
-
: say("Planning
|
|
240
|
+
? say("The planning AgentRun is open; this is not proof of native activity.",
|
|
241
|
+
"planning AgentRun 尚未结清;这不证明原生会话仍有活动。")
|
|
242
|
+
: say("Planning AgentRun reached " + planningTurn.status
|
|
236
243
|
+ "; messages reach the same Task Leader and queue the next Turn.",
|
|
237
|
-
"planning
|
|
244
|
+
"planning AgentRun 已" + planningTurn.status + ";消息仍送达同一 Task Leader 并为下一个 Turn 排队。")));
|
|
238
245
|
const facts = [
|
|
239
|
-
[say("Planning
|
|
246
|
+
[say("Planning AgentRun", "planning AgentRun"), planningTurn
|
|
240
247
|
? planningTurn.id + " (" + planningTurn.status + ")"
|
|
241
248
|
: withheldTurns ? say("withheld by this snapshot", "本快照未展开") : say("none", "无")],
|
|
242
249
|
[say("Provider terminal", "Provider 终态"),
|
|
@@ -251,7 +258,7 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
251
258
|
?? say("not currently running", "当前未在运行")],
|
|
252
259
|
[say("Environment", "环境"), environment?.environmentRef
|
|
253
260
|
?? say("empty (legal for planning)", "空环境(规划阶段合法)")],
|
|
254
|
-
[say("
|
|
261
|
+
[say("Run record updated", "Run 记录更新时间"),
|
|
255
262
|
planningTurn?.result?.completedAt ?? planningTurn?.updatedAt ?? say("unknown", "未知")]
|
|
256
263
|
];
|
|
257
264
|
for (const [label, value] of facts) {
|
|
@@ -259,8 +266,13 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
259
266
|
}
|
|
260
267
|
summary.append(planning);
|
|
261
268
|
}
|
|
262
|
-
if (task.description)
|
|
263
|
-
|
|
269
|
+
if (task.description) {
|
|
270
|
+
const requirements = node("details", "record-card");
|
|
271
|
+
requirements.dataset.viewKey = "requirements";
|
|
272
|
+
requirements.append(node("summary", "", say("Original requirements and boundaries", "原始要求与边界")),
|
|
273
|
+
richText(null, task.description, t));
|
|
274
|
+
summary.append(requirements);
|
|
275
|
+
}
|
|
264
276
|
const edit = node("details", "record-card");
|
|
265
277
|
edit.append(node("summary", "", say("Edit Task title", "修改任务标题")));
|
|
266
278
|
const form = node("form", "record-block");
|
|
@@ -316,6 +328,9 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
316
328
|
const value = entry.value;
|
|
317
329
|
const text = value.content || value.summary || value.body || value.objective || value.title || value.leaderSummary;
|
|
318
330
|
if (text) card.append(richText(null, text, t));
|
|
331
|
+
if (entry.ref.store === "task-decision" && value.rationale) {
|
|
332
|
+
card.append(richText(say("Reason and boundaries", "理由与边界"), value.rationale, t));
|
|
333
|
+
}
|
|
319
334
|
if (value.status) card.append(node("p", "muted", value.status));
|
|
320
335
|
if (value.roleName) card.append(node("p", "muted", value.roleName));
|
|
321
336
|
if (entry.ref.store === "work-item") {
|
|
@@ -357,7 +372,10 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
357
372
|
if (!entries.length) body.append(node("p", "muted", core.omitted.records
|
|
358
373
|
? say("Not included in this compact read", "此紧凑读取未包含该记录")
|
|
359
374
|
: say("No records", "暂无记录")));
|
|
360
|
-
|
|
375
|
+
const folded = node("details", "record-card");
|
|
376
|
+
folded.dataset.viewKey = id;
|
|
377
|
+
folded.append(node("summary", "", name + " · " + entries.length), body);
|
|
378
|
+
scaffold.append(anchorSection(id, null, folded));
|
|
361
379
|
return body;
|
|
362
380
|
}
|
|
363
381
|
const focus = section("detail-focus", say("Goal and current focus", "目标与当前关注"), records("task-brief"));
|
|
@@ -366,13 +384,10 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
366
384
|
focus.prepend(richText(t("detail.focus"), brief.currentFocus, t));
|
|
367
385
|
if (brief.boundaries.length) focus.append(richText(say("Boundaries", "边界"), brief.boundaries.join("\\n"), t));
|
|
368
386
|
}
|
|
369
|
-
const questions = node("div", "section-body");
|
|
370
|
-
records("input-request").filter((entry) => entry.omitted || entry.value.status === "open").forEach((entry) => {
|
|
371
|
-
questions.append(entry.omitted ? recordCard(entry) : inputCard(entry.value, { single: true }, t, locale, actions));
|
|
372
|
-
});
|
|
373
|
-
scaffold.append(anchorSection("detail-attention", sectionHead(t("detail.attention")), questions));
|
|
374
387
|
section("detail-work", say("Responsibilities and acceptance", "工作责任与验收"), records("work-item"));
|
|
375
|
-
|
|
388
|
+
const evidence = node("div", "section-body");
|
|
389
|
+
renderEvidence(evidence, data, t, locale, actions);
|
|
390
|
+
scaffold.append(anchorSection("detail-results", sectionHead(say("Results and evidence", "成果与证据")), evidence));
|
|
376
391
|
section("detail-messages", t("detail.messages"), records("task-message"));
|
|
377
392
|
section("detail-history", say("Decisions and milestones", "决策与里程碑"), records("task-decision").concat(records("task-milestone")));
|
|
378
393
|
section("detail-reviews", t("detail.reviews"), records("review-round"));
|
|
@@ -381,10 +396,9 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
381
396
|
if (entry.omitted) { roles.append(recordCard(entry)); return; }
|
|
382
397
|
const role = entry.value;
|
|
383
398
|
const active = values("run").find((run) => run.roleName === role.name && run.status === "active");
|
|
384
|
-
const incomplete = core.omitted.records > 0 || records("run").some((entry) => entry.omitted);
|
|
385
399
|
roles.append(roleCard({
|
|
386
400
|
...role, providerRetry: entry.providerRetry,
|
|
387
|
-
status: entry.providerRetry?.status === "waiting" ? "waiting" :
|
|
401
|
+
status: entry.providerRetry?.status === "waiting" ? "waiting" : "unknown",
|
|
388
402
|
effectiveLaunch: active ? active.effective : null,
|
|
389
403
|
launchDrift: active && active.effective.sourceDesiredRevision !== role.launchRevision
|
|
390
404
|
}, task, t, locale, actions));
|
|
@@ -392,8 +406,12 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
392
406
|
"No active AgentRun in this read; Session activity is a separate observation.",
|
|
393
407
|
"此读取中没有活跃 AgentRun;Session 活动属于独立观察。")));
|
|
394
408
|
});
|
|
395
|
-
|
|
409
|
+
const roleDetails = node("details", "record-card");
|
|
410
|
+
roleDetails.dataset.viewKey = "roles";
|
|
411
|
+
roleDetails.append(node("summary", "", t("detail.roles")), roles);
|
|
412
|
+
scaffold.append(anchorSection("detail-roles", null, roleDetails));
|
|
396
413
|
const execution = node("details", "record-card");
|
|
414
|
+
execution.dataset.viewKey = "execution";
|
|
397
415
|
execution.append(node("summary", "", say("Execution and observations", "展开执行与观察")));
|
|
398
416
|
execution.append(node("p", "muted", say(
|
|
399
417
|
"An open execution record is not proof of Agent activity. Native admission and Task progress are separate facts.",
|
|
@@ -3,6 +3,7 @@ export const VIEW_SCRIPT = `
|
|
|
3
3
|
// All reusable widgets and cards come from components.js.
|
|
4
4
|
import { clear, node } from "/assets/js/dom.js";
|
|
5
5
|
import { renderTaskSurface } from "/assets/js/task-surface.js";
|
|
6
|
+
import { renderSessionSummary } from "/assets/js/task-summary.js";
|
|
6
7
|
import { byNewest, formatDateTime } from "/assets/js/format.js";
|
|
7
8
|
import {
|
|
8
9
|
anchorSection,
|
|
@@ -162,6 +163,10 @@ export function renderOverview(detail, state, t, locale, onSelect) {
|
|
|
162
163
|
const counts = state.counts;
|
|
163
164
|
|
|
164
165
|
const total = counts ? counts.total : 0;
|
|
166
|
+
const say = (en, cn) => locale.startsWith("zh") ? cn : en;
|
|
167
|
+
wrap.append(node("p", "muted", say(
|
|
168
|
+
"Attention counts cover the authorized catalog before search/status filters; task lists below cover this page.",
|
|
169
|
+
"关注计数覆盖搜索/状态过滤前的可见目录;下方任务列表仅覆盖当前页。")));
|
|
165
170
|
const rail = node("div", "command-rail");
|
|
166
171
|
rail.append(
|
|
167
172
|
metricTile(t("metrics.active"), counts ? counts.active : "—", { hot: true }),
|
|
@@ -208,6 +213,32 @@ export function renderOverview(detail, state, t, locale, onSelect) {
|
|
|
208
213
|
}
|
|
209
214
|
wrap.append(inbox);
|
|
210
215
|
if (state.catalogScope) wrap.append(node("p", "", t("catalog.pageScope")));
|
|
216
|
+
const sessions = node("section", "overview-block");
|
|
217
|
+
sessions.append(sectionHead(say("Session activity · current page only", "会话活动 · 仅当前页")));
|
|
218
|
+
sessions.append(node("p", "muted", say(
|
|
219
|
+
"Not loaded by the compact list. Active Task / AgentRun counts cannot establish how many native Sessions are running.",
|
|
220
|
+
"紧凑列表不加载会话观察。Active Task/AgentRun 数不能说明有几个原生会话在运行。")));
|
|
221
|
+
const observe = node("button", "record-open", say("Read this page's Sessions", "读取本页会话"));
|
|
222
|
+
observe.type = "button";
|
|
223
|
+
observe.dataset.observeSessions = "";
|
|
224
|
+
observe.disabled = state.sessionLoading || !state.tasks.length;
|
|
225
|
+
sessions.append(observe);
|
|
226
|
+
if (state.sessionError) sessions.append(node("p", "muted", state.sessionError));
|
|
227
|
+
if (state.sessionOverview) {
|
|
228
|
+
sessions.append(node("p", "muted", say("Read at: ", "读取于:") + formatDateTime(state.sessionOverview.readAt, locale)));
|
|
229
|
+
for (const task of state.sessionOverview.tasks) {
|
|
230
|
+
const card = node("article", "record-card");
|
|
231
|
+
const open = node("button", "record-open", task.taskId + " · " + task.title);
|
|
232
|
+
open.type = "button";
|
|
233
|
+
open.addEventListener("click", () => onSelect(task.taskId));
|
|
234
|
+
card.append(open);
|
|
235
|
+
const body = node("div", "");
|
|
236
|
+
renderSessionSummary(body, task, locale);
|
|
237
|
+
card.append(body);
|
|
238
|
+
sessions.append(card);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
wrap.append(sessions);
|
|
211
242
|
|
|
212
243
|
// Tasks whose Task-first projection says they need attention: blocked,
|
|
213
244
|
// recovering, or in an attention state. This replaces the raw stalled-AgentRun
|
|
@@ -228,6 +259,8 @@ export function renderOverview(detail, state, t, locale, onSelect) {
|
|
|
228
259
|
task,
|
|
229
260
|
t("exec.status." + task.executionStatus),
|
|
230
261
|
"has-inputs",
|
|
262
|
+
t,
|
|
263
|
+
locale,
|
|
231
264
|
onSelect
|
|
232
265
|
));
|
|
233
266
|
});
|
package/dist/web/assets/shell.js
CHANGED
|
@@ -89,6 +89,7 @@ export const DASHBOARD_HTML = `<!doctype html>
|
|
|
89
89
|
<nav id="detail-tabs" class="detail-tabs" aria-label="Task sections" data-i18n-aria-label="tabs.label" hidden>
|
|
90
90
|
<button class="tab" type="button" data-target="detail-top" data-i18n="tabs.summary">Summary</button>
|
|
91
91
|
<button class="tab" type="button" data-target="detail-focus" data-i18n="tabs.focus">Focus</button>
|
|
92
|
+
<button class="tab" type="button" data-target="detail-results" data-i18n="tabs.results">Results</button>
|
|
92
93
|
<button class="tab" type="button" data-target="detail-work" data-i18n="tabs.work">Work items</button>
|
|
93
94
|
<button class="tab" type="button" data-target="detail-exec" data-i18n="tabs.exec">AgentRuns</button>
|
|
94
95
|
<button class="tab" type="button" data-target="detail-reviews" data-i18n="tabs.reviews">Reviews</button>
|
|
@@ -23,6 +23,27 @@ export const CARD_STYLES = `
|
|
|
23
23
|
.task-surface button { min-height:40px; }
|
|
24
24
|
.task-surface summary { cursor:pointer; padding:8px 0; font-weight:600; }
|
|
25
25
|
.task-surface [role=status] { overflow-wrap:anywhere; }
|
|
26
|
+
.summary-block h3 { margin:0 0 12px; font-size:16px; }
|
|
27
|
+
.summary-block h4 { margin:12px 0 4px; }
|
|
28
|
+
.summary-block p { overflow-wrap:anywhere; }
|
|
29
|
+
.task-summary-grid { display:grid; gap:12px; min-width:0; align-items:start; }
|
|
30
|
+
.task-summary-grid .summary-block { display:block; }
|
|
31
|
+
.task-summary-grid .summary-block p { margin:8px 0; }
|
|
32
|
+
.task-surface .record-pills { flex-wrap:wrap; max-width:100%; }
|
|
33
|
+
@media (min-width:1100px) {
|
|
34
|
+
.task-summary-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }
|
|
35
|
+
}
|
|
36
|
+
.session-counts { display:flex; flex-wrap:wrap; gap:8px; }
|
|
37
|
+
.session-count { padding:6px 10px; background:var(--bg-3); border:1px solid var(--border); border-radius:var(--radius); }
|
|
38
|
+
.session-row { padding:12px 0; border-top:1px solid var(--border); overflow-wrap:anywhere; }
|
|
39
|
+
.artifact-choice { text-align:left; white-space:normal; overflow-wrap:anywhere; }
|
|
40
|
+
.task-surface .artifact-text { max-height:32rem; padding:16px; border:1px solid var(--border); background:var(--bg-1); }
|
|
41
|
+
.task-surface .mono { overflow-wrap:anywhere; }
|
|
42
|
+
.task-surface .muted { overflow-wrap:anywhere; min-width:0; }
|
|
43
|
+
.task-surface .observability-metrics { grid-template-columns:repeat(auto-fit,minmax(min(100%,12rem),1fr)); }
|
|
44
|
+
.task-surface .observability-metrics .metric { flex-direction:column; align-items:flex-start; min-width:0; }
|
|
45
|
+
.task-surface .observability-metrics .metric-value { overflow-wrap:anywhere; max-width:100%; }
|
|
46
|
+
.task-surface button { min-height:44px; }
|
|
26
47
|
/* Overview scaffolding */
|
|
27
48
|
.overview{display:grid;gap:14px;padding-top:2px}
|
|
28
49
|
.overview-duo{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}
|
package/dist/web/webServer.js
CHANGED
|
@@ -6,7 +6,7 @@ import { parseTaskCatalogOptions } from "../context/taskCatalog.js";
|
|
|
6
6
|
import { WebRequestRejected } from "./webMutation.js";
|
|
7
7
|
import { TASK_SUBMISSION_INTENTS } from "../message/message.js";
|
|
8
8
|
import { DASHBOARD_HTML, findWebAsset } from "./assets/assetManifest.js";
|
|
9
|
-
import { buildWebTaskCatalog, buildWebTaskDetail } from "./webSnapshot.js";
|
|
9
|
+
import { buildWebTaskCatalog, buildWebPageSessions, buildWebTaskDetail } from "./webSnapshot.js";
|
|
10
10
|
const MAX_JSON_BODY_BYTES = 16 * 1024;
|
|
11
11
|
const MAX_TERMINAL_MESSAGE_BYTES = 64 * 1024;
|
|
12
12
|
const MAX_TERMINAL_BUFFERED_BYTES = 1024 * 1024;
|
|
@@ -100,6 +100,37 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
100
100
|
sendJson(response, 403, { error: "Invalid Yui web token.", disposition: "not-submitted" }, method === "HEAD");
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
|
+
const artifactTarget = /^\/api\/tasks\/([^/]+)\/(artifacts|evidence)$/.exec(pathname);
|
|
104
|
+
if (artifactTarget && dependencies.surface) {
|
|
105
|
+
if (method !== "GET") {
|
|
106
|
+
sendJson(response, 405, { error: "Artifacts are read-only." }, false);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const taskId = decodeURIComponent(artifactTarget[1]);
|
|
111
|
+
const query = new URL(request.url, "http://localhost").searchParams;
|
|
112
|
+
if (artifactTarget[2] === "evidence") {
|
|
113
|
+
if ([...query.keys()].length)
|
|
114
|
+
throw new WebRequestRejected("Evidence read takes no additional parameters.");
|
|
115
|
+
sendJson(response, 200, dependencies.surface.evidence(taskId), false);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if ([...query.keys()].some(key => !["path", "commit"].includes(key))) {
|
|
119
|
+
throw new WebRequestRejected("Artifact read accepts only path and commit.");
|
|
120
|
+
}
|
|
121
|
+
const path = query.get("path");
|
|
122
|
+
const commit = query.get("commit");
|
|
123
|
+
if ((path === null) !== (commit === null))
|
|
124
|
+
throw new WebRequestRejected("Select path and fixed commit together.");
|
|
125
|
+
const result = path === null ? await dependencies.surface.artifacts(taskId)
|
|
126
|
+
: await dependencies.surface.artifact(taskId, path, commit);
|
|
127
|
+
sendJson(response, 200, result, false);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
sendJson(response, 409, { error: error instanceof Error ? error.message : "Artifact unavailable." }, false);
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
103
134
|
const globalControl = /^\/api\/roles\/([^/]+)\/control$/.exec(pathname);
|
|
104
135
|
if (globalControl && dependencies.surface) {
|
|
105
136
|
try {
|
|
@@ -236,18 +267,21 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
236
267
|
else if (asset !== null) {
|
|
237
268
|
sendAsset(response, asset, method === "HEAD");
|
|
238
269
|
}
|
|
239
|
-
else if (pathname === "/api/dashboard") {
|
|
270
|
+
else if (pathname === "/api/dashboard" || pathname === "/api/dashboard/sessions") {
|
|
240
271
|
const query = new URL(request.url, "http://localhost").searchParams;
|
|
241
|
-
const
|
|
272
|
+
const options = parseTaskCatalogOptions([...query].flatMap(([key, value]) => key === "all"
|
|
242
273
|
? (value === "true" ? ["--all"] : ["--invalid-all"])
|
|
243
|
-
: [`--${key}`, value]))
|
|
274
|
+
: [`--${key}`, value]));
|
|
275
|
+
const snapshot = pathname.endsWith("/sessions")
|
|
276
|
+
? buildWebPageSessions(store, options, now())
|
|
277
|
+
: buildWebTaskCatalog(store, options);
|
|
244
278
|
sendJson(response, 200, snapshot, method === "HEAD");
|
|
245
279
|
}
|
|
246
280
|
else if (pathname.startsWith("/api/tasks/")) {
|
|
247
281
|
const taskId = decodeURIComponent(pathname.slice("/api/tasks/".length));
|
|
248
282
|
const detail = taskId.length === 0 || taskId.includes("/")
|
|
249
283
|
? null
|
|
250
|
-
: buildWebTaskDetail(store, taskId);
|
|
284
|
+
: buildWebTaskDetail(store, taskId, now());
|
|
251
285
|
sendJson(response, detail === null ? 404 : 200, detail ?? { error: "Task not found." }, method === "HEAD");
|
|
252
286
|
}
|
|
253
287
|
else {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
2
|
+
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
3
|
+
import { projectRuntimeTaskEvents, classifyRuntimeHealth } from "../runtime/runtimeProjection.js";
|
|
4
|
+
import { DEFAULT_RUNTIME_HEALTH_POLICY } from "../runtime/runtimeHealthPolicy.js";
|
|
5
|
+
import { providerRetryProjection } from "../runtime/providerRetry.js";
|
|
6
|
+
/** Read-only presentation of the selected native connections, never an AgentRun
|
|
7
|
+
* count. Foreground facts are fenced to the exact input; retained continuations
|
|
8
|
+
* stay attached to their original input and are not counted as extra Sessions.
|
|
9
|
+
* No live probe, timers, persistence or recovery actions are introduced here. */
|
|
10
|
+
export function projectWebSessions(input) {
|
|
11
|
+
const policy = input.policy ?? DEFAULT_RUNTIME_HEALTH_POLICY;
|
|
12
|
+
const sessions = [];
|
|
13
|
+
const seen = new Map();
|
|
14
|
+
const observations = input.events.map(event => ({ event, observation: runtimeObservationFromTaskEvent(event) }))
|
|
15
|
+
.filter(entry => entry.observation !== null);
|
|
16
|
+
for (const set of input.sessionSets) {
|
|
17
|
+
const session = set.sessions[set.activeAgentId];
|
|
18
|
+
const binding = set.providerBinding;
|
|
19
|
+
const conversation = binding?.conversations.find(c => c.epoch === binding.currentConversationEpoch && c.status === "current");
|
|
20
|
+
const nativeSessionId = session?.nativeSessionId ?? conversation?.conversationId ?? null;
|
|
21
|
+
const identity = JSON.stringify([binding?.providerNamespace ?? session?.adapterId,
|
|
22
|
+
binding?.accountScope ?? set.activeAgentId, nativeSessionId ?? set.owner.roleName]);
|
|
23
|
+
const prior = seen.get(identity);
|
|
24
|
+
if (prior !== undefined) {
|
|
25
|
+
if (sessions[prior].roleName !== set.owner.roleName)
|
|
26
|
+
sessions[prior] = {
|
|
27
|
+
...sessions[prior], group: "unknown", reason: "Multiple Roles name this native Session; inspect ownership."
|
|
28
|
+
};
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
seen.set(identity, sessions.length);
|
|
32
|
+
const turn = binding?.run;
|
|
33
|
+
const row = {
|
|
34
|
+
taskId: input.taskId, roleName: set.owner.roleName, agentId: set.activeAgentId,
|
|
35
|
+
nativeSessionId, attemptId: turn?.attemptId ?? null, nativeTurnId: turn?.nativeTurnId ?? null,
|
|
36
|
+
runId: turn?.runId ?? null, group: "unknown", reason: "Current native input is not observable.",
|
|
37
|
+
layer: null, lastActivityAt: null, semanticProgressAt: input.semanticProgressAt ?? null,
|
|
38
|
+
sourceUpdatedAt: turn?.updatedAt ?? session?.updatedAt ?? set.updatedAt,
|
|
39
|
+
waitingReason: null, operations: [], background: []
|
|
40
|
+
};
|
|
41
|
+
if (!session || !binding || !conversation || conversation.conversationId !== session.nativeSessionId) {
|
|
42
|
+
sessions.push(row);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
let driverId;
|
|
46
|
+
try {
|
|
47
|
+
driverId = builtinDriverIdForAdapter(session.adapterId);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
sessions.push(row);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const fence = { taskId: input.taskId, roleName: set.owner.roleName,
|
|
54
|
+
agentId: session.agentId, driverId, nativeSessionId: session.nativeSessionId };
|
|
55
|
+
const sessionFacts = observations.filter(({ observation }) => Object.entries(fence).every(([key, value]) => observation.fence[key] === value));
|
|
56
|
+
// Each Run's continuation fold has its own authority. Foreground operations
|
|
57
|
+
// from older inputs must not make the current input look active.
|
|
58
|
+
const groups = new Map();
|
|
59
|
+
for (const { event, observation } of sessionFacts) {
|
|
60
|
+
const runId = observation.fence.runId;
|
|
61
|
+
const group = groups.get(runId) ?? [];
|
|
62
|
+
group.push(event);
|
|
63
|
+
groups.set(runId, group);
|
|
64
|
+
}
|
|
65
|
+
const background = [...groups].flatMap(([runId, events]) => {
|
|
66
|
+
const projection = projectRuntimeTaskEvents({ ...fence, ...(runId ? { runId } : {}) }, session.createdAt, events);
|
|
67
|
+
const children = Object.entries(projection.continuations)
|
|
68
|
+
.filter(([, c]) => c.execution !== "quiescent" || c.identityConflict)
|
|
69
|
+
.map(([id, c]) => ({ id: `${runId ?? "native"}/${id}`, execution: c.execution }));
|
|
70
|
+
return [...children, ...Object.entries(projection.operations)
|
|
71
|
+
.filter(([, operation]) => operation.kind === "subagent")
|
|
72
|
+
.map(([id]) => ({ id: `${runId ?? "native"}/operation:${id}`, execution: "unsettled" }))];
|
|
73
|
+
});
|
|
74
|
+
if (!turn) {
|
|
75
|
+
sessions.push({ ...row, background, ...(background.length ? {
|
|
76
|
+
group: "background", reason: "Retained background execution is not settled."
|
|
77
|
+
} : {}) });
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const exactEvents = sessionFacts.filter(({ observation }) => {
|
|
81
|
+
const observed = observation;
|
|
82
|
+
const f = observed.fence;
|
|
83
|
+
if (f.continuationId !== undefined || f.runId !== turn.runId)
|
|
84
|
+
return false;
|
|
85
|
+
if (f.receiptId === undefined && f.nativeTurnId === undefined
|
|
86
|
+
&& ["host.observed", "session.ended", "session.failed", "observer.health"].includes(observed.kind)) {
|
|
87
|
+
return observed.receivedAt >= turn.submittedAt;
|
|
88
|
+
}
|
|
89
|
+
if (f.receiptId !== undefined && f.receiptId !== turn.attemptId)
|
|
90
|
+
return false;
|
|
91
|
+
if (f.nativeTurnId !== undefined && f.nativeTurnId !== turn.nativeTurnId)
|
|
92
|
+
return false;
|
|
93
|
+
return f.receiptId === turn.attemptId
|
|
94
|
+
|| (turn.nativeTurnId !== undefined && f.nativeTurnId === turn.nativeTurnId);
|
|
95
|
+
}).map(entry => entry.event);
|
|
96
|
+
const projection = projectRuntimeTaskEvents({
|
|
97
|
+
...fence, ...(turn.runId ? { runId: turn.runId } : {}),
|
|
98
|
+
receiptId: turn.attemptId, ...(turn.nativeTurnId ? { nativeTurnId: turn.nativeTurnId } : {})
|
|
99
|
+
}, turn.submittedAt, exactEvents);
|
|
100
|
+
const health = classifyRuntimeHealth({ projection, now: input.now, policy,
|
|
101
|
+
semanticProgressAt: input.semanticProgressAt ?? turn.submittedAt });
|
|
102
|
+
const retry = providerRetryProjection(binding);
|
|
103
|
+
let group;
|
|
104
|
+
let reason = health.reason;
|
|
105
|
+
if (turn.status === "delivery-unknown") {
|
|
106
|
+
group = "unknown";
|
|
107
|
+
reason = "Original input delivery is unknown; do not replay it.";
|
|
108
|
+
}
|
|
109
|
+
else if (session.status === "ended") {
|
|
110
|
+
group = background.length ? "background" : "stopped";
|
|
111
|
+
reason = "The selected Session ended; Task acceptance and retained background execution are separate.";
|
|
112
|
+
}
|
|
113
|
+
else if (retry?.status === "waiting") {
|
|
114
|
+
group = "waiting";
|
|
115
|
+
reason = `Controller-owned Provider retry waiting (${retry.attempts}/${retry.limit}); next eligible ${retry.nextEligibleAt}. Existing work is preserved.`;
|
|
116
|
+
}
|
|
117
|
+
else if (["failed", "cancelled", "rejected"].includes(turn.status)) {
|
|
118
|
+
group = background.length ? "background" : "stopped";
|
|
119
|
+
reason = turn.terminalReason ?? "The selected native execution stopped; Task acceptance is separate.";
|
|
120
|
+
}
|
|
121
|
+
else if (turn.status === "completed") {
|
|
122
|
+
group = background.length ? "background" : "idle";
|
|
123
|
+
reason = background.length ? "Main Turn ended; background execution remains unsettled."
|
|
124
|
+
: "The native Turn ended; this is not Task completion.";
|
|
125
|
+
}
|
|
126
|
+
else if (turn.status !== "accepted") {
|
|
127
|
+
group = "waiting";
|
|
128
|
+
reason = "Waiting for native input acceptance.";
|
|
129
|
+
}
|
|
130
|
+
else if (projection.run === "waiting") {
|
|
131
|
+
group = "waiting";
|
|
132
|
+
}
|
|
133
|
+
else if (["broken", "stopped"].includes(health.layer)) {
|
|
134
|
+
group = "stopped";
|
|
135
|
+
}
|
|
136
|
+
else if (["completed", "failed", "cancelled"].includes(projection.run)) {
|
|
137
|
+
group = "unknown";
|
|
138
|
+
reason = "Native terminal and current input binding disagree; inspect the exact original input.";
|
|
139
|
+
}
|
|
140
|
+
else if (projection.observer.status === "unavailable") {
|
|
141
|
+
group = "unknown";
|
|
142
|
+
}
|
|
143
|
+
else if (health.layer === "diagnostic-needed") {
|
|
144
|
+
group = "diagnostic";
|
|
145
|
+
}
|
|
146
|
+
else if (health.runtimeIdleMs < policy.quietAfterMs && projection.activity.kind !== "provider") {
|
|
147
|
+
group = "active";
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
group = "quiet";
|
|
151
|
+
reason = "Accepted input has no recent structured activity; silence is not a proven deadlock.";
|
|
152
|
+
}
|
|
153
|
+
sessions.push({ ...row, group, reason, layer: health.layer, background,
|
|
154
|
+
lastActivityAt: projection.lastRuntimeActivityAt ?? null,
|
|
155
|
+
waitingReason: projection.waitingReason ?? null, operations: health.activeOperations });
|
|
156
|
+
}
|
|
157
|
+
const counts = {
|
|
158
|
+
active: 0, waiting: 0, quiet: 0, diagnostic: 0, unknown: 0, stopped: 0, idle: 0, background: 0
|
|
159
|
+
};
|
|
160
|
+
for (const session of sessions)
|
|
161
|
+
counts[session.group]++;
|
|
162
|
+
return { scope: "task-selected-native-sessions", taskId: input.taskId,
|
|
163
|
+
readAt: input.now.toISOString(), counts, sessions,
|
|
164
|
+
coverage: "Recorded selected Sessions only; not a live process or historical resource inventory." };
|
|
165
|
+
}
|
package/dist/web/webSnapshot.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { providerRetryProjection } from "../runtime/providerRetry.js";
|
|
2
|
+
import { projectWebSessions } from "./webSessions.js";
|
|
2
3
|
import { readTaskCatalog } from "../context/taskCatalog.js";
|
|
3
4
|
import { isRoleRunStalled, latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
|
|
4
5
|
import { retiredTaskRecordIds } from "../task/taskRecordRetirement.js";
|
|
@@ -14,6 +15,23 @@ import { projectTaskRemoteDelivery } from "../task/remoteDelivery.js";
|
|
|
14
15
|
export function buildWebTaskCatalog(store, options) {
|
|
15
16
|
return store.transaction(reader => readTaskCatalog(reader, options));
|
|
16
17
|
}
|
|
18
|
+
/** Explicit optional read of just one visible catalog page. Never part of the
|
|
19
|
+
* compact discovery contract, and never an unfiltered global Session count. */
|
|
20
|
+
export function buildWebPageSessions(store, options, now) {
|
|
21
|
+
return store.transaction(reader => {
|
|
22
|
+
const page = readTaskCatalog(reader, options);
|
|
23
|
+
const tasks = page.tasks.map(task => ({
|
|
24
|
+
title: task.title,
|
|
25
|
+
...projectWebSessions({ taskId: task.id, sessionSets: reader.listRoleSessionSets(task.id),
|
|
26
|
+
events: reader.listEvents(task.id), now,
|
|
27
|
+
...(reader.getTaskBrief(task.id)?.updatedAt === undefined ? {} : {
|
|
28
|
+
semanticProgressAt: reader.getTaskBrief(task.id).updatedAt
|
|
29
|
+
}),
|
|
30
|
+
policy: resolveRuntimeHealth(reader.getConfig().runtimeHealth) })
|
|
31
|
+
}));
|
|
32
|
+
return { scope: "current-catalog-page", readAt: now.toISOString(), tasks };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
17
35
|
export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
18
36
|
return store.transaction((reader) => {
|
|
19
37
|
const task = reader.getTask(taskId);
|
|
@@ -90,6 +108,11 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
|
90
108
|
...(projectNames.length === 0 ? {} : { projectNames })
|
|
91
109
|
},
|
|
92
110
|
execution,
|
|
111
|
+
sessions: projectWebSessions({ taskId, sessionSets: roleSessionSets, events, now,
|
|
112
|
+
...(reader.getTaskBrief(taskId)?.updatedAt === undefined ? {} : {
|
|
113
|
+
semanticProgressAt: reader.getTaskBrief(taskId).updatedAt
|
|
114
|
+
}),
|
|
115
|
+
policy: resolveRuntimeHealth(reader.getConfig().runtimeHealth) }),
|
|
93
116
|
remoteDelivery,
|
|
94
117
|
observability: execution.observability,
|
|
95
118
|
brief: reader.getTaskBrief(taskId),
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readArtifactCapability } from "../artifacts/artifactCapability.js";
|
|
2
|
+
import { openTaskArtifactRepository } from "../artifacts/taskArtifactRepository.js";
|
|
1
3
|
import { applyGlobalInputControl } from "../commands/globalRoleCommands.js";
|
|
2
4
|
import { applyTaskInputControl, sendTaskMessageCommand, updateTaskMetadataCommand } from "../commands/taskCommands.js";
|
|
3
5
|
import { runTaskInputCommand } from "../commands/taskInputCommands.js";
|
|
@@ -37,6 +39,27 @@ export function createWebTaskSurface(store, options = {}, observations = [], hos
|
|
|
37
39
|
? { kind: "role", taskId, roleName: "leader" } : { kind: "task", taskId });
|
|
38
40
|
};
|
|
39
41
|
return {
|
|
42
|
+
evidence: (taskId) => store.transaction(reader => {
|
|
43
|
+
if (reader.getTask(taskId) === null)
|
|
44
|
+
throw new WebRequestRejected("Task not found.");
|
|
45
|
+
return { taskId, integrations: reader.listIntegrationAttempts(taskId),
|
|
46
|
+
reviews: reader.listReviewRounds(taskId), workspaces: reader.listManagedWorkspaces(taskId) };
|
|
47
|
+
}),
|
|
48
|
+
artifacts: async (taskId) => {
|
|
49
|
+
if (store.getTask(taskId) === null)
|
|
50
|
+
throw new WebRequestRejected("Task not found.");
|
|
51
|
+
const repo = openTaskArtifactRepository(store.rootDirectory(), taskId);
|
|
52
|
+
const commit = await repo.head();
|
|
53
|
+
const entries = commit === null ? [] : await repo.list(commit);
|
|
54
|
+
return { taskId, commit, entries };
|
|
55
|
+
},
|
|
56
|
+
artifact: async (taskId, relativePath, commit) => {
|
|
57
|
+
if (store.getTask(taskId) === null)
|
|
58
|
+
throw new WebRequestRejected("Task not found.");
|
|
59
|
+
// The browser always names a fixed revision. Never substitute HEAD after
|
|
60
|
+
// a missing object, and never execute/render artifact HTML as an app.
|
|
61
|
+
return readArtifactCapability(store.rootDirectory(), taskId, { relativePath, commit });
|
|
62
|
+
},
|
|
40
63
|
globalState: (roleName) => {
|
|
41
64
|
const role = store.getGlobalRole(roleName);
|
|
42
65
|
if (role === null)
|