@zq-silk/yui 0.15.9 → 0.15.12
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/ARCHITECTURE.md +8 -4
- package/ARCHITECTURE.zh-CN.md +5 -2
- package/README.md +13 -5
- package/dist/agent/launchEnvironment.js +7 -0
- package/dist/agentRun/agentRun.js +3 -0
- package/dist/cli/commandCatalog.js +64 -16
- package/dist/cli/interactionPolicy.js +7 -3
- package/dist/cli/managedDiagnostics.js +1 -1
- package/dist/cli/updateOrchestrator.js +24 -1
- package/dist/cli/updatePorts.js +7 -3
- package/dist/cli/upgradeCommand.js +42 -2
- package/dist/cli.js +381 -107
- package/dist/commands/executionAuditCommands.js +10 -0
- package/dist/commands/globalRoleCommands.js +339 -4
- package/dist/commands/projectCommands.js +50 -22
- package/dist/commands/releaseCommands.js +18 -0
- package/dist/commands/taskActor.js +25 -0
- package/dist/commands/taskCommands.js +586 -96
- package/dist/commands/taskIntegrationCommands.js +19 -39
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskOverviewCommand.js +4 -3
- package/dist/commands/taskPublicationAdoptCommand.js +127 -0
- package/dist/commands/taskPublicationCommands.js +11 -2
- package/dist/commands/taskPublicationVerifyCommand.js +23 -39
- package/dist/commands/taskRemoteDeliveryCommand.js +22 -11
- package/dist/commands/taskRoleRuntimeStatus.js +35 -0
- package/dist/context/runContextPack.js +3 -0
- package/dist/context/taskCatalog.js +187 -0
- package/dist/context/taskContext.js +55 -6
- package/dist/controller/agentHostObservation.js +155 -0
- package/dist/controller/clientRuntime.js +17 -2
- package/dist/controller/controller.js +14 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +519 -25
- package/dist/controller/globalInputDelivery.js +132 -0
- package/dist/controller/jobControl.js +6 -2
- package/dist/controller/providerRetryAdmission.js +100 -0
- package/dist/controller/providerRetryDelivery.js +218 -0
- package/dist/controller/resourceInventory.js +14 -4
- package/dist/controller/resourceInventoryLinux.js +2 -6
- package/dist/controller/runtime.js +117 -7
- package/dist/controller/runtimeEventInbox.js +32 -3
- package/dist/controller/runtimeEventProcessor.js +26 -6
- package/dist/controller/runtimeHookRunFence.js +75 -19
- package/dist/controller/structuredProviderObservation.js +133 -70
- package/dist/coordination/workMailboxQueue.js +5 -0
- package/dist/execution/workItemExecutionProjection.js +1 -1
- package/dist/executor/agentExecutor.js +64 -4
- package/dist/executor/executorRegistry.js +3 -0
- package/dist/executor/fileRoleLaunchPlanner.js +78 -118
- package/dist/integration/deliveryObligation.js +2 -1
- package/dist/integration/gitIntegrationService.js +329 -386
- package/dist/integration/integrationAttempt.js +30 -4
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/integration/integrationSourceApplication.js +323 -0
- package/dist/lifecycle/exactRunTerminalization.js +4 -1
- package/dist/message/globalInterrupt.js +33 -0
- package/dist/message/globalProviderRetry.js +15 -0
- package/dist/message/inputControlResolution.js +106 -0
- package/dist/message/message.js +367 -0
- package/dist/message/messageContinuation.js +126 -3
- package/dist/message/taskInterrupt.js +34 -0
- package/dist/observability/executionAudit.js +19 -0
- package/dist/observability/orchestrationMetrics.js +1 -1
- package/dist/release/releaseHandover.js +22 -0
- package/dist/release/releaseWorkflowPorts.js +15 -7
- package/dist/repository/gitWorkspace.js +430 -107
- package/dist/repository/projectMaintenanceLock.js +75 -18
- package/dist/repository/taskWorkspaceCoordinator.js +182 -101
- package/dist/repository/taskWorkspacePreparer.js +205 -72
- package/dist/repository/workItemCandidateSnapshot.js +34 -0
- package/dist/repository/workspaceCleanupInspection.js +187 -0
- package/dist/resources/resourceDiscovery.js +3 -2
- package/dist/runtime/agentError.js +5 -3
- package/dist/runtime/agentHost.js +179 -82
- package/dist/runtime/agentHostCompatibility.js +127 -0
- package/dist/runtime/agentHostProtocol.js +53 -0
- package/dist/runtime/builtinAgentErrorMappers.js +91 -0
- package/dist/runtime/codexAppServerRuntime.js +34 -3
- package/dist/runtime/executionEnvironment.js +0 -19
- package/dist/runtime/launchBroker.js +6 -0
- package/dist/runtime/providerControl.js +5 -1
- package/dist/runtime/providerRetry.js +198 -0
- package/dist/runtime/providerRuntimeIdentity.js +28 -2
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTokenMetrics.js +15 -5
- package/dist/runtime/structuredProviderHost.js +6 -2
- package/dist/runtime/taskRuntimeIsolation.js +30 -6
- package/dist/runtime/taskUsageMetrics.js +275 -0
- package/dist/runtime/tmuxAdapters.js +5 -3
- package/dist/scheduler/activeRoleRunDelivery.js +12 -0
- package/dist/scheduler/leaderWakeupProcessor.js +5 -0
- package/dist/scheduler/operatorEvent.js +4 -0
- package/dist/scheduler/taskExecutionProjection.js +38 -6
- package/dist/scheduler/taskObservabilityProjection.js +6 -44
- package/dist/scheduler/wakeReason.js +7 -1
- package/dist/scheduler/wakeupQueue.js +2 -0
- package/dist/setup/setupCommand.js +26 -8
- package/dist/storage/homeLayout.js +130 -0
- package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
- package/dist/storage/migrations/integrationContinuation.js +104 -0
- package/dist/storage/migrations/unifyHomeLayout.js +925 -0
- package/dist/storage/sqliteSchema.js +167 -4
- package/dist/storage/sqliteStore.js +57 -1
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskCatalog.js +123 -0
- package/dist/storage/taskStore.js +2 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
- package/dist/task/archiveDiagnostics.js +129 -0
- package/dist/task/archivePreflight.js +124 -0
- package/dist/task/nextAction.js +44 -11
- package/dist/task/publicationAdoption.js +56 -0
- package/dist/task/publicationReference.js +10 -0
- package/dist/task/remoteDelivery.js +31 -16
- package/dist/web/assets/client/app.js +147 -17
- package/dist/web/assets/client/components.js +56 -13
- package/dist/web/assets/client/i18n.js +78 -4
- package/dist/web/assets/client/taskSurface.js +108 -1
- package/dist/web/assets/client/view.js +39 -8
- package/dist/web/assets/shell.js +29 -0
- package/dist/web/assets/styles/layout.js +8 -1
- package/dist/web/assets/styles/widgets.js +12 -0
- package/dist/web/webServer.js +131 -4
- package/dist/web/webSnapshot.js +16 -6
- package/dist/web/webTaskSurface.js +222 -5
- package/dist/workspace/cleanupInspection.js +63 -0
- package/dist/workspace/workItemChangeSetManager.js +111 -35
- package/docs/agent-result-consumption.md +4 -0
- package/docs/agent-result-consumption.zh-CN.md +3 -0
- package/docs/agent-runtime-drivers.md +7 -0
- package/docs/agent-runtime-drivers.zh-CN.md +5 -0
- package/docs/architecture/README.md +2 -0
- package/docs/architecture/README.zh-CN.md +3 -1
- package/docs/architecture/capabilities-and-resources.md +30 -5
- package/docs/architecture/capabilities-and-resources.zh-CN.md +23 -3
- package/docs/managed-turn-and-session-runtime.md +47 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +40 -0
- package/docs/observability/README.md +62 -0
- package/docs/observability/README.zh-CN.md +47 -0
- package/docs/project-refresh.md +77 -0
- package/docs/project-refresh.zh-CN.md +59 -0
- package/docs/provider-retry.md +70 -0
- package/docs/release-workflow.md +39 -0
- package/docs/release-workflow.zh-CN.md +29 -0
- package/docs/sqlite-control-plane-design.md +223 -1
- package/docs/task-delivery.md +133 -13
- package/docs/task-delivery.zh-CN.md +99 -10
- package/docs/task-discovery.md +102 -0
- package/docs/task-discovery.zh-CN.md +86 -0
- package/docs/testing/verification-levels.md +40 -0
- package/docs/testing/verification-levels.zh-CN.md +23 -0
- package/i18n/README.zh-CN.md +13 -7
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +154 -51
- package/skills/yui-leader/references/integration.md +52 -2
- package/skills/yui-operator/SKILL.md +19 -3
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +42 -0
- package/skills/yui-runtime/references/publication.md +42 -0
- package/skills/yui-runtime/references/recovery.md +24 -0
|
@@ -91,6 +91,112 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
summary.append(chat);
|
|
94
|
+
// decision-3 three-action input control (queue / steer / interrupt). This is
|
|
95
|
+
// the same application-layer path the CLI drives; the Web surface adds no
|
|
96
|
+
// fourth action and no auto-fallback. Steer/interrupt name an exact current
|
|
97
|
+
// Turn (expected-target); a mismatch or missing Turn is a visible failure
|
|
98
|
+
// receipt, never a silent downgrade to a queue.
|
|
99
|
+
const control = node("details", "record-card");
|
|
100
|
+
control.append(node("summary", "", say("Redirect a running Role (queue / steer / interrupt)",
|
|
101
|
+
"改向运行中的 Role(排队/即时插话/打断)")));
|
|
102
|
+
const controlForm = node("form", "record-block");
|
|
103
|
+
const actionLabel = node("label", "", say("Action", "动作"));
|
|
104
|
+
const action = node("select", "");
|
|
105
|
+
for (const [val, en, cn] of [["queue", "Queue (deliver at next opportunity)", "排队(下次机会送达)"],
|
|
106
|
+
["steer", "Steer (inject into the exact current Turn)", "即时插话(注入当前 Turn)"],
|
|
107
|
+
["interrupt", "Interrupt (native cancel of the current Turn)", "打断(原生取消当前 Turn)"]]) {
|
|
108
|
+
const opt = node("option", "", say(en, cn));
|
|
109
|
+
opt.value = val;
|
|
110
|
+
action.append(opt);
|
|
111
|
+
}
|
|
112
|
+
actionLabel.append(action);
|
|
113
|
+
const roleLabel = node("label", "", say("Target Role", "目标 Role"));
|
|
114
|
+
const role = node("input", "");
|
|
115
|
+
role.placeholder = say("e.g. worker (blank = Leader for queue)", "如 worker(排队留空 = Leader)");
|
|
116
|
+
roleLabel.append(role);
|
|
117
|
+
const bodyLabel = node("label", "", say("Message", "消息内容"));
|
|
118
|
+
const body = node("textarea", "");
|
|
119
|
+
body.maxLength = 8000;
|
|
120
|
+
bodyLabel.append(body);
|
|
121
|
+
const targetLabel = node("label", "", say("Expected current Turn", "预期当前 Turn"));
|
|
122
|
+
const expectedTarget = node("input", "");
|
|
123
|
+
expectedTarget.placeholder = say("exact current Turn id (attempt/native)", "当前 Turn 的精确标识");
|
|
124
|
+
targetLabel.append(expectedTarget);
|
|
125
|
+
const workItemLabel = node("label", "", say("Work Item (optional)", "Work Item(可选)"));
|
|
126
|
+
const workItem = node("input", "");
|
|
127
|
+
workItemLabel.append(workItem);
|
|
128
|
+
const thenLabel = node("label", "", say("Then-message ref (optional)", "打断后续消息引用(可选)"));
|
|
129
|
+
const thenMessage = node("input", "");
|
|
130
|
+
thenMessage.placeholder = say("task/message id already saved", "已保存的 task/message 引用");
|
|
131
|
+
thenLabel.append(thenMessage);
|
|
132
|
+
const controlSend = node("button", "record-open", say("Submit control", "提交控制"));
|
|
133
|
+
controlSend.type = "submit";
|
|
134
|
+
controlSend.disabled = !["active", "draft"].includes(task.status);
|
|
135
|
+
const controlSent = node("p", "muted", say("Not submitted", "未提交"));
|
|
136
|
+
controlSent.setAttribute("role", "status");
|
|
137
|
+
// Field visibility follows the selected action so the form can only submit a
|
|
138
|
+
// legal shape: interrupt carries no body; queue needs no expected-target;
|
|
139
|
+
// then-message is an interrupt-only continuation.
|
|
140
|
+
const applyAction = () => {
|
|
141
|
+
const kind = action.value;
|
|
142
|
+
bodyLabel.hidden = kind === "interrupt";
|
|
143
|
+
targetLabel.hidden = kind === "queue";
|
|
144
|
+
thenLabel.hidden = kind !== "interrupt";
|
|
145
|
+
roleLabel.hidden = false;
|
|
146
|
+
role.required = kind !== "queue";
|
|
147
|
+
body.required = kind !== "interrupt";
|
|
148
|
+
expectedTarget.required = kind !== "queue";
|
|
149
|
+
};
|
|
150
|
+
action.addEventListener("change", applyAction);
|
|
151
|
+
applyAction();
|
|
152
|
+
controlForm.addEventListener("input", () => { controlForm.dataset.unsent = "true"; });
|
|
153
|
+
controlForm.append(actionLabel, roleLabel, bodyLabel, targetLabel, workItemLabel, thenLabel, controlSend, controlSent);
|
|
154
|
+
controlForm.addEventListener("submit", async (event) => {
|
|
155
|
+
event.preventDefault();
|
|
156
|
+
if (controlSend.disabled) return;
|
|
157
|
+
controlSend.disabled = true;
|
|
158
|
+
controlForm.dataset.unsent = "true";
|
|
159
|
+
const requestId = crypto.randomUUID();
|
|
160
|
+
const kind = action.value;
|
|
161
|
+
const payload = { action: kind, requestId };
|
|
162
|
+
if (kind === "interrupt") {
|
|
163
|
+
payload.role = role.value.trim();
|
|
164
|
+
payload.expectedTarget = expectedTarget.value.trim();
|
|
165
|
+
if (thenMessage.value.trim()) payload.thenMessage = thenMessage.value.trim();
|
|
166
|
+
} else {
|
|
167
|
+
payload.body = body.value;
|
|
168
|
+
if (kind === "steer") { payload.to = role.value.trim(); payload.expectedTarget = expectedTarget.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
|
+
controlSent.textContent = say("Waiting for receipt · ", "等待回执 · ") + requestId;
|
|
173
|
+
try {
|
|
174
|
+
const receipt = await actions.controlInput(task.id, payload, requestId);
|
|
175
|
+
// The receipt states exactly what settled: a live steer/interrupt names its
|
|
176
|
+
// Host outcome; a queue or a not-delivered/idempotent result names its own
|
|
177
|
+
// state. None of these implies the Provider executed the input.
|
|
178
|
+
const settled = receipt.steer || receipt.interrupt || receipt.delivery
|
|
179
|
+
|| { state: receipt.disposition };
|
|
180
|
+
const outcome = settled.outcome ? " · outcome " + settled.outcome : "";
|
|
181
|
+
const detail = settled.detail ? " · " + settled.detail : "";
|
|
182
|
+
controlSent.textContent = say("Receipt: ", "回执:") + receipt.action + " · " + settled.state
|
|
183
|
+
+ outcome + detail + (receipt.record ? " · " + receipt.record.id : "");
|
|
184
|
+
controlForm.dataset.unsent = "false";
|
|
185
|
+
controlSend.disabled = false;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (error.disposition === "not-submitted") {
|
|
188
|
+
controlSent.textContent = say("Not submitted: ", "未提交:") + error.message;
|
|
189
|
+
controlSend.disabled = false;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// A committed input whose live edge is delivery-unknown must not be
|
|
193
|
+
// blindly re-sent under a new requestId or a different action.
|
|
194
|
+
controlSent.textContent = say("Unknown outcome. Re-read the Session before acting; do not resend · ",
|
|
195
|
+
"结果未知。请先重新读取 Session,不要盲目重发 · ") + requestId;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
control.append(controlForm);
|
|
199
|
+
summary.append(control);
|
|
94
200
|
// A Draft's Leader conversation IS its planning Turn, so this entry point does
|
|
95
201
|
// reach a Leader. Report the real planning facts the snapshot carries — Turn,
|
|
96
202
|
// Session, environment and the time they were observed — instead of asserting
|
|
@@ -277,7 +383,8 @@ export function renderTaskSurface(container, data, t, locale, actions) {
|
|
|
277
383
|
const active = values("run").find((run) => run.roleName === role.name && run.status === "active");
|
|
278
384
|
const incomplete = core.omitted.records > 0 || records("run").some((entry) => entry.omitted);
|
|
279
385
|
roles.append(roleCard({
|
|
280
|
-
...role,
|
|
386
|
+
...role, providerRetry: entry.providerRetry,
|
|
387
|
+
status: entry.providerRetry?.status === "waiting" ? "waiting" : active || incomplete ? "unknown" : "idle",
|
|
281
388
|
effectiveLaunch: active ? active.effective : null,
|
|
282
389
|
launchDrift: active && active.effective.sourceDesiredRevision !== role.launchRevision
|
|
283
390
|
}, task, t, locale, actions));
|
|
@@ -53,7 +53,7 @@ export function renderFilters(container, state, t, onFilter) {
|
|
|
53
53
|
dot.classList.add(status);
|
|
54
54
|
btn.append(dot);
|
|
55
55
|
}
|
|
56
|
-
btn.append(node("span", "filter-label", translatedStatus(t, "status", status)));
|
|
56
|
+
btn.append(node("span", "filter-label", status === "all" ? t("filter.unarchived") : translatedStatus(t, "status", status)));
|
|
57
57
|
const badge = node("span", "filter-count");
|
|
58
58
|
btn.append(badge);
|
|
59
59
|
btn.addEventListener("click", function () { onFilter(status); });
|
|
@@ -66,10 +66,17 @@ export function renderFilters(container, state, t, onFilter) {
|
|
|
66
66
|
if (!btn) return;
|
|
67
67
|
btn.classList.toggle("is-active", state.filter === status);
|
|
68
68
|
const label = btn.querySelector(".filter-label");
|
|
69
|
-
if (label) label.textContent =
|
|
70
|
-
|
|
69
|
+
if (label) label.textContent = status === "all"
|
|
70
|
+
? t(state.catalogAll ? "catalog.all" : "filter.unarchived") : translatedStatus(t, "status", status);
|
|
71
|
+
const count = status === "all"
|
|
72
|
+
? (counts.total === undefined ? undefined : counts.total - (state.catalogAll ? 0 : counts.archived || 0))
|
|
73
|
+
: counts[status];
|
|
71
74
|
const badge = btn.querySelector(".filter-count");
|
|
72
75
|
if (badge) {
|
|
76
|
+
if (status === "archived" && state.catalogScope && state.catalogScope.archived === "excluded") {
|
|
77
|
+
badge.hidden = true;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
73
80
|
if (count !== undefined && count !== null) {
|
|
74
81
|
badge.textContent = String(count);
|
|
75
82
|
badge.hidden = false;
|
|
@@ -81,7 +88,7 @@ export function renderFilters(container, state, t, onFilter) {
|
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
function taskGroupOf(task, attentionIds) {
|
|
84
|
-
if (attentionIds.has(task.id)) return "attention";
|
|
91
|
+
if (attentionIds.has(task.id) || (task.attention && (task.attention.openInputs > 0 || task.attention.unknownOperations > 0))) return "attention";
|
|
85
92
|
if (task.status === "active") return "active";
|
|
86
93
|
if (task.status === "draft") return "draft";
|
|
87
94
|
if (task.status === "cancelled") return "cancelled";
|
|
@@ -105,8 +112,9 @@ export function renderTasks(container, state, t, locale, onSelect) {
|
|
|
105
112
|
const attentionIds = new Set((state.attention || []).map(function (item) { return item.taskId; }));
|
|
106
113
|
const groups = { attention: [], active: [], draft: [], finished: [], cancelled: [], archived: [] };
|
|
107
114
|
(state.tasks || []).forEach(function (task) {
|
|
115
|
+
if (state.filter === "all" && !state.catalogAll && task.status === "archived") return;
|
|
108
116
|
if (state.filter !== "all" && task.status !== state.filter) return;
|
|
109
|
-
if (!taskMatchesQuery(task, query, locale)) return;
|
|
117
|
+
if (!state.catalogScope && !taskMatchesQuery(task, query, locale)) return;
|
|
110
118
|
groups[taskGroupOf(task, attentionIds)].push(task);
|
|
111
119
|
});
|
|
112
120
|
|
|
@@ -163,15 +171,33 @@ export function renderOverview(detail, state, t, locale, onSelect) {
|
|
|
163
171
|
);
|
|
164
172
|
wrap.append(rail);
|
|
165
173
|
|
|
174
|
+
if (state.catalogAttention) {
|
|
175
|
+
const catalog = node("section", "overview-block");
|
|
176
|
+
catalog.append(sectionHead(t("catalog.attention")));
|
|
177
|
+
catalog.append(node("p", "", t("catalog.signalsHelp")));
|
|
178
|
+
const groups = node("div", "overview-list");
|
|
179
|
+
Object.entries(state.catalogAttention).forEach(function (entry) {
|
|
180
|
+
const kind = entry[0], value = entry[1];
|
|
181
|
+
const button = node("button", "overview-row",
|
|
182
|
+
t("catalog." + kind) + " · " + value.count + " / " + value.taskCount + " " + t("catalog.tasks"));
|
|
183
|
+
button.type = "button";
|
|
184
|
+
button.dataset.catalogAttention = kind;
|
|
185
|
+
button.disabled = value.taskCount === 0;
|
|
186
|
+
groups.append(button);
|
|
187
|
+
});
|
|
188
|
+
catalog.append(groups);
|
|
189
|
+
wrap.append(catalog);
|
|
190
|
+
}
|
|
166
191
|
const inbox = node("section", "overview-block");
|
|
167
192
|
const inboxHead = sectionHead(t("overview.inbox"), {
|
|
168
|
-
count: (state.attention || []).length
|
|
193
|
+
count: counts ? counts.openInputs : (state.attention || []).length
|
|
169
194
|
});
|
|
170
195
|
inbox.append(inboxHead);
|
|
171
196
|
const items = state.attention || [];
|
|
172
197
|
if (!items.length) {
|
|
173
198
|
const empty = node("div", "inbox-empty");
|
|
174
|
-
empty.append(node("span", "dot"), node("span", "",
|
|
199
|
+
empty.append(node("span", "dot"), node("span", "", counts && counts.openInputs > 0
|
|
200
|
+
? t("catalog.inspectInputs") : t("overview.inboxEmpty")));
|
|
175
201
|
inbox.append(empty);
|
|
176
202
|
} else {
|
|
177
203
|
const list = node("div", "inbox-list");
|
|
@@ -181,6 +207,7 @@ export function renderOverview(detail, state, t, locale, onSelect) {
|
|
|
181
207
|
inbox.append(list);
|
|
182
208
|
}
|
|
183
209
|
wrap.append(inbox);
|
|
210
|
+
if (state.catalogScope) wrap.append(node("p", "", t("catalog.pageScope")));
|
|
184
211
|
|
|
185
212
|
// Tasks whose Task-first projection says they need attention: blocked,
|
|
186
213
|
// recovering, or in an attention state. This replaces the raw stalled-AgentRun
|
|
@@ -215,7 +242,7 @@ export function renderOverview(detail, state, t, locale, onSelect) {
|
|
|
215
242
|
if (activeTasks.length) {
|
|
216
243
|
duo.append(overviewBlock(t("overview.activeNow"), activeTasks, t, locale, onSelect));
|
|
217
244
|
}
|
|
218
|
-
const recentTasks = (state.tasks || []).
|
|
245
|
+
const recentTasks = (state.tasks || []).filter(function (task) { return task.status !== "archived"; }).sort(byNewest).slice(0, 8);
|
|
219
246
|
if (recentTasks.length) {
|
|
220
247
|
duo.append(overviewBlock(t("overview.recent"), recentTasks, t, locale, onSelect));
|
|
221
248
|
}
|
|
@@ -332,9 +359,13 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
|
|
|
332
359
|
"muted mono",
|
|
333
360
|
project.directory + " · " + project.coverage + " · "
|
|
334
361
|
+ (project.expectedLocalCommit ? project.expectedLocalCommit.slice(0, 12) : "unknown")
|
|
362
|
+
+ " → " + (project.deliveryLocalCommit ? project.deliveryLocalCommit.slice(0, 12) : "unknown")
|
|
335
363
|
+ " → "
|
|
336
364
|
+ (project.remoteCommit ? project.remoteCommit.slice(0, 12) : "unknown")
|
|
365
|
+
+ " · PR/MR: " + (project.state || "none")
|
|
366
|
+
+ (project.adoption ? " · " + project.adoption.id : "")
|
|
337
367
|
));
|
|
368
|
+
card.append(node("p", "record-copy", project.reason));
|
|
338
369
|
});
|
|
339
370
|
summaryBody.append(card);
|
|
340
371
|
}
|
package/dist/web/assets/shell.js
CHANGED
|
@@ -30,6 +30,7 @@ export const DASHBOARD_HTML = `<!doctype html>
|
|
|
30
30
|
</div>
|
|
31
31
|
<span class="live" title="Local loopback"><i aria-hidden="true"></i><span class="sr-only" data-i18n="brand.connection">Local loopback</span></span>
|
|
32
32
|
</div>
|
|
33
|
+
<button id="global-input-open" class="operator-open" type="button">Global inputs · 全局消息</button>
|
|
33
34
|
<label class="search">
|
|
34
35
|
<span class="sr-only" data-i18n="search.label">Search tasks</span>
|
|
35
36
|
<input id="search" type="search" placeholder="Filter by title, ID, tag, or project…" data-i18n-placeholder="search.placeholder">
|
|
@@ -37,6 +38,12 @@ export const DASHBOARD_HTML = `<!doctype html>
|
|
|
37
38
|
</label>
|
|
38
39
|
<div id="status-filters" class="filters" role="group" aria-label="Filter by status" data-i18n-aria-label="filters.label"></div>
|
|
39
40
|
<div id="task-list" class="task-list" aria-label="Tasks" data-i18n-aria-label="board.title" aria-live="polite"><div class="loading" data-i18n="loading.dashboard">Reading local state…</div></div>
|
|
41
|
+
<div id="catalog-controls" class="catalog-pager" aria-live="polite">
|
|
42
|
+
<button id="catalog-reset" type="button" data-i18n="catalog.first">First page</button>
|
|
43
|
+
<span id="catalog-count"></span>
|
|
44
|
+
<button id="catalog-next" type="button" data-i18n="catalog.next" disabled>Next page</button>
|
|
45
|
+
<button id="catalog-attention-reset" type="button" data-i18n="catalog.clearAttention" hidden>Clear attention filter</button>
|
|
46
|
+
</div>
|
|
40
47
|
<div class="sidebar-foot">
|
|
41
48
|
<div class="sidebar-controls">
|
|
42
49
|
<label class="select-control">
|
|
@@ -91,6 +98,28 @@ export const DASHBOARD_HTML = `<!doctype html>
|
|
|
91
98
|
</nav>
|
|
92
99
|
<main id="detail" class="detail" aria-labelledby="page-title" tabindex="-1"></main>
|
|
93
100
|
</div>
|
|
101
|
+
<dialog id="global-input-dialog" class="global-input-dialog" aria-labelledby="global-input-title">
|
|
102
|
+
<h2 id="global-input-title">Global Role input · 全局 Role 消息</h2>
|
|
103
|
+
<p class="muted">Queue is the default. A cancel request does not prove the Turn stopped.<br>默认排队;取消请求不代表执行已停止。</p>
|
|
104
|
+
<form id="global-input-form" class="record-block">
|
|
105
|
+
<label>Role<input id="global-input-role" value="operator" required pattern="[A-Za-z0-9_-]+"></label>
|
|
106
|
+
<label>Action · 动作<select id="global-input-action">
|
|
107
|
+
<option value="queue">Queue · 排队</option>
|
|
108
|
+
<option value="steer">Steer · 当前轮插话</option>
|
|
109
|
+
<option value="interrupt">Interrupt · 请求取消</option>
|
|
110
|
+
</select></label>
|
|
111
|
+
<label id="global-input-body-label">Message · 消息<textarea id="global-input-body" maxlength="8000" required></textarea></label>
|
|
112
|
+
<label id="global-input-target-label" hidden>Expected Turn · 精确目标<input id="global-input-target"></label>
|
|
113
|
+
<label id="global-input-then-label" hidden>Then-message ID · 后续消息引用<input id="global-input-then"></label>
|
|
114
|
+
<div class="record-actions">
|
|
115
|
+
<button id="global-input-inspect" type="button" class="record-open">Read state · 读取状态</button>
|
|
116
|
+
<button id="global-input-submit" type="submit" class="record-open">Submit · 提交</button>
|
|
117
|
+
<button id="global-input-close" type="button" class="record-open">Close · 关闭</button>
|
|
118
|
+
</div>
|
|
119
|
+
<p id="global-input-receipt" role="status" class="muted">Not submitted · 未提交</p>
|
|
120
|
+
<pre id="global-input-state" class="global-input-state"></pre>
|
|
121
|
+
</form>
|
|
122
|
+
</dialog>
|
|
94
123
|
<aside id="terminal-panel" class="terminal-panel" aria-labelledby="terminal-title" aria-hidden="true" hidden>
|
|
95
124
|
<header class="terminal-head">
|
|
96
125
|
<div>
|
|
@@ -9,7 +9,7 @@ body{margin:0;min-width:320px;height:100vh;overflow:hidden;font-size:14px}
|
|
|
9
9
|
.app-shell{display:grid;grid-template-columns:var(--sidebar-w) minmax(0,1fr) 0;grid-template-rows:100vh;height:100vh;transition:grid-template-columns var(--motion-slow) var(--ease)}
|
|
10
10
|
body.terminal-active .app-shell{grid-template-columns:var(--sidebar-w) minmax(0,1fr) var(--terminal-w)}
|
|
11
11
|
/* Sidebar = persistent work index */
|
|
12
|
-
.sidebar{display:flex;flex-direction:column;min-height:0;height:100vh;padding:16px 14px 14px}
|
|
12
|
+
.sidebar{display:flex;flex-direction:column;min-width:0;min-height:0;height:100vh;padding:16px 14px 14px}
|
|
13
13
|
.sidebar-brand{display:flex;gap:10px;align-items:center;flex:none;margin-bottom:14px}
|
|
14
14
|
.brand-text{display:grid;gap:3px;min-width:0;flex:1}
|
|
15
15
|
.sidebar .search{flex:none;margin-bottom:10px}
|
|
@@ -19,6 +19,13 @@ body.terminal-active .app-shell{grid-template-columns:var(--sidebar-w) minmax(0,
|
|
|
19
19
|
.task-group-head{display:flex;align-items:baseline;gap:8px;padding:3px 4px 2px}
|
|
20
20
|
.sidebar-foot{flex:none;padding-top:10px;margin-top:8px;display:grid;gap:8px}
|
|
21
21
|
.sidebar-controls{display:grid;grid-template-columns:1fr 1fr;gap:8px;align-items:center}
|
|
22
|
+
.catalog-pager{display:grid;grid-template-columns:1fr 1fr;gap:6px;flex:none;padding-top:10px}
|
|
23
|
+
.catalog-pager span{grid-column:1/-1;grid-row:1;text-align:center;font-size:11px;color:var(--muted)}
|
|
24
|
+
.catalog-pager button{min-height:32px;min-width:0;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font:inherit;font-size:11px;cursor:pointer}
|
|
25
|
+
.catalog-pager button:hover:not(:disabled){border-color:var(--accent)}
|
|
26
|
+
.catalog-pager button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
27
|
+
.catalog-pager button:disabled{opacity:.45;cursor:default}
|
|
28
|
+
#catalog-attention-reset{grid-column:1/-1}
|
|
22
29
|
/* Main column = top bar + optional section nav + reading surface */
|
|
23
30
|
.main-col{display:flex;flex-direction:column;min-width:0;min-height:0;height:100vh;overflow-y:auto;scrollbar-gutter:stable}
|
|
24
31
|
.topbar{position:sticky;top:0;z-index:30;flex:none;display:flex;align-items:center;justify-content:space-between;gap:18px;padding:11px max(var(--page-space),calc((100% - 1100px) / 2));background:color-mix(in srgb,var(--bg) 92%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-bottom:1px solid var(--border)}
|
|
@@ -15,6 +15,18 @@ body{background:var(--ambient),var(--bg);background-attachment:fixed;color:var(-
|
|
|
15
15
|
.skip-link:focus{top:14px}
|
|
16
16
|
button{font:inherit;cursor:pointer}
|
|
17
17
|
input:focus-visible,select:focus-visible,button:focus-visible,.task:focus-visible,.tab:focus-visible,.overview-row:focus-visible{outline:none;box-shadow:0 0 0 2px var(--bg),0 0 0 4px var(--accent)}
|
|
18
|
+
.global-input-dialog{width:min(34rem,calc(100vw - 2rem));max-height:85vh;overflow:auto;color:var(--text);background:var(--bg-1);border:1px solid var(--border-strong);border-radius:var(--radius-lg);padding:var(--page-space)}
|
|
19
|
+
.global-input-dialog::backdrop{background:color-mix(in srgb,var(--bg) 75%,transparent)}
|
|
20
|
+
.global-input-dialog form,.global-input-dialog label{display:grid;gap:8px}
|
|
21
|
+
.global-input-dialog form{gap:14px}
|
|
22
|
+
.global-input-dialog p{overflow-wrap:anywhere}
|
|
23
|
+
.global-input-dialog form,.global-input-dialog label{min-width:0}
|
|
24
|
+
.global-input-dialog input,.global-input-dialog select,.global-input-dialog textarea{width:100%;box-sizing:border-box;padding:10px;color:var(--text);background:var(--bg);border:1px solid var(--border-strong);border-radius:var(--radius)}
|
|
25
|
+
.global-input-dialog textarea{min-height:100px}
|
|
26
|
+
.global-input-dialog .record-actions{display:flex;flex-wrap:wrap;gap:8px}
|
|
27
|
+
.global-input-dialog button{min-height:44px}
|
|
28
|
+
.global-input-state{white-space:pre-wrap;overflow-wrap:anywhere;font:12px var(--font-mono)}
|
|
29
|
+
.global-input-dialog [hidden]{display:none}
|
|
18
30
|
/* Sidebar */
|
|
19
31
|
.sidebar{border-right:1px solid var(--border);background:color-mix(in srgb,var(--bg-1) 84%,transparent);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
|
|
20
32
|
.brand-mark{width:40px;height:40px;flex:none;display:grid;place-items:center;font-family:var(--font-display);font-size:21px;font-weight:600;color:var(--on-accent);border-radius:11px;background:linear-gradient(140deg,var(--accent),var(--accent-2));box-shadow:var(--glow)}
|
package/dist/web/webServer.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import WebSocket, { WebSocketServer } from "ws";
|
|
4
|
-
import { usageError } from "../errors/cliError.js";
|
|
4
|
+
import { CliError, usageError } from "../errors/cliError.js";
|
|
5
|
+
import { parseTaskCatalogOptions } from "../context/taskCatalog.js";
|
|
5
6
|
import { WebRequestRejected } from "./webMutation.js";
|
|
6
7
|
import { TASK_SUBMISSION_INTENTS } from "../message/message.js";
|
|
7
8
|
import { DASHBOARD_HTML, findWebAsset } from "./assets/assetManifest.js";
|
|
8
|
-
import { buildWebDashboardSnapshot, buildWebTaskDetail } from "./webSnapshot.js";
|
|
9
|
+
import { buildWebDashboardSnapshot, buildWebTaskCatalog, buildWebTaskDetail } from "./webSnapshot.js";
|
|
9
10
|
const MAX_JSON_BODY_BYTES = 16 * 1024;
|
|
10
11
|
const MAX_TERMINAL_MESSAGE_BYTES = 64 * 1024;
|
|
11
12
|
const MAX_TERMINAL_BUFFERED_BYTES = 1024 * 1024;
|
|
@@ -99,6 +100,38 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
99
100
|
sendJson(response, 403, { error: "Invalid Yui web token.", disposition: "not-submitted" }, method === "HEAD");
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
103
|
+
const globalControl = /^\/api\/roles\/([^/]+)\/control$/.exec(pathname);
|
|
104
|
+
if (globalControl && dependencies.surface) {
|
|
105
|
+
try {
|
|
106
|
+
const roleName = decodeURIComponent(globalControl[1]);
|
|
107
|
+
if (!/^[A-Za-z0-9_-]+$/.test(roleName))
|
|
108
|
+
throw new WebRequestRejected("Invalid Global Role.");
|
|
109
|
+
if (method === "GET") {
|
|
110
|
+
sendJson(response, 200, dependencies.surface.globalState(roleName), false);
|
|
111
|
+
}
|
|
112
|
+
else if (method === "POST") {
|
|
113
|
+
const body = await readMutationBody(request);
|
|
114
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)
|
|
115
|
+
|| Object.keys(body).some(key => !["action", "requestId", "body", "expectedTarget", "thenMessage"].includes(key))) {
|
|
116
|
+
throw new WebRequestRejected("Global input accepts only action, input and exact target fields.");
|
|
117
|
+
}
|
|
118
|
+
const input = body;
|
|
119
|
+
const parsed = parseWebControlInput({ ...input,
|
|
120
|
+
...(input.action === "interrupt" ? { role: roleName }
|
|
121
|
+
: input.action === "steer" ? { to: roleName } : {}) });
|
|
122
|
+
sendJson(response, 200, {
|
|
123
|
+
...await dependencies.surface.globalControl(roleName, parsed), requestId: parsed.requestId
|
|
124
|
+
}, false);
|
|
125
|
+
}
|
|
126
|
+
else
|
|
127
|
+
sendJson(response, 405, { error: "Method not allowed.", disposition: "not-submitted" }, false);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
sendJson(response, 409, { error: error instanceof Error ? error.message : "Global input unavailable.",
|
|
131
|
+
disposition: error instanceof WebRequestRejected ? "not-submitted" : "unknown" }, false);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
102
135
|
const panelTarget = /^\/api\/tasks\/([^/]+)\/panels$/.exec(pathname);
|
|
103
136
|
if (panelTarget && dependencies.panels) {
|
|
104
137
|
try {
|
|
@@ -130,7 +163,7 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
130
163
|
}
|
|
131
164
|
return;
|
|
132
165
|
}
|
|
133
|
-
const surfaceTarget = /^\/api\/tasks\/([^/]+)\/(context|delta|inspect|metadata|messages)$/.exec(pathname);
|
|
166
|
+
const surfaceTarget = /^\/api\/tasks\/([^/]+)\/(context|delta|inspect|metadata|messages|control)$/.exec(pathname);
|
|
134
167
|
if (surfaceTarget && dependencies.surface) {
|
|
135
168
|
try {
|
|
136
169
|
let taskId;
|
|
@@ -169,6 +202,10 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
169
202
|
// requestId is threaded as the submission key (§2.3) and echoed back on the receipt.
|
|
170
203
|
value = { ...dependencies.surface.message(taskId, body.body, intent, body.requestId), requestId: body.requestId };
|
|
171
204
|
}
|
|
205
|
+
else if (method === "POST" && action === "control") {
|
|
206
|
+
const body = await readMutationBody(request);
|
|
207
|
+
value = { ...await dependencies.surface.control(taskId, parseWebControlInput(body)), requestId: parseWebControlRequestId(body) };
|
|
208
|
+
}
|
|
172
209
|
else if (method === "POST" && action === "metadata") {
|
|
173
210
|
const body = await readMutationBody(request);
|
|
174
211
|
if (typeof body !== "object" || body === null || Array.isArray(body)
|
|
@@ -200,7 +237,13 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
200
237
|
sendAsset(response, asset, method === "HEAD");
|
|
201
238
|
}
|
|
202
239
|
else if (pathname === "/api/dashboard") {
|
|
203
|
-
|
|
240
|
+
const query = new URL(request.url, "http://localhost").searchParams;
|
|
241
|
+
const snapshot = query.has("view")
|
|
242
|
+
? buildWebTaskCatalog(store, parseTaskCatalogOptions([...query].flatMap(([key, value]) => key === "all"
|
|
243
|
+
? (value === "true" ? ["--all"] : ["--invalid-all"])
|
|
244
|
+
: [`--${key}`, value])))
|
|
245
|
+
: buildWebDashboardSnapshot(store, now());
|
|
246
|
+
sendJson(response, 200, snapshot, method === "HEAD");
|
|
204
247
|
}
|
|
205
248
|
else if (pathname.startsWith("/api/tasks/")) {
|
|
206
249
|
const taskId = decodeURIComponent(pathname.slice("/api/tasks/".length));
|
|
@@ -214,6 +257,10 @@ async function handleHttpRequest(request, response, store, dependencies, token,
|
|
|
214
257
|
}
|
|
215
258
|
}
|
|
216
259
|
catch (error) {
|
|
260
|
+
if (error instanceof CliError) {
|
|
261
|
+
sendJson(response, 400, { error: error.message }, method === "HEAD");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
217
264
|
if (error instanceof URIError) {
|
|
218
265
|
sendJson(response, 400, { error: "Invalid URL encoding." }, method === "HEAD");
|
|
219
266
|
return;
|
|
@@ -429,6 +476,86 @@ function parseAnswerPath(pathname) {
|
|
|
429
476
|
return null;
|
|
430
477
|
}
|
|
431
478
|
}
|
|
479
|
+
/** The three-action control body is validated to the exact CLI-equivalent
|
|
480
|
+
* shape before it reaches the surface, so an unknown or malformed field is a
|
|
481
|
+
* visible not-submitted rejection rather than a silent default. `requestId` is
|
|
482
|
+
* the caller-supplied idempotency key common to all three actions. */
|
|
483
|
+
function parseWebControlRequestId(value) {
|
|
484
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
485
|
+
throw new WebRequestRejected("Control input must be an object.");
|
|
486
|
+
}
|
|
487
|
+
const requestId = value.requestId;
|
|
488
|
+
if (typeof requestId !== "string" || !requestId.trim()) {
|
|
489
|
+
throw new WebRequestRejected("A non-empty requestId is required.");
|
|
490
|
+
}
|
|
491
|
+
return requestId;
|
|
492
|
+
}
|
|
493
|
+
function requiredControlString(input, key) {
|
|
494
|
+
const value = input[key];
|
|
495
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
496
|
+
throw new WebRequestRejected(`${key} is required.`);
|
|
497
|
+
}
|
|
498
|
+
return value;
|
|
499
|
+
}
|
|
500
|
+
function optionalControlString(input, key) {
|
|
501
|
+
if (!(key in input) || input[key] === undefined)
|
|
502
|
+
return undefined;
|
|
503
|
+
const value = input[key];
|
|
504
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
505
|
+
throw new WebRequestRejected(`${key} must be a non-empty string when provided.`);
|
|
506
|
+
}
|
|
507
|
+
return value;
|
|
508
|
+
}
|
|
509
|
+
function parseWebControlInput(value) {
|
|
510
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
511
|
+
throw new WebRequestRejected("Control input must be an object.");
|
|
512
|
+
}
|
|
513
|
+
const input = value;
|
|
514
|
+
const action = input.action;
|
|
515
|
+
if (action === "queue") {
|
|
516
|
+
for (const key of Object.keys(input)) {
|
|
517
|
+
if (!["action", "body", "requestId", "to", "workItem", "reviewRound"].includes(key)) {
|
|
518
|
+
throw new WebRequestRejected(`Unexpected field for queue: ${key}.`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (input.workItem !== undefined && input.reviewRound !== undefined) {
|
|
522
|
+
throw new WebRequestRejected("A queue takes at most one of workItem or reviewRound.");
|
|
523
|
+
}
|
|
524
|
+
return { action: "queue", body: requiredControlString(input, "body"),
|
|
525
|
+
requestId: requiredControlString(input, "requestId"),
|
|
526
|
+
...(optionalControlString(input, "to") === undefined ? {} : { to: optionalControlString(input, "to") }),
|
|
527
|
+
...(optionalControlString(input, "workItem") === undefined ? {} : { workItem: optionalControlString(input, "workItem") }),
|
|
528
|
+
...(optionalControlString(input, "reviewRound") === undefined ? {} : { reviewRound: optionalControlString(input, "reviewRound") }) };
|
|
529
|
+
}
|
|
530
|
+
if (action === "steer") {
|
|
531
|
+
for (const key of Object.keys(input)) {
|
|
532
|
+
if (!["action", "body", "requestId", "expectedTarget", "to", "workItem", "reviewRound"].includes(key)) {
|
|
533
|
+
throw new WebRequestRejected(`Unexpected field for steer: ${key}.`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (input.workItem !== undefined && input.reviewRound !== undefined) {
|
|
537
|
+
throw new WebRequestRejected("A steer takes at most one of workItem or reviewRound.");
|
|
538
|
+
}
|
|
539
|
+
return { action: "steer", body: requiredControlString(input, "body"),
|
|
540
|
+
requestId: requiredControlString(input, "requestId"),
|
|
541
|
+
expectedTarget: requiredControlString(input, "expectedTarget"),
|
|
542
|
+
to: requiredControlString(input, "to"),
|
|
543
|
+
...(optionalControlString(input, "workItem") === undefined ? {} : { workItem: optionalControlString(input, "workItem") }),
|
|
544
|
+
...(optionalControlString(input, "reviewRound") === undefined ? {} : { reviewRound: optionalControlString(input, "reviewRound") }) };
|
|
545
|
+
}
|
|
546
|
+
if (action === "interrupt") {
|
|
547
|
+
for (const key of Object.keys(input)) {
|
|
548
|
+
if (!["action", "requestId", "expectedTarget", "role", "thenMessage"].includes(key)) {
|
|
549
|
+
throw new WebRequestRejected(`Unexpected field for interrupt: ${key}.`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return { action: "interrupt", requestId: requiredControlString(input, "requestId"),
|
|
553
|
+
expectedTarget: requiredControlString(input, "expectedTarget"),
|
|
554
|
+
role: requiredControlString(input, "role"),
|
|
555
|
+
...(optionalControlString(input, "thenMessage") === undefined ? {} : { thenMessage: optionalControlString(input, "thenMessage") }) };
|
|
556
|
+
}
|
|
557
|
+
throw new WebRequestRejected("action must be one of queue, steer, or interrupt.");
|
|
558
|
+
}
|
|
432
559
|
function parseWebInputAnswer(value) {
|
|
433
560
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
434
561
|
throw new Error("Input answer must be an object.");
|
package/dist/web/webSnapshot.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { providerRetryProjection } from "../runtime/providerRetry.js";
|
|
2
|
+
import { readTaskCatalog } from "../context/taskCatalog.js";
|
|
1
3
|
import { isRoleRunStalled, latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
|
|
2
4
|
import { retiredTaskRecordIds } from "../task/taskRecordRetirement.js";
|
|
3
5
|
import { buildTaskExecutionProjection } from "../scheduler/taskExecutionProjection.js";
|
|
@@ -9,6 +11,9 @@ import { runExecutionObservation } from "../agentRun/agentRun.js";
|
|
|
9
11
|
import { resolveRuntimeHealth } from "../config/yuiConfig.js";
|
|
10
12
|
import { projectSessionTokenMetrics, resolveSessionTokenIdentity } from "../runtime/sessionTokenMetrics.js";
|
|
11
13
|
import { projectTaskRemoteDelivery } from "../task/remoteDelivery.js";
|
|
14
|
+
export function buildWebTaskCatalog(store, options) {
|
|
15
|
+
return store.transaction(reader => readTaskCatalog(reader, options));
|
|
16
|
+
}
|
|
12
17
|
export function buildWebDashboardSnapshot(store, now = new Date()) {
|
|
13
18
|
return store.transaction((reader) => {
|
|
14
19
|
const statusCounts = {
|
|
@@ -25,14 +30,15 @@ export function buildWebDashboardSnapshot(store, now = new Date()) {
|
|
|
25
30
|
statusCounts[task.status] += 1;
|
|
26
31
|
const taskOpenInputs = reader.listInputRequests(task.id)
|
|
27
32
|
.filter((request) => request.status === "open").length;
|
|
28
|
-
|
|
33
|
+
if (task.status !== "archived")
|
|
34
|
+
openInputs += taskOpenInputs;
|
|
29
35
|
const taskOpen = reader.listInputRequests(task.id)
|
|
30
36
|
.filter((request) => request.status === "open");
|
|
31
|
-
for (const request of taskOpen) {
|
|
37
|
+
for (const request of task.status === "archived" ? [] : taskOpen) {
|
|
32
38
|
attention.push({ taskId: task.id, taskTitle: task.title, request });
|
|
33
39
|
}
|
|
34
40
|
const events = reader.listEvents?.(task.id) ?? [];
|
|
35
|
-
const needsAttentionCount = reader.listRuns(task.id)
|
|
41
|
+
const needsAttentionCount = task.status === "archived" ? 0 : reader.listRuns(task.id)
|
|
36
42
|
.filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
|
|
37
43
|
.length;
|
|
38
44
|
const execution = buildTaskExecutionProjection(reader, task.id, task, now);
|
|
@@ -94,12 +100,15 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
|
94
100
|
const activeSession = sessions?.sessions[activeRun?.effective.agentId ?? sessions.activeAgentId];
|
|
95
101
|
const effectiveLaunch = activeRun?.effective ?? activeSession?.effective ?? null;
|
|
96
102
|
const delivery = sessions?.providerBinding?.run?.status;
|
|
103
|
+
const providerRetry = providerRetryProjection(sessions?.providerBinding);
|
|
97
104
|
return {
|
|
98
105
|
...role,
|
|
106
|
+
providerRetry,
|
|
99
107
|
// An open record is not proof that its Agent is running.
|
|
100
|
-
status:
|
|
101
|
-
: delivery === "
|
|
102
|
-
:
|
|
108
|
+
status: providerRetry?.status === "waiting" ? "waiting"
|
|
109
|
+
: delivery === "accepted" ? "running"
|
|
110
|
+
: delivery === "submitting" || delivery === "deferred" ? "waiting"
|
|
111
|
+
: activeSession === undefined && activeRun === undefined ? "idle" : "unknown",
|
|
103
112
|
sessionTokens: projectSessionTokenMetrics(events, resolveSessionTokenIdentity(activeSession === undefined
|
|
104
113
|
? null
|
|
105
114
|
: { taskId, roleName: role.name, ...activeSession })),
|
|
@@ -161,6 +170,7 @@ function webRemoteDelivery(reader, task) {
|
|
|
161
170
|
publications: reader.listPublicationReferences(task.id),
|
|
162
171
|
managedWorkspaces: reader.listManagedWorkspaces(task.id),
|
|
163
172
|
runs: reader.listRuns(task.id),
|
|
173
|
+
integrations: reader.listIntegrationAttempts(task.id),
|
|
164
174
|
currentCandidate: null
|
|
165
175
|
});
|
|
166
176
|
}
|